AI 컨트롤러와 비헤이비어 트리
‘이득우의 언리얼 c++ 게임 개발의 정석’ 책을 참고하여 작성한 포스트입니다.
Behavior Tree 모델을 사용해 인공지능을 설계해 본다
AIController와 내비게이션 시스템
- AIController를 부모 클래스로 하는 클래스를 생성해 NPC를 제어해 보자.
- NPC의 AIController 속성을 새로 만든 클래스로 설정해주고, PlaceInWorldOrSpawned로 AI의 생성 옵션을 설정해 주면,
- 플레이어가 조종하는 캐릭터를 제외한 모든 캐릭터는 새로 생성한 AIController 클래스의 지배를 받는다.
//character.cpp
#include "LSAIController.h"
//constructor
AIControllerClass = ALSAIController::StaticClass();
AutoPossessAI = EAutoPossessAI::PlacedInWorldOrSpawned;
AIControllerClass
- Default class to use when pawn is controlled by AI.
TSubclassOf< AController > AIControllerClass
EAutoPossessAI
- Specifies if an AI pawn will automatically be possed by an AI controller
enum EAutoPossessAI
{
// Feature is disabled (do not automatically possess AI).
Disabled,
// Only possess by an AI Controller if Pawn is placed in the world.
PlacedInWorld,
// Only possess by an AI Controller if Pawn is spawned after the world has loaded.
Spawned,
PlacedInWorldOrSpawned,
}
네비게이션 메시
- NPC가 스스로 움직이기 위해 보조할 장치이다.
- 네비게이션 시스템에는 다양한 함수 들이 있다..
- NavigationSystem 모듈을 빌드에 추가해주어야 한다
- AIController에는 PathFollowingComponent가 부착되어 있는데, 폰이 길찾기를 사용해 목적지 까지 도달하는지 지속적으로 관리한다.
UAIBlueprintHelperLibrary::SimpleMoveToLocation
- UNavigationSystemV1::SimpleMoveToLocation 은 deprecated 되었다.
#include "Blueprint/AIBlueprintHelperLibrary.h"
static void SimpleMoveToLocation
(
AController * Controller,
const FVector & Goal
)
UNavigationSystemV1
#include "NavigationSystem.h"
UNavigationSystemV1::GetNavigationSystem
static UNavigationSystemV1 * GetNavigationSystem
(
UObject * WorldContextObject
)
UNavigationSystemV1::GetRandomPointInNavigableRadius
- Finds random, point in navigable space restricted to Radius around Origin.
bool GetRandomPointInNavigableRadius
(
const FVector & Origin,
float Radius,
FNavLocation & ResultLocation,
ANavigationData * NavData,
FSharedConstNavQueryFilter QueryFilter
) const
비헤이비어 트리 시스템
- Behavior Tree를 제작하기 위해서는 Behavior Tree와 blackboard 에셋을 생성해야 한다.
- 인공지능 메뉴에서 블랙보드 에셋과 비헤이비어 트리 에셋을 생성한다.
- 블랙보드
- 인공지능의 판단에 사용하는 데이터 집합
- 비헤이비어 트리
- 베헤이비어 트리의 정보를 저장한 에셋
- 블랙보드
- 비헤이비어 트리 내 태스크는 독립적으로 실행될 수 없고, composite 노드를 거쳐 실행되어야 한다.
- composite 노드에는 대표적으로 sequence 와 selector 가 있다.
- sequence
- failed 결과가 나올 때까지 왼쪽에서 오른쪽으로 태스크를 계속 실행한다.
- selector
- succeess or inprogress 결과가 나올 때까지 왼쪽에서 오른쪽으로 태스크를 계속 실행
- c++ 코드에서 비헤이비어 트리 관련 기능을 사용하려면 “AIModule” 모듈을 빌드에 추가해 주어야 한다.
- 비헤이비어 트리는 UBehaviorTree 클래스로 접근하고, 블랙보드는 UBlackboardData로 접근하면 된다.
- 블랙보드에 키를 생성해 비헤이비어 트리에서 사용 가능하다. 게임 실행 중 각 키의 값의 변화는 블랙보드에서 볼 수 있다.
UBlackboardComponent::SetValueAsVector
- 블랙보드의 키의 타입이 벡터인 경우 KeyName 이름의 키에 VectorValue 값을 전달한다..
void SetValueAsVector
(
const FName& KeyName,
FVector VectorValue
)
Behavior tree task node
- 사용하기 위해서는 “GameplayTasks” 모듈을 빌드에 추가해 주어야 한다.
- BTTaskNode 클래스를 부모로 하는 클래스를 생성하자.
- BTTask_를 접두사로 해서 이름을 지으면 접두사 부분이 자동으로 걸러진다.
- 비헤이비어 트리는 태스크 실행시 태스크 클래스의 ExecuteTask 라는 멤버 함수를 실행한다.
- ExecuteTask 함수는 다음 넷 중 하나의 값을 반환해야 한다.
- Aborted
- 태스크 실행 중 중단(실패)
- Failed
- 태스크 수행했지만 실패
- Succeeded
- 태스크를 성공적으로 수행
- InProgress
- 태스크를 계속 수행
- 결과는 나중에
- Aborted
- ExecuteTask 함수 실행 결과에 따라 컴포짓 내에 있는 다음 태스크를 계속 수행할지 중단할지 결정된다.
UBTTaskNode::ExecuteTask
virtual EBTNodeResult::Type ExecuteTask
(
// 나중에 코드 보기
UBehaviorTreeComponent& OwnerComp,
uint8 * NodeMemory
)
EBTNodeResult::Type
namespace EBTNodeResult
{
enum Type
{
Succeeded,
Failed,
Aborted,
InProgress,
}
}
UBrainComponent::GetBlackboardComponent
- return the blackboard used with this component
UBlackboardComponent* GetBlackboardComponent()
FNavLocation
- Describes a point in navigation data
UNavigationSystemV1::GetRandomPointInNavigableRadius
- Finds random, point in navigable space restricted to Radius around Origin.
bool GetRandomPointInNavigableRadius
(
const FVector & Origin,
float Radius,
// Found point is put here
FNavLocation & ResultLocation,
// If NavData == NULL then MainNavData is used.
ANavigationData * NavData,
FSharedConstNavQueryFilter QueryFilter
) const
UBrainComponent::GetAIOwner
AAIController * GetAIOwner() const
AActor::GetWorld
- Getter for the cached world pointer, will return null if the actor is not actually spawned in a level
virtual UWorld * GetWorld() const
NPC의 추격 기능 구현
- 블랙보드에 object 타입으로 변수를 생성해 플레이어를 타겟으로 설정할 수 있다.
- 서비스 노드는 독립적으로 작동하지 않고 컴포짓 노드에 부착되는 노드다.
- 서비스 노드는 해당 컴포짓에 속한 태스크들이 실행되는 동안 반복적인 작업을 실행하는 데 적합하다.
- 새로운 서비스 제작을 위해 BTService를 부모로 하는 클래스를 생성한다.
- BTService_ 접두사는 무시된다.
- 서비스 노드는 자신이 속한 컴포짓 노드가 활성화 될 경우 주기적으로 TickNode 함수를 호출한다.
- 주기는 서비스 노드 내부에 설정된 Interval 속성 값으로 지정 가능하다.
- 서비스 생성 후 비헤이비어 트리 컴포짓에 부착해 주면 된다.
UBTService::TickNode
- Update next tick interval this function should be considered as const (don’t modify state of object) if node is not instanced!
virtual void TickNode
(
UBehaviorTreeComponent & OwnerComp,
uint8 * NodeMemory,
float DeltaSeconds
)
데코레이터 노드
- 블랙보드의 값을 기반으로 특정 컴포짓 실행 여부를 결정하는 데코레이터(decorator) 노드를 사용해 본다.
- 컴포짓에 데코레이터 추가를 하면 된다.
- 데코레이터 설정에서 블랙보드 키를 원하는 키로 설정하고,
- 옵저버 값을 On Value Change로 하면 키 값 변경 시 현재 컴포짓 노드의 실행을 취소한다.
- 관찰자 중단 항목을 설정하면 컴포짓에 속한 태스크가 마무리 되기 전에 다음 컴포짓으로 넘어갈 수 있따.
NPC의 공격
- 블랙보드의 값을 참조하지 않고 판단하는 데코레이터를 사용하려면,
- BTDecorator 클래스를 상속받는 클래스를 생성하면 된다.
- BTDecorator_ 접두사는 무시된다.
- 데코레이터 클래스는 CalculateRawConditionValue 함수를 통해 원하는 조건이 달성됐는지를 파악한다.
- 이 함수는 const로 선언돼어 있따.
- BTTaskNode를 부모로 하는 클래스를 생성해서 공격 태스크를 만들어 보자.
- 공격 애니메이션이 끝날 때까지 기다려야 하는 지연 태스크 이므로,
- 공격 중에는 ExecuteTask의 결과를 InProgress로 반환하고, 끝났을 때 태스크가 끝났다고 알려줘야 한다.
- FinishLatentTask 가 이를 알려준다. 호출해 주지 않으면 영영 이 태스크를 벗어나지 못한다.
- 이 함수를 호출하기 위해서 노드의 Tick 기능을 활성화 한 후 Tick에서 조건 파악한 뒤 종료 명령을 내려 줘야 한다.
플레이어를 바라보는 태스크
- 플레이어에게 다가가 공격을 시작하고 나면, 플레이어가 NPC의 뒤로 돌아가도 앞만 보고 있다.
- BTTask를 하나 더 추가해 플레이어를 바라보며 공격하게 해주자
- 일정한 속도로 회전할 수 있게 FMath::RInterpTo 함수를 이용해보자
- simple parallel 컴포짓을 이용해 attack 태스크를 메인으로 하고, turn 태스크를 서브로 해서 동시에 실행시켜 주자.
- simple parallel 컴포짓은 두 태스크로 이루어지며, 메인 태스크가 완료되기 전까지 서브 태스크가 계속해서 반복 실행된다.
비헤이비어 트리 노드 레퍼런스: 태스크
- 노드 이름(Node Name) 비헤이비어 트리 그래프에 표시되는 노드 이름
AActor::SetActorRotation
- Set the Actor’s rotation instantly to the specified rotation.
bool SetActorRotation ( FRotator NewRotation, ETeleportType Teleport )
FMath::RInterpTo
- Interpolate rotator from Current to Target.
static FRotator RInterpTo
(
const FRotator & Current,
const FRotator & Target,
float DeltaTime,
float InterpSpeed
)
Leave a comment