언어/UNITY

[C# / UNITY] 간단한 Penguin 게임 예제 진행해보기 6 PenguinAgent scripts

코린영이 2023. 3. 20. 11:57
728x90
반응형

이 전 글에서 게임을 다 만들어봤다.

https://corinyoungee.tistory.com/29

 

[C# & UNITY] 간단한 Penguin 게임 예제 진행해보기 5 Fish Scripts

이 전 글에서 게임을 다 만들어봤다. https://corinyoungee.tistory.com/28 [C# & UNITY] 간단한 Penguin 게임 예제 진행해보기 4 https://corinyoungee.tistory.com/25 [C# & UNITY] 간단한 Penguin 게임 예제 진행해보기 3 이 글

corinyoungee.tistory.com

PenguinAgent script - 엄마 펭귄

using UnityEngine;
using Unity.MLAgents;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Sensors;

public class PenguinAgent : Agent
{
    [Tooltip("How fast the agent moves forward")]
    public float moveSpeed = 5f;

    [Tooltip("How fast the agent turns")]
    public float turnSpeed = 180f;

    [Tooltip("Prefab of the heart that appears when the baby is fed")]
    public GameObject heartPrefab;

    [Tooltip("Prefab of the regurgitated fish that appears when the baby is fed")]
    public GameObject regurgitatedFishPrefab;

    private PenguinArea penguinArea;
    new private Rigidbody rigidbody;
    private GameObject baby;
    private bool isFull; // If true, penguin has a full stomach

    /// <summary>
    /// Initial setup, called when the agent is enabled
    /// </summary>
    public override void Initialize()
    {
        base.Initialize();
        penguinArea = GetComponentInParent<PenguinArea>();
        baby = penguinArea.penguinBaby;
        rigidbody = GetComponent<Rigidbody>();
    }

    /// <summary>
    /// Perform actions based on a vector of numbers
    /// </summary>
    /// <param name="actionBuffers">The struct of actions to take</param>
    public override void OnActionReceived(ActionBuffers actionBuffers)
    {
        // Convert the first action to forward movement
        float forwardAmount = actionBuffers.DiscreteActions[0];

        // Convert the second action to turning left or right
        float turnAmount = 0f;
        if (actionBuffers.DiscreteActions[1] == 1f)
        {
            turnAmount = -1f;
        }
        else if (actionBuffers.DiscreteActions[1] == 2f)
        {
            turnAmount = 1f;
        }

        // Apply movement
        rigidbody.MovePosition(transform.position + transform.forward * forwardAmount * moveSpeed * Time.fixedDeltaTime);
        transform.Rotate(transform.up * turnAmount * turnSpeed * Time.fixedDeltaTime);

        // Apply a tiny negative reward every step to encourage action
        if (MaxStep > 0) AddReward(-1f / MaxStep);
    }

    /// <summary>
    /// Read inputs from the keyboard and convert them to a list of actions.
    /// This is called only when the player wants to control the agent and has set
    /// Behavior Type to "Heuristic Only" in the Behavior Parameters inspector.
    /// </summary>
    public override void Heuristic(in ActionBuffers actionsOut)
    {
        int forwardAction = 0;
        int turnAction = 0;
        if (Input.GetKey(KeyCode.W))
        {
            // move forward
            forwardAction = 1;
        }
        if (Input.GetKey(KeyCode.A))
        {
            // turn left
            turnAction = 1;
        }
        else if (Input.GetKey(KeyCode.D))
        {
            // turn right
            turnAction = 2;
        }

        // Put the actions into the array
        actionsOut.DiscreteActions.Array[0] = forwardAction;
        actionsOut.DiscreteActions.Array[1] = turnAction;
    }

    /// <summary>
    /// When a new episode begins, reset the agent and area
    /// </summary>
    public override void OnEpisodeBegin()
    {
        isFull = false;
        penguinArea.ResetArea();
    }

    /// <summary>
    /// Collect all non-Raycast observations
    /// </summary>
    /// <param name="sensor">The vector sensor to add observations to</param>
    public override void CollectObservations(VectorSensor sensor)
    {
        // Whether the penguin has eaten a fish (1 float = 1 value)
        sensor.AddObservation(isFull);

        // Distance to the baby (1 float = 1 value)
        sensor.AddObservation(Vector3.Distance(baby.transform.position, transform.position));

        // Direction to baby (1 Vector3 = 3 values)
        sensor.AddObservation((baby.transform.position - transform.position).normalized);

        // Direction penguin is facing (1 Vector3 = 3 values)
        sensor.AddObservation(transform.forward);

        // 1 + 1 + 3 + 3 = 8 total values
    }

    /// <summary>
    /// When the agent collides with something, take action
    /// </summary>
    /// <param name="collision">The collision info</param>
    private void OnCollisionEnter(Collision collision)
    {
        if (collision.transform.CompareTag("fish")) // 충돌 대상이 fish면
        {
            // Try to eat the fish
            EatFish(collision.gameObject); // fish를 먹고
        }
        else if (collision.transform.CompareTag("baby")) // 충돌 대상이 babypenguin이면
        {
            // Try to feed the baby
            RegurgitateFish(); // 손질한 물고기를 가져와 아기 펭귄에게 먹이를 준다.
        }
    }

    /// <summary>
    /// Check if agent is full, if not, eat the fish and get a reward
    /// </summary>
    /// <param name="fishObject">The fish to eat</param>
    private void EatFish(GameObject fishObject)
    {
        if (isFull) return; // Can't eat another fish while full
        isFull = true;

        penguinArea.RemoveSpecificFish(fishObject);

        AddReward(1f); // reward를 결정해준다.
    }

    /// <summary>
    /// Check if agent is full, if yes, feed the baby
    /// </summary>
    private void RegurgitateFish() // 손질한 물고기
    {
        if (!isFull) return; // Nothing to regurgitate
        isFull = false;

        // Spawn regurgitated fish
        // 유니티 게임 상에서 손질된 물고기를 생성한다.
        GameObject regurgitatedFish = Instantiate<GameObject>(regurgitatedFishPrefab);
        // transform.parent : 기준점이 되는 위치를 parent의 위치로
        regurgitatedFish.transform.parent = transform.parent;
        // transform.position : 실제 놓여질 위치를 정의
        regurgitatedFish.transform.position = baby.transform.position;
        Destroy(regurgitatedFish, 4f);

        // Spawn heart, 하트 표시를 생성한다.
        GameObject heart = Instantiate<GameObject>(heartPrefab);
        heart.transform.parent = transform.parent;
        heart.transform.position = baby.transform.position + Vector3.up;
        Destroy(heart, 4f);

        AddReward(1f);

        if (penguinArea.FishRemaining <= 0)
        {
            EndEpisode();
        }
    }
}

// ML-agent에 중요한 함수는 PenguinAgent에 몰려있다.
// OnActionReceived()
// OnEpisodeBegin() : 에피소드를 시작할 때마다
// CollectObservation() : 입력을 결정하는 부분
// OnCollisionEnter() : 충돌이 발생했을 때 어떻게 해야할지

 

 

 

 

참고 사이트

https://www.immersivelimit.com/tutorials/reinforcement-learning-penguins-part-1-unity-ml-agents

 

Reinforcement Learning Penguins (Part 1/4) | Unity ML-Agents — Immersive Limit

Unity Project Setup and Asset Import

www.immersivelimit.com

이 페이지를 보고 만들어봤다.

 

교수님 강의를 보고 적었습니당

첫 이미지의 상어와 암석 asset은 unity asset store에서 무료 asset을 찾아서 다운받았습니다.

다음에 asset을 다운받아서 적용하는 방법에 대한 글을 올리겠습니당.

https://assetstore.unity.com/packages/3d/characters/animals/fish/fish-polypack-202232

 

Fish - PolyPack | 캐릭터 | Unity Asset Store

Get the Fish - PolyPack package from Alstra Infinite and speed up your game development process. Find this & other 캐릭터 options on the Unity Asset Store.

assetstore.unity.com

https://assetstore.unity.com/packages/3d/environments/landscapes/stylized-snow-forest-230653#content

 

Stylized Snow Forest | 3D 풍경 | Unity Asset Store

Elevate your workflow with the Stylized Snow Forest asset from Frag112. Find this & other 풍경 options on the Unity Asset Store.

assetstore.unity.com

팀플이였는데.. 혼자서 찾느라 힘들었다,,

728x90
반응형