정보 전달성 포스팅이라기 보다는 TIL 기록 겸 설계를 위한 생각 정리용 포스팅이 되겠다.
현재 유니티에서 BT를 구현하고 있는데, 프로토타입 기본 기능까지는 완성했으나 응용에 여러 어려움이 있어 오히려 구현 과정이 복잡해지는 느낌이다.
특히 변수 처리. 트리를 만들 때마다 정의한 task 내부에서 변수를 처리하고 있는데 이 부분이 비효율적이라 석연치 않다.
근본 문제가 무엇인가 고민해보니 기능 구현에 급급해 일반화를 하지 않아서 라는 결론이 나왔다.
따라서 우선 목표를 다음과 같이 잡고 프레임워크를 수정하려 한다.
개발 목표
- 유니티로 구현하지만 Base node들은 최대한 엔진에 종속적이지 않게 설계할 것
- 혼자 구현하고 활용하는데 그치지 않고, 타인도 사용하는 코드라고 생각하며 확장성과 일반화를 고려할 것
나에게 친숙한 것은 언리얼에서 사용되는 BT라 이번 포스팅에서는 이것과 일반 비헤이비어 트리의 기본적인 사항을 다시 정리하고 해당 내용을 반영해 설계를 손 볼 것이다.
참고한 자료는 언리얼 공식 문서와 이득우의 언리얼 C++ 게임 개발의 정석 등이다.
https://docs.unrealengine.com/5.0/ko/behavior-tree-in-unreal-engine---quick-start-guide/
비헤이비어 트리 퀵 스타트 가이드
이 가이드에서는 순찰하거나 플레이어를 추격하는 AI 캐릭터를 비헤이비어 트리로 구성하는 방법을 알아봅니다.
docs.unrealengine.com
· 오늘 공부한 내용
비헤이비어 트리(Behavior Tree, BT)
- AI가 해야 할 행동을 분석하고 우선순위가 높은 행동부터 실행할 수 있도록 트리 구조로 설계하는 기법
- 내부에 구현된 로직을 실행함
특징과 사용시 장점
- 가질 수 있는 노드와 자식 수에 제약이 없다. 그렇기 때문에, 확장이 자유롭고 복잡한 AI를 표현하는데 적합하다
- Task(비헤이비어 트리가 수행할 액션)를 재사용하여 중복 코드를 줄일 수 있으며 빠르고 간편하게 개발할 수 있다.
비헤이비어 트리의 기본 구조
- 잠재적 복잡도로 인해 대부분 비동기로 구현된다.
- 계층 구조 가장 말단에 있는 노드를 리프(Leaf) 노드라고 하며, 언리얼 비헤이비어 트리에서는 Task라고 한다. 각 Task Node는 행동 또는 테스트 등을 표현할 수 있다.
- 트리 최상위의 Root 노드에서부터 평가를 시작하여 자식 노드를 타고 이동하면서 원하는 조건이 만족 될 때까지, 또는 가장 마지막 노드에 도달할 때까지 순회한다.
- 비헤이비어 트리의 각 노드는 항상 다음 3개의 상태 중 하나를 반환한다.
- Success : 노드가 조건을 만족할 때
- Failure : 노드가 조건을 만족하지 않을 때
- Running : 노드에서 검사하는 조건의 유효성이 정의되지 않았을 때, 즉 현재 노드의 평가가 진행 중일 때를 의미
▶ 노드 평가 과정이 필요에 따라 수 프레임간 진행 될 수 있고, 다수의 에이전트에 대한 평가가 이루어질 수도 있기 때문에 결가가 나오기까지 무한정 기다릴 경우 성능에 영향을 끼칠 수 있다. 이러한 문제를 방지하기 위해 해당 상태가 존재하는 것이다.
+) 언리얼에서는 상술한 부분을 기반으로 아래의 상태들을 사용한다.
- Succeeded : 테스크를 성공적으로 수행했다.
- Failed : 태스크를 수행했지만 실패했다.
- Aborted: 태스크 실행 중에 중단됐다. 결과적으로 실패했다.
- InProgress : 태스크를 계속 수행하고 있다. 태스크의 실행 결과는 향후 알려줄 예정이다.
합성노드 (Composite, 컴포짓)
- 하나 이상의 자식을 가지는 노드
- 루트 노드에는 이 컴포짓 노드만 어태치할 수 있다.
- 합성 노드의 상태는 자식들의 평가 결과로 결정되며, 자식들의 평가가 진행 중일 때는 Running 상태가 된다.
- 합성 노드의 형태는 자식의 평가 방식에 따라 나뉘며 기본적으로 시퀀스와 셀렉터, 2개의 종류가 있다. 언리얼 비헤이비어트리에는 추가로 단순 병렬이라는 1개의 노드가 더 존재한다.
- 시퀀스(Sequence) : 일종의 AND 연산자. 왼쪽에서부터 오른쪽으로의 깊이 우선 탐색을 실시한다. 이때 전체 자식이 모두 조건을 만족할 때만 성공으로 인정한다. 반대로 한 자식이라도 조건을 만족하지 못하면 실패한다. 실패하는 노드에 도달 할 때까지 자식을 계속해서 실행시키며, 이러한 특성 때문에 일반적으로 복수의 자식을 순서대로 실행시키는데 활용된다.
EX) 몬스터의 정찰 패턴. 타겟이 시야 안에 없을 때 잠시 기다렸다가(Wait) -> 순찰 위치를 선택하고(SetPatrolPos) -> 해당 위치로 이동(Move To)하는 일련의 동작들을 순서대로 실행
EX) 몬스터의 공격 패턴. 플레이어를 향해 움직여 일정 거리 안에 들어왔는지 확인(1)한 다음 회전(2)하여 공격(3)하는 기능이라고 할 때, 플레이어가 일정 거리 안에 들어왔는지 확인하는 (1)번 자식이 실패하면, 이후 순서로 존재하는 (2)번과 (3)번 자식에 할당된 기능은 실행되지 않는다. - 셀렉터(Selector) : 일종의 OR 연산자. 시퀀스와 동일하게 왼쪽에서부터 오른쪽으로의 깊이 우선 탐색을 실시한다. 시퀀스와 다른 점은 실행 시에 자식 노드 중 하나라도 조건을 만족하면 성공으로 인정하며, 실패에 도달할 때까지 계속 자식을 실행시키는 시퀀스와 달리 나머지 자식들을 더 이상 검사하지 않고 즉시 성공을 보고한다. 반대로 모든 자식이 조건 검사를 만족하지 못하면 실패를 보고한다. 이러한 특성 때문에 복수의 자식 중에 하나를 선택하는데 사용한다.
EX) 몬스터의 공격 패턴. 타겟이 시야에 있을 때 공격 가능 범위 안에 해당 타겟이 있는지 유무에 따라 공격을 하는 기능을 구현할 때, 셀렉터를 이용하면 공격을 한다(Can Attack)와 공격 하지 않는다(Can Not Attack)라는 2개의 상태 중 하나를 선택하여 실행할 수 있다.
EX) 몬스터의 추격 패턴. AI가 플레이어를 추격하는 기능이 적용된 자식 노드가 성공적으로 실행 중이라면, 해당 노드의 실행이 완료될 때까지 계속 해당 노드에 머물러 있다가, 완료되면 셀렉터의 부모 노드로 이동하여 다음 동작을 결정하는 흐름을 계속 진행하게 된다.
서브 노드 (데코레이터 노드)
- 합성 노드와 달리 단 하나의 자식 노드를 가진다는 차이가 있다.
- 노드 자체에 조건을 두고 실행하는 것과 결과적으로는 동일한 기능이지만 차이가 있다. 데코레이터 노드는 근본적으로 자식이 반환한 상태를 취하고, 스스로 매개변수에 기반하여 반응한다. 또한 자식이 평가되는 방식과 횟수를 직접 결정할 수도 있다.
- 데코레이터 노드의 종류는 다음과 같다.
- 인버터(Inverter)
- 일종의 Not 수정자이다. 자식이 반환한 상태의 반대를 취한다. 따라서 자식이 True를 반환했을 때의 데코레이터 평가는 False가 되며, C#에서 bool 변수 앞에 ! 오퍼레이터를 붙인 것과 동일한 결과가 나온다. - 리미터(Limiter)
- 에이전트가 무한 루프에 빠지지 않도록 평가 횟수를 제한하는 기능을 가진다. 이 데코레이터에서는 리피터와 반대로 조건이 아닌 횟수를 제한할 때 유용하게 사용된다.
EX) 포기할 때 까지 문을 걷어차는 횟수 - 데코레이터(Decorator)
- 언리얼 비헤이비어 트리의 파란색 노드가 바로 이 데코레이터다. 일반적인 BT 시스템에서는 조건식이라고도 한다.
- 일반적인 BT에서는 데코레이터에서 정의된 대로 True 또는 False가 될 때까지 정해진 횟수(또는 무한대)만큼 반복하면서 노드를 평가한다.
EX) "에너지가 충분히 참" 상태가 될 때까지 계속해서 조건을 검사하다가 성립될 때 공격을 시작하는 기능 구현 가능
- 언리얼에서 이 노드는 컴포짓 노드와 태스크 노드에 어태치(일반적으로는 컴포짓에서 많이 쓰임)되어 블랙보드 키가 True인지 검증하는데 사용되며, 이를 통해 트리의 분기나 단일 노드의 실행 여부를 결정하는 조건식으로 쓰인다. 정해진 횟수만큼 반복하여 노드를 평가하는 기능은 언리얼에서 서비스(Service) 기능으로 별도 구현한 것으로 보인다.
EX) 블랙보드 키 값을 결정하고 이것이 유효한 경우 해당 분기를 실행할 수 있도록 구성할 수 있음 - 언리얼에서 제공하는 기본 데코레이터 종류와 세부사항에 대해서는 하단의 문서를 참고
https://docs.unrealengine.com/5.0/ko/unreal-engine-behavior-tree-node-reference-decorators/
비헤이비어 트리 노드 레퍼런스: 데코레이터
비헤이비어 트리 데코레이터 노드의 레퍼런스 정보입니다.
docs.unrealengine.com
- 데코레이터 노드들은 다음처럼 디버깅과 테스트 용도로도 사용할 수 있다.
- 가짜 상태 : 데코레이터에 정의된 대로 항상 참이나 거짓을 반환하여 에이전트의 특정 행동을 검증하고자 할 때 사용한다. 또는 주변의 다른 에이전트를 관찰하기 위해서 가짜로 Running 상태를 유지시킬 수도 있다.
- 브레이크 포인트 : 코드에서의 브레이크포인트와 같다. 이 노드에 도달하면 디버그 노드나 다른 메소드를 통해 알림을 받게끔 설정할 수 있다. - 데코레이터 노드들은 상호 배타적인 단일체 원형이 아니기 때문에 각자의 목적에 맡게 조합할 수 있으나 너무 많은 기능을 하나의 데코레이터에 결합하는 것은 지양해야 한다. 이 경우에는 효율성과 편의성 측면에서는 시퀀스 노드를 사용하는 쪽이 더 나을 수도 있기 때문이다.
- 서비스(Service)
- 이 노드는 다른 비헤이비어 트리 시스템에서의 전통적인 병렬(Parallel) 노드를 대체하기 위해 언리얼 엔진에서 활용하는 특수 노드이다.
- 태스크(Task) 및 셀렉터, 시퀀스, 단순 병렬을 포함한 컴포짓(Composite) 노드와(이쪽이 일반적) 태스크 노드에 어태치하여 자신의 분기가 실행되는 동안 정의된 주기만큼 실행된다. 지정된 시간(초)마다 콜백을 등록하고 주기적으로 발생시킬 필요가 있는 다양한 유형의 업데이트를 수행할 수 있으며 보통 블랙보드의 확인 및 업데이트에 사용된다.
- 디폴트로 사용할 수 있는 서비스가 기본으로 제공되며, 커스텀 서비스를 생성할 수 있으며 이러한 서비스를 이용해 비헤이비어 트리 의 실행 방식을 결정하는 데 보조적으로 사용하는 경우가 많다.
EX) AI 폰이 현재의 적을 쫓는 비헤이비어 트리를 정상적으로 따라가는 동안 어느 적이 최적의 대상인지 결정하기 위해 서비스를 사용할 수 있음
커스텀 노드로 Detect 기능을 작성할 수 있고 이에 따른 세부 구조는 다음과 같다.
- Interval : 틱 간격으로, 설정된 시간 간격만큼 서비스의 틱이 실행된다.
- Random Deviation : 랜덤 편차로, 틱 간격에 임의의 랜덤 범위를 적용한다.
- 단순 병렬(Simple Parallel, 심플 페러렐) : 단순 병렬 처리를 위한 언리얼 비헤이비어 트리의 추가 컴포짓 노드로, 자손을 두 개만 갖는다. 한 자손은 반드시 단일 태스크 노드여야 하며(데코레이터 선택 가능), 나머지 자손은 완전한 서브트리일 수 있다. 이를 통해 전체 노드 트리와 함께 하나의 메인 태스크를 실행할 수 있다. '적을 공격하는 동안 적을 향해 이동' 처럼 'A를 수행하는 동안 B도 수행한다'와 같은 식이다. 이때 A는 메인 태스크이고, B는 A가 완료되기까지 기다리는 도중의 부가 태스크 또는 필러 태스크이다.
- 부가 태스크(태스크 B)를 처리하는 방법에는 몇 가지 옵션이 있지만, 노드 개념은 전통적인 병렬 노드에 비해 비교적 단순하다. 그러면서도 병렬 노드가 사용되는 일반적인 경우를 대부분 지원한다. 단순 병렬 노드를 사용하면 이벤트 주도형 최적화를 활용하기 쉽지만, 완전 병렬 노드는 최적화하기 훨씬 까다롭다.
- Finish Mode에 따라 메인 테스크의 실행이 완료되었을 때 백그라운드 트리가 처리되는 방식을 선택할 수 있다. Immediate를 선택하면 메인 테스크 종료 즉시 바로 백그라운드 트리를 중단하며, Delayed를 선택하면 메인 테스크가 종료된 후 백그라운드 트리가 끝날 때까지 기다린다. 이러한 특징 때문에 동시에 2가지 작업을 하도록 병렬 실행을 설정하는 데 활용된다.
Task(= 리프 노드)
- AI가 수행할 수 있는 액션을 의미하며 트리의 가장 말단에 위치한다. 언리얼에서 보라색으로 표시되는 노드이다.
- 리프 노드는 어떤 종류든 제약 없이 행동을 지정할 수 있으며 에이전트가 가질 수 있는 모든 로직을 표현할 수 있다. 단지 계층의 가장 마지막 노드라는 특징만 있으며 후술할 3개의 상태 중에 하나의 상태만 반환할 수 있다.
- 리프 노드는 독립적으로 실행될 수 없고 반드시 합성 노드를 거쳐 실행되어야 한다.
- 언리얼에서 제공하는 기본 태스크의 종류와 세부사항에 대해서는 다음 문서를 참고
https://docs.unrealengine.com/5.0/ko/unreal-engine-behavior-tree-node-reference-tasks/
비헤이비어 트리 노드 레퍼런스: 태스크
비헤이비어 트리 태스크 노드의 레퍼런스입니다.
docs.unrealengine.com
- 현재 작업하고 있는 Task인 Move To의 작동 방식에 대한 의문점이 있어 언리얼 공식 문서 외의 자료들을 찾아보았다. 아래 포스트가 궁금했던 내용에 대해 잘 정리되어 있어 링크를 첨부한다.
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=raveneer&logNo=220796853517 - 참고로 언리얼의 네비게이션 시스템이 목적지로 폰을 이동시킬 때는 SimpleMoveToLocation 함수를 사용한다고 한다.
- Move To 태스크에서 사용하는 이동 함수가 AI Move To인지 SimpleMoveToLocation 인지 모르겠다. 관련 내용을 더 찾아보도록 해야지.. 했는데 아래의 영상을 찾았다.
- https://www.youtube.com/watch?v=JrgETMo6jko
- 타겟으로 지정된 Player 자체를 추적(location이 아니라) 하며 허용 반경에 들어가 태스크가 성공 상태가 되어 종료하더라도 BT 구조상 다시 상위 노드로 이동하기 때문에 플레이어 위치가 갱신되면 갱신된 위치로 다시 이동하게 된다. 즉, 계속해서 위치가 갱신된 플레이어를 따라 간다.
언리얼에서의 사용
- 언리얼에서는 BT와 블랙보드를 함께 활용함
- 언리얼 사용 시 일반적으로 요구되는 선행 작업은 AI 컨트롤러와 NavMesh의 적용(이동 관련 로직을 적용할 경우)이며, 이후 블랙보드 생성과 비헤이비어 트리를 구현한다. C++로 개발할 때에도 캐릭터 에 적용할 AI 컨트롤러 클래스 안에 비헤이비어 트리와 블랙보드를 할당하여 사용한다. 이때 비헤이비어 트리가 AI 컨트롤러에서는 컨트롤러가 폰에 '빙의'할 때 비헤이비어 트리를 실행한다.
- 언리얼 비헤이비어 트리는 총 3개의 패널로 구성되어 있다.
- 비헤이비어 트리 그래프 : 실제 분기와 노드의 레이아웃을 포함한 Tree 구현
- 디테일 : 각 노드의 프로퍼티를 정의
- 블랙보드 : 게임이 실행 중일 때 블랙 보드 키와 현재 값을 확인할 수 있는 디버깅 용 패널
- 커스텀 태스크, 데코레이터, 서비스를 만들 수 있다.
블랙보드
- 인공지능의 판단에 따라 사용하는 데이터 집합으로 여러 사용자 정의 키(Key, 모니터링 할 변수) 를 추가하고 트래킹할 수 있으며, NPC의 의사 결정은 이 블랙보드에 있는 데이터를 기반으로 진행됨
- Root의 디테일 부분에서 해당 트리에서 활용할 블랙보드를 선택할 수 있음
- 세부적인 구조는 다음과 같이 구성됨
- Key의 이름 : 사용자가 정의한 키 이름
- 설명 : 해당 블랙보드 키에 대한 설명을 적을 수 있다. 필수요소X, 개발 편의를 위한 기능
- Key 타입 : 키 타입(Key Type) 키를 생성할 때 정의되며, 지원하는 타입은 Bool, Class, Enum, Float, Int, Name, Object, Rotator, String, Vector가 있다. 오브젝트(Object) 및 클래스(Class) 키는 특정 클래스를 정의하는 추가적인 옵션을 제공하며 어떤 베이스 액터 클래스를 사용할지 정의할 수있다. 또한 열겨형은 열거형 타입(사용하도록 할당된 열거형), 열거형 이름(C++ 코드로 정의된 열거형 이름으로, 열거형 타입 에 할당된 에셋보다 우선함), 열거형 이름 유효 여부(열거형 이름 오버라이드가 유효하고 가능하며 활성화된 경우 설정)으로 구성되어 있다.
- 타입의 Base Class
- 동기화된 인스턴스(Instance Synced) : True로 설정 시 해당 키는 이 블랙보드의 모든 인스턴스에서 동기화되어 같은 값을 가진다.
- Parent
- BT에서 사용할 변수 문제를 이것으로 해결할 수 있을 것으로 보인다!
- 처음엔 단순하게 글로벌 변수들의 집합인가 했는데 어떤 경우에는 로컬 변수로 사용될 때가 있어서, 내부에서 기능적으로 이 둘을 구별하는 것이 명확해보임.
- 구현 시 클래스 내부에 변수를 Get/Set하는 부분과 디버그를 위한 테이블 값 출력 기능 필요
- 다양한 타입의 변수를 저장해야하니 hashtable을 사용하는 것이 적합하다고 생각했으나, 딕셔너리로 구성해서, Object 타입으로 변수들을 저장하는 쪽이 더 나은 것이 아닌가 고민이 된다. 기본적으로 task에 사용될 트랜스폼 정보라던지.. 다양한 변수들이 Object로 저장된 객체 하위에 있을 것이므로. 이 부분은 더 생각해보자
AI 인지 컴포넌트(AIPerception Component)
https://docs.unrealengine.com/4.27/ko/Basics/Components/AI/
AI 컴포넌트
AI 인지에 사용되는 AI 관련 컴포넌트 및 폰 감각에 대한 설명입니다.
docs.unrealengine.com
https://docs.unrealengine.com/5.0/ko/ai-perception-in-unreal-engine/
AI 퍼셉션
이 문서에서는 AI 퍼셉션 컴포넌트를 소개하고 이를 사용하여 AI 인지 기능을 생성하는 방법에 대해 설명합니다.
docs.unrealengine.com
언리얼 비헤이비어 트리의 차이점
1. 이벤트 주도형 비헤이비어 트리
- 기존 BT 시스템과 달리 언리얼 비헤이비어 트리는 이벤트 주도형이다. 즉 프레임마다 불필요한 작업을 하지 않는다. 관련 변경 사항이 발생했는지는 계속해서 체크하지만, 트리 자체는 트리 내부의 변경 사항을 트리거 할 수 있는 '이벤트'를 수동적으로 리스닝한다. 추기적인 반복 작업이 발생하지 않기 때문에 퍼모먼스와 디버깅이 모두 개선된다. 디버깅 시에는 트리 내의 실행 위치 또는 블랙보드 값의 변경만 고려하면 된다.
2. 조건문이 리프 노드가 아님
- 표준 BT에서 조건문은 Task 리프 노드에 해당되며, 성공과 실패 이외에는 아무것도 하지 않는다. 반면 언리얼 비헤이비어 트리에서 조건문은 데코레이터를 사용해 처리한다. 자신이 제어하는 서브트리의 루트에 다음과 같이 조건문을 붙여주는 것이다.
이와 같이 구현된 이유는 다음과 같은 이점 때문이다.
- 기존 모델에서는 조건문이 리프 사이에 있으므로 어떤 리프가 조건문이고 어떤 리프가 액션인지 알아내는 데 시간이 걸린다. 반면 조건문 데코레이터는 비헤이비어 트리 UI를 보다 직관적이고 읽기 쉽게 만들어 준다.
- 모든 리프가 액션 태스크이므로 트리를 통해 실제 어떤 액션이 지시되는지 알기가 더 쉽다.
- 트리 내 중요 노드에서 이벤트를 기다리는 관찰자 역할로 만들기가 쉽다. 이러한 속성은 트리의 이벤트 주도형 속성을 활용하는데 중요한 영향을 끼친다. 예를 들어서, 위에 첨부된 사진에서는 충분히 가까움(Close Enough) 과 블랙보드 데코레이터가 시퀀스 노드의 자손이 실행되는 것을 방지한다.
3. 동시 발생 비헤이비어
- 표준 BT는 일반적으로 병렬 컴포짓 노드를 사용해 동시 발생 행동을 처리하며, 이 병렬 노드들이 모든 자손에서 동시에 실행되기 시작한다. 그렇기 때문에 자손 트리 중에서 하나 이상이 종료되었을 때 어떤 액션을 취할지는 원하는 행동에 따라 특수 규칙으로 결정된다. 반면 언리얼 BT에서는 복잡한 병렬 노드 대신에 단순 병렬 노드와 서비스라는 특수 노드, 데코레이터의 관찰자 중단(Observer Aborts) 프로퍼티를 이용해 동일한 유형의 행동을 달성한다. 이러한 처리 방식은 다음과 같은 장점을 가진다.
1. 명료성 - 서비스와 단순 병렬 노드를 사용하여 읽기 쉽고 이해하기 쉬운 단순한 트리를 만들 수 있습니다.
2. 쉬운 디버깅 - 그래프가 명료하므로 디버깅도 쉽습니다. 또한 동시 실행 경로가 더 적어서 지금 어떤 것이 실행되고 있는지 파악하기 더 쉽습니다.
3. 더 쉬운 최적화 - 이벤트 주도형 그래프는 동시에 실행되는 서브트리가 많지 않다면 최적화하기 더 쉽습니다.
+) 생각해보아야 할 점
병렬 노드가 반드시 태스크를 동시에 실행하는 멀티 스레딩일 필요는 없습니다. 병렬 노드는 한 번에 여러 태스크를 수행하는 개념적 방식 중 하나일 뿐입니다. 흔히 같은 스레드에서 실행되고, 어떤 순서로 시작됩니다. 해당 순서는 모두 같은 프레임에 발생하는 것을 가정하기 때문에 관련이 없어야 하지만 여전히 중요한 경우도 있습니다.
4. 관찰자 중단
표준 병렬 노드의 흔한 용도 중 하나는 조건을 지속적으로 확인하여 요구 조건이 False가 되는 경우 태스크를 중단하는 것이다. 예를 들어 '쉭쉭거리기' 와 '달려들기' 시퀀스를 수행하는 고양이가 있다면, 쥐가 쥐구멍으로 도망치는 순간 즉시 포기하도록 하는 것이 좋다. 병렬 노드로는 쥐에게 달려들 수 있는지 확인하는 자손과 시퀀스가 수행할 자손을 가질 수 있다. 언리얼 엔진 비헤이비어 트리는 이벤트 주도형이므로 이렇게 하는 대신에 조건문 데코레이터에서 해당 값을 관찰하게 하고 필요시 중단시키는 방식으로 처리한다. 이 예시에서는 '쥐에게 달려들 수 있는가?'를 들 수 있다. 이 경우, 시퀀스의 데코레이터에서 '관찰자 중단'을 '셀프(Self)'로 설정하면 된다.
언리얼 비헤이비어 트리의 노드 세부 구조
· 어려웠던 내용
단순히 문서를 읽고 실습하는 것이 아니라 최대한 일반적인 설계 관점에서 생각하려 하니 어려운 점이 많다.
그래도 비헤이비어 트리에 대한 전반적인 내용은 이제 어느정도 숙지되었다고 느껴진다.
· 궁금한 내용과 부족한 내용
조사를 하다보니 NavMesh와 AI를 연결하는 부분에 대한 설계 보강이 필요해 보인다.
현재 로직에서는 임의로 설정한 waypoint를 활용했는데, 이 NavMesh를 어떻게 처리할지에 따라 추가 구현이 달라질 것 같다. 우선적으로 유니티에서 지원하는 NavMesh를 활용하는 방법을 생각 중인데, 자체적으로 만든다는 선택지도 있다. 단순히 기존 방법을 유지해도 되겠지만.. 공부가 되는 건 자체 제작쪽일 거다. 다만 이 경우에는 구현 주제의 분류가 BT가 아니라 길찾기 알고리즘 쪽으로 넘어갈 것 같아서, 당장은 BT 구현에 집중하는 것이 좋을 것 같다.
· 느낀점
구현하려는 내용이 단순히 언리얼 엔진에서의 비헤이비어 트리를 모작 하는 것이 아니라 내가 생각한 설계대로 (내가 지금 구현 할 수 있는 수준에서, 주어진 시간 내에) 해내려고 하니 그 부분을 늘 염두해야 한다는 점이 제일 까다롭다. 그런데 과정이 재미있다. 어렵긴 하지만 그만 두고 싶지는 않다. 코딩을 잘 하고 못하고를 떠나서 나는 이런 생각과 구현 과정 자체가 너무 흥미롭다. 차근차근 하다보면 분명 이 과정을 통해 한 단계 더 성장할 수 있을 것이라 믿는다! 내일 이어서 할 설계 수정이 기대 된다.
'TIL' 카테고리의 다른 글
23-05-11 (0) | 2023.05.11 |
---|---|
23-05-10 (0) | 2023.05.10 |
23-04-05 TIL (0) | 2023.04.05 |
220802 디버깅 (0) | 2022.08.02 |
220715 비헤이비어 트리 설계 변경 (0) | 2022.07.15 |