K-digital traning/Final Project

Gazzlers 개발일지 - Enemy 이동(Player 추적)

내꺼블로그 2023. 11. 30. 18:19

 

먼저 enemy가 player 이동 방향대로 움직이도록 구현하였다.

 

 

 

 

우선 enemy를 요로코롬 설정.

 

 

 

 

Scene은 기존에 했던 MapScene에서 TestEnemy1MoveMain과 위의 enemy를 추가한 형태로 구성해보았다.

 

 

 

 

일단 enemy의 움직임을 구현하기 위해 SerializeField로 enemy와 enemy가 쫓아야 될 target의 transform을 할당하였다.

쫓는 위치를 랜덤하게 부여하기 위해 Random.Range를 사용하여 임의의 값으로 arrTargetPos에 접근하도록 한 뒤 enemy에게 target의 transform을 전달하였다.

 

 

Main에다가 target, enemy 할당

 

 

대략적인 target 설정

 

 

 

Enemy1Move의 Field

 

 

Enemy1Move의 UpdateTargetPos method

 

UpdateTargetPos에서는 Main으로부터 받은 transform을 자신의 target으로 할당하는 역할을 한다.

isReady는 target이 설정되지 않은 상태에서 enemy가 target에 접근하려는 것을 막기 위해 설정한 값으로, target이 설정이되면 isReady는 true가 된다.

 

 

 

Enemy1Move의 Update method

 

target이 null 상태면 return, 그렇지 않으면 밑의 코드를 실행한다.

enemy가 targetPos방향으로 회전하도록 LookAt을 사용하고 바라본 방향에서 움직일 수 있도록 Translate를 사용하였다.

 

 



 

1차 결과

 

 

 

나름 따라간답시고 된 거 같은데 어설프다. update문에 LookAt을 바로 돌린거 + target위치의 문제이지 않을까 예상중. 코드를 수정해보쟝!

 

 

 

 

 

수정된 코드다. player의 방향이 전환되자마자 바로 enemy의 방향도 전환되던 기존의 코드와 달리 특정 시간이 경과될 때마다 target의 position을 받아 slerp하는 방식으로 작성하여 방향 전환이 지연되는 것처럼 보이도록 구현하였다.

 

 

 

 

결과

 

 

 

아까보단 나은 것도 같,,,,,,고? 아닌것도 같,,,,,,,,,,,,,,,,고,,,,,,,,,,,,,,,,

 

 

 

 

 

일단 코드 ㄱㄱ

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

    public class TestEnemy1MoveMain : MonoBehaviour
    {
        [SerializeField] private Enemy1Move enemy;
        [SerializeField] private Transform[] arrTargetPos;
        // Start is called before the first frame update
        void Start()
        {
            int rand = Random.Range(0, arrTargetPos.Length);
            this.enemy.UpdateTargetPos(this.arrTargetPos[rand]);
        }

        //void Update()
        //{

        //}
    }

 

 

 

 

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

    public class Enemy1Move : MonoBehaviour
    {
        private Transform targetTrans;
        private Vector3 Dir;
        private bool isReady;
        public float moveSpeed = 3f;
        public float rotSpeed = 3f;
        private float elapsedTime = 0;
        public float updateDirTime = 0.1f;

        private void Update()
        {
            this.transform.Translate(Vector3.forward * this.moveSpeed * Time.deltaTime);
            if (!this.isReady) return;
            this.elapsedTime += Time.deltaTime;
            if (this.elapsedTime > this.updateDirTime)
            {
                this.Dir = this.targetTrans.position;
                this.elapsedTime = 0;
            }
            Vector3 moveDir = this.Dir - this.transform.position;
            this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(moveDir), this.rotSpeed*Time.deltaTime);
        }

        public void UpdateTargetPos(Transform targetTrans)
        {
            this.targetTrans = targetTrans;
            if (!isReady) { 
                isReady = true;
                this.Dir = this.targetTrans.position;
            }
        }
    }

 

 

 

 

 

 

 

 

한쪽만 바라보고 이동하는 것은 좀 심심한듯하니(?) 기존 코드를 수정하여 targetPos를 랜덤하게 돌려보쟝

 

 

기존 TestEnemy1MoveMain 스크립트에 SetTargetPos 함수를 추가하였다.

SetTargetPos에서는 기존코드처럼 랜덤하게 Enemy에게 TargetPos를 알려주지만 이미 target이 선정된 상태인지 아닌지를 구분하여 Setting해준다는 차이점이 있다.

isTargeted에서 선정되지 않은 target의 index를 저장하는 listIndex를 선언 및 할당하였다.

그 뒤 listIndex의 Count를 초과하지 않는 선에서 난수를 생성하고 그 난수로 listIndex에 접근하여 listIndex[rand]에 할당되어 있는 값을 취한다.

idx와 idx로 접근한 arrTargetPos에 할당된 transform을 enemy에게 전달하고 isTargeted[idx]를 true로 전환!(새로 target으로 선정되었음)

 

 

 

 

 

기존 Enemy1Move의 TargetTrans를 transform이 아닌 KeyValuePair로 설정하였다.

int는 TestEnemy1MoveMain에서 선언된 arrTargetPos 내에서 Target.Value가 저장되어 있을 index를 할당하는 부분이다.

int를 선언한 이유는 enemy가 target을 바꿀 때 해제된 기존 target을 TestEnemy1MoveMain에게 알리기 위함이다.

 

 

 

target이 변경될 시간을 임의로 선언하였으며 target을 변경해야 함을 main에게 알리기 위해 대리자를 선언하였다.

대리자를 선언할 때 int값을 넘겨주는 이유는 현재 target으로 잡은 transform의 index를 main에 알리게 하여 현재 target으로 선정되어 있지 않음을 나타내야 하기 때문이다.

 

 

기존 UpdateTargetPos의 수정된 코드이다.

기존 코드에서는 transform만 받고 저장했던 것과 달리 현 코드에서는 main에서 transform이 저장된 index와 transform이 같이 Target에 저장되고 있음을 알 수 있다.

또한 Target을 받는게 처음이라면, 즉 첫 시작인 부분이라면 CoChangeTarget이라는 코루틴을 돌리도록 구현하였다.

 

 

CoChangeTarget이다. enemy가 살아있는 한 계속 돌아가는 method이다.

CoChangeTarget에서는 기존에 설정해놓은 특정 시간(changeTargetTime)이 지나면 대리자인 onChangeTarget을 호출하도록 구현되어있다.

 

 

main의 Start method에서는 enemy의 대리자인 onChangeTarget이 선언되어 있다.

onChangeTarget에서는 target을 다시 설정하는 SetTargetPos를 호출하고 이전 타겟이 현재 선언되어 있지 않음을 저장하기 위해 idx를 이용하여 isTargeted[idx]를 false로 전환하고 있다.

 

 

결과

 

 

나무에 부딪히는게 마음에 들지 않다만 우선 구현하고 싶은 부분은 구현되었당,,,,,

 

 

 

 

 

 

전체코드

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

    public class TestEnemy1MoveMain : MonoBehaviour
    {
        [SerializeField] private Enemy1Move enemy;
        [SerializeField] private Transform[] arrTargetPos;
        private List<bool> isTargeted = new List<bool>();
        // Start is called before the first frame update
        void Start()
        {
            for(int i = 0; i < arrTargetPos.Length; i++)
            {
                isTargeted.Add(false);
            }
            this.SetTargetPos();
            this.enemy.onChangeTarget = (idx) =>
            {
                Debug.Log("target change!");
                Debug.LogFormat("<color=yellow>preTargetPos: {0}</color>", arrTargetPos[idx]);
                this.SetTargetPos();
                this.isTargeted[idx] = false;
            };
        }

        //void Update()
        //{

        //}

        private void SetTargetPos()
        {
            List<int> listIndex = new List<int>();
            for(int i = 0; i < this.isTargeted.Count; i++)
            {
                if (!isTargeted[i])
                {
                    listIndex.Add(i);
                }
            }
            int rand = Random.Range(0, listIndex.Count);
            int idx = listIndex[rand];
            this.enemy.UpdateTargetPos(idx, this.arrTargetPos[idx]);
            this.isTargeted[idx] = true;
            Debug.LogFormat("<color=magenta>curTargetPos: {0}</color>", arrTargetPos[idx]);
        }
    }

 

 

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


    public class Enemy1Move : MonoBehaviour
    {
        private KeyValuePair<int, Transform> Target;
        private Vector3 Dir;
        private bool isReady;
        public float moveSpeed = 3f;
        public float rotSpeed = 3f;
        private float elapsedTime = 0;
        public float updateDirTime = 0.1f;
        public float changeTargetTime = 5f;
        public System.Action<int> onChangeTarget;

        private void Update()
        {
            if (!this.isReady) return;
            this.transform.Translate(Vector3.forward * this.moveSpeed * Time.deltaTime);
            this.elapsedTime += Time.deltaTime;
            if (this.elapsedTime > this.updateDirTime)
            {
                this.Dir = this.Target.Value.position;
                this.elapsedTime = 0;
            }
            Vector3 moveDir = this.Dir - this.transform.position;
            this.transform.rotation = Quaternion.Slerp(this.transform.rotation, Quaternion.LookRotation(moveDir), this.rotSpeed*Time.deltaTime);
        }

        public void UpdateTargetPos(int idx, Transform targetTrans)
        {
            this.Target = new KeyValuePair<int, Transform>(idx, targetTrans);
            if (!isReady) { 
                isReady = true;
                this.Dir = this.Target.Value.position;
                this.StartCoroutine(this.CoChangeTarget());
            }
        }

        private IEnumerator CoChangeTarget()
        {
            while (true)
            {
                yield return new WaitForSeconds(this.changeTargetTime);
                this.onChangeTarget(this.Target.Key);
            }
        }
    }

 

 

 

 

 

 

 

 

현재 구현에서는 enemy의 시작 위치가 이미 자리잡은 곳으로 되어있다. enemy가 주어진 자리가 아닌 랜덤한 곳에서 spawn될 수 있도록 코드를 수정해보쟈.

 

 

Enemy1Move스크립트에 Init함수를 작성하여 enemy의 위치를 받아온 pos로 할당하였다.

 

 

 

main의 SpawnEnemy에서는 enemy의 위치를 지정해주고 있다.

x와 z 값을 Random.Range로 할당한 뒤 그 값을 이용하여 Vector3 pos를 계산하였다.

계산하고 나온 값 pos는 enemy.Init을 호출할 때 사용되었다.

 

 

 

enemy의 시작 위치가 random하게 나오고 있는 모습이다.