文章目录
GameplayAbility
赋予GA
UGameplayAbility* Ability
AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(Ability, 0, INDEX_NONE, this))
// ActivatableAbilities 所有可激活的能力数组
UPROPERTY(ReplicatedUsing=OnRep_ActivateAbilities, BlueprintReadOnly, Category = "Abilities")
FGameplayAbilitySpecContainer ActivatableAbilities;
// This property will only send to the replay connection, or to the actors owner
Params.Condition = COND_ReplayOrOwner;
DOREPLIFETIME_WITH_PARAMS_FAST(UAbilitySystemComponent, ActivatableAbilities, Params);
FGameplayAbilitySpecHandle UAbilitySystemComponent::GiveAbility(const FGameplayAbilitySpec& Spec)
FGameplayAbilitySpec& OwnedSpec = ActivatableAbilities.Items[ActivatableAbilities.Items.Add(Spec)];
if (OwnedSpec.Ability->GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerActor)
CreateNewInstanceOfAbility(OwnedSpec, Spec.Ability);
// 标记为Dirty,使Spec复制到客户端
MarkAbilitySpecDirty(OwnedSpec, true);
GA实例化策略
namespace EGameplayAbilityInstancingPolicy
enum Type
// 使用CDO执行业务逻辑,无法存在状态信息
NonInstanced,
// 每个Actor实例化一个,注意多次触发之间的延时行为
InstancedPerActor,
// 每次执行都创建实例
InstancedPerExecution
创建GASpec
FGameplayAbilitySpec::FGameplayAbilitySpec(UGameplayAbility* InAbility, int32 InLevel, int32 InInputID, UObject* InSourceObject)
: Ability(InAbility)
, Level(InLevel)
, InputID(InInputID)
, SourceObject(InSourceObject)
...
// 创建时获取一个Handle
Handle.GenerateNewHandle();
void FGameplayAbilitySpecHandle::GenerateNewHandle()
static int32 GHandle = 1;
Handle = GHandle++;
创建GA实例
UGameplayAbility* UAbilitySystemComponent::CreateNewInstanceOfAbility(FGameplayAbilitySpec& Spec, UGameplayAbility* Ability)
// 使用GA CDO创建GA Instance
if (Ability->HasAllFlags(RF_ClassDefaultObject))
AbilityInstance = NewObject<UGameplayAbility>(Owner, Ability->GetClass());
else
AbilityInstance = NewObject<UGameplayAbility>(Owner, Ability->GetClass(), NAME_None, RF_NoFlags, Ability);
// 存入GA Instance数组
if (AbilityInstance->GetReplicationPolicy() != EGameplayAbilityReplicationPolicy::ReplicateNo)
Spec.ReplicatedInstances.Add(AbilityInstance);
AllReplicatedInstancedAbilities.Add(AbilityInstance);
else
Spec.NonReplicatedInstances.Add(AbilityInstance);
激活GA
通过Handle激活GA
bool UAbilitySystemComponent::TryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool bAllowRemoteActivation)
FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(AbilityToActivate);
// GA CDO
UGameplayAbility* Ability = Spec->Ability;
// 跳过模拟端
if (NetMode == ROLE_SimulatedProxy)
return false;
// Local Only Filter: ClientTryActivateAbility
...
// Server Only Filter: CallServerTryActivateAbility
...
return InternalTryActivateAbility(AbilityToActivate);
当然,有其它上层的激活方法:
- 通过Tag激活GA
bool UAbilitySystemComponent::TryActivateAbilitiesByTag(const FGameplayTagContainer& GameplayTagContainer, bool bAllowRemoteActivation)
TArray<FGameplayAbilitySpec*> AbilitiesToActivate;
// 找到所有可激活的匹配Tag的GA
GetActivatableGameplayAbilitySpecsByAllMatchingTags(GameplayTagContainer, AbilitiesToActivate);
for (const FGameplayAbilitySpec& Spec : ActivatableAbilities.Items)
// 匹配Tag
if (Spec.Ability && Spec.Ability->AbilityTags.HasAll(GameplayTagContainer))
// 满足其它限制条件
if (!bOnlyAbilitiesThatSatisfyTagRequirements || Spec.Ability->DoesAbilitySatisfyTagRequirements(*this))
MatchingGameplayAbilities.Add(const_cast<FGameplayAbilitySpec*>(&Spec));
bSuccess |= TryActivateAbility(GameplayAbilitySpec->Handle, bAllowRemoteActivation);
是否有Tag的Miss、Block
bool UGameplayAbility::DoesAbilitySatisfyTagRequirements(const UAbilitySystemComponent& AbilitySystemComponent, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, OUT FGameplayTagContainer* OptionalRelevantTags) const
bool bBlocked = false;
bool bMissing = false;
- 通过GA类激活GA
bool TryActivateAbilityByClass(TSubclassOf<UGameplayAbility> InAbilityToActivate, bool bAllowRemoteActivation = true)
// 判断CDO是否相同
if (Spec.Ability == InAbilityCDO)
- 通过Event激活GA
bool TriggerAbilityFromGameplayEvent(FGameplayAbilitySpecHandle AbilityToTrigger, FGameplayAbilityActorInfo* ActorInfo, FGameplayTag Tag, const FGameplayEventData* Payload, UAbilitySystemComponent& Component);
if (Ability->ShouldAbilityRespondToEvent(ActorInfo, &TempEventData))
底层的激活函数都是InternalTryActivateAbility:
bool UAbilitySystemComponent::InternalTryActivateAbility(FGameplayAbilitySpecHandle Handle, FPredictionKey InPredictionKey, UGameplayAbility** OutInstancedAbility, FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData)
FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(Handle);
// 跳过模拟端
if (NetRole == ROLE_SimulatedProxy)
return false;
UGameplayAbility* const CanActivateAbilitySource = InstancedAbility ? InstancedAbility : Ability;
// 过程中维护InternalTryActivateAbilityFailureTags,讲失败原因通过Tag进行记录
if (!CanActivateAbilitySource->CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, &InternalTryActivateAbilityFailureTags))
NotifyAbilityFailed(Handle, CanActivateAbilitySource, InternalTryActivateAbilityFailureTags);
return false;
// InstancedPerActor下禁止重复激活
if (Ability->GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerActor)
if (Spec->IsActive())
...
Spec->ActiveCount++;
// 见下
if (Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalOnly || (NetRole == ROLE_Authority))
// PredictionKey
// ClientActivateAbilitySucceed
// MulticastActivateAbilitySucceed
// CallActivateAbility
else if (Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalPredicted)
// PredictionKey
// CallServerTryActivateAbility
// CallActivateAbility
是否可以激活
bool UGameplayAbility::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, OUT FGameplayTagContainer* OptionalRelevantTags) const
!ShouldActivateAbility(AvatarActor->GetLocalRole())
// 检测是否有Cooldown Tag
!CheckCooldown(Handle, ActorInfo, OptionalRelevantTags)
// 通过CanApplyAttributeModifiers判断
!CheckCost(Handle, ActorInfo, OptionalRelevantTags)
!DoesAbilitySatisfyTagRequirements(*AbilitySystemComponent, SourceTags, TargetTags, OptionalRelevantTags)
// Input阻塞
AbilitySystemComponent->IsAbilityInputBlocked(Spec->InputID)
// 蓝图自定义
!K2_CanActivateAbility(*ActorInfo, Handle, OutTags)
最后的执行部分
void UGameplayAbility::CallActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData)
PreActivate(Handle, ActorInfo, ActivationInfo, OnGameplayAbilityEndedDelegate);
ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
// 自己去重载 Do something
void UGameplayAbility::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
// 可拆为CommitCost()、CommitCooldown()
// CommitCheck、CommitExecute
if (!CommitAbility(Handle, ActorInfo, ActivationInfo))
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, true, true);
...
可以自己设置Timer或者回调调用EndAbility或者CancelAbility
EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
void UGameplayAbility::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility)
在示例中,ActivateAbility创建GameplayTask进行非瞬时操作,通过绑定OnCompleted、OnCancelled、EventReceived进行GA流程控制
激活GA - 预测
执行策略:
namespace EGameplayAbilityNetExecutionPolicy
enum Type
// Client上传并本地预测
LocalPredicted UMETA(DisplayName = "Local Predicted"),
// Client不上传
LocalOnly UMETA(DisplayName = "Local Only"),
// Client上传,等Server
ServerInitiated UMETA(DisplayName = "Server Initiated"),
// Server不下发
ServerOnly UMETA(DisplayName = "Server Only"),
通过PKey判断需要取消哪些GA
UAbilitySystemComponent::InternalTryActivateAbility
// ROLE_Authority && (!InPredictionKey || ServerInitiated || ServerOnly)
// Server新建PKey
if (bCreateNewServerKey)
ActivationInfo.ServerSetActivationPredictionKey(FPredictionKey::CreateNewServerInitiatedKey(this));
// 使用传入PKey
else if (InPredictionKey.IsValidKey())
ActivationInfo.ServerSetActivationPredictionKey(InPredictionKey);
// NetExecutionPolicy: LocalPredicted
// 见下
FScopedPredictionWindow ScopedPredictionWindow(this, ActivationInfo.GetActivationPredictionKey());
CallServerTryActivateAbility(Handle, Spec->InputPressed, ScopedPredictionKey);
// RPC Server
ServerTryActivateAbility(AbilityHandle, InputPressed, PredictionKey);
// Server
InternalServerTryActivateAbility(Handle, InputPressed, PredictionKey, nullptr);
// 找到Server上的该技能(Handle的作用)
FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(Handle);
FScopedPredictionWindow ScopedPredictionWindow(this, PredictionKey);
// Server激活GA
InternalTryActivateAbility(Handle, PredictionKey, &InstancedAbility, nullptr, TriggerEventData)
// 如果过程中失败,会回调通知Client
ClientActivateAbilityFailed(Handle, PredictionKey.Current);
Server失败的RPC通知
void UAbilitySystemComponent::ClientActivateAbilityFailed_Implementation(FGameplayAbilitySpecHandle Handle, int16 PredictionKey)
// 广播预测键Rejected(作用参考见GE预测)
FPredictionKeyDelegates::BroadcastRejectedDelegate(PredictionKey);
FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(Handle);
if (Spec->ActivationInfo.GetActivationPredictionKey().Current == PredictionKey)
Spec->ActivationInfo.SetActivationRejected();
ActivationMode = EGameplayAbilityActivationMode::Rejected;
// 取消所有相同PKey的GA
TArray<UGameplayAbility*> Instances = Spec->GetAbilityInstances();
for (UGameplayAbility* Ability : Instances)
if (Ability->CurrentActivationInfo.GetActivationPredictionKey().Current == PredictionKey)
Ability->K2_EndAbility();
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicateEndAbility, bWasCancelled);
结束技能
void UGameplayAbility::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
// 移除timers、latent actions
MyWorld->GetLatentActionManager().RemoveActionsForObject(this);
if (FAbilitySystemTweaks::ClearAbilityTimers)
MyWorld->GetTimerManager().ClearAllTimersForObject(this);
Task->TaskOwnerEnded();
// 通知远端
AbilitySystemComponent->ReplicateEndOrCancelAbility(Handle, ActivationInfo, this, false);
// 移除Tag
AbilitySystemComponent->RemoveLooseGameplayTags(ActivationOwnedTags);
// 移除Cue
AbilitySystemComponent->RemoveGameplayCue(GameplayCueTag);
AbilitySystemComponent->HandleChangeAbilityCanBeCanceled(AbilityTags, this, false);
AbilitySystemComponent->ApplyAbilityBlockAndCancelTags(AbilityTags, this, false, BlockAbilitiesWithTag, false, CancelAbilitiesWithTag);
GameplayEffect
施加GE
FActiveGameplayEffectHandle UGameplayAbility::ApplyGameplayEffectSpecToOwner(const FGameplayAbilitySpecHandle AbilityHandle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEffectSpecHandle SpecHandle) const
if (SpecHandle.IsValid() && (HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo)))
return AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get(), AbilitySystemComponent->GetPredictionKeyForNewAction());
bool UGameplayAbility::HasAuthorityOrPredictionKey(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo* ActivationInfo) const
bool UAbilitySystemComponent::HasAuthorityOrPredictionKey(const FGameplayAbilityActivationInfo* ActivationInfo) const
bool UAbilitySystemComponent::CanPredict() const
bool IsValidForMorePrediction() const
return Current > 0 && bIsStale == false && bIsServerInitiated == false;
FPredictionKey GetPredictionKeyForNewAction() const
return ScopedPredictionKey.IsValidForMorePrediction() ? ScopedPredictionKey : FPredictionKey();
FActiveGameplayEffectHandle UAbilitySystemComponent::ApplyGameplayEffectSpecToSelf(const FGameplayEffectSpec &Spec, FPredictionKey PredictionKey)
// 不预测周期GE
if(PredictionKey.IsValidKey() && Spec.GetPeriod() > 0.f)
if(IsOwnerActorAuthoritative())
// 权威方仅本地执行
PredictionKey = FPredictionKey();
else
// 非权威方不执行
return FActiveGameplayEffectHandle();
// 检查免疫 HasApplicationImmunityToSpec
// 随机概率触发 Spec.GetChanceToApplyToTarget()
// 需要的Tag和有就移除的Tag RequirementsMet RemovalTagRequirements
// 本地预测的瞬时GE,后面由于需要校验、纠正,所以时间需要是Infinite
bool bTreatAsInfiniteDuration = GetOwnerRole() != ROLE_Authority && PredictionKey.IsLocalClientKey() && Spec.Def->DurationPolicy == EGameplayEffectDurationType::Instant;
// 非瞬时GE或本地预测的瞬时GE
if (Spec.Def->DurationPolicy != EGameplayEffectDurationType::Instant || bTreatAsInfiniteDuration)
// 执行,维护FActiveGameplayEffectsContainer(见下文)
AppliedEffect = ActiveGameplayEffects.ApplyGameplayEffectSpec(Spec, PredictionKey, bFoundExistingStackableGE);
// Modifier
for (const FGameplayModifierInfo& Modifier : Spec.Def->Modifiers)
Modifier.ModifierMagnitude.AttemptCalculateMagnitude(Spec, Magnitude);
// 本地预测的瞬时GE改为InfiniteDuration
if (bTreatAsInfiniteDuration)
OurCopyOfSpec->SetDuration(UGameplayEffect::INFINITE_DURATION, true);
// GameplayCue
// 瞬时GE的执行
if (bTreatAsInfiniteDuration)
else if (Spec.Def->DurationPolicy == EGameplayEffectDurationType::Instant)
ExecuteGameplayEffect(*OurCopyOfSpec, PredictionKey);
// 添加后可能移除其它GE
if (bIsNetAuthority)
ActiveGameplayEffects.AttemptRemoveActiveEffectsOnEffectApplication(Spec, MyHandle);
// 添加后添加其它GE
ApplyGameplayEffectSpecToSelf(*TargetSpec.Data.Get(), PredictionKey);
预测GE
当本地预测Instant GE的时候
FActiveGameplayEffectHandle UAbilitySystemComponent::ApplyGameplayEffectSpecToSelf(const FGameplayEffectSpec &Spec, FPredictionKey PredictionKey)
bool bTreatAsInfiniteDuration = GetOwnerRole() != ROLE_Authority && PredictionKey.IsLocalClientKey() && Spec.Def->DurationPolicy == EGameplayEffectDurationType::Instant;
if (Spec.Def->DurationPolicy != EGameplayEffectDurationType::Instant || bTreatAsInfiniteDuration)
AppliedEffect = ActiveGameplayEffects.ApplyGameplayEffectSpec(Spec, PredictionKey, bFoundExistingStackableGE);
删除GE的函数绑定到PKey,有两个触发入口:Reject和CaughtUp
FActiveGameplayEffect* FActiveGameplayEffectsContainer::ApplyGameplayEffectSpec(const FGameplayEffectSpec& Spec, FPredictionKey& InPredictionKey, bool& bFoundExistingStackableGE)
AppliedActiveGE = new FActiveGameplayEffect(NewHandle, Spec, GetWorldTime(), GetServerWorldTime(), InPredictionKey);
*PendingGameplayEffectNext = AppliedActiveGE;
if (!(InPredictionKey.IsLocalClientKey() == false || IsNetAuthority()))
InPredictionKey.NewRejectOrCaughtUpDelegate(FPredictionKeyEvent::CreateUObject(Owner, &UAbilitySystemComponent::RemoveActiveGameplayEffect_NoReturn, AppliedActiveGE->Handle, -1));
void FActiveGameplayEffectsContainer::InternalOnActiveGameplayEffectAdded(FActiveGameplayEffect& Effect)
CaughtUp
如果本地预测成功,当客户端收到服务器下发的GE时,本地预测的GE就可以删除了(替代)。而收到了PKey的同步,就说明收到了服务器下发的GE
具体流程:
PKey使用FReplicatedPredictionKeyMap作为容器:
UPROPERTY(Replicated)
FReplicatedPredictionKeyMap UAbilitySystemComponent::ReplicatedPredictionKeyMap;
Params.Condition = COND_OwnerOnly;
DOREPLIFETIME_WITH_PARAMS_FAST(UAbilitySystemComponent, ReplicatedPredictionKeyMap, Params);
再看ScopedPredictionWindow的作用:在Scope结束后,恢复之前的PKey,并把当前的PKey加入ReplicatedPredictionKeyMap
FScopedPredictionWindow::FScopedPredictionWindow(UAbilitySystemComponent* AbilitySystemComponent, FPredictionKey InPredictionKey, bool InSetReplicatedPredictionKey /*=true*/)
if (AbilitySystemComponent->IsNetSimulating() == false)
Owner = AbilitySystemComponent;
// 设置ScopedPredictionKey,存下原先的ScopedPredictionKey
RestoreKey = AbilitySystemComponent->ScopedPredictionKey;
AbilitySystemComponent->ScopedPredictionKey = InPredictionKey;
ClearScopedPredictionKey = true;
SetReplicatedPredictionKey = InSetReplicatedPredictionKey;
FScopedPredictionWindow::~FScopedPredictionWindow()
if (SetReplicatedPredictionKey)
// 记录已经结束的Scope的PKey
if (OwnerPtr->ScopedPredictionKey.IsValidKey())
// 加入环形数组并同步,这里是预测的关键
OwnerPtr->ReplicatedPredictionKeyMap.ReplicatePredictionKey(OwnerPtr->ScopedPredictionKey);
PredictionKeys[Index].PredictionKey = Key;
MarkItemDirty(PredictionKeys[Index]);
OwnerPtr->bIsNetDirty = true;
// 恢复ScopedPredictionKey
if (ClearScopedPredictionKey)
OwnerPtr->ScopedPredictionKey = RestoreKey;
ReplicatedPredictionKeyMap同步时调用FReplicatedPredictionKeyItem的同步:
bool FReplicatedPredictionKeyMap::NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)
return FastArrayDeltaSerialize<FReplicatedPredictionKeyItem>(PredictionKeys, DeltaParms, *this);
FReplicatedPredictionKeyItem
void PostReplicatedAdd(const struct FReplicatedPredictionKeyMap &InArray) { OnRep(); }
void PostReplicatedChange(const struct FReplicatedPredictionKeyMap &InArray) { OnRep(); }
void FReplicatedPredictionKeyItem::OnRep()
FPredictionKeyDelegates::CatchUpTo(PredictionKey.Current);
for (auto& Delegate : DelPtr->CaughtUpDelegates)
Delegate.ExecuteIfBound();
通过刚才绑定的CatchUp回调,移除GE
void RemoveActiveGameplayEffect_NoReturn(FActiveGameplayEffectHandle Handle, int32 StacksToRemove=-1)
UAbilitySystemComponent::RemoveActiveGameplayEffect(Handle, StacksToRemove);
bool FActiveGameplayEffectsContainer::RemoveActiveGameplayEffect(FActiveGameplayEffectHandle Handle, int32 StacksToRemove)
InternalRemoveActiveGameplayEffect(ActiveGEIdx, StacksToRemove, true);
bool FActiveGameplayEffectsContainer::InternalRemoveActiveGameplayEffect(int32 Idx, int32 StacksToRemove, bool bPrematureRemoval)
FActiveGameplayEffect& Effect = *GetActiveGameplayEffect(Idx);
// 清除层数
if (StacksToRemove > 0 && Effect.Spec.StackCount > StacksToRemove)
Effect.Spec.StackCount -= StacksToRemove;
return false;
// 移除Tag和Magnitude
InternalOnActiveGameplayEffectRemoved(Effect, ShouldInvokeGameplayCueEvent, GameplayEffectRemovalInfo);
// 持续
Owner->GetWorld()->GetTimerManager().ClearTimer(Effect.DurationHandle);
// 周期
Owner->GetWorld()->GetTimerManager().ClearTimer(Effect.PeriodHandle);
// 移除后施加其它GE
InternalApplyExpirationEffects(Effect.Spec, bPrematureRemoval);
Reject
在服务器拒接GA,导致本地预测失败时,在上述GA的预测流程中,预测失败后会广播该委托,后续流程同上
void UAbilitySystemComponent::ClientActivateAbilityFailed_Implementation(FGameplayAbilitySpecHandle Handle, int16 PredictionKey)
if (PredictionKey > 0)
FPredictionKeyDelegates::BroadcastRejectedDelegate(PredictionKey);
Spec->ActivationInfo.SetActivationRejected();
Ability->CurrentActivationInfo.SetActivationRejected();
Ability->K2_EndAbility();
依赖性预测
例如一个前后两件事情存在依赖关系,A成功触发后,才能在某个时刻触发B。如果A预测失败,那边这种情况下B也应该被取消。
在PredictionKey内,有一个Base的字段,存的就是当前键依赖的键。
FPredictionKey::GenerateDependentPredictionKey,将自己变为子Key
// Key1生成Key2,Key2生成Key3
Key1. Base:0 Cur:New1
Key2. Base:New1 Cur:New2
Key3. Base:New1 Cur:New3
Key3的base是最开始的Key1,与Key2的依赖是手动调用FPredictionKeyDelegates::AddDependency
进行的绑定,就是Reject Key2的时候,也会调用Reject Key3。
而Key2、Key3与Key1之间的依赖关系,走的Base这个字段。通过FPredictionKey::DependsOn
判断是否需要Reject。但是我看代码的时候没有找到哪里触发了Key2的Reject。
ActiveGameplayEffects
ASC上存储GE的是
UPROPERTY(Replicated)
FActiveGameplayEffectsContainer ActiveGameplayEffects;
这个是Replicated的,有很多直接对ActiveGameplayEffects的操作,这些操作都需要在服务器进行,之后再同步到客户端,例如SetActiveGameplayEffectLevel
AttributeSet
在Character上新建
AttributeSet = CreateDefaultSubobject<UXXXAttributeSet>(TEXT("AttributeSet"));
ASC上有一份AttributeSet的指针数组
UPROPERTY(Replicated)
TArray<UAttributeSet*> SpawnedAttributes;
在UAbilitySystemComponent::InitializeComponent()
的时候,从Owner上拉取所有的UAttributeSet成员加入到SpawnedAttributes内部
TArray<UAttributeSet*>& SpawnedAttributesRef = GetSpawnedAttributes_Mutable();
TArray<UObject*> ChildObjects;
GetObjectsWithOuter(Owner, ChildObjects, false, RF_NoFlags, EInternalObjectFlags::PendingKill);
for (UObject* Obj : ChildObjects)
UAttributeSet* Set = Cast<UAttributeSet>(Obj);
if (Set)
SpawnedAttributesRef.AddUnique(Set);
bIsNetDirty = true;
属性通知
// 在瞬时、周期性执行的GE执行之前调用,可以修改、增加、屏蔽改Modifier
virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData &Data) { return true; }
// 在瞬时、周期性执行的GE执行后调用
virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData &Data) { }
// Current值变化前回调,做Clamp之类的简单的值修改
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) { }
// Current值变化后回调
virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) { }
// Base值变化前回调,做Clamp之类的简单的值修改
virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const { }
// Base值变化后回调
virtual void PostAttributeBaseChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) const { }
Montage
ASC内部算是自己弄了一套Montage的同步,可以直接借用这个来同步蒙太奇。
播放
ASC内部专门弄了个结构体记录Montage信息:
/** Data structure for replicating montage info to simulated clients */
UPROPERTY(ReplicatedUsing=OnRep_ReplicatedAnimMontage)
FGameplayAbilityRepAnimMontage RepAnimMontageInfo;
这个结构体虽然会同步到所有客户端,但是只在模拟端有用(不清楚为什么不改成COND_SimulatedOnly)
DOREPLIFETIME_WITH_PARAMS_FAST(UAbilitySystemComponent, RepAnimMontageInfo, Params);
通过 ASC封装的PlayMontage接口,就可以完成蒙太奇同步到模拟端:
float UAbilitySystemComponent::PlayMontage(UGameplayAbility* InAnimatingAbility, FGameplayAbilityActivationInfo ActivationInfo, UAnimMontage* NewAnimMontage, float InPlayRate, FName StartSectionName, float StartTimeSeconds)
Duration = AnimInstance->Montage_Play(NewAnimMontage, InPlayRate, EMontagePlayReturnType::MontageLength, StartTimeSeconds);
LocalAnimMontageInfo.AnimMontage = NewAnimMontage;
LocalAnimMontageInfo.AnimatingAbility = InAnimatingAbility;
LocalAnimMontageInfo.PlayInstanceId = (LocalAnimMontageInfo.PlayInstanceId < UINT8_MAX ? LocalAnimMontageInfo.PlayInstanceId + 1 : 0);
InAnimatingAbility->SetCurrentMontage(NewAnimMontage);
// 是否是权威端
if (ShouldRecordMontageReplication())
FGameplayAbilityRepAnimMontage& MutableRepAnimMontageInfo = GetRepAnimMontageInfo_Mutable();
// 更新数据
MutableRepAnimMontageInfo.AnimMontage = NewAnimMontage;
...
// 更新数据
AnimMontage_UpdateReplicatedData();
OutRepAnimMontageInfo.AnimMontage = LocalAnimMontageInfo.AnimMontage;
OutRepAnimMontageInfo.PlayRate = AnimInstance->Montage_GetPlayRate(LocalAnimMontageInfo.AnimMontage);
OutRepAnimMontageInfo.Position = AnimInstance->Montage_GetPosition(LocalAnimMontageInfo.AnimMontage);
OutRepAnimMontageInfo.BlendTime = AnimInstance->Montage_GetBlendTime(LocalAnimMontageInfo.AnimMontage);
if (IsOwnerActorAuthoritative())
// 马上执行属性同步
AbilityActorInfo->AvatarActor->ForceNetUpdate();
else
// 如果在 ScopedPredictionKey 期间,说明本次播放是本地预测,绑定预测失败回调,取消动画
FPredictionKey PredictionKey = GetPredictionKeyForNewAction();
PredictionKey.NewRejectedDelegate().BindUObject(this, &UAbilitySystemComponent::OnPredictiveMontageRejected, NewAnimMontage);
// 每次调用都会 Mark_Dirty
FGameplayAbilityRepAnimMontage& UAbilitySystemComponent::GetRepAnimMontageInfo_Mutable()
MARK_PROPERTY_DIRTY_FROM_NAME(UAbilitySystemComponent, RepAnimMontageInfo, this);
return RepAnimMontageInfo;
因为这部分同步不涉及主控端,所以主控端需要自行调用PlayMontage。
UAbilityTask_PlayMontageAndWait其实就是只进行了PlayMontage,所以需要在主控端和服务器都调用。
至于模拟端,会根据收到的服务器数据,进行本地复刻:
void UAbilitySystemComponent::OnRep_ReplicatedAnimMontage()
if (!AbilityActorInfo->IsLocallyControlled())
// New Montage to play
if ((LocalAnimMontageInfo.AnimMontage != ConstRepAnimMontageInfo.AnimMontage) ||
(LocalAnimMontageInfo.PlayInstanceId != ConstRepAnimMontageInfo.PlayInstanceId))
LocalAnimMontageInfo.PlayInstanceId = ConstRepAnimMontageInfo.PlayInstanceId;
PlayMontageSimulated(ConstRepAnimMontageInfo.AnimMontage, ConstRepAnimMontageInfo.PlayRate);
AnimInstance->Montage_SetPlayRate(LocalAnimMontageInfo.AnimMontage, ConstRepAnimMontageInfo.PlayRate);
if (bReplicatedIsStopped)
if (!bIsStopped)
CurrentMontageStop(ConstRepAnimMontageInfo.BlendTime);
...
AnimInstance->Montage_SetPosition(LocalAnimMontageInfo.AnimMontage, ConstRepAnimMontageInfo.Position);
维护
ASC会Tick更新蒙太奇数据
void UAbilitySystemComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
if (IsOwnerActorAuthoritative())
AnimMontage_UpdateReplicatedData();
至于自己调用的修改,可以参考UAbilitySystemComponent::CurrentMontageJumpToSection,只在主控端调用即可:
void UAbilitySystemComponent::CurrentMontageJumpToSection(FName SectionName)
// 本地执行
AnimInstance->Montage_JumpToSection(SectionName, LocalAnimMontageInfo.AnimMontage);
// 服务器更新数据同步模拟端
if (ShouldRecordMontageReplication())
...
AnimMontage_UpdateReplicatedData();
// 主控端调用Server
if (!IsOwnerActorAuthoritative())
ServerCurrentMontageJumpToSectionName(LocalAnimMontageInfo.AnimMontage, SectionName);
使用问题
项目遇到一个问题,技能使用的 ASC 的 PlayMontage,但是有一个其它的地方播放死亡蒙太奇(通过属性同步)。
同一帧执行了技能的蒙太奇播放和死亡蒙太奇播放,RepAnimMontageInfo 内部记录的是技能的蒙太奇,然后属性同步在下一帧 Tick 之前执行,也就是说 Tick 抓取本地蒙太奇信息还没有执行。
ASC 的蒙太奇信息同步和死亡蒙太奇播放信息同步,不能保证顺序,就有可能出现一个问题:先播放死亡蒙太奇,再播放技能蒙太奇。
所以,要么蒙太奇信息抓取放到 PreReplication,要么全都都使用 ASC 的 PlayMontage,注意 ASC 的 PlayMontage 传入的技能可以为空。
GameplayTag
除了GE内部的Tag管理外,可以自己通过UAbilitySystemBlueprintLibrary::Remove、AddLooseGameplayTags进行额外的Tag操作。注意,不要使用AddLooseGameplayTags、AddReplicatedLooseGameplayTags,除非自己清楚这里面的同步相关事情。
使用 GameplayTags.PrintReport 输出同步的各个 Tag 次数,对于高频 Tag 加入 Config 进一步减少带宽消耗。
参考这篇:https://zhuanlan.zhihu.com/p/652646177