注:1.这个分类是按照源码里的注释分类的
2.本篇是通读并给出一些注释形式的,并不涉及结构性的分析
3.看之前要对UE的GAS系统的定义有初步了解
1 ApplyGameplayEffectSpecToTarget/Self
1.1 ApplyGameplayEffectSpecToTarget
应用一个已经创建的GameplayEffectSpec到目标,指向的是ASC
声明:
/** Applies a previously created gameplay effect spec to a target */
UFUNCTION(BlueprintCallable, Category = GameplayEffects, meta = (DisplayName = "ApplyGameplayEffectSpecToTarget", ScriptName = "ApplyGameplayEffectSpecToTarget"))
FActiveGameplayEffectHandle BP_ApplyGameplayEffectSpecToTarget(const FGameplayEffectSpecHandle& SpecHandle, UAbilitySystemComponent* Target);
virtual FActiveGameplayEffectHandle ApplyGameplayEffectSpecToTarget(const FGameplayEffectSpec& GameplayEffect, UAbilitySystemComponent *Target, FPredictionKey PredictionKey=FPredictionKey());
下面是实现:
1.SCOPE_CYCLE_COUNTER是系统中用来性能分析的,会记录执行时间
2.ShouldPredictTargetGameplayEffects() 是一个全局配置函数,用于判断是否需要对目标效果进行预测
如果全局配置中明确不需要预测目标效果,则将 PredictionKey重设为默认的构造(预测允许客户端提前执行某些操作,并在后续与服务器同步时修正结果,从而减少玩家感知到的延迟)
3.Target调用ApplyGameplayEffectSpecToSelf,并返回Handle
FActiveGameplayEffectHandle UAbilitySystemComponent::ApplyGameplayEffectSpecToTarget(const FGameplayEffectSpec &Spec, UAbilitySystemComponent *Target, FPredictionKey PredictionKey)
{
SCOPE_CYCLE_COUNTER(STAT_AbilitySystemComp_ApplyGameplayEffectSpecToTarget);
UAbilitySystemGlobals& AbilitySystemGlobals = UAbilitySystemGlobals::Get();
if (!AbilitySystemGlobals.ShouldPredictTargetGameplayEffects())
{
// If we don't want to predict target effects, clear prediction key
PredictionKey = FPredictionKey();
}
FActiveGameplayEffectHandle ReturnHandle;
if (Target)
{
ReturnHandle = Target->ApplyGameplayEffectSpecToSelf(Spec, PredictionKey);
}
return ReturnHandle;
}
1.2 ApplyGameplayEffectSpecToSelf
声明:
/** Applies a previously created gameplay effect spec to this component */
UFUNCTION(BlueprintCallable, Category = GameplayEffects, meta = (DisplayName = "ApplyGameplayEffectSpecToSelf", ScriptName = "ApplyGameplayEffectSpecToSelf"))
FActiveGameplayEffectHandle BP_ApplyGameplayEffectSpecToSelf(const FGameplayEffectSpecHandle& SpecHandle);
virtual FActiveGameplayEffectHandle ApplyGameplayEffectSpecToSelf(const FGameplayEffectSpec& GameplayEffect, FPredictionKey PredictionKey = FPredictionKey());
实现太长了,分段看:
(注释写在代码块里了)
#if WITH_SERVER_CODE
SCOPE_CYCLE_COUNTER(STAT_AbilitySystemComp_ApplyGameplayEffectSpecToSelf);
#endif
//还是同上,用于性能分析
// Scope lock the container after the addition has taken place to prevent the new effect from potentially getting mangled during the remainder
// of the add operation
FScopedActiveGameplayEffectLock ScopeLock(ActiveGameplayEffects);
//设置锁来保证线程的安全
FScopeCurrentGameplayEffectBeingApplied ScopedGEApplication(&Spec, this);
//明确作用域中正在被应用的GE
const bool bIsNetAuthority = IsOwnerActorAuthoritative();
// Check Network Authority
if (!HasNetworkAuthorityToApplyGameplayEffect(PredictionKey))
{
return FActiveGameplayEffectHandle();
}
//检查网络权限,没有对应权限就返回空的Handle
// Don't allow prediction of periodic effects
if (PredictionKey.IsValidKey() && Spec.GetPeriod() > 0.f)
{
if (IsOwnerActorAuthoritative())
{
// Server continue with invalid prediction key
PredictionKey = FPredictionKey();
}
else
{
// Client just return now
return FActiveGameplayEffectHandle();
}
}
//服务端继续处理预测效果,客户端直接返回
1.是否有应用查询会Block
2.检查能否成功应用
3.检查所有Modifiers的Attribute是有效的
// Check if there is a registered "application" query that can block the application
for (const FGameplayEffectApplicationQuery& ApplicationQuery : GameplayEffectApplicationQueries)
{
const bool bAllowed = ApplicationQuery.Execute(ActiveGameplayEffects, Spec);
if (!bAllowed)
{
return FActiveGameplayEffectHandle();
}
}
// check if the effect being applied actually succeeds
if (!Spec.Def->CanApply(ActiveGameplayEffects, Spec))
{
return FActiveGameplayEffectHandle();
}
// Check AttributeSet requirements: make sure all attributes are valid
// We may want to cache this off in some way to make the runtime check quicker.
// We also need to handle things in the execution list
for (const FGameplayModifierInfo& Mod : Spec.Def->Modifiers)
{
if (!Mod.Attribute.IsValid())
{
ABILITY_LOG(Warning, TEXT("%s has a null modifier attribute."), *Spec.Def->GetPathName());
return FActiveGameplayEffectHandle();
}
}
这部分还是看注释:
// Clients should treat predicted instant effects as if they have infinite duration. The effects will be cleaned up later.
bool bTreatAsInfiniteDuration = GetOwnerRole() != ROLE_Authority && PredictionKey.IsLocalClientKey() && Spec.Def->DurationPolicy == EGameplayEffectDurationType::Instant;
//检查是否视为无限持续时间
// Make sure we create our copy of the spec in the right place
// We initialize the FActiveGameplayEffectHandle here with INDEX_NONE to handle the case of instant GE
// Initializing it like this will set the bPassedFiltersAndWasExecuted on the FActiveGameplayEffectHandle to true so we can know that we applied a GE
FActiveGameplayEffectHandle MyHandle(INDEX_NONE);
bool bInvokeGameplayCueApplied = Spec.Def->DurationPolicy != EGameplayEffectDurationType::Instant; // Cache this now before possibly modifying predictive instant effect to infinite duration effect.
//缓存一个标志,是否应用GC
bool bFoundExistingStackableGE = false;
FActiveGameplayEffect* AppliedEffect = nullptr;
FGameplayEffectSpec* OurCopyOfSpec = nullptr;
TUniquePtr<FGameplayEffectSpec> StackSpec;
{
//应用效果
if (Spec.Def->DurationPolicy != EGameplayEffectDurationType::Instant || bTreatAsInfiniteDuration)
{
AppliedEffect = ActiveGameplayEffects.ApplyGameplayEffectSpec(Spec, PredictionKey, bFoundExistingStackableGE);
if (!AppliedEffect)
{
return FActiveGameplayEffectHandle();
}
MyHandle = AppliedEffect->Handle;
OurCopyOfSpec = &(AppliedEffect->Spec);
// Log results of applied GE spec
//记录应用GE结果日志信息
if (UE_LOG_ACTIVE(VLogAbilitySystem, Log))
{
UE_VLOG(GetOwnerActor(), VLogAbilitySystem, Log, TEXT("Applied %s"), *OurCopyOfSpec->Def->GetFName().ToString());
for (const FGameplayModifierInfo& Modifier : Spec.Def->Modifiers)
{
float Magnitude = 0.f;
Modifier.ModifierMagnitude.AttemptCalculateMagnitude(Spec, Magnitude);
UE_VLOG(GetOwnerActor(), VLogAbilitySystem, Log, TEXT(" %s: %s %f"), *Modifier.Attribute.GetName(), *EGameplayModOpToString(Modifier.ModifierOp), Magnitude);
}
}
}
//处理未应用的效果
if (!OurCopyOfSpec)
{
StackSpec = MakeUnique<FGameplayEffectSpec>(Spec);
OurCopyOfSpec = StackSpec.Get();
UAbilitySystemGlobals::Get().GlobalPreGameplayEffectSpecApply(*OurCopyOfSpec, this);
OurCopyOfSpec->CaptureAttributeDataFromTarget(this);
}
// if necessary add a modifier to OurCopyOfSpec to force it to have an infinite duration
//如果需要的话强制修改持续时间为为无限
if (bTreatAsInfiniteDuration)
{
// This should just be a straight set of the duration float now
OurCopyOfSpec->SetDuration(UGameplayEffect::INFINITE_DURATION, true);
}
}
// Update (not push) the global spec being applied [we want to switch it to our copy, from the const input copy)
UAbilitySystemGlobals::Get().SetCurrentAppliedGE(OurCopyOfSpec);
//更新全局即将应用的GESpec
// UE5.4: We are following the same previous implementation that there is a special case for Gameplay Cues here (caveat: may not be true):
// We are Stacking an existing Gameplay Effect. That means the GameplayCues should already be Added/WhileActive and we do not have a proper
// way to replicate the fact that it's been retriggered, hence the RPC here. I say this may not be true because any number of things could have
// removed the GameplayCue by the time we getting a Stacking GE (e.g. RemoveGameplayCue).
//处理Stackable GE的Gameplay Cues,还不太了解GC()
if (!bSuppressGameplayCues && !Spec.Def->bSuppressStackingCues && bFoundExistingStackableGE && AppliedEffect && !AppliedEffect->bIsInhibited)
{
ensureMsgf(OurCopyOfSpec, TEXT("OurCopyOfSpec will always be valid if bFoundExistingStackableGE"));
if (OurCopyOfSpec && OurCopyOfSpec->GetStackCount() > Spec.GetStackCount())
{
// Because PostReplicatedChange will get called from modifying the stack count
// (and not PostReplicatedAdd) we won't know which GE was modified.
// So instead we need to explicitly RPC the client so it knows the GC needs updating
UAbilitySystemGlobals::Get().GetGameplayCueManager()->InvokeGameplayCueAddedAndWhileActive_FromSpec(this, *OurCopyOfSpec, PredictionKey);
}
}
// Execute the GE at least once (if instant, this will execute once and be done. If persistent, it was added to ActiveGameplayEffects in ApplyGameplayEffectSpec)
// Execute if this is an instant application effect
if (bTreatAsInfiniteDuration)
{
// This is an instant application but we are treating it as an infinite duration for prediction. We should still predict the execute GameplayCUE.
// (in non predictive case, this will happen inside ::ExecuteGameplayEffect)
if (!bSuppressGameplayCues)
{
UAbilitySystemGlobals::Get().GetGameplayCueManager()->InvokeGameplayCueExecuted_FromSpec(this, *OurCopyOfSpec, PredictionKey);
//还是处理GC
}
}
else if (Spec.Def->DurationPolicy == EGameplayEffectDurationType::Instant)
{
// This is a non-predicted instant effect (it never gets added to ActiveGameplayEffects)
//这是一个不需要预测的GE,直接执行,不会加入到ActiveGameplayEffects里
ExecuteGameplayEffect(*OurCopyOfSpec, PredictionKey);
}
// Notify the Gameplay Effect (and its Components) that it has been successfully applied
Spec.Def->OnApplied(ActiveGameplayEffects, *OurCopyOfSpec, PredictionKey);
//通知GE成功应用
UAbilitySystemComponent* InstigatorASC = Spec.GetContext().GetInstigatorAbilitySystemComponent();
// Send ourselves a callback
OnGameplayEffectAppliedToSelf(InstigatorASC, *OurCopyOfSpec, MyHandle);
// Send the instigator a callback
if (InstigatorASC)
{
InstigatorASC->OnGameplayEffectAppliedToTarget(this, *OurCopyOfSpec, MyHandle);
}
//处理两个应用完成的回调
return MyHandle;
2 GetGameplayEffectDefForHandle
核心这一行:返回指定Handle的Spec.Def
return ActiveGE->Spec.Def;
3 RemoveActiveGameplayEffect/RemoveActiveGameplayEffectBySourceEffect
最后还是交给ActiveGameplayEffects来完成
ActiveGameplayEffects.RemoveActiveGameplayEffect(Handle, StacksToRemove);
然后FActiveGameplayEffectsContainer中的RemoveActiveGameplayEffect中调用的这个
InternalRemoveActiveGameplayEffect(ActiveGEIdx, StacksToRemove, true);
这个太长了,总体就是处理一堆相关信息,然后锁着就PendingRemoves++;,没锁就直接GameplayEffects_Internal.RemoveAtSwap(Idx);,这个变量是一个TArray
而RemoveActiveGameplayEffectBySourceEffect本质上也是查询之后调用RemoveActiveGameplayEffect
4 MakeOutgoingSpec
按类构造一个FGameplayEffectSpecHandle
FGameplayEffectSpecHandle UAbilitySystemComponent::MakeOutgoingSpec(TSubclassOf<UGameplayEffect> GameplayEffectClass, float Level, FGameplayEffectContextHandle Context) const
{
SCOPE_CYCLE_COUNTER(STAT_GetOutgoingSpec);
if (Context.IsValid() == false)
{
Context = MakeEffectContext();
}
if (GameplayEffectClass)
{
UGameplayEffect* GameplayEffect = GameplayEffectClass->GetDefaultObject<UGameplayEffect>();
FGameplayEffectSpec* NewSpec = new FGameplayEffectSpec(GameplayEffect, Context, Level);
return FGameplayEffectSpecHandle(NewSpec);
}
return FGameplayEffectSpecHandle(nullptr);
}
5 MakeEffectContext
构造Effect上下文,默认用ASC的Owner和Avatar
FGameplayEffectContextHandle UAbilitySystemComponent::MakeEffectContext() const
{
FGameplayEffectContextHandle Context = FGameplayEffectContextHandle(UAbilitySystemGlobals::Get().AllocGameplayEffectContext());
// By default use the owner and avatar as the instigator and causer
if (ensureMsgf(AbilityActorInfo.IsValid(), TEXT("Unable to make effect context because AbilityActorInfo is not valid.")))
{
Context.AddInstigator(AbilityActorInfo->OwnerActor.Get(), AbilityActorInfo->AvatarActor.Get());
}
return Context;
}
6 GetGameplayEffectCount/GetGameplayEffectCount_IfLoaded
先看声明里的这段注释,对于不可堆叠的返回所有激活的实例,对于可堆叠的返回所有有效的堆叠数(指定instigator)
/**
* Get the count of the specified source effect on the ability system component. For non-stacking effects, this is the sum of all active instances.
* For stacking effects, this is the sum of all valid stack counts. If an instigator is specified, only effects from that instigator are counted.
*
* @param SourceGameplayEffect Effect to get the count of
* @param OptionalInstigatorFilterComponent If specified, only count effects applied by this ability system component
*
* @return Count of the specified source effect
*/
UFUNCTION(BlueprintCallable, BlueprintPure, Category=GameplayEffects)
int32 GetGameplayEffectCount(TSubclassOf<UGameplayEffect> SourceGameplayEffect, UAbilitySystemComponent* OptionalInstigatorFilterComponent, bool bEnforceOnGoingCheck = true) const;
Query是用来查询的,绑定了一个lambda,作为传入继续调用GetActiveEffectCount
Count = ActiveGameplayEffects.GetActiveEffectCount(Query, bEnforceOnGoingCheck);
返回匹配query的effects数量
/**
* Get the count of the effects matching the specified query (including stack count)
*
* @return Count of the effects matching the specified query
*/
int32 GetActiveEffectCount(const FGameplayEffectQuery& Query, bool bEnforceOnGoingCheck = true) const;
GetGameplayEffectCount_IfLoaded就是加了一个检查是否加载好的步骤,没有就返回0
7 GetAggergatedStackCount
返回所有通过查询的堆叠数
/** Returns the sum of StackCount of all gameplay effects that pass query */
int32 GetAggregatedStackCount(const FGameplayEffectQuery& Query) const;