0. 개요
이전 포스트에서 스택을 활용한 체력 회복 / 감소 액터를 만들었다.
그러나 체력이 최대 체력보다 커지거나 0보다 작아지는 현상은 아직 수정하지 않았다.
이번 포스트에서는 속성 세트의 PreAttributeChange 함수를 오버라이딩하여 체력을 클램핑(Clamping) 하도록 한다.
- 클램프(Clamp)
어떤 값이 증가하거나 감소할 때, 특정 최소값과 최댓값을 넘지 못하도록 고정시키는 것.
- std::clamp 함수
모던 C++에서 추가된 Clamp 함수를 사용하여 쉽게 값을 고정하는 방법도 있다.
1. PreAttributeChange 함수 오버라이드
속성이 변경되기 직전에 호출되는 함수이다.
언리얼 공식에서는 해당 함수를 값을 클램핑할 때만 사용하도록 권장하고 있다.
void UAuraAttributeSet::PreAttributeChange(const FGameplayAttribute & Attribute, float & NewValue)
{
// 클램핑 용으로만 사용할 것
Super::PreAttributeChange(Attribute, NewValue);
// 인수로 넘겨받은 Attribute가 해당 속성과 동일한지 확인하기
if (Attribute == GetHealthAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.f, GetMaxHealth());
}
if (Attribute == GetMaxHealthAttribute())
{
}
if (Attribute == GetManaAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.f, GetMaxMana());
}
if (Attribute == GetMaxManaAttribute())
{
}
}
FMath 라이브러리의 Clamp 함수를 사용하여 값이 최소값과 최대값 사이에만 존재할 수 있도록 고정할 수 있다.
그러나 PreAttributeChange는 수정자의 값을 반환하여 변경하는 것이기 때문에 현재값을 수정하지 않는다.
(잠재값-Pending Value-을 수정)
-> 즉 현재값을 수정하기 위한 추가적인 스텝을 취해야 한다.
2. PostGameplayEffectExecute 함수
게임플레이 이펙트가 속성을 변경한 이후에 호출된다.
- 함수 호출 순서
PreAttributeChange -> 속성 변경 -> PostGameplayEffectExecute
GameplayEffectModCallbackData를 인수로 사용한다.
(1) 콜백 데이터의 멤버 변수
- EffectSpec
GameplayEffectSpec 구조체 형의 스펙이다.
정의(Def), 유지 시간(Period), 작동 시간(Duration) 등의 정보를 가진다.
EffectSpec의 EffectContext에 접근해 발생자에 대한 정보를 얻을 수 있다.
- EvaluatedData
속성(Attribute), 수정자 연산자(Modifier Op), 크기(Magnitude), 핸들의 정보를 가진다.
- Target
UAbilitySystemComponent를 가진다.
-> 대상의 Ability info에 접근할 수 있다.
(2) 코드
void UAuraAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData & Data)
{
Super::PostGameplayEffectExecute(Data);
// 게임플레이 이펙트가 속성을 변경한 후에 실행(PreAttributeChange -> 속성 변경 -> 이 함수)
const FGameplayEffectContextHandle EffectContextHandle = Data.EffectSpec.GetContext();
// 소스 - 이펙트 발동자, 타겟 - 이펙트의 대상(속성 세트의 주인)
const UAbilitySystemComponent* SourceASC = EffectContextHandle.GetOriginalInstigatorAbilitySystemComponent();
if (SourceASC && SourceASC->AbilityActorInfo.IsValid() && SourceASC->AbilityActorInfo->AvatarActor.IsValid())
{
AActor* SourceAvatarActor = SourceASC->AbilityActorInfo->AvatarActor.Get();
const AController* SourceController = SourceASC->AbilityActorInfo->PlayerController.Get();
if (SourceController == nullptr && SourceAvatarActor != nullptr)
{
// 폰에서 플레이어 컨트롤러 가져오기
if (const APawn* Pawn = Cast<APawn>(SourceAvatarActor))
{
SourceController = Pawn->GetController();
}
}
if (SourceController)
{
ACharacter* SourceCharacter = Cast<ACharacter>(SourceController->GetPawn());
}
}
// 타겟의 어빌리티 시스템 컴포넌트에 접근하기
if (Data.Target.AbilityActorInfo.IsValid() && Data.Target.AbilityActorInfo->AvatarActor.IsValid())
{
AActor* TargetAvatarActor = Data.Target.AbilityActorInfo->AvatarActor.Get();
AController* TargetController = Data.Target.AbilityActorInfo->PlayerController.Get();
ACharacter* TargetCharacter = Cast<ACharacter>(TargetAvatarActor);
UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(TargetAvatarActor);
}
}
- 이펙트 발생자(Source)의 정보 가져오기
Data의 EffectSpec에서 GetContext 함수를 사용하여 이펙트 컨텍스트 핸들을 통해 대상의 어빌리티 시스템 컴포넌트에 접근하여 게임플레이 이펙트를 발생시킨 액터의 정보를 가져올 수 있다.
- 이펙트 대상(Target)의 정보 가져오기
Data의 Target에서 AbilityActorInfo에 직접 접근하여 타겟의 정보에 대한 여러 포인터를 가져올 수 있다.
3. 소스와 대상에 쉽게 접근할 수 있는 구조체 만들기 - FEffectProperties 구조체
위에서 확인했듯 여러가지 구성요소를 통해 소스와 타겟의 정보를 꺼내올 수 있었다.
이제 더 간단하게 정보를 얻어올 수 있도록 구조체와 Getter 함수를 정의한다.
(1) 코드
USTURCT()
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;
};
이제 기존에 PostGameplayEffectExecute 함수에서 소스와 타겟의 정보를 꺼내오던 것은 함수로 래핑해서 이용할 것이다.
private:
// 콜백 데이터에서 꺼내와 구조체에 저장
void SetEffectProperties(const FGameplayEffectModCallbackData & Data, FEffectProperties& Props) const;
void UAuraAttributeSet::SetEffectProperties(const FGameplayEffectModCallbackData & Data, FEffectProperties& Props) const
{
// 게임플레이 이펙트가 속성을 변경한 후에 실행(PreAttributeChange -> 속성 변경 -> 이 함수)
Props.EffectContextHandle = Data.EffectSpec.GetContext();
// 소스 - 이펙트 발동자, 타겟 - 이펙트의 대상(속성 세트의 주인)
Props.SourceASC = Props.EffectContextHandle.GetOriginalInstigatorAbilitySystemComponent();
if (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 (const APawn* Pawn = Cast<APawn>(Props.SourceAvatarActor))
{
Props.SourceController = Pawn->GetController();
}
}
if (Props.SourceController)
{
Props.SourceCharacter = Cast<ACharacter>(Props.SourceController->GetPawn());
}
}
// 타겟의 어빌리티 시스템 컴포넌트에 접근하기
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);
}
}
그리고 PostGameplayEffectExecute 함수에서 이 함수에 콜백 데이터를 넘겨주면 된다.
void UAuraAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData & Data)
{
Super::PostGameplayEffectExecute(Data);
// 소스가 캐릭터가 아닐 경우에 컨트롤러를 얻지 못할 수 있음!!
FEffectProperties Props;
SetEffectProperties(Data, Props);
}
게임플레이 이펙트를 발동한 주체가 Character가 아닐 경우에는 컨트롤러를 얻을 수 없음에 주의하자.
'UE 5 스터디 > Gameplay Ability System(GAS)' 카테고리의 다른 글
5-1. 게임플레이 태그(Gameplay Tags) (0) | 2024.08.30 |
---|---|
4-9. 커브 테이블(Curve Tables) (1) | 2024.08.28 |
4-7. 게임플레이 이펙트 클래스 - (5) 이펙트 수명 관리 (0) | 2024.08.23 |
4-6. 게임플레이 이펙트 클래스 - (4) 무한 지속(Infinte) (0) | 2024.08.23 |
4-5. 게임플레이 이펙트 클래스 - (3) 스택(Stacking) (0) | 2024.08.23 |