BossFight - Boss Attack

김대겸·2025년 5월 2일

Boss Attack

이번에는 보스 공격을 구현 해보자. 보스의 공격을 구현 하기 위해서는 BTTask를 상속 받아 Ability를 작동 시키는 Task를 구현하고, Ability가 종료 되면 Task를 종료 하도록 구현 하면 된다.

Ability를 작동 시키는 Task를 구현 해보자.

🎮 UBTT_ActivateAbility.h

UCLASS()
class BOSSFIGHT_API UBTT_ActivateAbility : public UBTTaskNode
{
	GENERATED_BODY()
private:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Value", meta = (AllowPrivateAccess = "true"))
	FGameplayTagContainer TagContainer;

	UBTT_ActivateAbility();

	virtual EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;

	virtual void TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds) override;
};

🎮 UBTT_ActivateAbility.cpp

UBTT_ActivateAbility::UBTT_ActivateAbility()
{
	NodeName = "Activte Ability";
	bNotifyTick = true;
}

EBTNodeResult::Type UBTT_ActivateAbility::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
	Super::ExecuteTask(OwnerComp, NodeMemory);

	ABFBaseCharacter* Character = Cast<ABFBaseCharacter>(OwnerComp.GetAIOwner()->GetCharacter());

	if (UBFAbilitySystemComponent* ASC =  Character->GetBFAbilitySystemComponent())
	{
		ASC->TryActivateAbilitiesByTag(TagContainer);
		return EBTNodeResult::InProgress;
	}
	else
	{
		return EBTNodeResult::Failed;
	} 
}

void UBTT_ActivateAbility::TickTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory, float DeltaSeconds)
{
	Super::TickTask(OwnerComp, NodeMemory, DeltaSeconds);
	AEnemyAIController* AI = Cast<AEnemyAIController>(OwnerComp.GetAIOwner());

	if (AI->GetBlackboardComponent()->GetValueAsEnum(AI->AIState) == uint8(EBFEnemyAIState::Idle))
	{
		FinishLatentTask(OwnerComp, EBTNodeResult::Succeeded);
		return;
	}
}

위와같이 UBTTaskNode를 상속 받아 ActiavteAbilityTask를 작성 하였다.

기본적으로 AbilitySystemComponent를 통해 Ability를 작동 시키고, Task를 InProgress상태로 유지하여 Ability작동 후 해당 Ability가 종료 될때까지 대기 시키도록 구현 하였다. 그후 TickTask()를 통해 AIState를 확인하여 Idle상태로 돌아오면, Succeeded를 반환 하여 성공적으로 Task를 종료 하도록 구현 하였다.

다음으로 Enemy가 사용할 Gameplay Tag를 정의 하였다.

// Enemy Tags
BOSSFIGHT_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Enemy_Ability_Attack);
BOSSFIGHT_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Enemy_Ability_Attack_1);
BOSSFIGHT_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Enemy_Ability_Attack_2);
BOSSFIGHT_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(Enemy_Ability_Attack_3);
// Enemy Tags
UE_DEFINE_GAMEPLAY_TAG(Enemy_Ability_Attack, "Enemy.Ability.Attack");
UE_DEFINE_GAMEPLAY_TAG(Enemy_Ability_Attack_1, "Enemy.Ability.Attack.1");
UE_DEFINE_GAMEPLAY_TAG(Enemy_Ability_Attack_2, "Enemy.Ability.Attack.2");
UE_DEFINE_GAMEPLAY_TAG(Enemy_Ability_Attack_3, "Enemy.Ability.Attack.3");

우선 공격에 해당하는 Tag만 정의 해주었다.

다음으로 EnemyAbility에서 AIState를 변경할수 있도록하는 함수를 작성 해주자.

🎮 UBFEnemyAbility.h

UCLASS()
class BOSSFIGHT_API UBFEnemyAbility : public UBFGameplayAbility
{
	GENERATED_BODY()
    // 중략
	AEnemyAIController* GetAIControllerFromActorInfo();

	UFUNCTION(BlueprintCallable, Category = "BossFight | Ability", meta = (ExpandEnumAsExecs = "OutConfirmType"))
	void ChangeAIState(EBFEnemyAIState AIState, EBFConfirmType& OutConfirmType);
private:
	TWeakObjectPtr<AEnemyAIController> CachedEnemyAIController;
};

🎮 UBFEnemyAbility.cpp

AEnemyAIController* UBFEnemyAbility::GetAIControllerFromActorInfo()
{
	if (!CachedEnemyCharacter.IsValid())
	{
		CachedEnemyCharacter = Cast<ABFEnemyCharacter>(CurrentActorInfo->AvatarActor);
	}
	if (!CachedEnemyAIController.IsValid())
	{
		CachedEnemyAIController = Cast<AEnemyAIController>(CachedEnemyCharacter->GetController());
	}
	return CachedEnemyAIController.IsValid() ? CachedEnemyAIController.Get() : nullptr;
}

void UBFEnemyAbility::ChangeAIState(EBFEnemyAIState AIState, EBFConfirmType& OutConfirmType)
{
	if (AEnemyAIController* AI = GetAIControllerFromActorInfo())
	{
		AI->GetBlackboardComponent()->SetValueAsEnum(AI->AIState, uint8(AIState));
		EBFConfirmType::Yes;
	}
	else { EBFConfirmType::No; }
}

GetAIControllerFromActorInfo()함수를 통해 AIController를 저장 및 반환 하고 ChangeAIState()함수를 통해 입력된 AIState로 현재 AIState를 변경하도록 코드를 구현 하였다.

또한 EBFConfirmType을 통해 AEnemyAIController가 유효하여 값의 변경에 성공하면 Yes를 반환 아니면 No를 반환하여, 흐름을 제어 할수 있도록 하였다.

위의 코드를 사용하여 GA_Enemy_Attack_Base Ability를 아래와 같이 작성 하였다.

현재는 간단하게 AIState를 변경하고, 변경에 성공하면 Montage를 작동 실패하면 EndAbility를 통해 Ability를 종료 하도록 하였다.

또한 Ability종료시 현재 상태를 Idle로 변경하도록 하였다.

마지막으로 비헤이비어트리를 아래와 같이 간단하게 구현 하였다.

이후 GA_Enemy_Attack_Base를 상속받는 GA_Bear_Attak_1을 생성, 애니메이션을 할당 해주고 DA_Bear의 EnemyAbility에 추가, 테스트 해보았다.

작동 영상

의도했던 대로 Ability 작동-> 2초 대기 -> Ability 작동을 반복해서 실행 하고 있다.

다음에는 보스 캐릭터의 공격에 맞는 Trace를 작동 시키고, 충돌 판정을 구현 해보겠다.

0개의 댓글