虚幻GameAbilitySystem源码与设计解析-GameEffectComponent的实现

// 版权所有(c)Epic Games, Inc. 保留所有权利。

#pragma once

#include "CoreMinimal.h"
#include "GameplayEffectComponent.generated.h"

struct FActiveGameplayEffect;
struct FActiveGameplayEffectsContainer;
struct FGameplayEffectSpec;
struct FPredictionKey;
class UGameplayEffect;
没有屁用的头文件

GEC

/**
 * 游戏玩法效果组件(简称 GEComponent)
 * 
 * GE 组件用于定义游戏玩法效果(GameplayEffect)的行为。该组件在 UE 5.3 版本引入,出于设计考虑,UGameplayEffect 对 UGameplayEffectComponent 的调用非常少。
 * 实现者不能依赖提供的大型 API 来实现所有期望的功能,而必须仔细研读游戏玩法效果的流程,并注册所需的回调函数来达成目标。这在当前实际上将 GE 组件的实现限制在了原生代码层面。
 * 
 * GE 组件存在于游戏玩法效果(通常是一个仅包含数据的蓝图资产)之中。因此,和游戏玩法效果一样,对于所有已应用的实例,仅存在一个 GE 组件。
 * 这带来的一个不太直观的问题是,GE 组件不应包含任何运行时可操作/实例化的数据(例如每次执行时的存储状态)。
 * 开发者必须仔细考虑数据的存储位置(以及数据的评估时机)。早期的实现通常通过在所需的回调函数中存储少量运行时数据来解决这个问题(例如通过在委托中绑定额外的参数)。
 * 这或许解释了为什么某些功能仍在 UGameplayEffect 中,而非 UGameplayEffectComponent 中。未来的实现可能需要在 FGameplayEffectSpec(即游戏玩法效果规格组件)中存储额外的数据。
 * 
 * 有关更多说明,请参阅 GameplayEffect.h,尤其是其中使用的术语(添加、执行和应用之间的区别)。
 */

 

UCLASS(Abstract, Const, DefaultToInstanced, EditInlineNew, CollapseCategories, Within=GameplayEffect)
class GAMEPLAYABILITIES_API UGameplayEffectComponent : public UObject
{
    GENERATED_BODY()
public:
    /** 构造函数 */
    UGameplayEffectComponent();

    /** 返回拥有此组件的游戏玩法效果(即外部对象) */
    UGameplayEffect* GetOwner() const;

    /** 
     * 游戏玩法效果规格能否应用到传入的能力系统组件(ASC)上?游戏玩法效果的所有组件都必须返回 true,或者只要有一个组件返回 false 就会禁止应用。
     * 注意:应用和抑制是两个不同的概念。如果一个游戏玩法效果可以应用,那么我们要么将其添加到活动游戏玩法效果容器中(如果它有持续时间或需要预测),要么执行它(如果它是即时生效的)。
     */
    virtual bool CanGameplayEffectApply(const FActiveGameplayEffectsContainer& ActiveGEContainer, const FGameplayEffectSpec& GESpec) const { return true; }

    /**
     * 当一个游戏玩法效果被添加到活动游戏玩法效果容器时调用。当游戏玩法效果有持续时间(或者在本地进行预测)时,会将其添加到该容器中。
     * 注意:在复制过程中也会触发此调用(例如,当服务器将一个游戏玩法效果复制到拥有该效果的客户端时 —— 包括预测后的“重复”效果)。
     * 返回该效果是否应保持活动状态,返回 false 则抑制该效果。注意:抑制不会移除效果(效果仍会被添加,但处于休眠状态,等待解除抑制)。
     */
    virtual bool OnActiveGameplayEffectAdded(FActiveGameplayEffectsContainer& ActiveGEContainer, FActiveGameplayEffect& ActiveGE) const { return true; }

    /** 
     * 当一个游戏玩法效果被执行时调用。游戏玩法效果只能在 ROLE_Authority(服务器端)执行。只有在应用即时效果时,游戏玩法效果才会被执行(否则会被添加到活动游戏玩法效果容器中)。
     * 注意:周期性效果会每隔一个周期执行一次(并且也会被添加到活动游戏玩法效果容器中)。可以将其理解为周期性地执行一个即时效果(因此只能在服务器端发生)。
     */
    virtual void OnGameplayEffectExecuted(FActiveGameplayEffectsContainer& ActiveGEContainer, FGameplayEffectSpec& GESpec, FPredictionKey& PredictionKey) const {}

    /**
     * 当一个游戏玩法效果首次应用或堆叠时调用。无论是有持续时间的效果还是即时执行的效果,都会发生“应用”操作。此调用不会周期性发生,也不会通过复制触发。
     * 建议优先使用此函数,而非 OnActiveGameplayEffectAdded 和 OnGameplayEffectExecuted(不过根据具体情况,可能会同时使用多个函数)。
     */
    virtual void OnGameplayEffectApplied(FActiveGameplayEffectsContainer& ActiveGEContainer, FGameplayEffectSpec& GESpec, FPredictionKey& PredictionKey) const {}

    /**
     * 通知我们拥有该组件的游戏玩法效果已被修改,因此对拥有的游戏玩法效果应用与资产相关的更改(例如其任何字段)
     */
    virtual void OnGameplayEffectChanged() {}

    UE_DEPRECATED(5.4, "使用不带 const 的 OnGameplayEffectChanged 函数 —— 当游戏玩法效果更改时,我们通常希望在 GE 组件上缓存数据")
    virtual void OnGameplayEffectChanged() const {}

编辑器效果

protected:
#if WITH_EDITORONLY_DATA
    /** 在编辑器的游戏玩法效果组件索引中显示的友好名称(参见 UGameplayEffect::GEComponents)。我们在此处设置 EditCondition 为 False,这样它就不会在其他地方显示。 */
    UPROPERTY(VisibleDefaultsOnly, Transient, Category=AlwaysHidden, Meta=(EditCondition=False, EditConditionHides))
    FString EditorFriendlyName;
#endif
};

 

有一些单独定义的功能

// 在传入的游戏玩法效果的父类中查找相同的组件。这对于让子组件从父组件继承属性(例如继承标签)很有用。
template<typename GEComponentClass, typename LateBindGameplayEffect = UGameplayEffect> // LateBindGameplayEffect 用于避免包含 GameplayEffect.h
const GEComponentClass* FindParentComponent(const GEComponentClass& ChildComponent)
{
    const LateBindGameplayEffect* ChildGE = ChildComponent.GetOwner();
    const LateBindGameplayEffect* ParentGE = ChildGE ? Cast<LateBindGameplayEffect>(ChildGE->GetClass()->GetArchetypeForCDO()) : nullptr;
    return ParentGE ? ParentGE->template FindComponent<GEComponentClass>() : nullptr;
}

这段代码定义了 UGameplayEffectComponent 类,它是 Unreal Engine 中用于定义游戏玩法效果(GameplayEffect)行为的组件类。该类引入于 UE 5.3 版本,通过一系列回调函数来控制游戏玩法效果在不同阶段的行为,如应用、添加、执行等。同时,还提供了一个模板函数 FindParentComponent 用于在父类游戏玩法效果中查找相同类型的组件。

详细解释

 

    1. 类定义和特性
      • UCLASS 元数据:
        • Abstract:表示该类是抽象类,不能直接实例化,需要派生类来实现具体功能。
        • Const:表示该类的实例在运行时不会被修改。
        • DefaultToInstanced:默认情况下,该类的对象会被实例化。
        • EditInlineNew:允许在编辑器中直接创建该类的实例。
        • CollapseCategories:在编辑器中折叠类别显示。
        • Within=GameplayEffect:表示该类的对象必须存在于 GameplayEffect 对象内部。
      • 继承关系:继承自 UObject,是 Unreal Engine 中所有对象的基类。
    2. 公共成员函数
      • 构造函数 UGameplayEffectComponent():用于初始化 UGameplayEffectComponent 对象。
      • GetOwner():返回拥有此组件的 UGameplayEffect 对象指针。
      • CanGameplayEffectApply():判断游戏玩法效果规格是否可以应用到指定的活动游戏玩法效果容器上。所有组件都必须返回 true 才能允许应用,只要有一个组件返回 false 就会禁止应用。
      • OnActiveGameplayEffectAdded():当游戏玩法效果被添加到活动游戏玩法效果容器时调用,可用于控制效果是否保持活动状态。
      • OnGameplayEffectExecuted():当游戏玩法效果被执行时调用,仅在服务器端生效,用于处理即时效果的执行逻辑。
      • OnGameplayEffectApplied():当游戏玩法效果首次应用或堆叠时调用,推荐优先使用此函数来处理效果应用逻辑。
      • OnGameplayEffectChanged():当拥有该组件的游戏玩法效果被修改时调用,可用于更新组件的缓存数据等。有一个已弃用的 const 版本,建议使用非 const 版本。
  1. 编辑器相关功能
    • IsDataValid():在编辑器环境下,允许组件验证自身的数据。默认实现确保每个类只有一种类型,可重写该函数并调用基类版本。
  2. 受保护成员变量(仅编辑器环境)
    • EditorFriendlyName:在编辑器的游戏玩法效果组件索引中显示的友好名称,通过 EditCondition 控制其只在特定情况下显示。
  3. 模板函数 FindParentComponent
    • 功能:在传入组件所属的游戏玩法效果的父类中查找相同类型的组件,可用于实现属性继承。
    • 模板参数:
      • GEComponentClass:要查找的组件类型。
      • LateBindGameplayEffect:默认类型为 UGameplayEffect,用于避免包含 GameplayEffect.h 文件。
// 自定义游戏玩法效果组件类
class UMyGameplayEffectComponent : public UGameplayEffectComponent
{
    GENERATED_BODY()

public:
    virtual bool CanGameplayEffectApply(const FActiveGameplayEffectsContainer& ActiveGEContainer, const FGameplayEffectSpec& GESpec) const override
    {
        // 自定义应用条件判断逻辑
        return true;
    }

    virtual void OnGameplayEffectApplied(FActiveGameplayEffectsContainer& ActiveGEContainer, FGameplayEffectSpec& GESpec, FPredictionKey& PredictionKey) const override
    {
        // 处理游戏玩法效果应用时的逻辑
    }
};

// 使用 FindParentComponent 函数
UMyGameplayEffectComponent* ChildComponent = ...;
const UMyGameplayEffectComponent* ParentComponent = FindParentComponent(*ChildComponent);
AI生成的例子

 

posted @ 2025-02-19 18:20  mcwhirr  阅读(140)  评论(0)    收藏  举报