관리 메뉴

Mini

[UE5] Custum GE Context // Net Serialize, Custom Effect Context 본문

UE5/Advance Damage Techniques

[UE5] Custum GE Context // Net Serialize, Custom Effect Context

Mini_96 2024. 8. 14. 14:51

위 2개를 모방해서 커스텀타입을 만들거임.

 

일단 치명타, 블락 getter, setter만 만들어줌

#pragma once // 헤더2번포함방지

#include "GameplayEffectTypes.h"
#include "AuraAbilityTypes.generated.h" //generated_body 사용위해 필요

USTRUCT(BlueprintType)
struct FAuraGameplayEffectContext : public FGameplayEffectContext
{
	GENERATED_BODY()

public:

	//@Getter
	bool IsCriticalHit() const {return bIsCriticalHit ; }
	bool IsBlockHit() const { return bIsBlockedHit ; }

	//@Setter
	void SetIsCriticalHit(bool bInIsCriticalHit) {bIsCriticalHit = bInIsCriticalHit ; }
	void SetIsBlockHit(bool bInIsBlockHit) {bIsBlockedHit = bInIsBlockHit ; }


	/** Returns the actual struct used for serialization, subclasses must override this! */
	virtual UScriptStruct* GetScriptStruct() const
	{
		return FGameplayEffectContext::StaticStruct();
	}

	/** Custom serialization, subclasses must override this */
	virtual bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
	
protected:

	UPROPERTY()
	bool bIsBlockedHit = false;
	
	UPROPERTY()
	bool bIsCriticalHit = false;
};

 

* 직렬화 함수 모방(EffectContext) + 커스텀 속성추가

1. 0 0 0 0 0 0 0 0 

해당 비트는 속성의 역할을한다.

2. 속성이 유효한지 확인하고, 속성의 비트를 켠다.

+ 속성추가시 다음 비트를 켜면된다.

bool FAuraGameplayEffectContext::NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess)
{
    uint8 RepBits=0;
    if (Ar.IsSaving())
    {
       if (Instigator.IsValid() )
       {
          RepBits |= 1 << 0; //비트켜기
       }
       if (EffectCauser.IsValid() )
       {
          RepBits |= 1 << 1;
       }
       if (AbilityCDO.IsValid())
       {
          RepBits |= 1 << 2;
       }
       if (bReplicateSourceObject && SourceObject.IsValid())
       {
          RepBits |= 1 << 3;
       }
       if (Actors.Num() > 0)
       {
          RepBits |= 1 << 4;
       }
       if (HitResult.IsValid())
       {
          RepBits |= 1 << 5;
       }
       if (bHasWorldOrigin)
       {
          RepBits |= 1 << 6;
       }
       if(bIsBlockedHit) //2개 추가
       {
          RepBits |= 1<<7;
       }
       if(bIsCriticalHit)
       {
          RepBits |= 1<<8;
       }
    }

3. 직렬화후, 해당비트가 켜져있는지 확인후,

Ar을 역직렬화 한다.

Ar.SerializeBits(&RepBits, 9);

if (RepBits & (1 << 0)) //해당 비트가 켜진경우
{
    Ar << Instigator; 
}
if (RepBits & (1 << 1))
{
    Ar << EffectCauser;
}
if (RepBits & (1 << 2))
{
    Ar << AbilityCDO;
}
if (RepBits & (1 << 3))
{
    Ar << SourceObject;
}
if (RepBits & (1 << 4))
{
    SafeNetSerializeTArray_Default<31>(Ar, Actors);
}
if (RepBits & (1 << 5))
{
    if (Ar.IsLoading())
    {
       if (!HitResult.IsValid())
       {
          HitResult = TSharedPtr<FHitResult>(new FHitResult());
       }
    }
    HitResult->NetSerialize(Ar, Map, bOutSuccess);
}
if (RepBits & (1 << 6))
{
    Ar << WorldOrigin;
    bHasWorldOrigin = true;
}
else
{
    bHasWorldOrigin = false;
}

if(RepBits & (1<<7)) //2개 추가
{
    Ar << bIsBlockedHit;
}
if(RepBits & (1<<8)) //2개 추가
{
    Ar << bIsCriticalHit;
}

if (Ar.IsLoading())
{
    AddInstigator(Instigator.Get(), EffectCauser.Get()); // Just to initialize InstigatorAbilitySystemComponent
}   

 

* 함수추가

/** Returns the actual struct used for serialization, subclasses must override this! */
virtual UScriptStruct* GetScriptStruct() const
{
    return StaticStruct();
}

/** Creates a copy of this context, used to duplicate for later modifications */
virtual FGameplayEffectContext* Duplicate() const
{
    FGameplayEffectContext* NewContext = new FGameplayEffectContext();
    *NewContext = *this;
    if (GetHitResult())
    {
       // Does a deep copy of the hit result
       NewContext->AddHitResult(*GetHitResult(), true);
    }
    return NewContext;
}
//구조체 연산유형
template<>
struct TStructOpsTypeTraits<FAuraGameplayEffectContext> : public TStructOpsTypeTraitsBase2<FAuraGameplayEffectContext>
{
    enum
    {
       WithNetSerializer = true,
       WithCopy = true
    };
};

 

 

* 문제 : 프로젝트에서 커스텀 EffectContext를 사용하도록 설정해야함.

1. 커스텀 글로벌을 만듬 && 내가만든 EffectContext의 인스턴스를 리턴.

FGameplayEffectContext* UAuraAbilitySystemGlobals::AllocGameplayEffectContext() const
{
    //내가만든 EffectContext의 새로운 인스턴스 리턴.
    return new FAuraGameplayEffectContext();
}

2. DefaultGame.ini에서 Global클래스를 내가만든 글로벌로 설정.

[/Script/GameplayAbilities.AbilitySystemGlobals]
+AbilitySystemGlobalsClassName="/Script/Aura.AuraAbilitySystemGlobals"

 

3. 결과확인 : 불덩이발사후, EffectContextHandle에 내가 추가한 속성2개가 들어간것을 확인할수 있음.

 

* next : 추가한 변수이용 = > 치명타시 노란색글씨로 바꿀거임

1. 먼저 getter를 만들어야함,

Exec_Calc.cpp 에서

set하기

Context핸들 얻어옴 -> 캐스팅 -> setter 사용

FGameplayEffectContextHandle EffectContextHandle = Spec.GetContext();
FGameplayEffectContext* Context = EffectContextHandle.Get(); //ptr 반환
FAuraGameplayEffectContext* AuraContext = static_cast<FAuraGameplayEffectContext*>(Context); //내가만든 setter 쓸려면 캐스트 필요
AuraContext->SetIsBlockHit(bBlocked);

 

2. AuraAbilitySystremLibrary에서 BP용 getter 만들기. (선호됨)

//Getter의 경우 BP PURE 로 만드는게 국룰임.
UFUNCTION(BlueprintPure, Category="AuraAbilitySystemLibrary|GameplayEffects")

 

bool UAuraAbilitySystemLibrary::IsBlockedHit(const FGameplayEffectContextHandle& EffectContextHandle)
{
    if(const FAuraGameplayEffectContext* AuraEffectContext = static_cast<const FAuraGameplayEffectContext*>(EffectContextHandle.Get()))
    {
       return AuraEffectContext->IsBlockHit();
    }
    return false;
}

 

BP 에서 사용가능한 모습 && 자동캐스팅됨

 

3. BP용 setter 만들기.

//Setter는 BP PURE로 하면 안됨.
UFUNCTION(BlueprintCallable, Category="AuraAbilitySystemLibrary|GameplayEffects")
static void SetIsBlockedHit(FGameplayEffectContextHandle& EffectContextHandle, bool bInIsBlockedHit);

Context의 내용을 바꿔야함 -> ~const

void UAuraAbilitySystemLibrary::SetIsBlockedHit(FGameplayEffectContextHandle& EffectContextHandle, bool bInIsBlockedHit)
{
    if(FAuraGameplayEffectContext* AuraEffectContext = static_cast<FAuraGameplayEffectContext*>(EffectContextHandle.Get()))
    {
       AuraEffectContext->SetIsBlockHit(bInIsBlockedHit);
    }
}

* 문제 : Effect Context Handle이 Input 이여야함.

원인 : 언리얼엔진은 ~const 변수는 출력으로 간주함.

해결 : 매개변수앞에 UPARAM(ref) => 입력임을 명시.

//Setter는 BP PURE로 하면 안됨.
UFUNCTION(BlueprintCallable, Category="AuraAbilitySystemLibrary|GameplayEffects")
static void SetIsBlockedHit(UPARAM(ref) FGameplayEffectContextHandle& EffectContextHandle, bool bInIsBlockedHit);

결과

* refactor : 

//개선후
UAuraAbilitySystemLibrary::SetIsBlockedHit(EffectContextHandle, bBlocked);

//전
FGameplayEffectContext* Context = EffectContextHandle.Get(); //ptr 반환
FAuraGameplayEffectContext* AuraContext = static_cast<FAuraGameplayEffectContext*>(Context); //내가만든 setter 쓸려면 캐스트 필요
AuraContext->SetIsBlockHit(bBlocked);

 

* AS에서도 매개변수 추가(블록, 크리티컬)

void ShowFloatingText(const FEffectProperties& Props, float Damage, bool bBlockedHit, bool bCriticalHit) const;
void UAuraAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
    ...
          
          const bool bBlock = UAuraAbilitySystemLibrary::IsBlockedHit(Props.EffectContextHandle);
          const bool bCriticalHit = UAuraAbilitySystemLibrary::IsCriticalHit(Props.EffectContextHandle);

          ShowFloatingText(Props,LocalIncomingDamage,bBlock,bCriticalHit);
       }
    }
}
void UAuraAttributeSet::ShowFloatingText(const FEffectProperties& Props, float Damage, bool bBlockedHit, bool bCriticalHit) const
{