GameplayAbility Tick 구현 C++
- 프로그래밍/언리얼
- 2024. 10. 1.
250x250
GameplayAbility Tick
기본적으로 GameplayAbility자체는 Tick을 제공하지 않기 때문에 GameplayAbility에서 Tick을 사용하기 위해서는 추가적인 설정이 필요합니다.
Tick구현 - Ability Task.
GameplayAbility에서 Tick을 사용하기 위해 Ability Task를 이용해 Tick을 구현해서 사용했습니다.
C++로 구현했지만 조금만 수정하면 블루프린트에서도 사용가능합니다.
Ability Task.h
#pragma once
#include "CoreMinimal.h"
#include "Abilities/Tasks/AbilityTask.h"
#include "AbilityTask_ExecuteTaskOnTick.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAbilityTaskTickDelegate, float, DeltaTime);
/**
*
*/
UCLASS()
class BLADE_API UAbilityTask_ExecuteTaskOnTick : public UAbilityTask
{
GENERATED_BODY()
public:
UAbilityTask_ExecuteTaskOnTick();
static UAbilityTask_ExecuteTaskOnTick* ExecuteTaskTick(UGameplayAbility* OwningAbility);
//Gameplaytask Interface
virtual void TickTask(float DeltaTime) override;
FOnAbilityTaskTickDelegate OnAbilityTaskTick;
};
AbilityTask.cpp
#include "AbilitySystem/AbilityTasks/AbilityTask_ExecuteTaskOnTick.h"
UAbilityTask_ExecuteTaskOnTick::UAbilityTask_ExecuteTaskOnTick()
{
bTickingTask = true;
}
UAbilityTask_ExecuteTaskOnTick* UAbilityTask_ExecuteTaskOnTick::ExecuteTaskTick(UGameplayAbility* OwningAbility)
{
UAbilityTask_ExecuteTaskOnTick* Node = NewAbilityTask<UAbilityTask_ExecuteTaskOnTick>(OwningAbility);
return Node;
}
void UAbilityTask_ExecuteTaskOnTick::TickTask(float DeltaTime)
{
Super::TickTask(DeltaTime);
if (ShouldBroadcastAbilityTaskDelegates()) {
OnAbilityTaskTick.Broadcast(DeltaTime);
}
else {
EndTask();
}
}
Tick을 필요로 하는 GameplayAbility
위 코드를 보면 아시겠지만 Delegate를 이용한 방법이라 델리게이트를 바인딩하여 사용할 수 있습니다.
TargetLock을 구현하고있는 GameplayAbility를 예시로 보여드리겠습니다.
GameplayAbility.h
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystem/Abilites/BladePlayerGameplayAbility.h"
#include "PlayerTargetLockAbility.generated.h"
class UTargetLockWidget;
UCLASS()
class BLADE_API UPlayerTargetLockAbility : public UBladePlayerGameplayAbility
{
GENERATED_BODY()
protected:
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)override;
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)override;
private:
void TryLockOnTarget();
void GetAvailableActorsToTarget();
void CancelTargetLockAbility();
void CleanUp();
void DrawTargetLockWidget();
void SetTargetLockWidgetLoc();
/*Tick을 사용하기 위해 바인딩해야하는 함수. UFUNCTION 필수*/
UFUNCTION()
void OnTargetLockTick(float DeltaTime);
/*여기까지*/
AActor* GetNearestTargetFromAvailableActors(const TArray<AActor*>& InAvailableActors);
UPROPERTY(EditDefaultsOnly,Category="Target Lock")
float TraceDistance = 3000.f;
UPROPERTY(EditDefaultsOnly,Category="Target Lock")
FVector TraceBoxSize = FVector(3000.f, 3000.f, 300.f);
UPROPERTY(EditDefaultsOnly,Category="Target Lock")
TArray<TEnumAsByte< EObjectTypeQuery> > BoxTraceChannel;
UPROPERTY(EditDefaultsOnly,Category="Target Lock")
bool bIsDebugging;
UPROPERTY()
TArray<AActor* > AvailableActorsToLock;
UPROPERTY()
AActor* CurrentLockActor;
UPROPERTY(EditDefaultsOnly,Category="Target Lock")
TSubclassOf<UTargetLockWidget> TargetLockWidgetClass;
UPROPERTY()
FVector2D TargetLockBoxSIze = FVector2D::ZeroVector;
UPROPERTY()
UTargetLockWidget* TargetLockWidget;
};
GameplayAbility.cpp
ActivateAbility()와 OnTargetLockTick()부분만 보시면 됩니다.
// YunMinSeong. All right reserved.
#include "AbilitySystem/Abilites/PlayerTargetLockAbility.h"
#include "Characters\BladePlayerCharacter.h"
#include "Kismet\KismetSystemLibrary.h"
#include "Kismet\GameplayStatics.h"
#include "Controllers/BladeController.h"
//Widget
#include "Widgets/TargetLockWidget.h"
#include "Blueprint/WidgetLayoutLibrary.h"
#include "Components/SizeBox.h"
#include "AbilitySystem/AbilityTasks/AbilityTask_ExecuteTaskOnTick.h"
#include "BladeFunctionLibrary.h"
#include "BladeGameplayTags.h"
//Debug
#include "CustomDebugHelper.h"
void UPlayerTargetLockAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
TryLockOnTarget();
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
//UAbilityTask_ExecuteTaskOnTick* TickTask=UAbilityTask_ExecuteTaskOnTick::ExecuteTaskOnTick(this);
UAbilityTask_ExecuteTaskOnTick* TickTask=UAbilityTask_ExecuteTaskOnTick::ExecuteTaskTick(this);
TickTask->OnAbilityTaskTick.AddDynamic(this, &ThisClass::OnTargetLockTick);
TickTask->ReadyForActivation();
}
void UPlayerTargetLockAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
CleanUp();
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
void UPlayerTargetLockAbility::TryLockOnTarget()
{
GetAvailableActorsToTarget();
if (AvailableActorsToLock.IsEmpty()) {
CancelTargetLockAbility();
return;
}
CurrentLockActor=GetNearestTargetFromAvailableActors(AvailableActorsToLock);
if (CurrentLockActor) {
DrawTargetLockWidget();
SetTargetLockWidgetLoc();
}
else {
CancelTargetLockAbility();
}
}
void UPlayerTargetLockAbility::GetAvailableActorsToTarget()
{
TArray<FHitResult> BoxTraceHits;
UKismetSystemLibrary::BoxTraceMultiForObjects(
GetPlayerCharacterFromActorInfo(),
GetPlayerCharacterFromActorInfo()->GetActorLocation(),
GetPlayerCharacterFromActorInfo()->GetActorLocation()+GetPlayerCharacterFromActorInfo()->GetActorForwardVector()* TraceDistance,
TraceBoxSize/2.f,
GetPlayerCharacterFromActorInfo()->GetActorForwardVector().ToOrientationRotator(),
BoxTraceChannel, false,
TArray<AActor*>(),
bIsDebugging?EDrawDebugTrace::ForDuration:EDrawDebugTrace::None,
BoxTraceHits,true
);
for (const FHitResult& BoxHit : BoxTraceHits) {
if (AActor* HitActor=BoxHit.GetActor()) {
if (HitActor != GetPlayerCharacterFromActorInfo()) {
AvailableActorsToLock.AddUnique(HitActor);
}
}
}
}
void UPlayerTargetLockAbility::CancelTargetLockAbility()
{
CancelAbility(GetCurrentAbilitySpecHandle(),GetCurrentActorInfo(),GetCurrentActivationInfo(),true);
}
void UPlayerTargetLockAbility::CleanUp()
{
AvailableActorsToLock.Empty();
CurrentLockActor = nullptr;
if (TargetLockWidget) {
TargetLockWidget->RemoveFromParent();
}
TargetLockWidget = nullptr;
TargetLockBoxSIze = FVector2D::ZeroVector;
}
void UPlayerTargetLockAbility::DrawTargetLockWidget()
{
if (!TargetLockWidget) {
checkf(TargetLockWidgetClass, TEXT("TargetLockWidgetClass is none"));
TargetLockWidget = CreateWidget<UTargetLockWidget>(GetPlayerControllerFromActorInfo(), TargetLockWidgetClass);
if (TargetLockWidget) {
TargetLockWidget->AddToViewport();
}
}
}
void UPlayerTargetLockAbility::SetTargetLockWidgetLoc()
{
if (!TargetLockWidget || !CurrentLockActor) {
CancelTargetLockAbility();
return;
}
FVector2D ScreenPosition;
UWidgetLayoutLibrary::ProjectWorldLocationToWidgetPosition(
GetPlayerControllerFromActorInfo(),
CurrentLockActor->GetActorLocation(),
ScreenPosition,
true
);
if (TargetLockBoxSIze == FVector2D::ZeroVector) {
TargetLockBoxSIze.X = TargetLockWidget->TargetLockBox->GetWidthOverride();
TargetLockBoxSIze.Y = TargetLockWidget->TargetLockBox->GetHeightOverride();
}
ScreenPosition -= TargetLockBoxSIze / 2.f;
TargetLockWidget->SetPositionInViewport(ScreenPosition,false);
}
void UPlayerTargetLockAbility::OnTargetLockTick(float DeltaTime)
{
if (!CurrentLockActor||
UBladeFunctionLibrary::NativeDoesActorHaveTag(CurrentLockActor,BladeGameplayTags::Shared_Status_Dead)||
UBladeFunctionLibrary::NativeDoesActorHaveTag(GetPlayerCharacterFromActorInfo(), BladeGameplayTags::Shared_Status_Dead)) {
CancelTargetLockAbility();
return;
}
SetTargetLockWidgetLoc();
}
AActor* UPlayerTargetLockAbility::GetNearestTargetFromAvailableActors(const TArray<AActor*>& InAvailableActors)
{
float ClosestDistance=0.f;
return UGameplayStatics::FindNearestActor(GetPlayerCharacterFromActorInfo()->GetActorLocation(), InAvailableActors, ClosestDistance);
}
Tick구현 결과.
AbilityTask로 구현한 TargetLock입니다.
Tick을 넣어서 위젯의 위치가 계속 갱신되도록 만들었습니다.