Posted in: Unity, 프로젝트 When the Spring comes

10. 플레이어를 추적하는 몬스터 스크립트 수정

대충 참고용 예전 플레이어 추적 코드


void FixedUpdate()
{
    if(tracking)
    {
        Tracking();
    }

    target = Physics.OverlapSphere(transform.position, overlapRadius, whatIsLayer);

    if(target.Length > 0)
    {
        state = State.Tracking;
    }
}
public void Tracking()
{
    velocity = new Vector3(Mathf.Clamp(target[0].transform.position.x - transform.position.x, -1.0f, 1.0f),
                           0,
                           Mathf.Clamp(target[0].transform.position.z - transform.position.z, -1.0f, 1.0f));
    transform.position += velocity * speed * Time.deltaTime;

    Debug.Log("velocity: " + velocity);
}

플레이어를 추적하는 몬스터 스크립트를 미리 만들었었는데 문제가 있다.

Idle 상태와 Moving 상태는 8방향으로만 움직이는데 플레이어를 쫓는 Tracking 상태는 x와 z값이 (-1, -0.9, -0.8, ···, 0.8, 0.9, 1) 유동적으로 변한다. 다이나믹하게 이동한다고 보면 된다.

그런데 여기서 생기는 문제가 Idle과 Moving은 Direction() 함수가 적절하게 작동하지만 Tracking은 그렇지 않다. 그래서 몬스터가 Tracking 상태일때는 몬스터의 이동 방향을 받아오질 못하게 되었다.

그래서 이렇게 바꾸었다


public void Tracking()
{
    if(target.Length > 0)
    {
        inputX = Mathf.Clamp(target[0].transform.position.x - transform.position.x, -1.0f, 1.0f);
        inputZ = Mathf.Clamp(target[0].transform.position.z - transform.position.z, -1.0f, 1.0f);

        velocity = new Vector3(inputX, 0, inputZ);

        Vector3 temp = transform.position - target[0].transform.position;
        direction = Mathf.Atan2(temp.z, temp.x) * Mathf.Rad2Deg;
    }
    else if(target.Length == 0)
    {
        inputX = Mathf.Clamp(targetObj.transform.position.x - transform.position.x, -1.0f, 1.0f);
        inputZ = Mathf.Clamp(targetObj.transform.position.z - transform.position.z, -1.0f, 1.0f);

        velocity = new Vector3(inputX, 0, inputZ);

        Vector3 temp = transform.position - targetObj.transform.position;
        direction = Mathf.Atan2(temp.z, temp.x) * Mathf.Rad2Deg;
    }

    Direction(direction);

    transform.position += velocity * monsterStat.speed * Time.deltaTime;
}

private void OnTriggerStay(Collider other) {
    if(other.gameObject.layer == LayerMask.NameToLayer("Player"))
    {
        target = Physics.OverlapSphere(transform.position, overlapRadius, whatIsLayer);
        targetObj = other.gameObject;
    }
}

private void OnTriggerExit(Collider other) {
    if(other.gameObject.layer == LayerMask.NameToLayer("Player"))
    {
        targetObj = other.gameObject;
    }
}

저번에 플레이어 추적 코드를 만들면서 ‘어차피 OverlapSphere로 만들었는데 SphereCollider 컴포넌트를 연결하고 트리거로 만드는 게 낫겠다’라고 생각했었다. 결국엔 그렇게 만들었다.

추적하는 기능은 비슷하다. 다만 기획자님이 몬스터가 죽을 때까지 계속 추적하는 몬스터를 만들어 달라고 했었는데 정상적으로 작동은 했지만 OnTriggerStay에서 벗어날 때 target이 비어지면서 널레퍼런스이셉션 버그가 한 번씩 불렸었다. 그래서 OnTriggerExit를 만들었다.

사실 추적 기능을 수정한 이유가 몬스터가 추적 상태일때 8방향 중 어디로 이동하고 있는지 판별하는 Direction()을 호출하기 힘들어서였다. 그걸 두 벡터 사이의 각도를 구하는 방법으로 수정하였다.

public void Direction(float dir)
{
    if (dir >= -67.5f && dir < -25.5f)//UpLeft
    {
        directionVec = new Vector3(-1.14f, 0, 1.14f);
    }
    if (dir >= -112.5f && dir < -67.5f)//Up
    {
        directionVec = new Vector3(0, 0, 2.0f);
    }
    if (dir >= -157.5f && dir < -112.5f)//UpRight
    {
        directionVec = new Vector3(1.14f, 0, 1.14f);
    }
    if (dir >= -22.5f && dir < 22.5f)//Left
    {
        directionVec = new Vector3(-2.0f, 0, 0);
    }
    if (dir == 0)//Idle
    {
        directionVec = new Vector3(0, 0, 0);
    }
    if (dir >= -157.5f && dir <= -180.0f)//Right
    {
        directionVec = new Vector3(2.0f, 0, 0);
    }
    if (dir >= 157.5f && dir <= 180.0f)//Right
    {
        directionVec = new Vector3(2.0f, 0, 0);
    }
    if (dir >= 22.5f && dir < 67.5f)//DownLeft
    {
        directionVec = new Vector3(-1.14f, 0, -1.14f);
    }
    if (dir >= 67.5f && dir < 112.5)//Down
    {
        directionVec = new Vector3(0, 0, -2.0f);
    }
    if (dir >= 112.5 && dir < 157.5f)//DownRight
    {
        directionVec = new Vector3(1.14f, 0, -1.14f);
    }
}

Tracking() 코드를 보면 알겠지만 target으로 받아오는 플레이어의 position과 몬스터의 position 사이의 각도를 구해서 Direction() 함수의 매개변수로 받아오고있다.

다만 구해지는 각도는 -180도 ~ 180도까지 인데 이렇게되면 캐릭터가 8방향으로 움직인다고 했을 때 8방향 각 방향마다의 방향을 어떻게 설정하냐가 문제이다. 나는 각 방향마다 45도씩 차지한다고 설정하였고 나오는 각도에 따라 범위를 설정해 방향이 설정되게 코드 작성을 하였다.

덕분에 정상적으로 추적 상태일때도 몬스터 방향을 받아올 수 있었다.

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

public class MonsterMove : MonoBehaviour
{
    private MonsterStat monsterStat;
    private Vector3 velocity;
    public float stateChangeTime;
    private float inputX;
    private float inputZ;

    private bool stateChange;
    private bool idle;
    private bool moving;
    private bool tracking;
    private bool wmattackReady;

    public Vector3 directionVec;
    private float direction;
    private Rigidbody rigidbody;

    public Collider[] target;
    private GameObject targetObj;
    public LayerMask whatIsLayer;
    public float overlapRadius;
    private SphereCollider sphereCollider;

    enum State { Idle, Moving, Tracking}

    private State state
    {
        set
        {
            switch(value)
            {
                case State.Idle:
                    idle = true;

                    moving = false;
                    tracking = false;
                    break;

                case State.Moving:
                    moving = true;

                    idle = false;
                    tracking = false;
                    break;

                case State.Tracking:
                    tracking = true;

                    idle = false;
                    moving = false;
                    break;
            }
        }
    }

    void Start()
    {
        rigidbody = GetComponent<Rigidbody>();
        monsterStat = GetComponent<MonsterStat>();
        sphereCollider = transform.Find("TargetArea").GetComponent<SphereCollider>();

        state = State.Idle;
        stateChange = false;
    }

    void Update()
    {
        sphereCollider.radius = overlapRadius;
    }

    void FixedUpdate()
    {
        if(tracking)
        {
            Tracking();
        }

        if(target.Length > 0)
        {
            state = State.Tracking;
        }
    }

    public void Idle()
    {
        if(!stateChange) StartCoroutine(StateChange());

        inputX = 0;
        inputZ = 0;

        velocity = new Vector3(inputX, 0, inputZ);

        Vector3 temp = transform.position - (transform.position + velocity*3);
        direction = Mathf.Atan2(temp.z, temp.x) * Mathf.Rad2Deg;

        transform.position += velocity * monsterStat.speed * Time.deltaTime;

        Direction(direction);

    }

    public void Moving()
    {
        if(!stateChange) StartCoroutine(StateChange());
        
        velocity = new Vector3(inputX, 0, inputZ);

        Vector3 temp = transform.position - (transform.position + velocity*3);
        direction = Mathf.Atan2(temp.z, temp.x) * Mathf.Rad2Deg;

        transform.position += velocity * monsterStat.speed * Time.deltaTime;

        Direction(direction);
    }

    public void Tracking()
    {
        if(target.Length > 0)
        {
            inputX = Mathf.Clamp(target[0].transform.position.x - transform.position.x, -1.0f, 1.0f);
            inputZ = Mathf.Clamp(target[0].transform.position.z - transform.position.z, -1.0f, 1.0f);

            velocity = new Vector3(inputX, 0, inputZ);

            Vector3 temp = transform.position - target[0].transform.position;
            direction = Mathf.Atan2(temp.z, temp.x) * Mathf.Rad2Deg;
        }
        else if(target.Length == 0)
        {
            inputX = Mathf.Clamp(targetObj.transform.position.x - transform.position.x, -1.0f, 1.0f);
            inputZ = Mathf.Clamp(targetObj.transform.position.z - transform.position.z, -1.0f, 1.0f);

            velocity = new Vector3(inputX, 0, inputZ);

            Vector3 temp = transform.position - targetObj.transform.position;
            direction = Mathf.Atan2(temp.z, temp.x) * Mathf.Rad2Deg;
        }

        Direction(direction);

        transform.position += velocity * monsterStat.speed * Time.deltaTime;
    }

    public void Direction(float dir)
    {
        

        if (dir >= -67.5f && dir < -25.5f)//UpLeft
        {
            directionVec = new Vector3(-1.14f, 0, 1.14f);
        }
        if (dir >= -112.5f && dir < -67.5f)//Up
        {
            directionVec = new Vector3(0, 0, 2.0f);
        }
        if (dir >= -157.5f && dir < -112.5f)//UpRight
        {
            directionVec = new Vector3(1.14f, 0, 1.14f);
        }
        if (dir >= -22.5f && dir < 22.5f)//Left
        {
            directionVec = new Vector3(-2.0f, 0, 0);
        }
        if (dir == 0)//Idle
        {
            directionVec = new Vector3(0, 0, 0);
        }
        if (dir >= -157.5f && dir <= -180.0f)//Right
        {
            directionVec = new Vector3(2.0f, 0, 0);
        }
        if (dir >= 157.5f && dir <= 180.0f)//Right
        {
            directionVec = new Vector3(2.0f, 0, 0);
        }
        if (dir >= 22.5f && dir < 67.5f)//DownLeft
        {
            directionVec = new Vector3(-1.14f, 0, -1.14f);
        }
        if (dir >= 67.5f && dir < 112.5)//Down
        {
            directionVec = new Vector3(0, 0, -2.0f);
        }
        if (dir >= 112.5 && dir < 157.5f)//DownRight
        {
            directionVec = new Vector3(1.14f, 0, -1.14f);
        }
    }

    IEnumerator StateChange()
    {
        stateChange = true;

        inputX = Random.Range(-1, 2);
        inputZ = Random.Range(-1, 2);

        yield return new WaitForSeconds(stateChangeTime);

        state = (State)Random.Range(0, 2);
        stateChange = false;
    }

    private void OnTriggerStay(Collider other) {
        if(other.gameObject.layer == LayerMask.NameToLayer("Player"))
        {
            target = Physics.OverlapSphere(transform.position, overlapRadius, whatIsLayer);
            targetObj = other.gameObject;
        }
    }

    private void OnTriggerExit(Collider other) {
        if(other.gameObject.layer == LayerMask.NameToLayer("Player"))
        {
            targetObj = other.gameObject;
        }
    }
}

Direction() 함수를 수정하였으니 당연히 Idle과 Moving 상태일때도 각도를 구하여 넣어줘야한다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다