LyraLog10 GAS2

UEqZeroGameplayAbility

GA的子类

/**
 * UEqZeroGameplayAbility
 *      项目中使用的基础游戏玩法能力类。
 */
UCLASS(MinimalAPI, Abstract, HideCategories = Input, Meta = (ShortTooltip = "The base gameplay ability class used by this project."))
class UEqZeroGameplayAbility : public UGameplayAbility
{

protected:
    /*
     * 激活策略
     * - OnInputTriggered: 当输入触发时尝试激活技能
     * - WhileInputActive: 当输入持续激活时持续尝试激活技能
     * - OnSpawn: 当Avatar被分配时尝试激活技能
     */
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "EqZero|Ability Activation")
    EEqZeroAbilityActivationPolicy ActivationPolicy;

    /*
     * 定义此技能激活与其他技能激活之间的关系。例如死亡技能阻塞其他所有技能
     * - Independent: 独立技能,不受其他技能影响
     * - Exclusive_Replaceable: 排他可替换技能,会被其他排他技能取消
     * - Exclusive_Blocking: 排他阻塞技能,会阻止其他排他技能
     */
    UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "EqZero|Ability Activation")
    EEqZeroAbilityActivationGroup ActivationGroup;

    /*
     * 当前技能的花费, 我们自己定义的结构,并在CanActivateAbility中进行检查, 在ActivateAbility中进行扣除
     */
    UPROPERTY(EditDefaultsOnly, Instanced, Category = Costs)
    TArray<TObjectPtr<UEqZeroAbilityCost>> AdditionalCosts;

    /*
     * 进失败的时候UI显示的失败原因和动画等
     */
    UPROPERTY(EditDefaultsOnly, Category = "Advanced")
    TMap<FGameplayTag, FText> FailureTagToUserFacingMessages;

    UPROPERTY(EditDefaultsOnly, Category = "Advanced")
    TMap<FGameplayTag, TObjectPtr<UAnimMontage>> FailureTagToAnimMontage;

    UPROPERTY(EditDefaultsOnly, Category = "Advanced")
    bool bLogCancelation;

    /*
     * HeroComp会来拿这个CameraMode来切换摄像机, 例如ADS开镜技能就有这个配置
     */
    TSubclassOf<UEqZeroCameraMode> ActiveCameraMode;
};

看看属性把,都做了注解

排除摄像机模式和技能组。和Getter

都是一些技能释放流程的函数,这个感觉结合技能写好一点。

FLyraGameplayAbilityTargetData_SingleTargetHit 这是一个 FGameplayAbilityTargetData 的子类,用于传递技能目标输入,用到再说

技能流程函数一个一个讲很恐怖的,我们按流程来梳理吧

技能的激活

这个开头就写了技能的几个核心函数

/** Abilities define custom gameplay logic that can be activated by players or external game logic */
UCLASS(Blueprintable, MinimalAPI)
class UGameplayAbility : public UObject, public IGameplayTaskOwnerInterface
{
    GENERATED_UCLASS_BODY()
    REPLICATED_BASE_CLASS(UGameplayAbility)

    friend class UAbilitySystemComponent;
    friend class UGameplayAbilitySet;
    friend struct FScopedTargetListLock;

public:

    // ----------------------------------------------------------------------------------------------------------------
    //
    //  The important functions:
    //  
    //      CanActivateAbility()    - const function to see if ability is activatable. Callable by UI etc
    //
    //      TryActivateAbility()    - Attempts to activate the ability. Calls CanActivateAbility(). Input events can call this directly.
    //                              - Also handles instancing-per-execution logic and replication/prediction calls.
    //      
    //      CallActivateAbility()   - Protected, non virtual function. Does some boilerplate 'pre activate' stuff, then calls ActivateAbility()
    //
    //      ActivateAbility()       - What the abilities *does*. This is what child classes want to override.
    //  
    //      CommitAbility()         - Commits reources/cooldowns etc. ActivateAbility() must call this!
    //      
    //      CancelAbility()         - Interrupts the ability (from an outside source).
    //
    //      EndAbility()            - The ability has ended. This is intended to be called by the ability to end itself.
    //  
    // ----------------------------------------------------------------------------------------------------------------

我们通常会在ASC里面 TryActivateAbility(AbilitySpecHandle); 激活技能

参数是一个 FGameplayAbilitySpecHandle

我们在Give技能后,会获得这个属性

        ULyraGameplayAbility* AbilityCDO = AbilityToGrant.Ability->GetDefaultObject<ULyraGameplayAbility>();

        FGameplayAbilitySpec AbilitySpec(AbilityCDO, AbilityToGrant.AbilityLevel);
        AbilitySpec.SourceObject = SourceObject;
        AbilitySpec.GetDynamicSpecSourceTags().AddTag(AbilityToGrant.InputTag);

        const FGameplayAbilitySpecHandle AbilitySpecHandle = LyraASC->GiveAbility(AbilitySpec);

FGameplayAbilitySpec

这是一个 FFastArraySerializerItem的子类

USTRUCT(BlueprintType)
struct FGameplayAbilitySpec : public FFastArraySerializerItem
{
    UPROPERTY()
    FGameplayAbilitySpecHandle Handle; // int32的包装,句柄

    /** 指向技能的指针 (也是 CDO. 应该是 const 但是现在太多东西会修改他了) */
    UPROPERTY()
    TObjectPtr<UGameplayAbility> Ability;

    // 等级
    UPROPERTY()
    int32   Level;

    // 输入ID,没给就INDEX_NONE,目前不知道干嘛的
    UPROPERTY()
    int32   InputID;

    /** 描述技能是被什么对象创建的, 可以说 actor 或者 static object. 用于关联技能和对象,例如Lyra中的武器实例,看业务了*/
    UPROPERTY()
    TWeakObjectPtr<UObject> SourceObject;

    /** 该能力被激活的次数减去其被终止的次数的计数。
     * 对于实例化能力,这将是当前活跃实例的数量。
     * 在预测能够准确处理此问题之前,无法进行复制。
     */
    UPROPERTY(NotReplicated)
    uint8 ActiveCount;

    /** 输入当前是否被按下。当输入被释放时,设置为 false,Lyra中就直接改这个值了 */
    UPROPERTY(NotReplicated)
    uint8 InputPressed:1;

    // 激活后就删除
    UPROPERTY(NotReplicated)
    uint8 RemoveAfterActivation:1;

    /** 出作用域就删除吧 */
    UPROPERTY(NotReplicated)
    uint8 PendingRemove:1;

    /** 获得的时候就要激活一次 */
    UPROPERTY(NotReplicated)
    uint8 bActivateOnce : 1;

    // 如果技能在作用域中 等待激活或者添加,用于缓存 GameplayEventData
    TSharedPtr<FGameplayEventData> GameplayEventData = nullptr;

    /** Non replicating 的技能实例 */
    UPROPERTY(NotReplicated)
    TArray<TObjectPtr<UGameplayAbility>> NonReplicatedInstances;

    /** Replicated 的技能实例 */
    UPROPERTY()
    TArray<TObjectPtr<UGameplayAbility>> ReplicatedInstances;

    /**
     * 授予我们的GE(通常无效). FActiveGameplayEffectHandles 不通过网络同步 and 只有 Authority 是合法的.
     * 如果你需要 FGameplayAbilitySpec -> FActiveGameplayEffectHandle, 
     * 请使用 AbilitySystemComponent::FindActiveGameplayEffectHandle.
     */
    UPROPERTY(NotReplicated)
    FActiveGameplayEffectHandle GameplayEffectHandle;

    /** Passed on SetByCaller magnitudes if this ability was granted by a GE */
    TMap<FGameplayTag, float> SetByCallerTagMagnitudes;
}

除此之外都是写转发接口和构造。

还有一个 FGameplayEventData,这个用到了再看吧

UAbilitySystemComponent::GiveAbility

从这里开始

ULyraGameplayAbility* AbilityCDO = AbilityToGrant.Ability->GetDefaultObject<ULyraGameplayAbility>();

FGameplayAbilitySpec AbilitySpec(AbilityCDO, AbilityToGrant.AbilityLevel);
AbilitySpec.SourceObject = SourceObject;
AbilitySpec.GetDynamicSpecSourceTags().AddTag(AbilityToGrant.InputTag);

const FGameplayAbilitySpecHandle AbilitySpecHandle = LyraASC->GiveAbility(AbilitySpec);
FGameplayAbilitySpecHandle UAbilitySystemComponent::GiveAbility(const FGameplayAbilitySpec& Spec)
{
    // 由于 FGameplayAbilitySpec 的构造函数会用CDO初始化,所以通常是合法的
    if (!IsValid(Spec.Ability))
    {
        ABILITY_LOG(Error, TEXT("GiveAbility called with an invalid Ability Class."));
        return FGameplayAbilitySpecHandle();
    }

    // 只有权威能give技能
    if (!IsOwnerActorAuthoritative())
    {
        ABILITY_LOG(Error, TEXT("GiveAbility called on ability %s on the client, not allowed!"), *Spec.Ability->GetName());
        return FGameplayAbilitySpecHandle();
    }

    // 看起来是为了避免在 作用域只让进一次的锁。避免例如 A 函数中有一次调用到了 A。
    if (AbilityScopeLockCount > 0)
    {
        UE_LOG(LogAbilitySystem, Verbose, TEXT("%s: GiveAbility %s delayed (ScopeLocked)"), *GetNameSafe(GetOwner()), *GetNameSafe(Spec.Ability));
        AbilityPendingAdds.Add(Spec);
        return Spec.Handle;
    }

    // 这个就是那个作用域锁,累加和销毁 AbilityScopeLockCount
    ABILITYLIST_SCOPE_LOCK();

    if (OwnedSpec.Ability->GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerActor)
    {
        // 如果技能Policy是为每个Actor创建Actor对象,就在这里创建实例
        CreateNewInstanceOfAbility(OwnedSpec, Spec.Ability);
    }

    // ...
}

CreateNewInstanceOfAbility

UENUM(BlueprintType)
namespace EGameplayAbilityInstancingPolicy
{
    /**
     *  技能的实例化策略
     */
    enum Type : int
    {
        // 技能不实例化. 直接用 CDO 已经弃用了
        NonInstanced UE_DEPRECATED_FORGAME(5.5, "Use InstancedPerActor as the default to avoid confusing corner cases"),

        // 每一个Actor的技能都有自己的实例. 状态可以保存同步。一般用这个就好了
        InstancedPerActor,

        // 我们在每次执行该能力时都会对其进行实例化。目前不支持复制功能。
        InstancedPerExecution,
    };
}

简单来说就是New对象,然后看同步策略

UGameplayAbility* UAbilitySystemComponent::CreateNewInstanceOfAbility(FGameplayAbilitySpec& Spec, const UGameplayAbility* Ability)
{
    AActor* Owner = GetOwner();
    UGameplayAbility * AbilityInstance = NewObject<UGameplayAbility>(Owner, Ability->GetClass());

    // 同步策略就两个 同步/不同步 加进 Spec.ReplicatedInstances/NonReplicatedInstances 避免GC
    if (AbilityInstance->GetReplicationPolicy() != EGameplayAbilityReplicationPolicy::ReplicateNo)
    {
        Spec.ReplicatedInstances.Add(AbilityInstance);
        AddReplicatedInstancedAbility(AbilityInstance); // 对ActorComp 说我要同步
    }
    else
    {
        Spec.NonReplicatedInstances.Add(AbilityInstance);
    }

    return AbilityInstance;
}

OnGiveAbility

FGameplayAbilitySpecHandle UAbilitySystemComponent::GiveAbility(const FGameplayAbilitySpec& Spec)
{
    // ...

    if (OwnedSpec.Ability->GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerActor)
    {
        // Create the instance at creation time
        CreateNewInstanceOfAbility(OwnedSpec, Spec.Ability);
    }

    OnGiveAbility(OwnedSpec);
    MarkAbilitySpecDirty(OwnedSpec, true); // ActivatableAbilities 这个 FastArray 的dirty触发同步

    UE_LOG(LogAbilitySystem, Log, TEXT("%s: GiveAbility %s [%s] Level: %d Source: %s"), *GetNameSafe(GetOwner()), *GetNameSafe(Spec.Ability), *Spec.Handle.ToString(), Spec.Level, *GetNameSafe(Spec.SourceObject.Get()));
    UE_VLOG(GetOwner(), VLogAbilitySystem, Log, TEXT("GiveAbility %s [%s] Level: %d Source: %s"), *GetNameSafe(Spec.Ability), *Spec.Handle.ToString(), Spec.Level, *GetNameSafe(Spec.SourceObject.Get()));
    return OwnedSpec.Handle;
}
void UAbilitySystemComponent::OnGiveAbility(FGameplayAbilitySpec& Spec)
{
    if (!Spec.Ability)
    {
        return;
    }

    const UGameplayAbility* SpecAbility = Spec.Ability;
    const bool bInstancedPerActor = SpecAbility->GetInstancingPolicy() == EGameplayAbilityInstancingPolicy::InstancedPerActor;
    if (bInstancedPerActor && SpecAbility->GetReplicationPolicy() == EGameplayAbilityReplicationPolicy::ReplicateNo)
    {
        // NonReplicatedInstances里面要有一个实例,为啥不知道
        if (Spec.NonReplicatedInstances.Num() == 0)
        {
            CreateNewInstanceOfAbility(Spec, SpecAbility);
        }
    }

    // 如果这个 Ability Spec 是从GE创建的, 关联一下
    if (Spec.GameplayEffectHandle.IsValid())
    {
        UAbilitySystemComponent* SourceASC = Spec.GameplayEffectHandle.GetOwningAbilitySystemComponent();
        if (SourceASC)
        {
            FActiveGameplayEffect* SourceActiveGE = SourceASC->ActiveGameplayEffects.GetActiveGameplayEffect(Spec.GameplayEffectHandle);
            if (SourceActiveGE)
            {
                SourceActiveGE->GrantedAbilityHandles.AddUnique(Spec.Handle);
                SourceASC->ActiveGameplayEffects.MarkItemDirty(*SourceActiveGE);
            }
        }
    }

    // GA的蓝图里面可以配置这个AbilityTriggers,这里做一些事件的处理
    for (const FAbilityTriggerData& TriggerData : Spec.Ability->AbilityTriggers)
    {
        // ...
    }

    // If there's already a primary instance, it should be the one to receive the OnGiveAbility call
    // 如果已经存在主实例,那么它应该是接收 OnGiveAbility 调用的那个实例。
    // 主实例是只有bInstancedPerActor的情况下,按顺序找 NonReplicatedInstances[0], ReplicatedInstances[0]
    UGameplayAbility* PrimaryInstance = bInstancedPerActor ? Spec.GetPrimaryInstance() : nullptr;
    if (PrimaryInstance)
    {
        PrimaryInstance->OnGiveAbility(AbilityActorInfo.Get(), Spec);
    }
    else
    {
        Spec.Ability->OnGiveAbility(AbilityActorInfo.Get(), Spec);
    }
}

总结

只有权威能给予技能,会实例化技能对象并关联到Spec,触发 OnGiveAbility 流程

如果是GE赋予的技能,则关联GE

处理AbilityTriggers回调

触发技能实例的OnGiveAbility

最后mark dirty 触发为了同步

UAbilitySystemComponent::InitAbilityActorInfo

首先Owner和Avatar怎么理解

ASC可挂载角色和PlayerState上。

如果挂在PlayerState,那么Owner就是PlayerState, Avatar就是角色Pawn

如果是怪物,在Character上挂,就都是角色

void UAbilitySystemComponent::InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor)
{
    check(AbilityActorInfo.IsValid());
    bool WasAbilityActorNull = (AbilityActorInfo->AvatarActor == nullptr);
    bool AvatarChanged = (InAvatarActor != AbilityActorInfo->AvatarActor);

    // 初始化 AbilityActorInfo
    AbilityActorInfo->InitFromActor(InOwnerActor, InAvatarActor, this);

首先AbilityActorInfo

/**
 *  FGameplayAbilityActorInfo
 *
 *  与使用能力的角色相关联的缓存数据。
 *  在 InitFromActor 中从 AActor 初始化
 *  能力使用此数据来了解要作用于哪个角色。例如,而不是耦合到特定的角色类。
 *  这些通常作为指针传递以支持多态性。
 *  项目可以重写 UAbilitySystemGlobals::AllocAbilityActorInfo 来重写所创建的默认结构类型。
 */
USTRUCT(BlueprintType)
struct FGameplayAbilityActorInfo
{
    GENERATED_USTRUCT_BODY()

    virtual ~FGameplayAbilityActorInfo() {}

    UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
    TWeakObjectPtr<AActor>    OwnerActor;

    UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
    TWeakObjectPtr<AActor>    AvatarActor;

    UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
    TWeakObjectPtr<APlayerController> PlayerController;

    UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
    TWeakObjectPtr<UAbilitySystemComponent>   AbilitySystemComponent;

    UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
    TWeakObjectPtr<USkeletalMeshComponent>    SkeletalMeshComponent;

    UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
    TWeakObjectPtr<UAnimInstance> AnimInstance;

    UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
    TWeakObjectPtr<UMovementComponent>    MovementComponent;

    /** 此组件将在其中播放蒙太奇的关联动画实例。主动画实例请使用 NAME_None */
    UPROPERTY(BlueprintReadOnly, Category = "ActorInfo")
    FName AffectedAnimInstanceTag; 
}

回来

void UAbilitySystemComponent::InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor)
{
    check(AbilityActorInfo.IsValid());
    bool WasAbilityActorNull = (AbilityActorInfo->AvatarActor == nullptr);
    bool AvatarChanged = (InAvatarActor != AbilityActorInfo->AvatarActor);

    // 初始化 AbilityActorInfo
    AbilityActorInfo->InitFromActor(InOwnerActor, InAvatarActor, this);

    // 设置Owner,让Owner的Destory事件能回调到ASC这里的OnOwnerActorDestroyed,处理一些回收逻辑
    SetOwnerActor(InOwnerActor);

    // 缓存旧的Avatar可能还要用,然后设置新的上去
    const AActor* PrevAvatarActor = GetAvatarActor_Direct();
    SetAvatarActor_Direct(InAvatarActor);

    // 哈?
    if ((WasAbilityActorNull || PrevAvatarActor == nullptr) && InAvatarActor != nullptr)
    {
        HandleDeferredGameplayCues(&ActiveGameplayEffects);
    }

    if (AvatarChanged)
    {
        // 转发 技能的OnAvatarSet 事件 ...
    }

    // 这是全局的一个对象UAbilitySystemGlobals,把自己注册过去
    // 这个 UGameplayTagReponseTable 看注释是那里做 累计多少次 加一个GE这种功能的东西,但是不这么弄我们也有其他办法
    if (UGameplayTagReponseTable* TagTable = UAbilitySystemGlobals::Get().GetGameplayTagResponseTable())
    {
        TagTable->RegisterResponseForEvents(this);
    }

    // 同步蒙太奇,大概是Avatar 重建的时候,有蒙太奇没同步完成,触发个同步?
    LocalAnimMontageInfo = FGameplayAbilityLocalAnimMontage();
    if (IsOwnerActorAuthoritative())
    {
        SetRepAnimMontageInfo(FGameplayAbilityRepAnimMontage());
    }

    if (bPendingMontageRep)
    {
        OnRep_ReplicatedAnimMontage();
    }
}

UAbilitySystemComponent::TryActivateAbility

bool UAbilitySystemComponent::TryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool bAllowRemoteActivation)
{
    FGameplayTagContainer FailureTags;
    FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(AbilityToActivate);
    if (!Spec)
    {
        ABILITY_LOG(Warning, TEXT("TryActivateAbility called with invalid Handle"));
        return false;
    }

    // ...
}

根据Handle(本质是一个数字) 从自己的 ActivatableAbilities.Items 拿到 FGameplayAbilitySpec

ActivatableAbilities.Items 是在Give技能的时候加进这个容器的,由一个Tarray和Owner ASC指针组成

FGameplayAbilitySpec& OwnedSpec = ActivatableAbilities.Items[ActivatableAbilities.Items.Add(Spec)];

FGameplayAbilitySpec* UAbilitySystemComponent::FindAbilitySpecFromHandle(FGameplayAbilitySpecHandle Handle, EConsiderPending ConsiderPending) const
{
    SCOPE_CYCLE_COUNTER(STAT_FindAbilitySpecFromHandle);

    for (const FGameplayAbilitySpec& Spec : ActivatableAbilities.Items)
    {
        if (Spec.Handle == Handle)
        {
            if (!Spec.PendingRemove || EnumHasAnyFlags(ConsiderPending, EConsiderPending::PendingRemove))
            {
                return const_cast<FGameplayAbilitySpec*>(&Spec);
            }
        }
    }

    if (EnumHasAnyFlags(ConsiderPending, EConsiderPending::PendingAdd))
    {
        for (const FGameplayAbilitySpec& Spec : AbilityPendingAdds)
        {
            if (!Spec.PendingRemove || EnumHasAnyFlags(ConsiderPending, EConsiderPending::PendingRemove))
            {
                return const_cast<FGameplayAbilitySpec*>(&Spec);
            }
        }
    }

    return nullptr;
}
USTRUCT(BlueprintType)
struct FGameplayAbilitySpec : public FFastArraySerializerItem
{

};

USTRUCT(BlueprintType)
struct FGameplayAbilitySpecContainer : public FFastArraySerializer
{
    UPROPERTY()
    TArray<FGameplayAbilitySpec> Items;

    UPROPERTY(NotReplicated)
    TObjectPtr<UAbilitySystemComponent> Owner;
};

bool FGameplayAbilitySpecContainer::NetDeltaSerialize(FNetDeltaSerializeInfo & DeltaParms)
{
    return FFastArraySerializer::FastArrayDeltaSerialize<FGameplayAbilitySpec, FGameplayAbilitySpecContainer>(Items, DeltaParms, *this);
}

小结一下,

ASC 有一个

FGameplayAbilitySpecContainer ActivatableAbilities; 这是一个FastArray 通过 NetDeltaSerialize 指定了Item 的TArray

里面的Item是 FGameplayAbilitySpec 里面有一个Handle本质是一个数字

这个FGameplayAbilitySpec在GiveAbility之前,我们就构建,然后给技能,然后填充数据。

FGameplayAbilitySpec 是FastArray中的Item中所有没有被标记NotReplicated 的所有UPROPERTY都会被序列化。

然后就是 FRepLayout 那一套了,最后 ServerReplicateActors 内存比较布局信息变化,然后发包,扯远了。。

回到

bool UAbilitySystemComponent::TryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool bAllowRemoteActivation)
{
    FGameplayTagContainer FailureTags;
    FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(AbilityToActivate);
    if (!Spec)
    {
        ABILITY_LOG(Warning, TEXT("TryActivateAbility called with invalid Handle"));
        return false;
    }

    // skip some check

    UGameplayAbility* Ability = Spec->Ability;
    const FGameplayAbilityActorInfo* ActorInfo = AbilityActorInfo.Get();
    const ENetRole NetMode = ActorInfo->AvatarActor->GetLocalRole();

    // 只有主客户端和权威才能激活技能
    const ENetRole NetMode = ActorInfo->AvatarActor->GetLocalRole();
    if (NetMode == ROLE_SimulatedProxy)
    {
        return false;
    }

    bool bIsLocal = AbilityActorInfo->IsLocallyControlled();

    // Check to see if this a local only or server only ability, if so either remotely execute or fail
    if (!bIsLocal && (Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalOnly || Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalPredicted))
    {
        if (bAllowRemoteActivation)
        {
            // 远程调用技能,会在客户端跑 InternalTryActivateAbility
            ClientTryActivateAbility(AbilityToActivate);
            return true;
        }

        ABILITY_LOG(Log, TEXT("Can't activate LocalOnly or LocalPredicted ability %s when not local."), *Ability->GetName());
        return false;
    }

    // ...
}

ExecutionPolicy

UENUM(BlueprintType)
namespace EGameplayAbilityNetExecutionPolicy
{
    /** 技能执行策略 */
    enum Type : int
    {
        // 客户端先行预测,然后告诉服务器,然后可能需要回滚
        LocalPredicted      UMETA(DisplayName = "Local Predicted"),

        // This ability will only run on the client or server that has local control
        // 只需要本地执行的技能,例如客户端UI技能
        LocalOnly           UMETA(DisplayName = "Local Only"),

        // 服务器初始化,然后客户端如果有也要跑。
        ServerInitiated     UMETA(DisplayName = "Server Initiated"),

        // 纯服务器技能
        ServerOnly          UMETA(DisplayName = "Server Only"),
    };
}
bool UAbilitySystemComponent::TryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool bAllowRemoteActivation)
{
    // ...

    // Check to see if this a local only or server only ability, if so either remotely execute or fail
    if (!bIsLocal && (Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalOnly || Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::LocalPredicted))
    {
        if (bAllowRemoteActivation)
        {
            // 远程调用技能,会在客户端跑 InternalTryActivateAbility
            ClientTryActivateAbility(AbilityToActivate);
            return true;
        }

        ABILITY_LOG(Log, TEXT("Can't activate LocalOnly or LocalPredicted ability %s when not local."), *Ability->GetName());
        return false;
    }

    // 非权威,要执行服务器技能
    if (NetMode != ROLE_Authority && (Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::ServerOnly || Ability->GetNetExecutionPolicy() == EGameplayAbilityNetExecutionPolicy::ServerInitiated))
    {
        // 只能走远程
        if (bAllowRemoteActivation)
        {
            // 尝试 CanActivateAbility 然后 CallServerTryActivateAbility
            FScopedCanActivateAbilityLogEnabler LogEnabler;
            if (Ability->CanActivateAbility(AbilityToActivate, ActorInfo, nullptr, nullptr, &FailureTags))
            {
                // No prediction key, server will assign a server-generated key
                CallServerTryActivateAbility(AbilityToActivate, Spec->InputPressed, FPredictionKey());
                return true;
            }
            else
            {
                NotifyAbilityFailed(AbilityToActivate, Ability, FailureTags);
                return false;
            }
        }

        ABILITY_LOG(Log, TEXT("Can't activate ServerOnly or ServerInitiated ability %s when not the server."), *Ability->GetName());
        return false;
    }

    // 最后
    return InternalTryActivateAbility(AbilityToActivate);
}

我们总结一下

TryActivateAbility 成功到激活技能流程,必须是主客户端或者服务器

然后又由于技能激活策略。

!bIsLocal && (LocalOnly || LocalPredicted) => ClientTryActivateAbility(RPC client) => InternalTryActivateAbility

我理解是DS服务器激活了本地技能,需要让客户端执行

NetMode != Authority && (ServerOnly || ServerInitiated) => CallServerTryActivateAbility => RPC Server => InternalServerTryActivateAbility => InternalTryActivateAbility

客户端想跑DS的技能

最后都会到 InternalTryActivateAbility

InternalTryActivateAbility

bool UAbilitySystemComponent::InternalTryActivateAbility(FGameplayAbilitySpecHandle Handle, FPredictionKey InPredictionKey, UGameplayAbility** OutInstancedAbility, FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData)
{
    FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(Handle);
    UGameplayAbility* Ability = Spec->Ability;

    // ...

    if (!AbilitySource->CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, &InternalTryActivateAbilityFailureTags))
    {
        // ...
        return false;
    }

    // ...

    AbilitySource->CallActivateAbility(Handle, ActorInfo, ActivationInfo, OnGameplayAbilityEndedDelegate, TriggerEventData);
}
bool UGameplayAbility::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, OUT FGameplayTagContainer* OptionalRelevantTags) const
{
    // a lot of check ...

    // CD 检查
    if (!AbilitySystemGlobals.ShouldIgnoreCooldowns() && !CheckCooldown(Handle, ActorInfo, OptionalRelevantTags))
    {
        // LOG ...
        return false;
    }

    // 花费检查,Lyra就在这个 CheckCost 加上了自定义的逻辑
    // 这里基类的逻辑是通过GE 创建 Attr Modifier 去修改看能不能修改成功,来达到花费的作用
    if (!AbilitySystemGlobals.ShouldIgnoreCosts() && ! CheckCost(Handle, ActorInfo, OptionalRelevantTags))
    {
        // ...
        return false;
    }

    // 检查gameplay tag 的需求,就是GA里面配置的那堆阻塞Tag,Lyra直接重写并且不Super
    if (!DoesAbilitySatisfyTagRequirements(*AbilitySystemComponent, SourceTags, TargetTags, OptionalRelevantTags))
    {   
        // LOG
        return false;
    }

    // Check if this ability's input binding is currently blocked
    // 检查技能输入是不是被 阻塞了,目前这个 InputID 我还没看到那里修改了
    if (AbilitySystemComponent->IsAbilityInputBlocked(Spec->InputID))
    {
        // ...
        return false;
    }

    // 蓝图的能否使用判断

    return true;
}
void UGameplayAbility::CallActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate, const FGameplayEventData* TriggerEventData)
{
    PreActivate(Handle, ActorInfo, ActivationInfo, OnGameplayAbilityEndedDelegate, TriggerEventData); // 准备激活了,设置变量,广播事件
    ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData); // 调用激活
}

对比一下

    // ----------------------------------------------------------------------------------------------------------------
    //
    //  The important functions:
    //  
    //      CanActivateAbility()    - const function to see if ability is activatable. Callable by UI etc
    //
    //      TryActivateAbility()    - Attempts to activate the ability. Calls CanActivateAbility(). Input events can call this directly.
    //                              - Also handles instancing-per-execution logic and replication/prediction calls.
    //      
    //      CallActivateAbility()   - Protected, non virtual function. Does some boilerplate 'pre activate' stuff, then calls ActivateAbility()
    //
    //      ActivateAbility()       - What the abilities *does*. This is what child classes want to override.
    //  
    //      CommitAbility()         - Commits reources/cooldowns etc. ActivateAbility() must call this!
    //      
    //      CancelAbility()         - Interrupts the ability (from an outside source).
    //
    //      EndAbility()            - The ability has ended. This is intended to be called by the ability to end itself.
    //  

CommitAbility,CancelAbility,EndAbility呢

现在没找到例子,需要逻辑自己在 ActivateAbility 里面写。

目前还有GE触发流程,Attr修改流程,伤害计算emmmm。

bool UGameplayAbility::CommitAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, OUT FGameplayTagContainer* OptionalRelevantTags)
{
    // Last chance to fail (maybe we no longer have resources to commit since we after we started this ability activation)
    // 再次检查,例如CD和花费
    if (!CommitCheck(Handle, ActorInfo, ActivationInfo, OptionalRelevantTags))
    {
        return false;
    }

    // 执行,处理CD和花费
    CommitExecute(Handle, ActorInfo, ActivationInfo);

    // Fixme: Should we always call this or only if it is implemented? A noop may not hurt but could be bad for perf (storing a HasBlueprintCommit per instance isn't good either)
    K2_CommitExecute();

    // Broadcast this commitment
    ActorInfo->AbilitySystemComponent->NotifyAbilityCommit(this);

    return true;
}

也没啥东西

上一篇
下一篇