최종프로젝트 AI-MonsterState

정혜창·2025년 5월 15일

내일배움캠프

목록 보기
57/64

현재 BT

상태 변환 로직

📌 MonsterAIController.cpp

void AMonsterAIController::OnPossess(APawn* InPawn)
{
	Super::OnPossess(InPawn);

	if (IsValid(BehaviorTree))
	{
		UseBlackboard(BehaviorTree->BlackboardAsset, (UBlackboardComponent*&)BlackboardComponent);
		RunBehaviorTree(BehaviorTree);
		LOG(TEXT("AIController Possess"));

		// Initialize BlackboardKey (CurrnetState, TargetActor)
		BlackboardComponent->SetValueAsEnum("MonsterState", static_cast<uint8>(EMonsterState::Patrol));
		BlackboardComponent->ClearValue("TargetActor");
	}

	FTimerHandle TimerHandle;
	GetWorldTimerManager().SetTimer(TimerHandle, this, &AMonsterAIController::InitializePatrolPoint, 0.5f, false);
}

void AMonsterAIController::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	if (!bIsLosingTarget) return;

	float Elapsed = GetWorld()->GetTimeSeconds() - LostTargetTime;
	if (Elapsed > SightConfig->GetMaxAge())
	{
		BlackboardComponent->ClearValue("TargetActor");
		BlackboardComponent->SetValueAsEnum("MonsterState", static_cast<uint8>(EMonsterState::Patrol));
		bIsLosingTarget = false;
	}
}

void AMonsterAIController::OnTargetPerceptionUpdated(AActor* Actor, FAIStimulus Stimulus)
{
	if (Actor->IsA(ATestPlayerCharacter::StaticClass()))
	{
		BlackboardComponent = GetBlackboardComponent();

		if (Stimulus.WasSuccessfullySensed())
		{
			LOG(TEXT("[Perception] : %s"), *Actor->GetName());

			Blackboard->SetValueAsObject("TargetActor", Actor);
			Blackboard->SetValueAsEnum("MonsterState", static_cast<uint8>(EMonsterState::Chase));
		}
		else
		{
			// Lost Perception. but Target Value still remains for MaxAge
			bIsLosingTarget = true;
			LostTargetTime = GetWorld()->GetTimeSeconds(); // Timer On.
		}
	}
}
  • OnPossess 되면서 Patrol 상태로 전환
  • Tick에서 bIsLosingTarget 이 true일때, LostTargt 시간이 MaxAge second 이상이면 Patrol 상태로 전환
  • OnTargetPerception되면 Chase상태로 전환.
    • OnTargetPerception 작동방식
  • 만약에 감지가 끊기면 bIsLosingTarget 되면서 Tick 로직 실행.

📌 BTTask_ChasePlayer

EBTNodeResult::Type UBTTask_ChasePlayer::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	.
	. // 생략
	.
	if (Result.Code == EPathFollowingRequestResult::AlreadyAtGoal)
	{
		// Already Arrived -> State Change
		BlackboardComp->SetValueAsEnum("MonsterState", static_cast<uint8>(EMonsterState::Attack));
		return EBTNodeResult::Succeeded;
	}
	else if (Result.Code == EPathFollowingRequestResult::RequestSuccessful)
	{
		// MonsterState : Chase
		return EBTNodeResult::InProgress;
	}
	else
	{
		return EBTNodeResult::Failed;
	}
}

void UBTTask_ChasePlayer::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	.
	. // 생략
	.
	if (!CachedTargetActor || !IsValid(CachedTargetActor))
	{
		// Lost TargetActor
		FinishLatentTask(*CachedOwnerComp, EBTNodeResult::Failed);

		// MonsterAIController handles this processing, but explicitly writes
		BlackboardComp->SetValueAsEnum("MonsterState", static_cast<uint8>(EMonsterState::Patrol));
		BlackboardComp->ClearValue("TargetActor");
		return;
	}

	if (AIController->GetMoveStatus() == EPathFollowingStatus::Idle)
	{
		// When it arrives, switch the status to Attack
		BlackboardComp->SetValueAsEnum("MonsterState", static_cast<uint8>(EMonsterState::Attack));
		FinishLatentTask(*CachedOwnerComp, EBTNodeResult::Succeeded);
	}
}
  • Chase 될 때 이미 도착해있으면 상태 Attack으로 전환
  • 그리고 도착하지 않았다면 Chase를 하고 TargetActor가 소실되고 유효하지 않으면 Patrol로 돌아감
  • 만약 AIController의 GetMoveStatus 가 Idle 상태라면 Attack으로 상태가 바뀌고 노드 정상적으로 넘김
    • 지금 GetMoveStatus가 Idle이 안된다고 생각함

🌈 트러블 슈팅

방법1. AcceptanceRadius를 넉넉하게 설정해 봄.

MoveRequest.SetAcceptanceRadius(300.0f);
  • 공격을 제대로 하는 것 같은데 공격하자 마자 다음 문제 발생하면서 에디터 중단

  • ScriptDelegates.h

    • 델리게이트가 중복 바인딩 되어 있을 때 나오는 문제라고 함.
    • 델리게이트 제거를 제대로 하고 있는지, 중복되고 있는건 아닌지 살펴봐야 할듯
    • 근데 이게 어쩔 땐 이 문제가 발생하는데 어쩔땐 그대로임 10퍼센트 확률인듯…
    • 뭐지 ?;;;
  • 계속버튼을 누르니깐 다시 에디터로 돌아오지만 Attack상태에서 Chase, Patrol 상태로 돌아가지 않는 것 같음.

  • 그래서 Attack 상태에서 Chase, Patrol 상태로 돌아가는 로직 추가해봄.
profile
Unreal 1기

0개의 댓글