입력에 따라서 콤보 공격과 상태를 가진 점프 어빌리티를 구현해보자.

  • 게임플레이 어빌리티를 활용한 콤보 공격의 구현
  • 게임플레이 어빌리티와 어빌리티 태스크 동작의 이해
  • 블루프린트에서 활용가능한 어빌리티 태스크 설정 방법의 학습  

 

 

목차

 

 


 

 

콤보 공격의 구현하기

 

Gameplay Ability(GA)에서 콤보 공격을 구현


 

 

캐릭터 콤보 공격 구현을 위한 기획 

 

  • 공격시작 후 유효 시간 내에 추가 공격 입력을 넣으면, 다음 공격 모션을 발동한다.
  • 콤보 공격에 대한 정보는 ABComboActionData에서 불러들임.
  • AbilityTask(AT)를 발동하고 입력 점검 타이머도 함께 발동.
  • 입력점검 타이머가 발동되면 다음 공격 입력이 있는지 검사함.      
  • 다음 공격 입력이 있으면다음 공격 모션을 발동하고다시 입력 점검 타이머를 발동.

 

노란색 부분을 작업하였다. 주어진 어빌리티 태스크(AT)를 사용하는것이 아니라 직접 어빌리티 태스크(AT)를 만들어서 구현하였다.

 

 

 

노란색 부분을 작업하였다.


 

 

ABGA_Attack 클래스 

 

ABGA_Attack.h

더보기
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "ABGA_Attack.generated.h"

UCLASS()
class ARENABATTLEGAS_API UABGA_Attack : public UGameplayAbility
{
	GENERATED_BODY()
	
public:
	UABGA_Attack();

public:
	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
	virtual void InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override;
	virtual void CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility) override;
	virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;

protected:
	UFUNCTION()
	void OnCompleteCallback();

	UFUNCTION()
	void OnInterruptedCallback();

	FName GetNextSection();
	void StartComboTimer();
	void CheckComboInput();

protected:
	UPROPERTY()
	TObjectPtr<class UABComboActionData> CurrentComboData;

	uint8 CurrentCombo = 0;
	FTimerHandle ComboTimerHandle;
	bool HasNextComboInput = false;
};

 

 

 

ABGA_Attack.cpp

더보기
#include "GA/ABGA_Attack.h"
#include "Character/ABCharacterBase.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "ArenaBattleGAS.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Character/ABComboActionData.h"

UABGA_Attack::UABGA_Attack()
{
	InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
}

void UABGA_Attack::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);

	AABCharacterBase* ABCharacter = CastChecked<AABCharacterBase>(ActorInfo->AvatarActor.Get());
	CurrentComboData = ABCharacter->GetComboActionData();
	ABCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);

	UAbilityTask_PlayMontageAndWait* PlayAttackTask = UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(this, TEXT("PlayAttack"), ABCharacter->GetComboActionMontage(), 1.0f, GetNextSection());
	PlayAttackTask->OnCompleted.AddDynamic(this, &UABGA_Attack::OnCompleteCallback);
	PlayAttackTask->OnInterrupted.AddDynamic(this, &UABGA_Attack::OnInterruptedCallback);
	PlayAttackTask->ReadyForActivation();

	StartComboTimer();
}

void UABGA_Attack::InputPressed(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
	if (!ComboTimerHandle.IsValid()) // 마지막 공격인 경우
	{
		HasNextComboInput = false; // 콤보 공격이 이어지지 않도록 false 설정
	}
	else // 마지막 공격이 아닌 경우
	{
		HasNextComboInput = true; // 다음 몽타주 색션이 재생되도록 true 설정
	}
}

void UABGA_Attack::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility)
{
	Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancelAbility);

}

void UABGA_Attack::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
	Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);

	AABCharacterBase* ABCharacter = CastChecked<AABCharacterBase>(ActorInfo->AvatarActor.Get());
	ABCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);

	//** 콤보 초기화	
	CurrentComboData = nullptr;
	CurrentCombo = 0; 
	HasNextComboInput = false;
}

void UABGA_Attack::OnCompleteCallback()
{
	bool bReplicatedEndAbility = true;
	bool bWasCancelled = false;
	EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}

void UABGA_Attack::OnInterruptedCallback()
{
	bool bReplicatedEndAbility = true;
	bool bWasCancelled = true;
	EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}

FName UABGA_Attack::GetNextSection()
{
	CurrentCombo = FMath::Clamp(CurrentCombo + 1, 1, CurrentComboData->MaxComboCount);
	FName NextSection = *FString::Printf(TEXT("%s%d"), *CurrentComboData->MontageSectionNamePrefix, CurrentCombo);
	return NextSection;
}

void UABGA_Attack::StartComboTimer() // 콤보 타이머
{
	int32 ComboIndex = CurrentCombo - 1;
	ensure(CurrentComboData->EffectiveFrameCount.IsValidIndex(ComboIndex));

	const float ComboEffectiveTime = CurrentComboData->EffectiveFrameCount[ComboIndex] / CurrentComboData->FrameRate; // 몇 초 이후에 점검할지 변수
	if (ComboEffectiveTime > 0.0f)
	{
		// 타이머
		GetWorld()->GetTimerManager().SetTimer(ComboTimerHandle, this, &UABGA_Attack::CheckComboInput, ComboEffectiveTime, false);
	}
}

void UABGA_Attack::CheckComboInput()
{
	ComboTimerHandle.Invalidate(); // 타이머 핸들 무력화
	if (HasNextComboInput) // 다음 공격 입력이 있다면
	{
		MontageJumpToSection(GetNextSection()); // 다음 몽타주 색션으로 넘어감
		StartComboTimer(); // 콤보 타이머 새로 시작
		HasNextComboInput = false;
	}
}

 

 

실행화면

 


 

 

 

 

상태를가지는 점프 어빌리티의 구현하기

 

상태를 가지는 점프 GA 생성

점프 GA를 위한 새로운 AT의 생성

GA와 AT 사이의 통신 매커니즘 이해하기

블루프린트에서 AT를 사용하기 위한 매크로 설정 


 

 

어빌리티 태스크 Ability Task(AT)의 제작 규칙

 

  • AT는 UAbilityTask 클래스를 상속받아 제작한다.
  • AT 인스턴스를 생성해 반환하는 static 함수를 선언해 구현한다. (= 즉, AT 안에 자기 자신의 인스턴스를 생성해서 반환하는 static 함수를 선언하여 구현한다)
  • AT가 종료되면 이것을 호출한 GA에 알려줄 델리게이트를 선언한다.
  • 시작과 종료 처리를 위해 Activate OnDestroy 함수를 재정의(Override)해 구현한다. 
  • 일정 시간이 지난 후 AT를 종료하고자 한다면, 활성화 시 SetWaitingOnAvatar 함수를 호출해 Waiting 상태로 설정한다. 
  • 만일 Tick을 활성화하고 싶다면 bTickingTask 값을 true로 설정한다.
  • AT가 종료되면 델리게이트를 브로드캐스팅한다.

 

 

GA와 AT 사이의 실행 흐름

 


 

 

블루프린트에서 호출을 위한 제작 규칙 

 

  • static 함수에 UFUNCTION(BlueprintCallable)을 지정한다.
  • 콜백을 위한 델리게이트는 Dynamic Delegate로 선언한다.
  • AT의 델리게이트에 UPROPERTY(BlueprintAssignable)을 지정한다.


 

 

 

Visual Studio에서 찾아본 Gameplay Abilities


 

 

 

ABGA_Jump 생성

 

GameplayAbility - ABGA_Jump 생성

 

 

ABGA_Jump.h

더보기
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "ABGA_Jump.generated.h"

UCLASS()
class ARENABATTLEGAS_API UABGA_Jump : public UGameplayAbility
{
	GENERATED_BODY()
	
public:
	UABGA_Jump();

	virtual bool CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr, OUT FGameplayTagContainer* OptionalRelevantTags = nullptr) const override;
	virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
	virtual void InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo) override;

protected:
	UFUNCTION() // Dynamic이므로 UFUNCTION 매크로 붙임
	void OnLandedCallback();
};

 

 

 

ABGA_Jump.cpp

더보기
#include "GA/ABGA_Jump.h"
#include "GameFramework/Character.h"
#include "GA/AT/ABAT_JumpAndWaitForLanding.h"

UABGA_Jump::UABGA_Jump()
{
	InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
}

bool UABGA_Jump::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, OUT FGameplayTagContainer* OptionalRelevantTags) const
{
	bool bResult = Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags);
	if (!bResult)
	{
		return false;
	}

	const ACharacter* Character = Cast<ACharacter>(ActorInfo->AvatarActor.Get());
	return (Character && Character->CanJump());
}

void UABGA_Jump::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
	
    // 아래의 3줄의 코드처럼 콜백함수를 만들지않고 Blueprint로 구현하는 방법도 있다.
    // 코드 아래에 첨부된 이미지처럼 Blueprint를 만들면 된다.
	UABAT_JumpAndWaitForLanding* JumpAndWaitingForLandingTask = UABAT_JumpAndWaitForLanding::CreateTask(this);
	JumpAndWaitingForLandingTask->OnComplete.AddDynamic(this, &UABGA_Jump::OnLandedCallback);
	JumpAndWaitingForLandingTask->ReadyForActivation();
}

void UABGA_Jump::InputReleased(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo)
{
	ACharacter* Character = CastChecked<ACharacter>(ActorInfo->AvatarActor.Get());
	Character->StopJumping();
}

void UABGA_Jump::OnLandedCallback()
{
	bool bReplicatedEndAbility = true;
	bool bWasCancelled = false;
	EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled); // 어빌리티 종료
}

 

 

C++ 코드 대신 Blueprint를 사용하는 경우 아래와 같이 구현하면 된다.

 

 

BPGA_Jump


 

 

ABAT_JumpAndWaitForLanding 생성

 

점프를 수행해주고 땅에 떨어질 때까지 점프 상태를 유지해주는 어빌리티 테스크

 

AbilityTask - ABAT_JumpAndWaitForLanding 생성

 

 

ABAT_JumpAndWaitForLanding.h

더보기
#pragma once
#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "ABAT_JumpAndWaitForLanding.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FJumpAndWaitForLandingDelegate);

UCLASS()
class ARENABATTLEGAS_API UABAT_JumpAndWaitForLanding : public UAbilityTask
{
	GENERATED_BODY()
	
public:
	UABAT_JumpAndWaitForLanding();

	UFUNCTION(BlueprintCallable, Category = "Ability|Tasks", meta = (DisplayName = "JumpAndWaitForLanding", HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "TRUE"))
	static UABAT_JumpAndWaitForLanding* CreateTask(UGameplayAbility* OwningAbility);

	virtual void Activate() override;
	virtual void OnDestroy(bool AbilityEnded) override;

	UPROPERTY(BlueprintAssignable)
	FJumpAndWaitForLandingDelegate OnComplete;

protected:
	UFUNCTION() // Dynamic 콜백함수이기 때문에 UFUNCTION 매크로를 붙임
	void OnLandedCallback(const FHitResult& Hit);
};

 

 

 

ABAT_JumpAndWaitForLanding.cpp

더보기
#include "GA/AT/ABAT_JumpAndWaitForLanding.h"
#include "GameFramework/Character.h"

UABAT_JumpAndWaitForLanding::UABAT_JumpAndWaitForLanding()
{
}

UABAT_JumpAndWaitForLanding* UABAT_JumpAndWaitForLanding::CreateTask(UGameplayAbility* OwningAbility)
{
	UABAT_JumpAndWaitForLanding* NewTask = NewAbilityTask<UABAT_JumpAndWaitForLanding>(OwningAbility); // UABAT_JumpAndWaitForLanding라는 API 사용
	return NewTask;
}

void UABAT_JumpAndWaitForLanding::Activate()
{
	Super::Activate();

	ACharacter* Character = CastChecked<ACharacter>(GetAvatarActor());
	Character->LandedDelegate.AddDynamic(this, &UABAT_JumpAndWaitForLanding::OnLandedCallback); // 바닥에 착지하는 순간 델리게이트 OnLandedCallback콜백함수 호출되도록 AddDynamic등록
	Character->Jump(); // 점프

	//** 점프 후에 Waiting상태로 돌려줌
	SetWaitingOnAvatar(); // EAbilityTaskWaitState::WaitingOnAvatar로 설정
}

void UABAT_JumpAndWaitForLanding::OnDestroy(bool AbilityEnded)
{
	ACharacter* Character = CastChecked<ACharacter>(GetAvatarActor());
	Character->LandedDelegate.RemoveDynamic(this, &UABAT_JumpAndWaitForLanding::OnLandedCallback); // 등록된 Dynamic 델리게이트 OnLandedCallback콜백함수 해제

	Super::OnDestroy(AbilityEnded);
}

void UABAT_JumpAndWaitForLanding::OnLandedCallback(const FHitResult& Hit)
{
	if (ShouldBroadcastAbilityTaskDelegates())
	{
		OnComplete.Broadcast(); // 델리게이트 브로드캐스트
	}
}

 

 

 

BP_ABGASCharacterPlayer


 

 

실행화면