Unreal

阅读: 评论:0

Unreal

Unreal

文章目录

    • GameplayAbility
      • 赋予GA
      • 激活GA
      • 激活GA - 预测
    • GameplayEffect
      • 施加GE
      • 预测GE
        • CaughtUp
        • Reject
      • ActiveGameplayEffects
    • AttributeSet
      • 属性通知
    • Montage
      • 播放
      • 维护
      • 使用问题
    • GameplayTag

GameplayAbility

赋予GA

UGameplayAbility* AbilityAbilitySystemComponent->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 EGameplayAbilityInstancingPolicyenum 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)...// 创建时获取一个HandleHandle.GenerateNewHandle();
void FGameplayAbilitySpecHandle::GenerateNewHandle()static int32 GHandle = 1;Handle = GHandle++;

创建GA实例

UGameplayAbility* UAbilitySystemComponent::CreateNewInstanceOfAbility(FGameplayAbilitySpec& Spec, UGameplayAbility* Ability)// 使用GA CDO创建GA Instanceif (Ability->HasAllFlags(RF_ClassDefaultObject))AbilityInstance = NewObject<UGameplayAbility>(Owner, Ability->GetClass());elseAbilityInstance = NewObject<UGameplayAbility>(Owner, Ability->GetClass(), NAME_None, RF_NoFlags, Ability);// 存入GA Instance数组if (AbilityInstance->GetReplicationPolicy() != EGameplayAbilityReplicationPolicy::ReplicateNo)Spec.ReplicatedInstances.Add(AbilityInstance);AllReplicatedInstancedAbilities.Add(AbilityInstance);elseSpec.NonReplicatedInstances.Add(AbilityInstance);

激活GA

通过Handle激活GA

bool UAbilitySystemComponent::TryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool bAllowRemoteActivation)FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(AbilityToActivate);// GA CDOUGameplayAbility* Ability = Spec->Ability;// 跳过模拟端if (NetMode == ROLE_SimulatedProxy)return false;// Local Only Filter: // Server Only Filter: urn InternalTryActivateAbility(AbilityToActivate);

当然,有其它上层的激活方法:

  • 通过Tag激活GA
bool UAbilitySystemComponent::TryActivateAbilitiesByTag(const FGameplayTagContainer& GameplayTagContainer, bool bAllowRemoteActivation)TArray<FGameplayAbilitySpec*> AbilitiesToActivate;// 找到所有可激活的匹配Tag的GAGetActivatableGameplayAbilitySpecsByAllMatchingTags(GameplayTagContainer, AbilitiesToActivate);for (const FGameplayAbilitySpec& Spec : ActivatableAbilities.Items)// 匹配Tagif (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) constbool 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// CallActivateAbilityelse 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、CommitExecuteif (!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 EGameplayAbilityNetExecutionPolicyenum Type// Client上传并本地预测LocalPredicted		UMETA(DisplayName = "Local Predicted"),// Client不上传LocalOnly			UMETA(DisplayName = "Local Only"),// Client上传,等ServerServerInitiated		UMETA(DisplayName = "Server Initiated"),// Server不下发ServerOnly			UMETA(DisplayName = "Server Only"),

通过PKey判断需要取消哪些GA

UAbilitySystemComponent::InternalTryActivateAbility// ROLE_Authority && (!InPredictionKey || ServerInitiated || ServerOnly)// Server新建PKeyif (bCreateNewServerKey)	ActivationInfo.ServerSetActivationPredictionKey(FPredictionKey::CreateNewServerInitiatedKey(this));// 使用传入PKeyelse if (InPredictionKey.IsValidKey())ActivationInfo.ServerSetActivationPredictionKey(InPredictionKey);// NetExecutionPolicy: LocalPredicted// 见下FScopedPredictionWindow ScopedPredictionWindow(this, ActivationInfo.GetActivationPredictionKey());CallServerTryActivateAbility(Handle, Spec->InputPressed, ScopedPredictionKey);// RPC ServerServerTryActivateAbility(AbilityHandle, InputPressed, PredictionKey);// ServerInternalServerTryActivateAbility(Handle, InputPressed, PredictionKey, nullptr);// 找到Server上的该技能(Handle的作用)FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(Handle);FScopedPredictionWindow ScopedPredictionWindow(this, PredictionKey);// Server激活GAInternalTryActivateAbility(Handle, PredictionKey, &InstancedAbility, nullptr, TriggerEventData)// 如果过程中失败,会回调通知ClientClientActivateAbilityFailed(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的GATArray<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 actionsMyWorld->GetLatentActionManager().RemoveActionsForObject(this);if (FAbilitySystemTweaks::ClearAbilityTimers)MyWorld->GetTimerManager().ClearAllTimersForObject(this);Task->TaskOwnerEnded();// 通知远端AbilitySystemComponent->ReplicateEndOrCancelAbility(Handle, ActivationInfo, this, false);// 移除TagAbilitySystemComponent->RemoveLooseGameplayTags(ActivationOwnedTags);// 移除CueAbilitySystemComponent->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) constif (SpecHandle.IsValid() && (HasAuthorityOrPredictionKey(ActorInfo, &ActivationInfo)))return AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get(), AbilitySystemComponent->GetPredictionKeyForNewAction());bool UGameplayAbility::HasAuthorityOrPredictionKey(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo* ActivationInfo) constbool UAbilitySystemComponent::HasAuthorityOrPredictionKey(const FGameplayAbilityActivationInfo* ActivationInfo) constbool UAbilitySystemComponent::CanPredict() constbool IsValidForMorePrediction() constreturn Current > 0 && bIsStale == false && bIsServerInitiated == false;FPredictionKey GetPredictionKeyForNewAction() constreturn ScopedPredictionKey.IsValidForMorePrediction() ? ScopedPredictionKey : FPredictionKey();
FActiveGameplayEffectHandle UAbilitySystemComponent::ApplyGameplayEffectSpecToSelf(const FGameplayEffectSpec &Spec, FPredictionKey PredictionKey)// 不预测周期GEif(PredictionKey.IsValidKey() && Spec.GetPeriod() > 0.f)if(IsOwnerActorAuthoritative())// 权威方仅本地执行PredictionKey = FPredictionKey();else// 非权威方不执行return FActiveGameplayEffectHandle();// 检查免疫 HasApplicationImmunityToSpec// 随机概率触发 Spec.GetChanceToApplyToTarget()// 需要的Tag和有就移除的Tag RequirementsMet RemovalTagRequirements// 本地预测的瞬时GE,后面由于需要校验、纠正,所以时间需要是Infinitebool bTreatAsInfiniteDuration = GetOwnerRole() != ROLE_Authority && PredictionKey.IsLocalClientKey() && Spec.Def->DurationPolicy == EGameplayEffectDurationType::Instant;// 非瞬时GE或本地预测的瞬时GEif (Spec.Def->DurationPolicy != EGameplayEffectDurationType::Instant || bTreatAsInfiniteDuration)// 执行,维护FActiveGameplayEffectsContainer(见下文)AppliedEffect = ActiveGameplayEffects.ApplyGameplayEffectSpec(Spec, PredictionKey, bFoundExistingStackableGE);// Modifierfor (const FGameplayModifierInfo& Modifier : Spec.Def->Modifiers)Modifier.ModifierMagnitude.AttemptCalculateMagnitude(Spec, Magnitude);// 本地预测的瞬时GE改为InfiniteDurationif (bTreatAsInfiniteDuration)OurCopyOfSpec->SetDuration(UGameplayEffect::INFINITE_DURATION, true);// GameplayCue// 瞬时GE的执行if (bTreatAsInfiniteDuration)else if (Spec.Def->DurationPolicy == EGameplayEffectDurationType::Instant)ExecuteGameplayEffect(*OurCopyOfSpec, PredictionKey);// 添加后可能移除其它GEif (bIsNetAuthority)ActiveGameplayEffects.AttemptRemoveActiveEffectsOnEffectApplication(Spec, MyHandle);// 添加后添加其它GEApplyGameplayEffectSpecToSelf(*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,存下原先的ScopedPredictionKeyRestoreKey = AbilitySystemComponent->ScopedPredictionKey;AbilitySystemComponent->ScopedPredictionKey = InPredictionKey;ClearScopedPredictionKey = true;SetReplicatedPredictionKey = InSetReplicatedPredictionKey;FScopedPredictionWindow::~FScopedPredictionWindow()if (SetReplicatedPredictionKey)// 记录已经结束的Scope的PKeyif (OwnerPtr->ScopedPredictionKey.IsValidKey())// 加入环形数组并同步,这里是预测的关键OwnerPtr->ReplicatedPredictionKeyMap.ReplicatePredictionKey(OwnerPtr->ScopedPredictionKey);PredictionKeys[Index].PredictionKey = Key;MarkItemDirty(PredictionKeys[Index]);OwnerPtr->bIsNetDirty = true;// 恢复ScopedPredictionKeyif (ClearScopedPredictionKey)OwnerPtr->ScopedPredictionKey = RestoreKey;

ReplicatedPredictionKeyMap同步时调用FReplicatedPredictionKeyItem的同步:

bool FReplicatedPredictionKeyMap::NetDeltaSerialize(FNetDeltaSerializeInfo& DeltaParms)return FastArrayDeltaSerialize<FReplicatedPredictionKeyItem>(PredictionKeys, DeltaParms, *this);

之后会调到

FReplicatedPredictionKeyItemvoid PostReplicatedAdd(const struct FReplicatedPredictionKeyMap &InArray) { OnRep(); }void PostReplicatedChange(const struct FReplicatedPredictionKeyMap &InArray) { OnRep(); }

在PKey同步的时候会触发CatchUpTo委托

void FReplicatedPredictionKeyItem::OnRep()FPredictionKeyDelegates::CatchUpTo(PredictionKey.Current);for (auto& Delegate : DelPtr->CaughtUpDelegates)Delegate.ExecuteIfBound();void RemoveActiveGameplayEffect_NoReturn(FActiveGameplayEffectHandle Handle, int32 StacksToRemove=-1)UAbilitySystemComponent::RemoveActiveGameplayEffect(Handle, StacksToRemove);bool FActiveGameplayEffectsContainer::RemoveActiveGameplayEffect(FActiveGameplayEffectHandle Handle, int32 StacksToRemove)InternalRemoveActiveGameplayEffect(ActiveGEIdx, StacksToRemove, true);

移除GE

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和MagnitudeInternalOnActiveGameplayEffectRemoved(Effect, ShouldInvokeGameplayCueEvent, GameplayEffectRemovalInfo);// 持续Owner->GetWorld()->GetTimerManager().ClearTimer(Effect.DurationHandle);// 周期Owner->GetWorld()->GetTimerManager().ClearTimer(Effect.PeriodHandle);// 移除后施加其它GEInternalApplyExpirationEffects(Effect.Spec, bPrematureRemoval);
Reject

在服务器拒接GA,导致本地预测失败时,在上述GA的预测流程中,预测失败(服务器不允许Activate)后会广播该委托,后续流程同上

void UAbilitySystemComponent::ClientActivateAbilityFailed_Implementation(FGameplayAbilitySpecHandle Handle, int16 PredictionKey)if (PredictionKey > 0)FPredictionKeyDelegates::BroadcastRejectedDelegate(PredictionKey);

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();// 每次调用都会 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 playif ((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();// 主控端调用Serverif (!IsOwnerActorAuthoritative())ServerCurrentMontageJumpToSectionName(LocalAnimMontageInfo.AnimMontage, SectionName);

使用问题

项目遇到一个问题,技能使用的 ASC 的 PlayMontage,但是有一个其它的地方播放死亡蒙太奇(通过属性同步)。
同一帧执行了技能的蒙太奇播放和死亡蒙太奇播放,RepAnimMontageInfo 内部记录的是技能的蒙太奇,然后属性同步在下一帧 Tick 之前执行,也就是说 Tick 抓取本地蒙太奇信息还没有执行。
ASC 的蒙太奇信息同步和死亡蒙太奇播放信息同步,不能保证顺序,就有可能出现一个问题:先播放死亡蒙太奇,再播放技能蒙太奇。

所以,要么蒙太奇信息抓取放到 PreReplication,要么全都都使用 ASC 的 PlayMontage,注意 ASC 的 PlayMontage 传入的技能可以为空。


GameplayTag

使用 GameplayTags.PrintReport 输出同步的各个 Tag 次数,对于高频 Tag 加入 Config 进一步减少带宽消耗。

参考这篇:

本文发布于:2024-01-29 16:04:00,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170651544516448.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:Unreal
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23