AI
- AI는 언리얼 엔진에서 제공하는 Behavior Tree를 이용하거나 FSM을 통해 구현할 수 있다.
- Behavior Tree를 사용해서 AI를 구현할 수 있지만 작은 규모의 AI 같은 경우엔 FSM으로 충분히 대체할 수 있다.
AIController를 상속받은 클래스 생성
- bulid.cs에 AIMoudle을 추가해야한다.
public:
AMyAIController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());
virtual void OnPossess(APawn* InPawn) override;
AMyAIController::AMyAIController(const FObjectInitializer& ObjectInitializer)
{
}
void AMyAIController::OnPossess(APawn* InPawn)
{
Super::OnPossess(InPawn);
}
AIController를 상속받은 블루프린트 클래스 생성
- 위에서 만들어준 AIController를 상속받아 몬스터를 컨트롤 할 클래스를 생성한다

- 해당 블루프린트 클래스를 몬스터의 AIController 클래스로 설정한다
- Auto Possess Player: 빙의 할 플레이어
- Auto Possess AI: 빙의 할 AI
- Placed in World: Drag & Drop으로 레벨에 배치한 캐릭터
- Spawned: 코드상으로 선언한 경우
- Place in World or Spawned: 둘 다 해당
- AI Controller Class: 사용할 AIController 클래스
Behavior Tree
- Behavior Tree는 트리 구조로 되어 있으며 각 노드는 AI가 어떤 행동을 취할지 결정하는데 사용됩니다.
Blackboard
- Behavior Tree와 함께 사용되는 데이터 저장소로 AI의 상태나 게임에서 얻은 정보를 저장하고 활용합니다.
- Behavior Tree에서 특정 조건이 충족되었는지 혹은 변수를 비교할 때 사용됩니다.
Node
- Root: 트리의 시작점
- Composite: 하위 노드들의 실행 순서를 결정하는 역할
- Selector: 하위 노드 중 하나가 성공하면 그 즉시 실행을 종료하고 성공으로 처리. 실패할 경우 다음 하위 노드를 실행
- Sequence: 하위 노드를 순서대로 실행하며 하나라도 실패하면 전체가 실패
- Decorator: 노드에 조건을 부여하고 조건이 충족되었을 때만 노드가 실행되도록 할 수 있다.
- Task: AI가 실제로 수행할 행동을 정의
- Service: 특정 지점에서 반복적으로 상태를 업데이트하는 역할로 주기적으로 데이터를 확인하거나 업데이트하는 용도로 사용
Behavior Tree & Blackboard 만들기
Blackboard에 데이터 추가

- Target을 향해 움직이거나 특정 위치로 움직이기 위해 데이터 추가
Service 만들기
- BTSerivce를 상속받은 클래스 생성
- 범위안에 Target을 찾는 기능
public:
UBTService_FindTarget();
protected:
virtual void TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float SearchRadius = 500.f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FBlackboardKeySelector TargetKey;
UBTService_FindTarget::UBTService_FindTarget()
{
NodeName = TEXT("FindTargetService");
Interval = 0.5f;
}
void UBTService_FindTarget::TickNode(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
Super::TickNode(OwnerComp, NodeMemory, DeltaSeconds);
APawn* LocalPawn = OwnerComp.GetAIOwner()->GetPawn();
if(LocalPawn == nullptr)
{
return;
}
UWorld* World = LocalPawn->GetWorld();
if(World == nullptr)
{
return;
}
FVector Location = LocalPawn->GetActorLocation();
TArray<FOverlapResult> OverlapResults;
FCollisionQueryParams CollisionQueryParams(NAME_None, false, LocalPawn);
bool bResult = World->OverlapMultiByChannel(
OverlapResults,
Location,
FQuat::Identity,
ECC_EngineTraceChannel2,
FCollisionShape::MakeSphere(SearchRadius),
CollisionQueryParams
);
if(bResult)
{
for(FOverlapResult& OverlapResult : OverlapResults)
{
AMyCharacter* Character = Cast<AMyCharacter>(OverlapResult.GetActor());
if(Character)
{
OwnerComp.GetBlackboardComponent()->SetValueAsObject(TargetKey.SelectedKeyName, Character);
DrawDebugSphere(World, Location, SearchRadius, 16, FColor::Green, false, 0.2f);
return;
}
}
}
OwnerComp.GetBlackboardComponent()->SetValueAsObject(TargetKey.SelectedKeyName, nullptr);
DrawDebugSphere(World, Location, SearchRadius, 16, FColor::Red, false, 0.2f);
}
- 특정 범위 안에 캐릭터가 존재하면 해당 TargetKey를 해당 캐릭터로 업데이트하고 존재하지 않으면 nullptr로 업데이트한다.
- TargetKey를 Behavior Tree에서 해당 TargetActor로 설정
Decorator 만들기
- BTDecorator를 상속받은 클래스 생성
- 공격할 수 있는지 조건을 확인하는 기능
public:
UBTDecorator_CanAttack();
protected:
virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FBlackboardKeySelector TargetKey;
UBTDecorator_CanAttack::UBTDecorator_CanAttack()
{
NodeName = TEXT("CanAttack");
}
bool UBTDecorator_CanAttack::CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const
{
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if(ControllingPawn == nullptr)
{
return false;
}
AMyCharacter* Target = Cast<AMyCharacter>(OwnerComp.GetBlackboardComponent()->GetValueAsObject(TargetKey.SelectedKeyName));
if(Target == nullptr)
{
return false;
}
return Target->GetDistanceTo(ControllingPawn) <= 250.f;
}
- Blackboard에 Target을 가져와서 해당 Target이 존재하면 특정 거리 안에 있는지 여부를 반환
- TargetKey를 Behavior Tree에서 해당 TargetActor로 설정
Task 만들기
- BTTaskNode를 상속받은 클래스 생성
- 특정 범위안에 갈 수 있는 지역을 찾아가도록 수행
public:
UBTTaskNode_FindPatrolPos();
virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FBlackboardKeySelector PatrolPosKey;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float SerachRadius = 1000.f;
UBTTaskNode_FindPatrolPos::UBTTaskNode_FindPatrolPos()
{
NodeName = TEXT("FindPatrolPos");
}
EBTNodeResult::Type UBTTaskNode_FindPatrolPos::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
APawn* ControllingPawn = OwnerComp.GetAIOwner()->GetPawn();
if(ControllingPawn == nullptr)
{
return EBTNodeResult::Failed;
}
UNavigationSystemV1* NavSystem = UNavigationSystemV1::GetNavigationSystem(ControllingPawn);
if(NavSystem==nullptr)
{
return EBTNodeResult::Failed;
}
FNavLocation NextPatrol;
if(NavSystem->GetRandomPointInNavigableRadius(ControllingPawn->GetActorLocation(), SerachRadius,NextPatrol))
{
OwnerComp.GetBlackboardComponent()->SetValueAsVector(PatrolPosKey.SelectedKeyName, OUT NextPatrol);
return EBTNodeResult::Succeeded;
}
return EBTNodeResult::Failed;
}
- NavigationSystem을 통해 특정 범위안에 이동할 수 있는 지역이 있다면 Blackboard에 해당 위치를 업데이트한다.
- PatrolKey를 Behavior Tree에서 해당 PatrolPos로 설정