Machine learning ขับรถใน Unity ด้วย Python
Tags: python, unity3d, gamedev
Published: 2024-04-06 08:57:15

{% embed https://blackoil.itch.io/selfdrivingcar %}
self-driving car ในเกมมีความสามารถในการเรียนรู้ปรับตัว และสามารถขับรถให้เก่งขึ้นได้จากการใช้ machine learning ถายในเกมของ Unity 

ขั้นแรก ตัวรถต้องสามารถ ขยับได้ด้วยตัวเองก่อนโดยใช้โคด

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CarDriver : MonoBehaviour {

    #region Fields
    private float speed;
    public float speedMax = 70f;
    private float speedMin = -50f;
    public float acceleration = 30f;
    public float brakeSpeed = 100f;
    private float reverseSpeed = 30f;
    private float idleSlowdown = 10f;

    private float turnSpeed;
    public float turnSpeedMax = 300f;
    private float turnSpeedAcceleration = 300f;
    private float turnIdleSlowdown = 500f;

    private float forwardAmount;
    private float turnAmount;

    private Rigidbody carRigidbody;
    #endregion

    private void Awake() {
        carRigidbody = GetComponent<Rigidbody>();
    }

    private void Update() {
        if (forwardAmount > 0) {
            // Accelerating
            speed += forwardAmount * acceleration * Time.deltaTime;
        }
        if (forwardAmount < 0) {
            if (speed > 0) {
                // Braking
                speed += forwardAmount * brakeSpeed * Time.deltaTime;
            } else {
                // Reversing
                speed += forwardAmount * reverseSpeed * Time.deltaTime;
            }
        }

        if (forwardAmount == 0) {
            // Not accelerating or braking
            if (speed > 0) {
                speed -= idleSlowdown * Time.deltaTime;
            }
            if (speed < 0) {
                speed += idleSlowdown * Time.deltaTime;
            }
        }

        speed = Mathf.Clamp(speed, speedMin, speedMax);

        carRigidbody.velocity = transform.forward * speed;

        if (speed < 0) {
            // Going backwards, invert wheels
            turnAmount = turnAmount * -1f;
        }

        if (turnAmount > 0 || turnAmount < 0) {
            // Turning
            if ((turnSpeed > 0 && turnAmount < 0) || (turnSpeed < 0 && turnAmount > 0)) {
                // Changing turn direction
                float minTurnAmount = 20f;
                turnSpeed = turnAmount * minTurnAmount;
            }
            turnSpeed += turnAmount * turnSpeedAcceleration * Time.deltaTime;
        } else {
            // Not turning
            if (turnSpeed > 0) {
                turnSpeed -= turnIdleSlowdown * Time.deltaTime;
            }
            if (turnSpeed < 0) {
                turnSpeed += turnIdleSlowdown * Time.deltaTime;
            }
            if (turnSpeed > -1f && turnSpeed < +1f) {
                // Stop rotating
                turnSpeed = 0f;
            }
        }

        float speedNormalized = speed / speedMax;
        float invertSpeedNormalized = Mathf.Clamp(1 - speedNormalized, .75f, 1f);

        turnSpeed = Mathf.Clamp(turnSpeed, -turnSpeedMax, turnSpeedMax);

        carRigidbody.angularVelocity = new Vector3(0, turnSpeed * (invertSpeedNormalized * 1f) * Mathf.Deg2Rad, 0);

        if (transform.eulerAngles.x > 2 || transform.eulerAngles.x < -2 || transform.eulerAngles.z > 2 || transform.eulerAngles.z < -2) {
            transform.eulerAngles = new Vector3(0, transform.eulerAngles.y, 0);
        }
    }

    //private void OnCollisionEnter(Collision collision) {
      //  if (collision.gameObject.layer == GameHandler.SOLID_OBJECTS_LAYER) {
        //    speed = Mathf.Clamp(speed, 0f, 20f);
        //}
    //}

    public void SetInputs(float forwardAmount, float turnAmount) {
        this.forwardAmount = forwardAmount;
        this.turnAmount = turnAmount;
    }

    public void ClearTurnSpeed() {
        turnSpeed = 0f;
    }

    public float GetSpeed() {
        return speed;
    }


    public void SetSpeedMax(float speedMax) {
        this.speedMax = speedMax;
    }

    public void SetTurnSpeedMax(float turnSpeedMax) {
        this.turnSpeedMax = turnSpeedMax;
    }

    public void SetTurnSpeedAcceleration(float turnSpeedAcceleration) {
        this.turnSpeedAcceleration = turnSpeedAcceleration;
    }

    public void StopCompletely() {
        speed = 0f;
        turnSpeed = 0f;
    }

}

```
และใน version ที่พึ่งได้เล่นไป จะเป็น  version แรกที่รถ จะสามารถ ขับได้โดยผู้เล่นผ่านโค้ดของ 

```
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CarAi : MonoBehaviour
{
    [SerializeField] private Transform targetPositionTranform;
    private CarDriver CarDriver;
    private Vector3 TargetPosition;
    // Start is called before the first frame update
    private void Awake()
    {
        CarDriver = GetComponent<CarDriver>();
    }

    // Update is called once per frame
    void Update()
    {
        SetTargetPosition(targetPositionTranform.position);
        float forwardAmount = 0;
        float turnAmount = 0f;

        float reachTargetDistance = 7f;
        float distanceToTarget = Vector3.Distance(transform.position, TargetPosition);
        if(distanceToTarget > reachTargetDistance) {
            Vector3 dirToMovePosition = (TargetPosition - transform.position).normalized;
            float dot = Vector3.Dot(transform.forward, dirToMovePosition);

            if (dot > 0)
            {
                forwardAmount = 1f;
            }
            else
            {
                float reversDistance = 25f;
                if(distanceToTarget > reversDistance)
                {
                    forwardAmount = 1f;
                }
                else
                {
                    forwardAmount = -1f;
                }
                
            }

            float angleToDir = Vector3.SignedAngle(transform.forward, dirToMovePosition, Vector3.up);
            if (angleToDir > 0)
            {
                turnAmount = 1f;
            }
            else
            {
                turnAmount = -1f;
            }
        }
        else
        {
            if(CarDriver.GetSpeed() > 15f)
            {
                forwardAmount = -1f;
            }
            else
            {
                forwardAmount = 0f;
            }
            
            turnAmount = 0f;
        }
     
        CarDriver.SetInputs(forwardAmount, turnAmount);
    }
    public void SetTargetPosition(Vector3 TargetPosition)
    {
        this.TargetPosition = TargetPosition;
    }
}
```
นำไปใส้เป็น component ของรถให้สามารถขับได้

จากนั้นเครื่องมือต่อไปที่ใช้ คือ asset ML-agent ที่เป็น  asset ที่สามารถสร้าง agent ซึ่งจะเป็น component ของ ai ที่เราต้องการผ่าน GitHub 
{% embed https://github.com/Unity-Technologies/ml-agents %}
 
 เมือทำการ install เสร็จแล้วจึงสามารถใช้  ML-agents Component ได้

## โดยตัว Ai จะมีการทำ Reinforcement learning
โดยจะมีรำดับการทำงานที่

1.Observation
 ทำการสังเกตุ enivromentในเกมเพื่อเก็บข้อมูลประกอบการทำงานในขั้นต่อไป

```
public override void CollectObservations(VectorSensor sensor)(
       sensor.AddObervation(transform.position);//ให้ ai สั่งเกตุตำแหน่งของตัวรถ
       sensor.AddObervation(target.Transform.position);//ให้ ai สังเกตุตำแหน่งของเป้าหมาย
```

2.Decision 
 การตัดสินใจจากองค์ประกอบต้างๆที่ได้เก็บข้อมูลมา

```
public override void OnActionReceived(ActionBuffers action){
      float moveX = action.ContinuousActions[0];
      float moveZ = action.ContinuousActions[1];// ให้aiทำการเคลื่อนที่เข้าหาเป้าหมายมากขึ้น 
```


3.Reward
 รับรางวัลเพื่อพิสูจว่าสิ่งที่ Ai ทำไปนั้นเป็นสิ่งที่ถูกต้องตามที่กำหนดไว้ซึ่งหากผิดจะต้องกับไปที่กระบวนการเเรกไหม่ทั้งหมด

```
 private void OnTriggerEnter(Collider other){
              SetReward(1f); // เมื่อรถชนวัตถุที่เราต้องการแล้วจึงจะได้รางวัล
              EndEpisode(); 
```
เมือทำการCode สำเร็จ รถจะสามารถขับได้ด้วยตัวเอง>-<