/** Delegate for when an effect is applied */
DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnGameplayEffectAppliedDelegate, UAbilitySystemComponent*, const FGameplayEffectSpec&, FActiveGameplayEffectHandle);
/** Called on server whenever a GE is applied to self. This includes instant and duration based GEs. */
FOnGameplayEffectAppliedDelegate OnGameplayEffectAppliedDelegateToSelf;
/** Called on server whenever a GE is applied to someone else. This includes instant and duration based GEs. */
FOnGameplayEffectAppliedDelegate OnGameplayEffectAppliedDelegateToTarget;
/** Called on both client and server whenever a duration based GE is added (E.g., instant GEs do not trigger this). */
FOnGameplayEffectAppliedDelegate OnActiveGameplayEffectAddedDelegateToSelf;
/** Called on server whenever a periodic GE executes on self */
FOnGameplayEffectAppliedDelegate OnPeriodicGameplayEffectExecuteDelegateOnSelf;
/** Called on server whenever a periodic GE executes on target */
FOnGameplayEffectAppliedDelegate OnPeriodicGameplayEffectExecuteDelegateOnTarget;
/** This ASC has successfully applied a GE to something (potentially itself) */
void OnGameplayEffectAppliedToTarget(UAbilitySystemComponent* Target, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle);
void OnGameplayEffectAppliedToSelf(UAbilitySystemComponent* Source, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveHandle);
void OnPeriodicGameplayEffectExecuteOnTarget(UAbilitySystemComponent* Target, const FGameplayEffectSpec& SpecExecuted, FActiveGameplayEffectHandle ActiveHandle);
void OnPeriodicGameplayEffectExecuteOnSelf(UAbilitySystemComponent* Source, const FGameplayEffectSpec& SpecExecuted, FActiveGameplayEffectHandle ActiveHandle);
위 함수들은 LocalRole에 따라 호출 여부가 다르다.
Authority: 언제나 항상 호출
Autonomous: 대상 GE의 Replication Mode가 Full이거나 Mixed인 경우에만 호출 됨
Simulated: 대상 GE의 Replication Mode가 Full일 때에만 호출 됨
Remove
GE는 GameplayAbilities와 ASC에서 RemoveActiveGameplayEffect가 포함된 함수들에 의해 제거된다.
또한 Duration, Infinite Ge는 제거될 때 Delegate를 받을 수 있다.
/** Contains all of the gameplay effects that are currently active on this component */
UPROPERTY(Replicated)
FActiveGameplayEffectsContainer ActiveGameplayEffects;
DECLARE_MULTICAST_DELEGATE_OneParam(FOnGivenActiveGameplayEffectRemoved, const FActiveGameplayEffect&);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnActiveGameplayEffectRemoved_Info, const FGameplayEffectRemovalInfo&);
/** Called when any gameplay effects are removed */
FOnGivenActiveGameplayEffectRemoved& OnAnyGameplayEffectRemovedDelegate();
/** Returns delegate structure that allows binding to several gameplay effect changes */
FOnActiveGameplayEffectRemoved_Info* OnGameplayEffectRemoved_InfoDelegate(FActiveGameplayEffectHandle Handle);
위 함수들은 LocalRole에 따라 호출 여부가 다르다.
Authority: 언제나 항상 호출
Autonomous: 대상 GE의 Replication Mode가 Full이거나 Mixed인 경우에만 호출 됨
Simulated: 대상 GE의 Replication Mode가 Full일 때에만 호출 됨
/** Gameplay effect duration policies */
UENUM()
enum class EGameplayEffectDurationType : uint8
{
/** This effect applies instantly */
Instant,
/** This effect lasts forever */
Infinite,
/** The duration of this effect will be specified by a magnitude */
HasDuration
};
/**
* Helper function to sum all of the mods in the specified array, using the specified modifier bias and evaluation parameters
*
* @param InMods Mods to sum
* @param Bias Bias to apply to modifier magnitudes
* @param Parameters Evaluation parameters
*
* @return Summed value of mods
*/
float FAggregatorModChannel::SumMods(const TArray<FAggregatorMod>& InMods, float Bias, const FAggregatorEvaluateParameters& Parameters)
{
float Sum = Bias;
for (const FAggregatorMod& Mod : InMods)
{
if (Mod.Qualifies())
{
Sum += (Mod.EvaluatedMagnitude - Bias);
}
}
return Sum;
}
/**
* Evaluates the channel's mods with the specified base value and evaluation parameters
*
* @param InlineBaseValue Base value to use for the evaluation
* @param Parameters Additional evaluation parameters to use
*
* @return Evaluated value based upon the channel's mods
*/
float FAggregatorModChannel::EvaluateWithBase(float InlineBaseValue, const FAggregatorEvaluateParameters& Parameters) const
{
for (const FAggregatorMod& Mod : Mods[EGameplayModOp::Override])
{
if (Mod.Qualifies())
{
return Mod.EvaluatedMagnitude;
}
}
float Additive = SumMods(Mods[EGameplayModOp::Additive], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Additive), Parameters);
float Multiplicitive = SumMods(Mods[EGameplayModOp::Multiplicitive], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Multiplicitive), Parameters);
float Division = SumMods(Mods[EGameplayModOp::Division], GameplayEffectUtilities::GetModifierBiasByModifierOp(EGameplayModOp::Division), Parameters);
if (FMath::IsNearlyZero(Division))
{
ABILITY_LOG(Warning, TEXT("Division summation was 0.0f in FAggregatorModChannel."));
Division = 1.f;
}
return ((InlineBaseValue + Additive) * Multiplicitive) / Division;
}
Multiply/Divide Modifier는 둘 다 1의 Bias Value를 가지고 있다.
/** Notifies when GameplayEffectSpec is blocked by an ActiveGameplayEffect due to immunity */
DECLARE_MULTICAST_DELEGATE_TwoParams(FImmunityBlockGE, const FGameplayEffectSpec& /*BlockedSpec*/, const FActiveGameplayEffect* /*ImmunityGameplayEffect*/);
/** Immunity notification support */
FImmunityBlockGE OnImmunityBlockGameplayEffectDelegate;
GarantedApplicationImmunityTags
5.3 버전에서 Deprecated 됨
UImmunityGameplayEffectComponent를 대신 사용
GrantedApplicationImmunityQuery
5.3 버전에서 Deprecated 됨
UImmunityGameplayEffectComponent를 대신 사용
GameplayEffectSpec
GE의 인스턴스화로 취급할 수 있다.(이하 GESpec)
자신이 가르키는 GE의 Reference 만들어진 Level, 만든 주체에 대한 정보를 내포하고 있다.
이 정보들은 GE와 다르게 Runtime상에서 적용되기 전까지 자유롭게 생성/수정이 가능하다.
GE가 적용 될 때 GESpec이 GE로부터 생성되고 실질적으로 GESpec이 Target에 적용된다.
GESpec은 UASC::MakeOutgoingSpec()함수를 통해 GE로부터 생성된다.
/** Get an outgoing GameplayEffectSpec that is ready to be applied to other things. */
UFUNCTION(BlueprintCallable, Category = GameplayEffects)
virtual FGameplayEffectSpecHandle MakeOutgoingSpec(TSubclassOf<UGameplayEffect> GameplayEffectClass, float Level, FGameplayEffectContextHandle Context) const;
GESpec은 그 즉시 적용될 필요가 없다.
보통은 투사체에 GESpec을 전달하고 충돌 발생 시 연산에 사용하곤 한다.
GESpec이 성공적으로 적용되면 FActiveGameplayEffect를 반환한다.
GESpec에는 아래 내용들이 제공된다.
GESpec을 만드는데 사용된 GE 클래스
GESpec의 Level
보통 GESpec을 만드는데 사용한 Ability와 같지만 다를수도 있음
Duration
Default는 GE의 Duration이지만 값이 다를 수도 있다.
Period
Default는 GE의 Period이지만 다를 수도 있다.
현재 GESpec의 Stack Count
상한은 GE의 설정값을 따른다.
GameplayEffectContextHandle
이를 통해 누가 이 GeSpec을 만들었는지 인지할 수 있다.
GESpec이 생성 되었을 때 Snapshotting으로 인해 Capture되는 Attribute 들
DynamicGrantedTag
GESpec이 부여 될 때 GameplayTag에 GE가 추가로 부여해야 하는 GameplayEffectSpec
DynamicAssetTag
GESpec이 GE와 비교해 추가로 가져야 하는 Tag들
SetByCaller
SetByCallers
GESpec으로 하여금 GameplayTag나 FName과 관련된 float 값을 전달할 수 있도록 한다.
이들은 GESpec에 TMap 형태로 저장된다.
이 값들은 GE의 Modifier에 사용되거나, 일반적인 의미의 Ferring Floats로 사용될 수 있다.
Modifiers에서 사용
GE class보다 앞서 정의되어야만 한다.
GameTag 버전만 사용 가능하다.
GE class에 하나가 정의 되었는데 GESpec이 대응하는 Tag-float pair가 없는 경우, GESpec에서 Runtime Error가 발생하며 0을 반환한다.
Divide Operation에서 문제를 야기할 수 있다.
그 외에서 사용
어느 class보다도 앞서 정의 될 필요가 없다.
GESpec에 존재하지 않는 SetByCaller를 읽더라도 사용자로 하여금 Default 값과 warning을 반환할 수 있다.
관련 함수
보통 Ability 안에서 생성된 숫자 데이터들은 SetByCallers을 통해 GameplayEffectExecutionCalculations나 ModifierMagnitudeCalculations로 전달하는 것이 일반적이다.
SetByCaller를 BP에서 읽을 수 있으려면 BPLibrary에 Custom Node를 만들어 줘야 한다.
/** Sets the magnitude of a SetByCaller modifier */
void SetSetByCallerMagnitude(FName DataName, float Magnitude);
/** Sets the magnitude of a SetByCaller modifier */
void SetSetByCallerMagnitude(FGameplayTag DataTag, float Magnitude);
/** Returns the magnitude of a SetByCaller modifier. Will return 0.f and Warn if the magnitude has not been set. */
float GetSetByCallerMagnitude(FName DataName, bool WarnIfNotFound = true, float DefaultIfNotFound = 0.f) const;
/** Returns the magnitude of a SetByCaller modifier. Will return 0.f and Warn if the magnitude has not been set. */
float GetSetByCallerMagnitude(FGameplayTag DataTag, bool WarnIfNotFound = true, float DefaultIfNotFound = 0.f) const;
이 중 FName을 쓰는 것보다 GameplayTag를 사용하는 것을 권장한다.
GameplayEffectContext
GEContext는 GESpec의 Instigator와 TargetData를 가지고 있다.
이는 Subclass를 통해 임시 데이터를 다른 곳으로 전달하는 좋은 구조이다.
ModifierMagnitudeCalculations, GameplayEffectExecutionCalculations, AS, GameplayCues 등
/**
* Struct representing the definition of a custom execution for a gameplay effect.
* Custom executions run special logic from an outside class each time the gameplay effect executes.
*/
USTRUCT(BlueprintType)
struct GAMEPLAYABILITIES_API FGameplayEffectExecutionDefinition
{
GENERATED_USTRUCT_BODY()
/**
* Gathers and populates the specified array with the capture definitions that the execution would like in order
* to perform its custom calculation. Up to the individual execution calculation to handle if some of them are missing
* or not.
*
* @param OutCaptureDefs [OUT] Capture definitions requested by the execution
*/
void GetAttributeCaptureDefinitions(OUT TArray<FGameplayEffectAttributeCaptureDefinition>& OutCaptureDefs) const;
/** Custom execution calculation class to run when the gameplay effect executes */
UPROPERTY(EditDefaultsOnly, Category=Execution)
TSubclassOf<UGameplayEffectExecutionCalculation> CalculationClass;
/** These tags are passed into the execution as is, and may be used to do conditional logic */
UPROPERTY(EditDefaultsOnly, Category = Execution)
FGameplayTagContainer PassedInTags;
/** Modifiers that are applied "in place" during the execution calculation */
UPROPERTY(EditDefaultsOnly, Category = Execution)
TArray<FGameplayEffectExecutionScopedModifierInfo> CalculationModifiers;
/** Other Gameplay Effects that will be applied to the target of this execution if the execution is successful. Note if no execution class is selected, these will always apply. */
UPROPERTY(EditDefaultsOnly, Category = Execution)
TArray<FConditionalGameplayEffect> ConditionalGameplayEffects;
};
/** Array of executions that will affect the target of this effect */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = GameplayEffect)
TArray<FGameplayEffectExecutionDefinition> Executions;
Modifier에서 지원하는 정보 이상의 것이 필요할 때에는 사용
UGameplayEffectExecutionCalculation을 사용하면 GE의 Custom Behaviour를 정의
Modifier로 커버할 수 없는 복잡한 식을 정의할 때 유용
Application Requirements
GE를 적용하는 조건을 처리하는 부분
5.3 버전 기준으로 Deprecated
UCustomCanApplyGameplayEffectComponent를 대신 사용
Granted Abilities
GE를 통해 부여하는 Tag 혹은 Attribute
5.3 버전 기준으로 Deprecated
GetBlockedAbilityTags 함수를 통해 UTargetTagsGameplayEffectComponent를 대신 사용
Overflow Effects
/** Effects to apply when a stacking effect "overflows" its stack count through another attempted application. Added whether the overflow application succeeds or not. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Stacking|Overflow", meta = (EditConditionHides, EditCondition = "StackingType != EGameplayEffectStackingType::None"))
TArray<TSubclassOf<UGameplayEffect>> OverflowEffects;
/**
* FGameplayEffectCue
* This is a cosmetic cue that can be tied to a UGameplayEffect.
* This is essentially a GameplayTag + a Min/Max level range that is used to map the level of a GameplayEffect to a normalized value used by the GameplayCue system.
*/
USTRUCT(BlueprintType)
struct FGameplayEffectCue
{
GENERATED_USTRUCT_BODY()
FGameplayEffectCue()
: MinLevel(0.f)
, MaxLevel(0.f)
{
}
FGameplayEffectCue(const FGameplayTag& InTag, float InMinLevel, float InMaxLevel)
: MinLevel(InMinLevel)
, MaxLevel(InMaxLevel)
{
GameplayCueTags.AddTag(InTag);
}
/** The attribute to use as the source for cue magnitude. If none use level */
UPROPERTY(EditDefaultsOnly, Category = GameplayCue)
FGameplayAttribute MagnitudeAttribute;
/** The minimum level that this Cue supports */
UPROPERTY(EditDefaultsOnly, Category = GameplayCue)
float MinLevel;
/** The maximum level that this Cue supports */
UPROPERTY(EditDefaultsOnly, Category = GameplayCue)
float MaxLevel;
/** Tags passed to the gameplay cue handler when this cue is activated */
UPROPERTY(EditDefaultsOnly, Category = GameplayCue, meta = (Categories="GameplayCue"))
FGameplayTagContainer GameplayCueTags;
float NormalizeLevel(float InLevel)
{
float Range = MaxLevel - MinLevel;
if (Range <= KINDA_SMALL_NUMBER)
{
return 1.f;
}
return FMath::Clamp((InLevel - MinLevel) / Range, 0.f, 1.0f);
}
};
/** Cues to trigger non-simulated reactions in response to this GameplayEffect such as sounds, particle effects, etc */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "GameplayCues")
TArray<FGameplayEffectCue> GameplayCues;
Particle이나 Sound 같은 Decoration Effect를 관리하는 효율적인 Network 방식
GAS로 제어할 수 있다.
GA나 GE로 트리거 할 수 있다.
모든 Gameplay Cue에는 GameplayCue로 시작하는 Gameplay Tag를 붙여야 한다.
/** Called when a GameplayCue is executed, this is used for instant effects or periodic ticks */
UFUNCTION(BlueprintNativeEvent, Category = "GameplayCueNotify")
bool OnExecute(AActor* MyTarget, const FGameplayCueParameters& Parameters);
/** Called when a GameplayCue with duration is first activated, this will only be called if the client witnessed the activation */
UFUNCTION(BlueprintNativeEvent, Category = "GameplayCueNotify")
bool OnActive(AActor* MyTarget, const FGameplayCueParameters& Parameters);
/** Called when a GameplayCue with duration is first seen as active, even if it wasn't actually just applied (Join in progress, etc) */
UFUNCTION(BlueprintNativeEvent, Category = "GameplayCueNotify")
bool WhileActive(AActor* MyTarget, const FGameplayCueParameters& Parameters);
/** Called when a GameplayCue with duration is removed */
UFUNCTION(BlueprintNativeEvent, Category = "GameplayCueNotify")
bool OnRemove(AActor* MyTarget, const FGameplayCueParameters& Parameters);
GA 와의 Interaction
GE가 GA와 상호작용할 때에 AS에서 제공하는 함수를 이용한다.
하지만 이는 기본적인 기능만 제공하고, Attribute의 값 변경에 대한 추가 작업이나 범위 지정을 하려면 AS의 함수를 Override 해야 한다.
/**
* Called just before modifying the value of an attribute. AttributeSet can make additional modifications here. Return true to continue, or false to throw out the modification.
* Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
*/
virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData &Data) { return true; }
/**
* Called just after a GameplayEffect is executed to modify the base value of an attribute. No more changes can be made.
* Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
*/
virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) { }
/**
* An "On Aggregator Change" type of event could go here, and that could be called when active gameplay effects are added or removed to an attribute aggregator.
* It is difficult to give all the information in these cases though - aggregators can change for many reasons: being added, being removed, being modified, having a modifier change, immunity, stacking rules, etc.
*/
/**
* Called just before any modification happens to an attribute. This is lower level than PreAttributeModify/PostAttribute modify.
* There is no additional context provided here since anything can trigger this. Executed effects, duration based effects, effects being removed, immunity being applied, stacking rules changing, etc.
* This function is meant to enforce things like "Health = Clamp(Health, 0, MaxHealth)" and NOT things like "trigger this extra thing if damage is applied, etc".
*
* NewValue is a mutable reference so you are able to clamp the newly applied value as well.
*/
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) { }
/** Called just after any modification happens to an attribute. */
virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) { }
/**
* This is called just before any modification happens to an attribute's base value when an attribute aggregator exists.
* This function should enforce clamping (presuming you wish to clamp the base value along with the final value in PreAttributeChange)
* This function should NOT invoke gameplay related events or callbacks. Do those in PreAttributeChange() which will be called prior to the
* final value of the attribute actually changing.
*/
virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const { }
/** Called just after any modification happens to an attribute's base value when an attribute aggregator exists. */
virtual void PostAttributeBaseChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) const { }
UCLASS()
class MYPROJECT_API UMyAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
/** 퍼블릭 액세스 가능한 샘플 어트리뷰트 'Health'*/
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FGameplayAttributeData Health;
};
class MyAbilitySystemComponent : public UAbilitySystemComponent
{
....
/** 샘플 어트리뷰트 세트. */
UPROPERTY()
const UMyAttributeSet* AttributeSet;
....
}
// 이런 코드는 일반적으로 BeginPlay()에 나타나지만,
// 적절한 어빌리티 시스템 컴포넌트 구하기일 수 있습니다. 다른 액터에 있을 수도 있으므로 GetAbilitySystemComponent를 사용하여 결과가 유효한지 확인하세요.
AbilitySystemComponent* ASC = GetAbilitySystemComponent();
// AbilitySystemComponent가 유효한지 확인합니다. 실패를 허용할 수 없다면 if() 조건문을 check() 구문으로 대체합니다.
if (IsValid(ASC))
{
// 어빌리티 시스템 컴포넌트에서 UMyAttributeSet를 구합니다. 필요한 경우 어빌리티 시스템 컴포넌트가 UMyAttributeSet를 생성하고 등록할 것입니다.
AttributeSet = ASC->GetSet<UMyAttributeSet>();
// 이제 새 UMyAttributeSet에 대한 포인터가 생겨 나중에 사용할 수 있습니다. 초기화 함수가 있는 경우 여기서 호출하면 좋습니다.
}
float UMyAttributeSet::GetHealth() const
{
// Health의 현재 값을 반환하지만, 0 미만의 값은 반환하지 않습니다.
// 이 값은 Health에 영향을 미치는 모든 모디파이어가 고려된 후의 값입니다.
return FMath::Max(Health.GetCurrentValue(), 0.0f);
}
void UMyAttributeSet::SetHealth(float NewVal)
{
// 0 미만의 값을 받지 않습니다.
NewVal = FMath::Max(NewVal, 0.0f);
// 어빌리티 시스템 컴포넌트 인스턴스가 있는지 확인합니다. 항상 인스턴스가 있어야 합니다.
UAbilitySystemComponent* ASC = GetOwningAbilitySystemComponent();
if (ensure(ASC))
{
// 적절한 함수를 통해 현재 값이 아닌 베이스 값을 설정합니다.
// 그러면 적용한 모디파이어가 계속해서 적절히 작동합니다.
ASC->SetNumericAttributeBase(GetHealthAttribute(), NewVal);
}
}
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AttributeSet->GetHealthAttribute()).AddUObject(this, &AGASAbilityDemoCharacter::OnHealthChangedInternal);
Gameplay Effect와의 Interaction
GA의 값을 제어하는 가장 일반적인 방법은 관련 Gameplay Effect를 처리하는 것이다.
PostGameplayEffectExecute는 Attribute의 Base 값을 변경되는GameplayEffect가호출되기 전에 호출된다.
GameplayEffect가 호출(Execute) 되기 전에만 호출되고, 직접 접근해 수정할 때에는 호출되지 않는다.
또한 BaseValue가 아닌 current 값에 영향을 주는 GameplayEffect에서도 호출되지 않는다.
위 예시에서 PostGameplayEffectExecute 함수는 public 접근자가 지정되어야 한다.
함수 내에서 새로운 Body를 작성하되, Super::PostGameplayEffectExecute가 확실히 호출되어 있어야 한다.
class UMyAttributeSet : public AttributeSet
{
.....
public:
void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override;
.....
}
void UMyAttributeSet::PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data)
{
// 잊지 말고 부모 구현을 호출하세요.
Super::PostGameplayEffectExecute(Data);
// 프로퍼티 게터를 사용하여 이 호출이 Health에 영향을 미치는지 확인합니다.
if (Data.EvaluatedData.Attribute == GetHealthAttribute())
{
// 이 게임플레이 이펙트는 Health를 변경합니다. 적용하되 우선 값을 제한합니다.
// 이 경우 Health 베이스 값은 음수가 아니어야 합니다.
SetHealth(FMath::Max(GetHealth(), 0.0f));
}
}
Replication
Multiplayer의 경우, 여타 Property Replicate와 유사하게 AS을 통해 GA Replicate를 할 수 있다.
class UMyAttributeSet : public UAttributeSet
{
.....
/** 네트워크를 통해 새 Health 값이 도착할 때 호출됨 */
UFUNCTION()
virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);
/** 리플리케이트할 프로퍼티 표시 */
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
/** 샘플 어트리뷰트 'Health'*/
UPROPERTY(EditAnywhere, BlueprintReadOnly, ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
....
};
void UMyAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
// 디폴트 게임플레이 어트리뷰트 시스템의 repnotify 행동을 사용합니다.
GAMEPLAYATTRIBUTE_REPNOTIFY(UMyAttributeSet, Health, OldHealth);
}
void UMyAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
// 부모 함수를 호출합니다.
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Health에 대한 리플리케이션을 추가합니다.
DOREPLIFETIME_CONDITION_NOTIFY(UMyAttributeSet, Health, COND_None, REPNOTIFY_Always);
}
Replicate 할 Attribute에 PROPERTY에 ReplicatedUsing 지정자를 추가하고, Callback 함수를 선언한다.
Source File에서 Callback 함수를 정의한다.
Callback 함수 내에는 GAMEPLAYATTRIBUTE_REPNOTIFY 매크로로 Replicate 되는 Attribute에 대한 RepNotify 행동을 실행한다.
만약 이 AS가 처음 Replicate되는 Attribute라면, GetLifetimeReplicatedProps에 추가한다.
주요 함수
PreAttributeChange
/**
* Called just before any modification happens to an attribute. This is lower level than PreAttributeModify/PostAttribute modify.
* There is no additional context provided here since anything can trigger this. Executed effects, duration based effects, effects being removed, immunity being applied, stacking rules changing, etc.
* This function is meant to enforce things like "Health = Clamp(Health, 0, MaxHealth)" and NOT things like "trigger this extra thing if damage is applied, etc".
*
* NewValue is a mutable reference so you are able to clamp the newly applied value as well.
*/
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) { }
Attribute의 CurrentValue가 바뀌기 전에 호출되는 함수
NewValue를 이용해 CurrentValue를 Clamp한 상태로 적용할 때 사용 함.
if (Attribute == GetMoveSpeedAttribute())
{
// Cannot slow less than 150 units/s and cannot boost more than 1000 units/s
NewValue = FMath::Clamp<float>(NewValue, 150, 1000);
}
단, Attribute에 직접 접근하여 변경하는 것이 아닌 Macro에서 제공하는 Setter 함수나 GameplayEffects로만 호출된다.
그리고 Epic에서는 이 함수를 Attribute 변경에 대한 Delegate로 사용을 권장하지 않는다.
그저 Attribute 값의 보정(Clamp) 용도로 사용을 권장한다.
PostGameplayEffectExecute
/**
* Called just after a GameplayEffect is executed to modify the base value of an attribute. No more changes can be made.
* Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
*/
virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) { }
Attribute의 BaseValue가 Instant GameplayEffect로 인해 변경될 때 호출되는 함수
GameplayEffect로 인해 Attribute가 변동될 때 좀 더 복잡한 작업을 수행할 수 있다.
함수가 호출된 시점에는 이미 Attribute 값이 변경이 된 후다.
하지만 아직 Client에는 Replicate 되지 않았다.
때문에 여기서 Clamping을 하는 것은 Client에서는 발생하지 않고, 그 결과만 받는다.
OnAttributeAggregatorCreate
/** Callback for when an FAggregator is created for an attribute in this set. Allows custom setup of FAggregator::EvaluationMetaData */
virtual void OnAttributeAggregatorCreated(const FGameplayAttribute& Attribute, FAggregator* NewAggregator) const { }
Attribute가 set에서 생성되어 Aggregator가 생성될 때.
FAggregatorEvaluateMetaData에 대한 Custom Setup을 할 수 있다.
Attribute에서 Modifier로 적용하는데 가장 작은 음수 값을 가져올 수 있다.
Paragon에서 여러 개의 감속 디버프가 걸릴 때 가장 낮은 비율로 적용할 때 이 기능을 사용
AGDPlayerState::AGDPlayerState()
{
// Create ability system component, and set it to be explicitly replicated
AbilitySystemComponent = CreateDefaultSubobject<UGDAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
//...
}
class APACharacterBase : public AActor, public IAbilitySystemInterface
{
//~ IAbilitySystemInterface 시작
/** 어빌리티 시스템 컴포넌트를 반환합니다. */
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
//~ IAbilitySystemInterface 끝
/** 어빌리티 시스템 컴포넌트입니다. 게임플레이 어트리뷰트 및 게임플레이 어빌리티를 사용하려면 필요합니다. */
UPROPERTY(VisibleDefaultsOnly, BlueprintReadOnly, Category = "Abilities")
UAbilitySystemComponent* AbilitySystemComponent;
}
void APACharacterBase::PossessedBy(AController * NewController)
{
Super::PossessedBy(NewController);
if (AbilitySystemComponent)
{
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
// ASC MixedMode replication requires that the ASC Owner's Owner be the Controller.
SetOwner(NewController);
}
UAbilitySystemComponent* APACharacterBase::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
// Server only
void AGDHeroCharacter::PossessedBy(AController * NewController)
{
Super::PossessedBy(NewController);
AGDPlayerState* PS = GetPlayerState<AGDPlayerState>();
if (PS)
{
// Set the ASC on the Server. Clients do this in OnRep_PlayerState()
AbilitySystemComponent = Cast<UGDAbilitySystemComponent>(PS->GetAbilitySystemComponent());
// AI won't have PlayerControllers so we can init again here just to be sure. No harm in initing twice for heroes that have PlayerControllers.
PS->GetAbilitySystemComponent()->InitAbilityActorInfo(PS, this);
}
//...
}
// Client only
void AGDHeroCharacter::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
AGDPlayerState* PS = GetPlayerState<AGDPlayerState>();
if (PS)
{
// Set the ASC for clients. Server does this in PossessedBy.
AbilitySystemComponent = Cast<UGDAbilitySystemComponent>(PS->GetAbilitySystemComponent());
// Init ASC Actor Info for clients. Server will init its ASC when it possesses a new Actor.
AbilitySystemComponent->InitAbilityActorInfo(PS, this);
}
// ...
}
ASC가 PlayerState에 있는 경우에는 명시적으로 Initialize 하는데 호출되는 함수가 달라진다.
Server: Pawn::PossessedBy()
Client: Pawn::Onrep_PlayerState()
이 모든 과정은 Client에 PlayerState가 있다는 것을 상정하고 작업된다.
만약 아래 로그가 발생하면, ASC가 Client에서 Initialize가 되지 않은 것이다.
LogAbilitySystem: Warning: Can't activate LocalOnly or LocalPredicted ability %s when not local!
/**
* Declare a tag defined by LLM_DEFINE_TAG. It is used in LLM_SCOPE_BYTAG or referenced by name in other LLM_SCOPEs.
* @param UniqueName - The name of the tag for looking up by name, must be unique across all tags passed to
* LLM_DEFINE_TAG, LLM_SCOPE, or ELLMTag.
* @param ModuleName - (Optional, only in LLM_DECLARE_TAG_API) - The MODULENAME_API symbol to use to declare module
* linkage, aka ENGINE_API. If omitted, no module linkage will be used and the tag will create link errors if
* used from another module.
*/
#define LLM_DECLARE_TAG(UniqueNameWithUnderscores) extern FLLMTagDeclaration PREPROCESSOR_JOIN(LLMTagDeclaration_, UniqueNameWithUnderscores)
#define LLM_DECLARE_TAG_API(UniqueNameWithUnderscores, ModuleAPI) extern ModuleAPI FLLMTagDeclaration PREPROCESSOR_JOIN(LLMTagDeclaration_, UniqueNameWithUnderscores)
/**
* Define a tag which can be used in LLM_SCOPE_BYTAG or referenced by name in other LLM_SCOPEs.
* @param UniqueNameWithUnderscores - Modified version of the name of the tag. Used for looking up by name,
* must be unique across all tags passed to LLM_DEFINE_TAG, LLM_SCOPE, or ELLMTag.
* The modification: the usual separator / for parents must be replaced with _ in LLM_DEFINE_TAGs.
* @param DisplayName - (Optional) - The name to display when tracing the tag; joined with "/" to the name of its
* parent if it has a parent, or NAME_None to use the UniqueName.
* @param ParentTagName - (Optional) - The unique name of the parent tag, or NAME_None if it has no parent.
* @param StatName - (Optional) - The name of the stat to populate with this tag's amount when publishing LLM data each
* frame, or NAME_None if no stat should be populated.
* @param SummaryStatName - (Optional) - The name of the stat group to add on this tag's amount when publishing LLM
* data each frame, or NAME_None if no stat group should be added to.
*/
#define LLM_DEFINE_TAG(UniqueNameWithUnderscores, ...) FLLMTagDeclaration PREPROCESSOR_JOIN(LLMTagDeclaration_, UniqueNameWithUnderscores)(TEXT(#UniqueNameWithUnderscores), ##__VA_ARGS__)
UCLASS(BlueprintType, SparseClassDataTypes = MySparseClassData)
class AMyActor : public AActor
{
GENERATED_BODY()
#if WITH_EDITOR
public:
// ~ 이 함수는 기존 데이터를 FMySparseClassData로 전송합니다.
virtual void MoveDataToSparseClassDataStruct() const override;
#endif // WITH_EDITOR
#if WITH_EDITORONLY_DATA
//~ 이 프로퍼티는 FMySparseClassData 구조체로 이동합니다.
private:
// 이 프로퍼티는 에디터에서 변경할 수 있지만, 클래스별로만 변경됩니다.
// 블루프린트 그래프에서는 액세스하거나 변경할 수 없습니다.
// 희소 클래스 데이터의 잠재적 후보입니다.
UPROPERTY()
float MyFloatProperty_DEPRECATED;
// 이 프로퍼티는 에디터에서 변경할 수 없습니다.
// 블루프린트 그래프에서 액세스할 수 있지만, 변경할 수는 없습니다.
// 희소 클래스 데이터의 잠재적 후보입니다.
UPROPERTY()
int32 MyIntProperty_DEPRECATED;
// 이 프로퍼티는 에디터에서 클래스별로 편집할 수 있습니다.
// 블루프린트 그래프에서 액세스할 수 있지만, 변경할 수는 없습니다.
// 희소 클래스 데이터의 잠재적 후보입니다.
UPROPERTY()
FName MyNameProperty_DEPRECATED;
#endif // WITH_EDITORONLY_DATA
//~ 나머지 프로퍼티는 인스턴스별로 변경할 수 있습니다.
//~ 따라서 나머지 프로퍼티는 희소 클래스 데이터 구현에서 제외됩니다.
public:
// 이 프로퍼티는 배치된 MyActor 인스턴스에서 편집할 수 있습니다.
// 희소 클래스 데이터의 잠재적 후보가 아닙니다.
UPROPERTY(EditAnywhere)
FVector MyVectorProperty;
// 이 프로퍼티는 블루프린트 그래프에서 변경할 수 있습니다.
// 희소 클래스 데이터의 잠재적 후보가 아닙니다.
UPROPERTY(BlueprintReadWrite)
FVector2D MyVector2DProperty;
};