오늘 한 일
- 챌린지반 과제 진행
- 팀 프로젝트 계획 수립 및 진행
- 객체 지향 특강 듣기
- 기초적인 오브젝트 추적 구현해보기
오늘은 적들이 플레이어를 찾고 추적하는 방법의 기초적인 구현을 해보려고 한다.
1. 플레이어 오브젝트를 식별한다
적 오브젝트는 플레이어를 감지하는 기능 또는 플레이어의 정보를 어디선가 받아와야 된다.
2. 자신과 플레이어 오브젝트까지의 방향을 구한다.
플레이어 Transform이 요구되고, normalized를 하여 단위 벡터를 구한다.
3. 구한 방향을 향해서 플레이어를 쫓아간다.
FixedUpdate를 활용해서 전 과정에서 구한 단위 벡터값을 이용해 플레이어의 방향으로 이동한다.
플레이어의 Transform을 명확하게 아는 상태에서의 추적을 구현해보자
- Player의 오브젝트를 싱글턴화할 수는 없으니 싱글턴된 게임 매니저를 이용해야한다.
(스파르타 코딩클럽 스크립트를 참고해서 만들었습니다)
1. 플레이어 오브젝트 정보 받아오기
public static GameManager Instance; { public Transform Player { get; private set; } // 플레이어의 위치 참조 [SerializeField] private string playerTag = "Player"; // 플레이어 태그 private void Awake() { Instance = this; Player = GameObject.FindGameObjectWithTag(playerTag).transform; // 태그를 통해 플레이어 오브젝트를 찾는다. } } // Enemy.cs private Transform Target { get; private set; } // 플레이어 트랜스폼 참조할 변수 private void Start() { Target = GameManager.Instance.Player; // 게임 매니저에 있는 플레이어 트랜스폼을 받아온다 }
- 게임 매니저로부터 플레이어의 Transform을 받아온다
2. 플레이어 오브젝트 쪽 방향 구하기
private void FixedUpdate() { direction = DirectionToTarget(); // FixedUpdate로 실시간으로 플레이어의 위치를 계산한다. ... } private Vector2 DirectionToTarget() { return (Target.position - transform.position).normalized; // 플레이어까지의 단위 벡터를 구한다. }
3. 구한 방향 쪽으로 이동하기
private void FixedUpdate() { ... CallMoveEvent(direction); // CallMoveEvent 내에서 움직이는 메서드가 구현되어있습니다. }
- 구한 단위 벡터를 이용해서 플레이어 쪽으로 향한다.
결과물
- 플레이어가 어디에 있든 항상 따라온다. (뱀서류에 어울리는 추적 방식)
- 추가 구현 사항을 해주면 플레이어의 방향에 따라서 스프라이트를 뒤집거나 일정 거리 이상은 안 따라가게 하는 등의 기능을 넣을 수 있다.
적 오브젝트가 고유의 Collider를 이용해 플레이어를 추격하는 로직을 구현해보자
- 오브젝트 자체가 탐지 기능을 가지고 있기에 타 오브젝트의 의존도가 낮은 편이다.
(사실 식별 말고는 2번째는 차이가 없으니 식별과 이동에만 집중해보자)
플레이어 식별
public class ColliderVision : TopDownController { [SerializeField] private string targetTag = "Player"; // private GameObject FindTarget; // 찾은 적 오브젝트 정보를 담는다. [SerializeField] private Collider2D colliderVision; // 자식이 가지고 있는 Collider를 가져온다. private void OnTriggerEnter2D(Collider2D collision) // 특정 오브젝트가 Collider에서 진입할 때 호출 { if (collision.CompareTag(targetTag)) // 태그 확인 { ... (추후에 작성) ... FindTarget = collision.gameObject; } } private void OnTriggerExit2D(Collider2D collision) // 특정 오브젝트가 Collider에서 나갔을 때 호출 { if (collision.CompareTag(targetTag)) // 태그 확인 { ... (추후에 작성) ... } }
Collider 설정
- Collider를 이와 같이 설정하였다.
플레이어 추적
private Vector2 targetDirection; // 타겟 방향 (계산은 전 방법과 동일하니 생략) private bool isTargetFind = false; // 타겟 식별 유무 private Vector2 stopMove; // 정지를 위한 벡터 [SerializeField] private Transform colliderTransform; // 충돌체 Transform void Start() { stopMove = Vector2.zero; // 0 벡터 할당 } private void FixedUpdate() // 물리 처리 업데이트 { if (!isTargetFind) // 타겟을 찾지 못했을 경우 { return; } targetDirection = DirectionToTarget(); // 타겟 목표 방향 최신화 CallMoveEvent(targetDirection); // 이동 이벤트 호출 ChangeRotation(targetDirection); // 충돌체 방향 돌리기 } private void OnTriggerEnter2D(Collider2D collision) // 충돌체 진입 시 { if (collision.CompareTag(targetTag)) // 태그 확인 { isTargetFind = true; // 타겟 식별 true FindTarget = collision.gameObject; // 충돌체에 진입한 오브젝트를 할당한다 } } private void OnTriggerExit2D(Collider2D collision) // 충돌체 이탈 시 { if (collision.CompareTag(targetTag)) // 태그 확인 { isTargetFind = false; // 타겟 식별 false targetDirection = Vector2.zero; // 0벡터 할당 CallMoveEvent(stopMove); // 이동 이벤트 멈추기 } }
구현 결과
- 콜라이더에 플레이어가 진입하게 되면 적이 추적을 하기 시작한다.
- 플레이어의 위치를 최대한 놓치지 않기 위해서 콜라이더도 플레이어의 방향에 따라 움직이게 만들었다.
+ 추가 구현 (탐색 기능, 스프라이트 뒤집기)
- 추가 구현을 통해 적 오브젝트에게 매우 간단한 탐색 기능을 만들어서 주변을 탐색하게 만들어봤다.
상시 추적법
장점
- 매우 빠르고 간단하게 구현할 수 있다.
- 참조를 통해 플레이어의 정보를 가져오기 때문에 메모리가 덜 든다.
단점
- 플레이어 정보를 주는 오브젝트에게 의존해야한다.
- 추적 방식의 확장성이 떨어진다.
- 플레이어 중간에 가려지는 오브젝트가 있을 경우의 식별 처리가 힘들다
Collider 추적법
장점
- 탐색과 추적 방식의 확장성이 높다.
- 타 오브젝트에게 의존하지 않아도 된다.
단점
- 오브젝트가 많을 경우 그만큼 연산량이 많아진다.
- 플레이어 중간에 가려지는 오브젝트가 있을 경우의 식별 처리가 힘들다.
FixedUpdate와 이벤트
FixedUpdate에서 이벤트에게 한번이라도 direction을 전달할 경우 그 이벤트는 direction 값이 변경되거나 다른 명령으로 인해 중지될 때까지 계속 활성화된다.
버그 리포트)
private void OnTriggerExit2D(Collider2D collision) { if (collision.CompareTag(targetTag)) { isTargetFind = false; targetDirection = Vector2.zero; // CallMoveEvent(stopMove); <- 이를 제거할 경우 } }
- 플레이어를 놓쳤음에도 플레이어를 식별했던 마지막 방향으로 떠나버린다.
Transform.rotation와 Transform.eulerAngles의 반환값
- Transform.rotation는 쿼터니언으로 사실상 우리가 해석할 수 없는 값을 반환하고
- Transform.eulerAngle는 0~360도 값을 반환해준다.
(atant2 * Rad2Deg와 유니티에 보이는 rotation이랑 전혀 다른 값을 반환하니 주의하자)
내가 만들고 싶은 게임을 구현하기 위해서 더 많은 기능 구현 방법에 대해서 알고 싶어졌다. 내가 아는 선 안에서는 최대한의 기능 구현이 가능한 상태이지만, 아직까지도 모르는 것이 많다고 느껴졌다. 좀 더 많은 학습을 통해서 게임 내의 모든 로직을 구현해 구동이 가능한 나만의 게임을 출시해보고 싶다.