/* Get Set By Caller Magnitude from FGameplayEffectSpec */
// In Case of ProjectileSpell, SetByCallerMagnitude was set by FGameplayAbility(AuraProjectileSpell - AssignTagSetByCallerMagnitude())
float Damage = EffectSpec.GetSetByCallerMagnitude(AuraGameplayTags.Damage); struct AuraDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(Armor);
DECLARE_ATTRIBUTE_CAPTUREDEF(ArmorPenetration);
DECLARE_ATTRIBUTE_CAPTUREDEF(BlockChance);
DECLARE_ATTRIBUTE_CAPTUREDEF(CriticalHitChance);
DECLARE_ATTRIBUTE_CAPTUREDEF(CriticalHitDamage);
DECLARE_ATTRIBUTE_CAPTUREDEF(CriticalHitResistance);
AuraDamageStatics()
{
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, Armor, Target, false);
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, ArmorPenetration, Source, false);
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, BlockChance, Target, false);
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, CriticalHitChance, Source, false);
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, CriticalHitDamage, Source, false);
DEFINE_ATTRIBUTE_CAPTUREDEF(UAuraAttributeSet, CriticalHitResistance, Target, false);
}
};
UExecCalc_Damage::UExecCalc_Damage()
{
RelevantAttributesToCapture.Add(DamageStatics().ArmorDef);
RelevantAttributesToCapture.Add(DamageStatics().ArmorPenetrationDef);
RelevantAttributesToCapture.Add(DamageStatics().BlockChanceDef);
RelevantAttributesToCapture.Add(DamageStatics().CriticalHitChanceDef);
RelevantAttributesToCapture.Add(DamageStatics().CriticalHitDamageDef);
RelevantAttributesToCapture.Add(DamageStatics().CriticalHitResistanceDef);
} // AuraAbilitySystemLibrary.cpp
UCharacterClassInfo* UAuraAbilitySystemLibrary::GetCharacterClassInfo(const UObject* WorldContextObject)
{
AAuraGameModeBase* AuraGameMode = Cast<AAuraGameModeBase>(UGameplayStatics::GetGameMode(WorldContextObject));
if (AuraGameMode == nullptr) return nullptr;
return AuraGameMode->CharacterClassInfo;
} /* Get Coefficients from CharacterClassInfo */
UCharacterClassInfo* CharacterClassInfo = UAuraAbilitySystemLibrary::GetCharacterClassInfo(SourceAvatar);
// Armor Penetration
const FRealCurve* ArmorPenetrationCurve = CharacterClassInfo->DamageCalculationCoefficients->FindCurve(FName("ArmorPenetration"), FString());
const float ArmorPenetrationCoefficient = ArmorPenetrationCurve->Eval(SourceCombatInterface->GetPlayerLevel());
// Effective Armor (Coefficient for Armor - ArmorPenetration)
const FRealCurve* EffectiveArmorCurve = CharacterClassInfo->DamageCalculationCoefficients->FindCurve(FName("EffectiveArmor"), FString());
const float EffectiveArmorCoefficient = EffectiveArmorCurve->Eval(SourceCombatInterface->GetPlayerLevel());
// Effective CriticalHitChance (Coefficient for CriticalHitChance - CriticalHitResistance)
const FRealCurve* EffectiveCriticalHitChanceCurve = CharacterClassInfo->DamageCalculationCoefficients->FindCurve(FName("EffectiveCriticalHitChance"), FString());
const float EffectiveCriticalHitChanceCoefficient = EffectiveCriticalHitChanceCurve->Eval(SourceCombatInterface->GetPlayerLevel());
void UExecCalc_Damage::Execute_Implementation(const FGameplayEffectCustomExecutionParameters& ExecutionParams, FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
// 필요한 레퍼런스 획득
const UAbilitySystemComponent* SourceASC = ExecutionParams.GetSourceAbilitySystemComponent();
const UAbilitySystemComponent* TargetASC = ExecutionParams.GetTargetAbilitySystemComponent();
AActor* SourceAvatar = SourceASC ? SourceASC->GetAvatarActor() : nullptr;
AActor* TargetAvatar = TargetASC ? TargetASC->GetAvatarActor() : nullptr;
ICombatInterface* SourceCombatInterface = Cast<ICombatInterface>(SourceAvatar);
ICombatInterface* TargetCombatInterface = Cast<ICombatInterface>(TargetAvatar);
const FGameplayEffectSpec& EffectSpec = ExecutionParams.GetOwningSpec();
FAuraGameplayTags AuraGameplayTags = FAuraGameplayTags::Get();
FAggregatorEvaluateParameters EvaluationParameters;
EvaluationParameters.SourceTags = EffectSpec.CapturedSourceTags.GetAggregatedTags();
EvaluationParameters.TargetTags = EffectSpec.CapturedTargetTags.GetAggregatedTags();
/* Get Set By Caller Magnitude from FGameplayEffectSpec */
// In Case of ProjectileSpell, SetByCallerMagnitude was set by FGameplayAbility(AuraProjectileSpell - AssignTagSetByCallerMagnitude())
float Damage = EffectSpec.GetSetByCallerMagnitude(AuraGameplayTags.Damage);
// Attribute 캡쳐 및 보정 (PreAttributeChange()에서 처리하는 것과 동일하게)
/* Capture Attribute From Target and Source */
float TargetBlockChance = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().BlockChanceDef, EvaluationParameters, TargetBlockChance);
float TargetArmor = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorDef, EvaluationParameters, TargetArmor);
TargetArmor = FMath::Max<float>(0.f, TargetArmor);
float SourceArmorPenetration = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().ArmorPenetrationDef, EvaluationParameters, SourceArmorPenetration);
SourceArmorPenetration = FMath::Max<float>(0.f, SourceArmorPenetration);
float SourceCriticalHitChance = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().CriticalHitChanceDef, EvaluationParameters, SourceCriticalHitChance);
SourceArmorPenetration = FMath::Max<float>(0.f, SourceCriticalHitChance);
float TargetCriticalHitResistance = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().CriticalHitResistanceDef, EvaluationParameters, TargetCriticalHitResistance);
SourceArmorPenetration = FMath::Max<float>(0.f, TargetCriticalHitResistance);
float SourceCriticalHitDamage = 0.f;
ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(DamageStatics().CriticalHitDamageDef, EvaluationParameters, SourceCriticalHitDamage);
SourceArmorPenetration = FMath::Max<float>(0.f, SourceCriticalHitDamage);
// 커브 데이터 ...
// 데미지 계산 수행
/* Calculate Damage */
// 1. Calculate Block Chance, if Blocked, reduce Damage by 50%
const bool bBlocked = FMath::RandRange(1, 100) < TargetBlockChance;
Damage = bBlocked ? Damage * 0.5f : Damage;
// 2. Calculate Effective Armor by reducing Target's Armor by Source's Armor Penetration
// Reduce Damage by Effective Armor * Coefficient
const float EffectiveArmor = TargetArmor *= (100 - SourceArmorPenetration * ArmorPenetrationCoefficient) / 100.f;
Damage *= (100 - EffectiveArmor * EffectiveArmorCoefficient) / 100.f;
// 3. Calculate Critical Hit Chance and Damage
// Critical Hit Chance = Source's Critical Hit Chance - Target's Critical Hit Resistance
// If Critical Hit occurs, Double Damage + Critical Hit Damage
const float EffectiveCriticalHitChance = FMath::Max<float>(0.f, SourceCriticalHitChance - TargetCriticalHitResistance * EffectiveCriticalHitChanceCoefficient);
const bool bCriticalHit = FMath::RandRange(1, 100) < EffectiveCriticalHitChance;
Damage = bCriticalHit ? (Damage * 2) + SourceCriticalHitDamage : Damage;
// 계산된 데미지 전송
// Use as much FGameplayModifierEvaluatedData as we want
// FGameplayModifierEvaluatedData(FGameplayAttibute AttributeToModify, EGameplayModOp ModificationType, float Magnitude)
FGameplayModifierEvaluatedData EvaluatedData(UAuraAttributeSet::GetIncomingDamageAttribute(), EGameplayModOp::Additive, Damage);
OutExecutionOutput.AddOutputModifier(EvaluatedData);
}```cpp
// FGameplayEffectContextHandle
// Handle that wraps a FGameplayEffectContext or subclass, to allow it to be polymorphic and replicate properly
USTRUCT(BlueprintType)
struct GAMEPLAYABILITIES_API FGameplayEffectContextHandle
{
GENERATED_USTRUCT_BODY()
FGameplayEffectContextHandle()
{
}
virtual ~FGameplayEffectContextHandle()
{
}
/** Constructs from an existing context, should be allocated by new */
explicit FGameplayEffectContextHandle(FGameplayEffectContext* DataPtr)
{
Data = TSharedPtr<FGameplayEffectContext>(DataPtr);
}
/** Sets from an existing context, should be allocated by new */
void operator=(FGameplayEffectContext* DataPtr)
{
Data = TSharedPtr<FGameplayEffectContext>(DataPtr);
}
void Clear()
{
Data.Reset();
}
bool IsValid() const
{
return Data.IsValid();
}
/** Returns Raw effet context, may be null */
FGameplayEffectContext* Get()
{
return IsValid() ? Data.Get() : nullptr;
}
const FGameplayEffectContext* Get() const
{
return IsValid() ? Data.Get() : nullptr;
}
/** Returns the list of gameplay tags applicable to this effect, defaults to the owner's tags */
void GetOwnedGameplayTags(OUT FGameplayTagContainer& ActorTagContainer, OUT FGameplayTagContainer& SpecTagContainer) const
{
if (IsValid())
{
Data->GetOwnedGameplayTags(ActorTagContainer, SpecTagContainer);
}
}
/** Sets the instigator and effect causer. Instigator is who owns the ability that spawned this, EffectCauser is the actor that is the physical source of the effect, such as a weapon. They can be the same. */
void AddInstigator(class AActor *InInstigator, class AActor *InEffectCauser)
{
if (IsValid())
{
Data->AddInstigator(InInstigator, InEffectCauser);
}
}
/** Sets Ability instance and CDO parameters on context */
void SetAbility(const UGameplayAbility* InGameplayAbility)
{
if (IsValid())
{
Data->SetAbility(InGameplayAbility);
}
}
/** Returns the immediate instigator that applied this effect */
virtual AActor* GetInstigator() const
{
if (IsValid())
{
return Data->GetInstigator();
}
return nullptr;
}
/** Returns the Ability CDO */
const UGameplayAbility* GetAbility() const
{
if (IsValid())
{
return Data->GetAbility();
}
return nullptr;
}
/** Returns the Ability Instance (never replicated) */
const UGameplayAbility* GetAbilityInstance_NotReplicated() const
{
if (IsValid())
{
return Data->GetAbilityInstance_NotReplicated();
}
return nullptr;
}
/** Returns level this was executed at */
int32 GetAbilityLevel() const
{
if (IsValid())
{
return Data->GetAbilityLevel();
}
return 1;
}
/** Returns the ability system component of the instigator of this effect */
virtual UAbilitySystemComponent* GetInstigatorAbilitySystemComponent() const
{
if (IsValid())
{
return Data->GetInstigatorAbilitySystemComponent();
}
return nullptr;
}
/** Returns the physical actor tied to the application of this effect */
virtual AActor* GetEffectCauser() const
{
if (IsValid())
{
return Data->GetEffectCauser();
}
return nullptr;
}
/** Should always return the original instigator that started the whole chain. Subclasses can override what this does */
AActor* GetOriginalInstigator() const
{
if (IsValid())
{
return Data->GetOriginalInstigator();
}
return nullptr;
}
/** Returns the ability system component of the instigator that started the whole chain */
UAbilitySystemComponent* GetOriginalInstigatorAbilitySystemComponent() const
{
if (IsValid())
{
return Data->GetOriginalInstigatorAbilitySystemComponent();
}
return nullptr;
}
/** Sets the object this effect was created from. */
void AddSourceObject(const UObject* NewSourceObject)
{
if (IsValid())
{
Data->AddSourceObject(NewSourceObject);
}
}
/** Returns the object this effect was created from. */
UObject* GetSourceObject() const
{
if (IsValid())
{
return Data->GetSourceObject();
}
return nullptr;
}
/** Returns if the instigator is locally controlled */
bool IsLocallyControlled() const
{
if (IsValid())
{
return Data->IsLocallyControlled();
}
return false;
}
/** Returns if the instigator is locally controlled and a player */
bool IsLocallyControlledPlayer() const
{
if (IsValid())
{
return Data->IsLocallyControlledPlayer();
}
return false;
}
/** Add actors to the stored actor list */
void AddActors(const TArray<TWeakObjectPtr<AActor>>& InActors, bool bReset = false)
{
if (IsValid())
{
Data->AddActors(InActors, bReset);
}
}
/** Add a hit result for targeting */
void AddHitResult(const FHitResult& InHitResult, bool bReset = false)
{
if (IsValid())
{
Data->AddHitResult(InHitResult, bReset);
}
}
/** Returns actor list, may be empty */
const TArray<TWeakObjectPtr<AActor>> GetActors()
{
return Data->GetActors();
}
/** Returns hit result, this can be null */
const FHitResult* GetHitResult() const
{
if (IsValid())
{
return Data->GetHitResult();
}
return nullptr;
}
/** Adds an origin point */
void AddOrigin(FVector InOrigin)
{
if (IsValid())
{
Data->AddOrigin(InOrigin);
}
}
/** Returns origin point, may be invalid if HasOrigin is false */
virtual const FVector& GetOrigin() const
{
if (IsValid())
{
return Data->GetOrigin();
}
return FVector::ZeroVector;
}
/** Returns true if GetOrigin will give valid information */
virtual bool HasOrigin() const
{
if (IsValid())
{
return Data->HasOrigin();
}
return false;
}
/** Returns debug string */
FString ToString() const
{
return IsValid() ? Data->ToString() : FString(TEXT("NONE"));
}
/** Creates a deep copy of this handle, used before modifying */
FGameplayEffectContextHandle Duplicate() const
{
if (IsValid())
{
FGameplayEffectContext* NewContext = Data->Duplicate();
return FGameplayEffectContextHandle(NewContext);
}
else
{
return FGameplayEffectContextHandle();
}
}
/** Comparison operator */
bool operator==(FGameplayEffectContextHandle const& Other) const
{
if (Data.IsValid() != Other.Data.IsValid())
{
return false;
}
if (Data.Get() != Other.Data.Get())
{
return false;
}
return true;
}
/** Comparison operator */
bool operator!=(FGameplayEffectContextHandle const& Other) const
{
return !(FGameplayEffectContextHandle::operator==(Other));
}
/** Custom serializer, handles polymorphism of context */
bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
private:
friend UE::Net::FGameplayEffectContextHandleAccessorForNetSerializer;
TSharedPtr<FGameplayEffectContext> Data;
};
```
- FGameplayEffectContextHandle은 FGameplayEffectContext의 멤버에 쉽게 접근할 수 있게 해주는 일종의 래퍼 구조체이다. // FGameplayEffectContext
/**
* Data structure that stores an instigator and related data, such as positions and targets
* Games can subclass this structure and add game-specific information
* It is passed throughout effect execution so it is a great place to track transient information about an execution
*/
USTRUCT()
struct GAMEPLAYABILITIES_API FGameplayEffectContext
{
GENERATED_USTRUCT_BODY()
FGameplayEffectContext()
: AbilityLevel(1)
, WorldOrigin(ForceInitToZero)
, bHasWorldOrigin(false)
, bReplicateSourceObject(false)
, bReplicateInstigator(false)
, bReplicateEffectCauser(false)
{
}
FGameplayEffectContext(AActor* InInstigator, AActor* InEffectCauser)
: AbilityLevel(1)
, WorldOrigin(ForceInitToZero)
, bHasWorldOrigin(false)
, bReplicateSourceObject(false)
, bReplicateInstigator(false)
, bReplicateEffectCauser(false)
{
FGameplayEffectContext::AddInstigator(InInstigator, InEffectCauser);
}
virtual ~FGameplayEffectContext()
{
}
/** Returns the list of gameplay tags applicable to this effect, defaults to the owner's tags */
virtual void GetOwnedGameplayTags(OUT FGameplayTagContainer& ActorTagContainer, OUT FGameplayTagContainer& SpecTagContainer) const;
/** Sets the instigator and effect causer. Instigator is who owns the ability that spawned this, EffectCauser is the actor that is the physical source of the effect, such as a weapon. They can be the same. */
virtual void AddInstigator(class AActor *InInstigator, class AActor *InEffectCauser);
/** Sets the ability that was used to spawn this */
virtual void SetAbility(const UGameplayAbility* InGameplayAbility);
/** Returns the immediate instigator that applied this effect */
virtual AActor* GetInstigator() const
{
return Instigator.Get();
}
/** Returns the CDO of the ability used to instigate this context */
const UGameplayAbility* GetAbility() const;
/** Returns the specific instance that instigated this, may not always be set */
const UGameplayAbility* GetAbilityInstance_NotReplicated() const;
/** Gets the ability level this was evaluated at */
int32 GetAbilityLevel() const
{
return AbilityLevel;
}
/** Returns the ability system component of the instigator of this effect */
virtual UAbilitySystemComponent* GetInstigatorAbilitySystemComponent() const
{
return InstigatorAbilitySystemComponent.Get();
}
/** Returns the physical actor tied to the application of this effect */
virtual AActor* GetEffectCauser() const
{
return EffectCauser.Get();
}
/** Modify the effect causer actor, useful when that information is added after creation */
void SetEffectCauser(AActor* InEffectCauser)
{
EffectCauser = InEffectCauser;
bReplicateEffectCauser = CanActorReferenceBeReplicated(InEffectCauser);
}
/** Should always return the original instigator that started the whole chain. Subclasses can override what this does */
virtual AActor* GetOriginalInstigator() const
{
return Instigator.Get();
}
/** Returns the ability system component of the instigator that started the whole chain */
virtual UAbilitySystemComponent* GetOriginalInstigatorAbilitySystemComponent() const
{
return InstigatorAbilitySystemComponent.Get();
}
/** Sets the object this effect was created from. */
virtual void AddSourceObject(const UObject* NewSourceObject)
{
SourceObject = MakeWeakObjectPtr(const_cast<UObject*>(NewSourceObject));
bReplicateSourceObject = NewSourceObject && NewSourceObject->IsSupportedForNetworking();
}
/** Returns the object this effect was created from. */
virtual UObject* GetSourceObject() const
{
return SourceObject.Get();
}
/** Add actors to the stored actor list */
virtual void AddActors(const TArray<TWeakObjectPtr<AActor>>& IActor, bool bReset = false);
/** Add a hit result for targeting */
virtual void AddHitResult(const FHitResult& InHitResult, bool bReset = false);
/** Returns actor list, may be empty */
virtual const TArray<TWeakObjectPtr<AActor>>& GetActors() const
{
return Actors;
}
/** Returns hit result, this can be null */
virtual const FHitResult* GetHitResult() const
{
return const_cast<FGameplayEffectContext*>(this)->GetHitResult();
}
/** Returns hit result, this can be null */
virtual FHitResult* GetHitResult()
{
return HitResult.Get();
}
/** Adds an origin point */
virtual void AddOrigin(FVector InOrigin);
/** Returns origin point, may be invalid if HasOrigin is false */
virtual const FVector& GetOrigin() const
{
return WorldOrigin;
}
/** Returns true if GetOrigin will give valid information */
virtual bool HasOrigin() const
{
return bHasWorldOrigin;
}
/** Returns debug string */
virtual FString ToString() const;
/** Returns the actual struct used for serialization, subclasses must override this! */
virtual UScriptStruct* GetScriptStruct() const
{
return FGameplayEffectContext::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;
}
/** True if this was instigated by a locally controlled actor */
virtual bool IsLocallyControlled() const;
/** True if this was instigated by a locally controlled player */
virtual bool IsLocallyControlledPlayer() const;
/** Custom serialization, subclasses must override this */
virtual bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
protected:
static bool CanActorReferenceBeReplicated(const AActor* Actor);
// The object pointers here have to be weak because contexts aren't necessarily tracked by GC in all cases
/** Instigator actor, the actor that owns the ability system component */
UPROPERTY()
TWeakObjectPtr<AActor> Instigator;
/** The physical actor that actually did the damage, can be a weapon or projectile */
UPROPERTY()
TWeakObjectPtr<AActor> EffectCauser;
/** The ability CDO that is responsible for this effect context (replicated) */
UPROPERTY()
TWeakObjectPtr<UGameplayAbility> AbilityCDO;
/** The ability instance that is responsible for this effect context (NOT replicated) */
UPROPERTY(NotReplicated)
TWeakObjectPtr<UGameplayAbility> AbilityInstanceNotReplicated;
/** The level this was executed at */
UPROPERTY()
int32 AbilityLevel;
/** Object this effect was created from, can be an actor or static object. Useful to bind an effect to a gameplay object */
UPROPERTY()
TWeakObjectPtr<UObject> SourceObject;
/** The ability system component that's bound to instigator */
UPROPERTY(NotReplicated)
TWeakObjectPtr<UAbilitySystemComponent> InstigatorAbilitySystemComponent;
/** Actors referenced by this context */
UPROPERTY()
TArray<TWeakObjectPtr<AActor>> Actors;
/** Trace information - may be nullptr in many cases */
TSharedPtr<FHitResult> HitResult;
/** Stored origin, may be invalid if bHasWorldOrigin is false */
UPROPERTY()
FVector WorldOrigin;
UPROPERTY()
uint8 bHasWorldOrigin:1;
/** True if the SourceObject can be replicated. This bool is not replicated itself. */
UPROPERTY(NotReplicated)
uint8 bReplicateSourceObject:1;
/** True if the Instigator can be replicated. This bool is not replicated itself. */
UPROPERTY(NotReplicated)
uint8 bReplicateInstigator:1;
/** True if the Instigator can be replicated. This bool is not replicated itself. */
UPROPERTY(NotReplicated)
uint8 bReplicateEffectCauser:1;
};```cpp
struct FGameplayEffectModCallbackData
{
FGameplayEffectModCallbackData(const FGameplayEffectSpec& InEffectSpec, FGameplayModifierEvaluatedData& InEvaluatedData, UAbilitySystemComponent& InTarget)
: EffectSpec(InEffectSpec)
, EvaluatedData(InEvaluatedData)
, Target(InTarget)
{
}
const struct FGameplayEffectSpec& EffectSpec; // The spec that the mod came from
struct FGameplayModifierEvaluatedData& EvaluatedData; // The 'flat'/computed data to be applied to the target
class UAbilitySystemComponent &Target; // Target we intend to apply to
};
```
- 따라서 FGameplayEffectContext를 활용하면서도 원하는 정보를 전달하기 위해서 FGameplayEffectContext를 상속하는 구조체를 생성한다. #pragma once
#include "GameplayEffectTypes.h"
#include "AuraAbilityTypes.generated.h"
USTRUCT(BlueprintType)
struct FAuraGameplayEffectContext : public FGameplayEffectContext
{
GENERATED_BODY()
public:
bool IsBlockedHit() const { return bIsBlockedHit; }
void SetIsBlockedHit(const bool bInIsBlockedHit) { bIsBlockedHit = bInIsBlockedHit; }
bool IsCriticalHit() const { return bIsCriticalHit; }
void SetIsCriticalHit(const bool bInIsCriticalHit) { bIsCriticalHit = bInIsCriticalHit; }
/** Returns the actual struct used for serialization, subclasses must override this! */
virtual UScriptStruct* GetScriptStruct() const override
{
return FAuraGameplayEffectContext::StaticStruct();
}
/** Custom serialization, subclasses must override this */
virtual bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) override;
protected:
UPROPERTY()
bool bIsBlockedHit = false;
UPROPERTY()
bool bIsCriticalHit = false;
}; /** Returns the actual struct used for serialization, subclasses must override this! */
virtual UScriptStruct* GetScriptStruct() const
{
return FGameplayEffectContext::StaticStruct();
}FGameplayEffectContext::StaticStruct() 대신 FAuraGameplayEffectContext::StaticStruct()로 변경해주면 된다. bool FGameplayEffectContext::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
uint8 RepBits = 0;
if (Ar.IsSaving())
{
if (bReplicateInstigator && Instigator.IsValid())
{
RepBits |= 1 << 0;
}
if (bReplicateEffectCauser && 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;
}
}
Ar.SerializeBits(&RepBits, 7);
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 (Ar.IsLoading())
{
AddInstigator(Instigator.Get(), EffectCauser.Get()); // Just to initialize InstigatorAbilitySystemComponent
}
bOutSuccess = true;
return true;
} bool FAuraGameplayEffectContext::NetSerialize(FArchive& Ar, UPackageMap* Map, bool& bOutSuccess)
{
// 저장해야 할 변수의 종류가 늘어 uint8(8비트)만으로는 크기가 부족하므로, uint32를 사용
uint32 RepBits = 0;
// FArchive가 저장인지 확인
if (Ar.IsSaving())
{
if (bReplicateInstigator && Instigator.IsValid())
{
RepBits |= 1 << 0;
}
if (bReplicateEffectCauser && 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)
{
RepBits |= 1 << 7;
}
if (bIsCriticalHit)
{
RepBits |= 1 << 8;
}
}
// 전송할 비트의 길이
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))
{
Ar << bIsBlockedHit;
}
if (RepBits & (1 << 8))
{
Ar << bIsCriticalHit;
}
if (Ar.IsLoading())
{
AddInstigator(Instigator.Get(), EffectCauser.Get()); // Just to initialize InstigatorAbilitySystemComponent
}
bOutSuccess = true;
return true;
} template<>
struct TStructOpsTypeTraits< FGameplayEffectContext > : public TStructOpsTypeTraitsBase2< FGameplayEffectContext >
{
enum
{
WithNetSerializer = true,
WithCopy = true // Necessary so that TSharedPtr<FHitResult> Data is copied around
};
};WithNetSerialize: NetSerialize()를 사용하는 구조체
WithCopy: 복사하여 할당 가능한 연산을 지원하는 구조체 (Duplicate() 함수를 FAuraGameplayEffectContext에 추가하면 됨)
/** type traits to cover the custom aspects of a script struct **/
template <class CPPSTRUCT>
struct TStructOpsTypeTraitsBase2
{
enum
{
WithZeroConstructor = false, // struct can be constructed as a valid object by filling its memory footprint with zeroes.
WithNoInitConstructor = false, // struct has a constructor which takes an EForceInit parameter which will force the constructor to perform initialization, where the default constructor performs 'uninitialization'.
WithNoDestructor = false, // struct will not have its destructor called when it is destroyed.
WithCopy = !TIsPODType<CPPSTRUCT>::Value, // struct can be copied via its copy assignment operator.
WithIdenticalViaEquality = false, // struct can be compared via its operator==. This should be mutually exclusive with WithIdentical.
WithIdentical = false, // struct can be compared via an Identical(const T* Other, uint32 PortFlags) function. This should be mutually exclusive with WithIdenticalViaEquality.
WithExportTextItem = false, // struct has an ExportTextItem function used to serialize its state into a string.
WithImportTextItem = false, // struct has an ImportTextItem function used to deserialize a string into an object of that class.
WithAddStructReferencedObjects = false, // struct has an AddStructReferencedObjects function which allows it to add references to the garbage collector.
WithSerializer = false, // struct has a Serialize function for serializing its state to an FArchive.
WithStructuredSerializer = false, // struct has a Serialize function for serializing its state to an FStructuredArchive.
WithPostSerialize = false, // struct has a PostSerialize function which is called after it is serialized
WithNetSerializer = false, // struct has a NetSerialize function for serializing its state to an FArchive used for network replication.
WithNetDeltaSerializer = false, // struct has a NetDeltaSerialize function for serializing differences in state from a previous NetSerialize operation.
WithSerializeFromMismatchedTag = false, // struct has a SerializeFromMismatchedTag function for converting from other property tags.
WithStructuredSerializeFromMismatchedTag = false, // struct has an FStructuredArchive-based SerializeFromMismatchedTag function for converting from other property tags.
WithPostScriptConstruct = false, // struct has a PostScriptConstruct function which is called after it is constructed in blueprints
WithNetSharedSerialization = false, // struct has a NetSerialize function that does not require the package map to serialize its state.
WithGetPreloadDependencies = false, // struct has a GetPreloadDependencies function to return all objects that will be Preload()ed when the struct is serialized at load time.
WithPureVirtual = false, // struct has PURE_VIRTUAL functions and cannot be constructed when CHECK_PUREVIRTUALS is true
WithCanEditChange = false, // struct has an editor-only CanEditChange function that can conditionally make child properties read-only in the details panel (same idea as UObject::CanEditChange)
};
};
/** 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;
}
```cpp
template<>
struct TStructOpsTypeTraits<FAuraGameplayEffectContext> : public TStructOpsTypeTraitsBase2<FAuraGameplayEffectContext>
{
enum
{
WithCopy = true,
WithNetSerializer = true
};;
};
```/** Returns the actual struct used for serialization, subclasses must override this! */
virtual UScriptStruct* GetScriptStruct() const override
{
// 5.3 버전 이후, 구조체이름::StaticStruct()가 아닌 그냥 StaticStruct()를 반환.
return StaticStruct();
}
/** Creates a copy of this context, used to duplicate for later modifications */
// 5.3 버전 이후, 원래 FGameplayEffectContext* 였던 반환형을 직접 작성한 구조체와 일치하도록 변경.
virtual FAuraGameplayEffectContext* Duplicate() const
{
FAuraGameplayEffectContext* NewContext = new FAuraGameplayEffectContext();
*NewContext = *this;
if (GetHitResult())
{
// Does a deep copy of the hit result
NewContext->AddHitResult(*GetHitResult(), true);
}
return NewContext;
}```cpp
#pragma once
#include "CoreMinimal.h"
#include "AbilitySystemGlobals.h"
#include "AuraAbilitySystemGlobals.generated.h"
UCLASS()
class AURA_API UAuraAbilitySystemGlobals : public UAbilitySystemGlobals
{
GENERATED_BODY()
virtual FGameplayEffectContext* AllocGameplayEffectContext() const override;
};
// AuraAbilitySystemGlobals.cpp
#include "AbilitySystem/AuraAbilitySystemGlobals.h"
#include "AuraAbilityTypes.h"
FGameplayEffectContext* UAuraAbilitySystemGlobals::AllocGameplayEffectContext() const
{
return new FAuraGameplayEffectContext();
}
```
- 오버라이드한 AllocGameplayEffectContext()는 기존에는 FGameplayEffectContext()를 생성하여 return하는 함수이지만,
대신 FAuraGameplayEffectContext()를 생성하여 return하도록 해주면 된다.[/Script/GameplayAbilities.AbilitySystemGlobals]
+AbilitySystemGlobalsClassName="/Script/Aura.AuraAbilitySystemGlobals"
/Script/[모듈명(프로젝트명)].[AbilitySystemGlobals 클래스명]