1) Attribute에 Clamp를 적용
기존의 Attribute가 동작하는 방식을 보면,
따로 Clamp 함수를 사용하지 않아 Attribute에 반영할 변경 수치를 조정하지 못하고 있습니다.
이를 위해 2개의 함수를 사용할 예정입니다.
1) PreAttributeChange
PreAttributeChange 함수는 실제 Attribute에 변환이 적용되기 이전에 호출되는 함수입니다.
Attribute의 BaseValue와 CurrentValue 중에서
CurrentValue를 바꿔주기 위해 사용하는데,
CurrentValue를 바꾼 값으로 계속 유지하지 않고 바꾼 값만 리턴하게 됩니다.
따라서 PreAttributeChange 함수 이후의 연산들은 바뀌기 이전의 CurrentValue를 다시 사용하게 됩니다.
그러므로 바뀐 CurrentValue를 BaseValue에 적용해야 하는데
이를 수행하기 위한 함수가 바로 PostGameplayEffectExecute 함수입니다.
2) PostGameplayEffectExecute
PostGameplayEffectExecute 함수는
Attribute의 BaseValue를 바꾸고자 GE 적용 이후에 호출되는 함수입니다.
해당 함수가 호출된 이후로는 GE를 통해 더 이상 변화가 발생하지 않습니다.
PreAttributeChange를 통해 바꾼(=조정한) CurrentValue를
실제 BaseValue에 적용하기 위해 사용할 예정입니다.
이제 실제 코드를 확인해보겠습니다.
1 - 1) AuraAttributeSet
.
.
.
USTRUCT()
struct FEffectProperties
{
GENERATED_BODY()
FEffectProperties() {}
FGameplayEffectContextHandle EffectContextHandle;
UPROPERTY()
UAbilitySystemComponent* SourceASC = nullptr;
UPROPERTY()
AActor* SourceAvatarActor = nullptr;
UPROPERTY()
AController* SourceController = nullptr;
UPROPERTY()
ACharacter* SourceCharacter = nullptr;
UPROPERTY()
UAbilitySystemComponent* TargetASC = nullptr;
UPROPERTY()
AActor* TargetAvatarActor = nullptr;
UPROPERTY()
AController* TargetController = nullptr;
UPROPERTY()
ACharacter* TargetCharacter = nullptr;
};
UCLASS()
class AURA_API UAuraAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
.
.
.
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
private:
/*
* Member Function
*/
void SetEffectProperties(const FGameplayEffectModCallbackData& Data, FEffectProperties& Props) const;
.
.
.
};
코드 구조를 보면 새로운 구조체 FEffectProperties가 추가된 것을 볼 수 있습니다.
해당 구조체는 PostGameplayEffectExecute 함수에서
GE의 적용 주체인 SourceActor와 적용 대상인 TargetActor의 정보를 모아 관리하게 됩니다.
따라서 이전에 설명한 2개의 함수 외에도
추가적으로 FEffectProperties를 설정하는 함수 SetEffectProperties를 추가하겠습니다.
#include "AbilitySystem/AuraAttributeSet.h"
#include "Net/UnrealNetwork.h"
#include "GameplayEffectExtension.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "GameFramework/Character.h"
.
.
.
void UAuraAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
Super::PreAttributeChange(Attribute, NewValue);
if (Attribute == GetHealthAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth());
}
if (Attribute == GetManaAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.f, GetMaxMana());
}
}
void UAuraAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
FEffectProperties Props;
SetEffectProperties(Data, Props);
if (Data.EvaluatedData.Attribute == GetHealthAttribute())
{
SetHealth(FMath::Clamp(GetHealth(), 0.f, GetMaxHealth()));
}
if (Data.EvaluatedData.Attribute == GetManaAttribute())
{
SetMana(FMath::Clamp(GetMana(), 0.f, GetMaxMana()));
}
}
void UAuraAttributeSet::SetEffectProperties(const FGameplayEffectModCallbackData& Data, FEffectProperties& Props) const
{
Props.EffectContextHandle = Data.EffectSpec.GetContext();
Props.SourceASC = Props.EffectContextHandle.GetOriginalInstigatorAbilitySystemComponent();
// 다음 로직에서는 SourceActor를 가져오게 됨
// GE를 발동시킨 ASC가 유효한지 확인 + GE의 발동 주체가 유효한지 확인 + World에서 ASC를 들고 있는 AvatarActor가 유효한지 확인
if (IsValid(Props.SourceASC) && Props.SourceASC->AbilityActorInfo.IsValid() && Props.SourceASC->AbilityActorInfo->AvatarActor.IsValid())
{
Props.SourceAvatarActor = Props.SourceASC->AbilityActorInfo->AvatarActor.Get();
Props.SourceController = Props.SourceASC->AbilityActorInfo->PlayerController.Get();
if (Props.SourceController == nullptr && Props.SourceAvatarActor != nullptr)
{
if (APawn* Pawn = Cast<APawn>(Props.SourceAvatarActor))
{
Props.SourceController = Pawn->GetController();
}
}
if (Props.SourceController)
{
Props.SourceCharacter = Cast<ACharacter>(Props.SourceController->GetPawn());
}
}
// 다음 로직에서는 TargetActor를 가져오게 됨
if (Data.Target.AbilityActorInfo.IsValid() && Data.Target.AbilityActorInfo->AvatarActor.IsValid())
{
Props.TargetAvatarActor = Data.Target.AbilityActorInfo->AvatarActor.Get();
Props.TargetController = Data.Target.AbilityActorInfo->PlayerController.Get();
Props.TargetCharacter = Cast<ACharacter>(Props.TargetAvatarActor);
Props.TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Props.TargetAvatarActor);
}
}
PreAttributeChange 함수의 경우,
Attribute가 변환되기 이전의 Health와 Mana에 대해 Clamp를 진행하게 됩니다.
PostGameplayEffectExecute 함수의 경우,
GE가 적용된 이후에 호출되므로
GE를 적용한 SourceActor와 적용된 TargetActor를 설정하고
Health와 Mana에 대해 Clamp를 진행하게 됩니다.
SetEffectProperties 함수의 경우,
FGameplayEffectModCallbackData 구조체를 통해 GE의 동작 결과를 받아 이를 토대로
FEffectProperties의 SourceActor의 정보와 TargetActor의 정보를 설정하게 됩니다.
2) GE에 CurveTable 적용
GE는 레벨이라는 수치를 통해 적용하는 변화의 단계를 조정할 수 있습니다.
따라서 CurveTable을 사용하여 이 변화의 단계를 조정해보겠습니다.
코드는 다음과 같습니다.
2 - 1) AuraEffectActor
UCLASS()
class AURA_API AAuraEffectActor : public AActor
{
GENERATED_BODY()
public:
AAuraEffectActor();
protected:
.
.
.
/*
* Effect Curve
*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Applied Effects")
float ActorLevel = 1.0f;
.
.
.
};
위와 같이 GE의 레벨을 담당하는 ActorLevel 프로퍼티를 추가하였습니다.
.
.
.
void AAuraEffectActor::ApplyEffectToTarget(AActor* TargetActor, TSubclassOf<UGameplayEffect> GameplayEffectClass)
{
UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetActor);
if (TargetASC)
{
.
.
.
const FGameplayEffectSpecHandle EffectSpecHandle = TargetASC->MakeOutgoingSpec(GameplayEffectClass, ActorLevel, EffectContextHandle);
.
.
.
}
}
.
.
.
그리고 GE를 적용하는 함수 ApplyEffectToTarget에서
기존의 하드코딩된 레벨을 위와 같이 프로퍼티로 설정하였습니다.
이제 언리얼 에디터에서 CurveTable을 생성하고 실제 GE에 적용하겠습니다.
2 - 2) 언리얼 에디터
포션에 적용할 CurveTable을 Linear의 형태로 위와 같이 생성하였습니다.
이제 포션과 연관된 GE에 위의 CurveTable을 적용해보겠습니다.
위와 같이 적용된 것을 확인할 수 있습니다.
'언리얼 > 게임 프로젝트' 카테고리의 다른 글
Lyra 클론코딩 - (2) Asset Manager (0) | 2024.12.22 |
---|---|
Lyra 클론코딩 - (1) 모듈 + 커스텀 로그 (0) | 2024.12.22 |
GameplayAbilitySystem을 이용한 RPG 프로젝트 - (20) Gameplay Effect의 적용 / 삭제 정책을 본격적으로 사용하기 (0) | 2024.12.14 |
GameplayAbilitySystem을 이용한 RPG 프로젝트 - (19) 영구적인 Gameplay Effect를 위한 설정 (1) | 2024.12.13 |
GameplayAbilitySystem을 이용한 RPG 프로젝트 - (18) Gameplay Effect를 중복 적용하는 Effect Stacking (0) | 2024.12.12 |