Unity 최종 프로젝트 - 15

이준호·2024년 2월 3일
0

📌 Unity 최종 프로젝트



📌 중간 발표 정리

➔ 적용 기술 설명

Behavior Tree

기술 도입의 이유

  • 유한 상태 기계(Finite State Machine)가 비교적 간단한 행동표현들을 하기에 매우 직관적이고 관리 유용한것은 분명합니다.

  • 하지만 Enemy의 경우는 플레이어가 직접 키를 누르며 조종하며 상호작용하는 것이 아닌, Enemy가 알아서 그 상황에 맞는 적절한 행동이 나오도록 하게 해야합니다.

  • 그러기 위한 상황에 따른 조건의 분기들이 규모가 커지면 커질수록 오히려 스파게티처럼 꼬이고 복잡해져서 유지보수성이 떨어지고 가독성이 나빠집니다. 계층적 유한 상태기계(Hierarchical Finite State Machine)을 사용하면 나아지긴 하지만 Enemy의 입장으로는 근본적인 문제에 대한 해결책은 되지 못합니다.

  • 하지만, BT를 사용하여 트리 구조를 계측적으로 잘 짜두어 이미지화를 시켜 그에 맞게 클래스들을 나눠 행동 로직들의 추가나 제거가 편리해져 유지보수에 용이합니다.

  • 또한 한번 잘 짜둔 트리 구조를 기반으로 파생형을 만들거나, 다른 행동들을 편하게 확장하는 것이 가능하여 BT를 선정하였습니다.

구현 방법 및 구성

  • EnemyAI.cs에서 생성자를 통해 트리 구조를 구성

  • 만든 트리 구조를 BehaviorTreeRunner에 생성자의 파라미터에 할당하여 객체 생성

  • .Operater 메소드로 트리를 순회

  • Behavior Tree Core (BT를 돌아가게 하는 베이스)

    • INode
      • 노드의 통일성을 위한 Interface
      • 노드의 상태를 반환하는 Evaluate() 메소드
    • BehaviorTreeRunner
      • Root Node를 실행시켜주는 Operate 메소드
      • 처음 BT를 실행시켜주는 역할.
    • DataContext
      • BT를 이용한 AI를 만들 때, 사용할 컴포넌트 및 데이터변수 저장소
  • Action Node (실제 행동을 정의하는 가장 말단의 Leaf)

    • Func를 사용하여 실제 행동을 정의.
  • Composite Node (자식 노드가 2개이상, 자식 노드들의 평가를 진행)

    • RandomSelector
      • 하위 노드중 하나를 랜덤으로 진행
    • Selector
      • 자식 노드의 상태가 Fauilure라면 다음 자식노드 진행. (OR)
    • Sequence
      • 자식 노드의 상태가 Success라면 다음 자식노드 진행. (AND)
  • Decorator Node (자식 노드를 1개만 가질 수 있다, 자식을 꾸며준다)

    • Inverter
      • 자식 노드의 상태를 반전
    • Repeat
      • 자식 노드의 상태에 상관없이 지정된 횟수 반복 후 Success반환
    • Succed
      • 자식의 상태에 상관없이 항상 성공을 반환
    • UntilFail
      • 자식 노드가 실패를 반환할 때까지 계속 재검사

개선해야 할 점

  • 현재 Action Node를 Func를 이용해 생성자에서 생성할 때, 실제 구현 Action을 메소드로 받아 실행하는 형식입니다.

  • 이러한 방식은 간단한 BT를 구성 할 때는 매우 편리하지만, 점점 노드들이 늘어나다 보니 가독성이 매우 안좋아졌습니다.

  • 그래서 각 Action Node들을 클래스로 각각 나누고 Scriptable Object를 이용하여 유지보수 및 가독성을 향상시키는 작업을 준비중입니다.






➔ 트러블 슈팅

Field Of View

기술 도입의 이유

  • Enemy AI를 구현하며, 그저 어떠한 감지 범위 안에 들어와 인식하는 것이 아닌 실제 시야로 확인하여 인식하는 느낌을 주어 현실성과 자연스러움을 위해서 시야각을 이용한 인식(Field Of View)를 사용했습니다.

구현 방법

  • ”Physics.OverlapSphere” 를 이용하여 Player의 LayerMas를 체크하여 해당 범위에 들어오면 값을 할당합니다.

  • 값이 할당 된다면, 조건문을 통해 예외를 방지하고 ‘player의 transform’과 ‘Enemy에서 player로 향하는 방향'을 변수에 각각 저장합니다.

  • Vector3.Dot을 통하여 ‘Enemy의 정면’과 ‘Enemy에서 플레이어로 향하는 방향’의 내적이 0.9 이상인지 확인합니다.

  • 조건문이 True라면, Enemy에서 Player까지의 거리를 구하고 그 거리만큼 Enemy의 위치에서 Player방향으로 Ray를 쏩니다.

  • Ray에 “Obstacle(장애물)” LayerMask가 걸리지 않는다면, Enemy에게 Player의 transform을 할당하여 추적 및 공격을 실행하게 합니다.

개선한 내용과 이유

  • 원래는 Vector3.Dot이 아닌 Vector3.Angle을 이용하여 각도를 이용해 판단을 했었습니다.

  • 그 이유로는 Angle함수은 내부적으로 “제곱근 계산과 ArcCos계산 등 연산 비용이 높습니다" Dot함수는 “곱셈 및 덧셈만을 사용하기에 비교적 연산 비용이 비교적 크지 않습니다.

  • Enemy의 AI가 Behavior Tree를 이용하고 Tree의 순회가 자주 일어나는데 그 중에서도, Player를 감지하고 판단하는 노드는 가장 자주 일어나는 노드중 하나입니다. 그렇기에 이 부분에서 최대한 최적화를 해주기 위해서 Angle -> Dot을 사용하게 되었습니다.




Custom Editor

기술 도입의 이유

  • Field Of View를 이용한 범위 및 시야가 실제 잘 작동하는지 눈으로 확인할 수가 없어 어려움을 겪었습니다. 그 문제를 해결하기 위해 Custom Editor를 이용하여 눈으로 확인이 가능하도록 그려주기 위해서 사용했습니다.

구현 방법

  • 그려주는 코드를 짤 스크립트를 만들고 “Editor”를 상속시켜줍니다.

  • 클래스 위에 [CustomEditor(typeof(클래스 이름))] 이 에디터를 사용할 스크립트의 클래스 이름을 넣어 해당 타입으로 받아옵니다.

  • Scene View에서 GUI를 표시하는 함수인 “OnSceneGUI”를 사용합니다.

  • OnSceneGUI 메소드 안에 사용해야 할 정보들을 가져오고 Handles 클래스를 이용해 내부 메서드 color과 DiaWireArc를 이용해 색과 원(감지 범위)을 그려줍니다.

  • 시야각에 해당하는 값을 구해서 Handles.DrawLine을 이용해 시야각을 그려줍니다.

  • Enemy가 감지된 플레이어가 할당되어 있다면, DawLine을 이용해 Enemy에서 Player까지를 그려줍니다.

개선한 내용과 이유

  • 처음에는 Gizmos를 이용하여 그려서 확인하였는데, 2D 평면이 아닌 3D 입체적으로 밖에 그려지지 않아서 확인은 가능해도 불편함도 크고 따로 코드 관리하기도 불편하였습니다.

  • 그리하여 Custom Editor를 이용하여 따로 코드를 관리하여 유지보수도 편하고 2D 평면으로 그려져 다른 건물들과 걸리지 않게 확인이 가능하였습니다.

profile
No Easy Day

0개의 댓글