4 minute read

‘이득우의 언리얼 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
      • 태스크를 계속 수행
      • 결과는 나중에
  • 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