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

몬스터 AI를 제작하는 건 어느 정도가 적당한지 항상 고민이 된다. 제작하다 보면 굳이 필요 없는 기능까지 만들어야 되는지 고민하게 되는데 프로그래머의 입장으로서는 이 몬스터의 주변 맵이 어떻게 생겨먹은 지 예측하기가 힘들기 때문이다.

예를 들면 미로에 있는 적이면 좁은 통로를 잘 다닐 수 있는 이동 코드를 짜야 하는데 대충 만들면 벽을 향해 걸어 다니기 때문이다.

어쨌든 정확한 사정을 모르면 기본 기능에 충실하게 만들어야 되는데 물론 나도 그렇게 만들었다. 나중에 추가할 사항이 있다면 간단하게 짜인 코드가 알아보기도 쉽고 수정하기도 쉽다.

나는 OverlapSphere 메서드를 사용하여 플레이어가 저 범위안에 들어선다면 플레이어를 추적하게 만들었다. 벗어난다면 추적을 종료하고 Idle 또는 Moving 상태로 전환한다.

이런 식으로 OverlapSphere를 활용할 수 있지만 매 순간 플레이어가 범위 안에 있는지 없는지 체크를 해야 한다. 코드를 잘못 짜면 OutOfRange 오류가 지속해서 출력되므로 플레이어가 범위 안에 들어왔을 때 조건을 잘 부여하는 게 중요하다.

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

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

    public bool stateChange;//State 바꾸기용 불변수
    public bool idle;
    public bool moving;
    public bool tracking;

    public Vector3 directionVec;
    private Rigidbody rigidbody;
    public int rayDistance;

    //Target()용 변수들
    public Collider[] target;
    public LayerMask whatIsLayer;
    public float overlapRadius;

    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>();
        rayDistance = 1;

        state = State.Idle;
        stateChange = false;
    }

    void Update()
    {
        Debug.DrawRay(transform.position, directionVec * rayDistance);
    }

    void FixedUpdate()
    {
      /* 테스트를 위해 일단 주석처리
       * if(idle)
       * {
       *     Idle();
       * }
       * else if(moving)
       * {
       *     Moving();
       * }
       * else if(tracking)
       * {
       *     Tracking();
       * }
       */
        if(tracking)
        {
            Tracking();
        }

        if(idle)//제대로 작동 하는가?
        {
            Debug.Log("Current State:idle");
        }
        else if(moving)
        {
            Debug.Log("Current State:moving");
        }
        else if(tracking)
        {
            Debug.Log("Current State:tracking");
        }

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

        if(target.Length > 0)
        {
            state = State.Tracking;
        }
        else
        {
            //추적 종료 시 Idle, Moving 상태로 랜덤하게 돌입
            state = (State)Random.Range(0, 2);
            stateChange = false;
            return;
        }
    }

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

        inputX = 0;
        inputZ = 0;

        velocity = new Vector3(inputX, 0, inputZ);
        transform.position += velocity * speed * Time.deltaTime;

        Direction();

    }

    public void Moving()
    {
        if(!stateChange) StartCoroutine(StateChange());
        
        //float fallSpeed = rigidbody.velocity.y;

        //캐릭터가 움직이는 코드
        velocity = new Vector3(inputX, 0, inputZ);
        transform.position += velocity * speed * Time.deltaTime;

        Direction();
    }

    public void Tracking()
    {
        /*
         *이건 예시임 OverlapSphere(Vector3.point, float radius, int layerMask, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)
         *target = Physics.OverlapSphere(transform.position, overlapRadius, whatIsLayer);
         */
        

        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);
    }

    public void Direction()
    {
        if (inputX == -1 && inputZ == 1)//UpLeft
        {
            //left = true;
            //up = true;
            directionVec = new Vector3(-1.14f, 0, 1.14f);

            //down = false;
            //right = false;
        }
        if (inputX == 0 && inputZ == 1)//Up
        {
            //up = true;
            directionVec = new Vector3(0, 0, 2.0f);

            //down = false;
            //right = false;
            //left = false;
        }
        if (inputX == 1 && inputZ == 1)//UpRight
        {
            //right = true;
            //up = true;
            directionVec = new Vector3(1.14f, 0, 1.14f);

            //left = false;
            //down = false;
        }
        if (inputX == -1 && inputZ == 0)//Left
        {
            //left = true;
            directionVec = new Vector3(-2.0f, 0, 0);

            //right = false;
            //up = false;
            //down = false;
        }
        if (inputX == 0 && inputZ == 0)//Idle
        {
            //up = false;
            //down = false;
            //right = false;
            //left = false;
            directionVec = new Vector3(0, 0, 0);
        }
        if (inputX == 1 && inputZ == 0)//Right
        {
            //right = true;
            directionVec = new Vector3(2.0f, 0, 0);

            //up = false;
            //down = false;
            //left = false;
        }
        if (inputX == -1 && inputZ == -1)//DownLeft
        {
            //left = true;
            //down = true;
            directionVec = new Vector3(-1.14f, 0, -1.14f);

            //up = false;
            //right = false;
        }
        if (inputX == 0 && inputZ == -1)//Down
        {
            //down = true;
            directionVec = new Vector3(0, 0, -2.0f);

            //up = false;
            //right = false;
            //left = false;
        }
        if (inputX == 1 && inputZ == -1)//DownRight
        {
            //right = true;
            //down = true;
            directionVec = new Vector3(1.14f, 0, -1.14f);

            //up = false;
            //left = false;
        }
    }

    IEnumerator StateChange()
    {
        stateChange = true;

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

        yield return new WaitForSeconds(stateChangeTime);

        //State.Idle = 0, State.Moving = 1, State.Tracking = 2
        //0과 1까지만 대입
        state = (State)Random.Range(0, 2);
        stateChange = false;
    }

    void OnDrawGizmosSelected()
    {
        //충돌범위 기즈모
        Gizmos.color = Color.yellow;
        Gizmos.DrawSphere(transform.position, overlapRadius);
    }
}

위 코드는 현재 Tracking 함수만 발동되는 코드이다. 기획자님이 일단 추적기능만 제작해 달라고 하여 Idle과 Moving 기능은 작동하지 않는다.

나는 Physics.OverlapSphere의 반환값이 Collider[]이므로 Collider[].Length가 0 이상일 때 플레이어가 범위 안에 들어왔다는 조건을 주었다. 이렇게 작성하면 오류는 일어나지 않지만 이게 좋은 방법인 것 같지는 않다.

이 글을 작성하면서 떠오른 방법인데 추적 기능을 갖는 몬스터 게임 오브젝트에게 Collider 컴포넌트를 연결하고 Collider size를 원하는 추적 범위만큼 늘린 다음 플레이어가 트리거 충돌 했을 경우 추적하는 기능을 만들어도 될 것 같다는 생각이 떠올랐다.

왜 처음에는 이런 생각이 안 났을까..? 나중에 추적 기능이 문제가 생긴다면 위 방법으로 바꾸는 걸 고려해 봐야겠다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다