[UE5] AI

GamzaTori·2024년 10월 31일

UE5 C++

목록 보기
21/27

AI

  • AI는 언리얼 엔진에서 제공하는 Behavior Tree를 이용하거나 FSM을 통해 구현할 수 있다.
  • Behavior Tree를 사용해서 AI를 구현할 수 있지만 작은 규모의 AI 같은 경우엔 FSM으로 충분히 대체할 수 있다.

AIController를 상속받은 클래스 생성

  • bulid.cs에 AIMoudle을 추가해야한다.
// h
public:
AMyAIController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());

virtual void OnPossess(APawn* InPawn) override;

// cpp
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

  1. Root: 트리의 시작점
  2. Composite: 하위 노드들의 실행 순서를 결정하는 역할
    • Selector: 하위 노드 중 하나가 성공하면 그 즉시 실행을 종료하고 성공으로 처리. 실패할 경우 다음 하위 노드를 실행
    • Sequence: 하위 노드를 순서대로 실행하며 하나라도 실패하면 전체가 실패
  3. Decorator: 노드에 조건을 부여하고 조건이 충족되었을 때만 노드가 실행되도록 할 수 있다.
  4. Task: AI가 실제로 수행할 행동을 정의
  5. Service: 특정 지점에서 반복적으로 상태를 업데이트하는 역할로 주기적으로 데이터를 확인하거나 업데이트하는 용도로 사용

Behavior Tree & Blackboard 만들기

  • 우클릭 -> AI

Blackboard에 데이터 추가

  • Target을 향해 움직이거나 특정 위치로 움직이기 위해 데이터 추가

Service 만들기

  • BTSerivce를 상속받은 클래스 생성
  • 범위안에 Target을 찾는 기능
// h
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;
// cpp
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)
			{
            	// 캐릭터를 찾았으면 TargetKey를 해당 캐릭터로 업데이트
				OwnerComp.GetBlackboardComponent()->SetValueAsObject(TargetKey.SelectedKeyName, Character);
				DrawDebugSphere(World, Location, SearchRadius, 16, FColor::Green, false, 0.2f);
				return;
			}
		}
	}
    // 캐릭터를 못찾았으면 TargetKey를 nullptr로 설정
	OwnerComp.GetBlackboardComponent()->SetValueAsObject(TargetKey.SelectedKeyName, nullptr);
	DrawDebugSphere(World, Location, SearchRadius, 16, FColor::Red, false, 0.2f);
}
  • 특정 범위 안에 캐릭터가 존재하면 해당 TargetKey를 해당 캐릭터로 업데이트하고 존재하지 않으면 nullptr로 업데이트한다.
  • TargetKey를 Behavior Tree에서 해당 TargetActor로 설정

Decorator 만들기

  • BTDecorator를 상속받은 클래스 생성
  • 공격할 수 있는지 조건을 확인하는 기능
// h
public:
	UBTDecorator_CanAttack();

protected:
	virtual bool CalculateRawConditionValue(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) const override;

protected:
	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	FBlackboardKeySelector TargetKey;
    
// cpp
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를 상속받은 클래스 생성
  • 특정 범위안에 갈 수 있는 지역을 찾아가도록 수행
// h
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;
    
// cpp
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로 설정
profile
게임 개발 공부중입니다.

0개의 댓글