# Unreal - GAS


## ***什么是GAS***

*GAS 全称 Gameplay Ability System，是虚幻引擎提供的一套高度模块化、且原生支持网络同步的玩法逻辑框架。它将“技能逻辑”、“数值计算”和“表现效果”彻底解耦，是构建大型 RPG 或动作游戏的工业级标准。*

*GAS 的核心组成部分：*

*1. Ability System Component (ASC) —— 心脏与中枢*

*它是 GAS 的核心组件，所有需要使用 GAS 的对象都必须挂载它。它负责持有和管理所有的能力、效果和标签，并且是网络同步的权威中心。*

*2. Gameplay Abilities (GA) —— 技能逻辑*

*它定义了技能“具体要做什么”。包含技能的释放顺序、激活条件、消耗以及冷却。它是一段逻辑代码块，比如“闪避”、“火球术”或“打开宝箱”。*

*3. Attribute Set —— 属性仓库*

*它是专门存放数值的地方。比如 Health、Mana、Strength。它不仅存储浮点数，还负责处理属性变动时的限制逻辑，比如血量不能超过最大上限。*

*4. Gameplay Effects (GE) —— 技能效果*

*它是改变属性的载体。它不包含代码逻辑，只包含数据。比如“每秒扣 10 点血”、“永久增加 5 点力量”或者“给目标附加一个眩晕状态”。*

*5. Gameplay Tags ——  标签*

*它是层级化的字符串标签。它是 GAS 系统判断逻辑的灵魂，比如通过检查角色是否有“State.Dead”标签来决定是否允许释放技能。*

*6. Gameplay Cues (GC) —— 表现层*

*它专门处理非数值的表现效果，比如粒子特效、音效、震动。它将视觉反馈从复杂的逻辑计算中剥离出来，从而大幅提升性能和网络带宽利用率。*

*7. Ability Tasks —— 异步处理机*

*因为技能通常是持续的过程，它允许技能在运行中“等待”特定事件。比如“等待动画播放到某个节点”、“等待玩家再次输入”或“等待 2 秒钟”。*

*总结：*

*你可以把 GAS 想象成一个自动化的流程：一般来说玩家按下按键触发“能力(GA)”，能力检查“标签(Tags)”确认状态，通过“任务(Tasks)”执行异步过程，产生“效果(GE)”去修改“属性集(Attribute Set)”中的数值，最后触发“提示(GC)”播放华丽的特效。*

## ***IAbilitySystemInterface***

*一个契约。确保引擎和 GAS 内部（如 GameplayCue）无论在哪个 Actor 身上，都能通过一个统一的接口 GetAbilitySystemComponent() 找到它的心脏。*

## ***UAbilitySystemGlobals***

*存储能力系统（GAS）的全局数据。*

*可以通过“项目设置”（Project Settings）中的“游戏玩法能力设置”（Game -> Gameplay Abilities Settings）进行配置。*

*（注意，如果Unreal版本低于5.5，则 ProjectSetting 找不到，只能去 DefaultGame.ini 里面手动配置）*

## ***元数据与逻辑***

### ***FGameplayTag / FGameplayTagContainer***

*GAS 的 “神经递质”。*

*GAS 几乎所有的判断（能不能放技能、能不能吃 Buff、触发什么特效）全部依赖 Tag。没有标签，GAS 只是普通的属性库；有了标签，它才真正拥有了“逻辑”。*

### ***UAbilitySystemBlueprintLibrary***

*GAS 的“万能工具箱”。*

*提供了一系列静态函数，让你在蓝图中能轻松地获取 ASC、应用 GE、判断 Tag。它是 C++ 底层与蓝图表现层之间的快速通道。*

## ***GAS调试***

```cmd
showdebug abilitysystem
```

*在控制台输入 showdebug abilitysystem。这个命令显示的界面，本质上就是把上面提到的所有 **Spec**、**ActiveGE**、**Tags** 的实时状态可视化给你看。*

## ***AbilitySystemComponent***

*GAS 的心脏。用于与玩法能力系统 (GameplayAbilities System) 对接的核心 Actor 组件。*

*更加通俗的理解就是：它是连接你的角色（Actor）与技能系统（GAS）的桥梁/核心入口*

*它负责所有 Gameplay Ability 的赋予与激活，管理 Attribute 的聚合修改，处理 Gameplay Effect 的生命周期，并作为 Gameplay Tags 的宿主来驱动状态逻辑，同时在底层处理所有相关数据的网络同步与动作预测。*

### ***一些重要接口***

1. *设置该 ASC 的“主人”（Owner）和“化身”（Avatar）。*

   `virtual void InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor);`

   - *入参：*
     - *OwnerActor：逻辑上的所有者（通常是 PlayerState 或 Pawn）。*
     - *AvatarActor：表现上的物理实体（通常是 Character）。*
   - *核心逻辑：这是 ASC 的启动开关。如果不调用它，技能放不出来、属性同步不了、UI 也会报错。*

### ***FPredictionKey***

***FPredictionKey** 是 GameplayAbility 系统中支持“客户端预测”的一种通用方式。它本质上是一个 **ID**，用于标识客户端所执行的预测动作及其产生的“副作用”（Side Effects）。UAbilitySystemComponent（ASC）负责在客户端和服务器之间同步这个预测键及其关联的副作用。*

*本质上，任何行为都可以与一个预测键关联，例如“激活技能”。*

1. ***流程开始**：客户端生成一个全新的预测键，并在调用 ServerTryActivateAbility 时将其发送给服务器。*
2. ***服务器响应**：服务器会确认或拒绝该请求（触发 ClientActivateAbilitySucceed 或 Failed）。*
3. ***本地预测阶段**：在客户端预测技能运行期间，它会产生各种**副作用**（如应用 GameplayEffects、触发事件、播放动画等）。每产生一个副作用，客户端都会将其与启动时生成的那个预测键关联起来。*
4. ***结果处理**：*
   - ***如果激活被拒绝**：客户端可以立即回滚（撤销）这些关联的副作用*。
   - ***如果激活被接受**：客户端必须等待服务器同步真实的副作用数据。*
     - *（ClientActivatbleAbilitySucceed 远程调用会立即发送，但属性同步可能晚几帧到达）。*
     - *一旦服务器版本的副作用同步完成，客户端就会撤销本地预测的临时副作用（并以服务器同步过来的权威数据为准）。*

*一般并不会将伤害数值客户端会预测，只会预测技能特效这些表现效果*

***FPredictionKey 本身主要提供以下功能：***

- ***唯一 ID 系统**：支持“当前”和“基础”整数构成的依赖链系统。*
- ***特殊的网络序列化（NetSerialize）实现**：**该预测键只会序列化给发起预测的那个客户端**。*
  - *这一点至关重要：它允许我们将预测键存在同步状态中，但只有给服务器发送过该键的客户端才能真正看到它（其他客户端不会收到多余的预测数据）*

## ***能力系统***

### ***UGameplayAbility(GA)***

*能力 (Abilities) 定义了自定义的玩法逻辑，这些逻辑可以由玩家或外部游戏逻辑激活。*

*它包含技能的释放逻辑、CD、消耗、标签要求。*

### ***GA蓝图属性***

*Tags 标签属性组*

* *Ability Tags 用于标识技能本身的标签 能够让其他逻辑或系统识别出此技能的类别*

* *Cancel Abilities With Tag 当此技能激活时 会自动寻找并取消掉当前角色身上正在运行且带有这些标签的其他技能*

* *Block Abilities With Tag 当此技能处于激活状态时 会阻止任何带有这些标签的新技能被触发激活*

* *Activation Owned Tags 技能在激活期间 技能持有者（ASC）会自动获得这些标签 技能结束时标签随之移除*

* *Activation Required Tags 激活的前提条件 只有当技能持有者（ASC）已经具备了这些标签时 技能才允许被开启*

* *Activation Blocked Tags 激活的排除条件 如果技能持有者（ASC）身上带有这些标签 那么该技能将被锁定无法激活*

* *Source Required Tags 检查技能的发起者 只有当发起者（Source Object）具备这些标签时 技能才能成功触发*

* *Source Blocked Tags 检查技能的发起者 如果发起者（Source Object）具备这些标签 技能将无法被成功激活*

* *Target Required Tags 检查技能的目标 只有当目标对象具备这些标签时 技能才会对该目标产生效果*

* *Target Blocked Tags 检查技能的目标 如果目标对象具备这些标签 技能将无法对该目标产生任何效果*

*Input 输入属性组*

* *Replicate Input Directly 勾选后会将玩家的原始按键输入直接同步给服务器 而不是通过常规的技能指令流*

*Advanced 高级属性组*

* *Replication Policy 决定了技能的状态和变量是否在网络上进行同步的策略枚举*

  * *Do Not Replicate 技能不进行网络同步 仅在各自触发的那一端运行*

  * *Replicate 技能的内部状态和变量会从服务器同步给所有相关的客户端*

* *Instancing Policy 决定了技能在内存中如何创建实例对象的策略枚举*

  * *Non Instanced 技能不产生实例 所有角色共享一个类定义 内存效率最高但不能存储个体状态*

  * *Instanced Per Actor 每个角色在首次使用该技能时创建一个持久实例 适合存储角色的连招段数等*

  * *Instanced Per Execution 每次按下技能都会创建一个全新的独立实例 适合处理复杂的独立逻辑*

* *Server Respects Remote Ability Cancellation 决定了当客户端尝试主动取消技能时 服务器是否听从该请求*

* *Retrigger Instanced Ability 对于实例化技能 如果在运行中再次触发 是否停止当前运行并从头重新开始*

* *Net Execution Policy 决定了技能在客户端和服务器之间执行顺序和同步关系的策略枚举*

  * *Local Predicted 本地预测模式 客户端立即执行并在服务器端进行验证 这种模式下的操作感最流畅*

  * *Local Only 仅本地模式 技能只在本地客户端运行 不会与服务器进行任何通信*

  * *Server Only 仅服务器模式 技能只在服务器运行 客户端不运行任何逻辑 适合安全性高的数值逻辑*

  * *Server Initiated 服务器启动模式 必须由服务器决定何时启动技能并通知客户端同步执行*

* *Net Security Policy 决定了技能激活权限等级以防止客户端作弊的策略枚举*
  * *Client Or Server 客户端或服务器模式 允许客户端请求激活 灵活但安全性一般*
  * *Server Only Execution 即使客户端请求 也要等服务器确认指令传回后才允许开始执行*
  * *Server Only Termination 客户端无权主动停止技能 必须由服务器判定结束后发送指令*
  * *Server Only 技能的启动和停止完全由服务器掌控 具有最高的防作弊安全性*

*Costs 消耗属性组*

* *Cost Gameplay Effect Class 指定一个 Gameplay Effect 类 用于定义释放技能需要消耗的属性如法力或体力*

*Triggers 触发属性组*

* *Ability Triggers 定义了触发技能的各种外部条件数组 包含触发源类型和对应的触发标签*

* *Trigger Source 决定了技能响应何种外部逻辑变化来尝试自动激活的策略枚举*

  * *Gameplay Event 监听并响应特定的游戏事件标签 通常由其他逻辑发送事件及其数据载荷来精准触发*

  * *Owned Tag Added 当技能持有者（ASC）获得并新增了匹配的标签时 立即触发技能尝试自动激活*

  * *Owned Tag Present 状态检查触发 只要技能持有者（ASC）当前正持有匹配的标签 技能便满足自动激活前提*

*Cooldowns 冷却属性组*

* *Cooldown Gameplay Effect Class 指定一个 Gameplay Effect 类 用于通过标签和持续时间定义技能的冷却逻辑*

### ***重要接口***

1. *ASC里的函数：让角色拥有一项技能*

   ```cpp
   /*
   	FGameplayAbilitySpec AbilitySpec(AbilityCDO, AbilityLevel);
   	AbilitySpec.SourceObject = SourceObject;
   	AbilitySpec.DynamicAbilityTags.AddTag(InputTag);
   */
   
   FGameplayAbilitySpecHandle GiveAbility(const FGameplayAbilitySpec& AbilitySpec);
   ```

2. *ASC里的函数：尝试去激活一个技能。为什么说尝试？因为它里面会触发 `CanActivateAbility`等一系列检查*

   `bool TryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool bAllowRemoteActivation = true);`

   *bAllowRemoteActivation：*

   * *（True）当你在客户端调用此函数时，如果该技能被配置为“在服务器上运行”，客户端会自动向服务器发送一个 RPC 请求，告诉服务器：“我要放这个技能了*
   * *（False）激活请求只会留在本地。如果该技能需要服务器确认，而你禁用了远程激活，那么激活就会失败。*

3. *K2_CanActivateAbility：K2_ 是 Kismet 2 的缩写（虚幻蓝图系统的代号）。带有 K2_ 前缀的函数通常是 C++ 内部使用的“蓝图版本”。*

   *在 CanActivateAbility 的逻辑中就会调用 K2_CanActivateAbility，K2_CanActivateAbility就是在蓝图里面专门写的函数*

   ```cpp
   virtual bool CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr, OUT FGameplayTagContainer* OptionalRelevantTags = nullptr) const;
   
   UFUNCTION(BlueprintImplementableEvent, Category = Ability, DisplayName="CanActivateAbility", meta=(ScriptName="CanActivateAbility"))
   bool K2_CanActivateAbility(FGameplayAbilityActorInfo ActorInfo, const FGameplayAbilitySpecHandle Handle, FGameplayTagContainer& RelevantTags) const;
   ```

   - *判断技能当前能否激活。*
   - *入参：*
     - *SourceTags/TargetTags：来源方和目标方的标签容器。*
   - *核心逻辑：系统在调用 TryActivateAbility 时会先运行这个函数。它会检查技能配置里的各种标签要求（比如：处于晕眩状态不能放技能）。你也可以重写它来增加自定义的限制条件。*

4. *激活技能后的逻辑*

   `virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData);`

   - *入参：*
     - *TriggerEventData：如果是通过事件触发的技能，这里包含了触发时的详细数据。*
   - *核心逻辑：这是技能逻辑的总入口。所有的播放动画、产生特效、发射子弹等逻辑都从这里开始编写。*
   - *老司机提醒：此为技能逻辑，如果想释放一个技能，还得调用 `TryActivateAbility`*

5. *提交技能。*

   `virtual bool CommitAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, OUT FGameplayTagContainer* OptionalRelevantTags = nullptr);`

   - *入参：*
     - *与激活函数的入参基本一致。*
     - *OptionalRelevantTags：如果提交失败，可以用来返回失败的具体原因标签。*
   - *核心逻辑：这个函数会自动去检查技能的消耗（Cost）和冷却（Cooldown）。只有这两个检查都通过了，它才会扣除资源并开始计算冷却。如果返回 false，说明资源不足或冷却没好。*

6. *结束技能后的逻辑*

   `virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled);`

   - *入参：*
     - *bReplicateEndAbility：是否需要将结束信号同步给其他端。*
     - *bWasCancelled：技能是正常播放完结束的，还是被外部逻辑强行打断的。*
   - *核心逻辑：这是最重要的清理接口。如果你不调用它，技能会永远处于“运行中”状态，导致角色可能无法再次释放技能，或者标签一直挂在身上不消失。*

7. *取消/打断当前技能后的逻辑*

      `virtual void CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancelAbility);`

      - *入参：*
        - *bReplicateCancelAbility：是否需要将取消信号同步到其他端。*
      - *核心逻辑：这是技能响应“被强行中止”时的处理函数。它的内部实现通常会自动调用 EndAbility，并显式地将 bWasCancelled 参数设为 true。*
      - *开发提示：它与 EndAbility 的区别在于，CancelAbility 专门用于非自愿的打断（比如被敌人眩晕、被沉默或玩家按了另一个会互斥掉当前技能的操作）。当你需要针对“被动中断”写一些特殊逻辑（比如中断施法后进入更长的冷却）时，会关注这个流程。*

8. *ASC里的函数：根据标签强行取消/打断技能。*

   ```cpp
   void CancelAbility(UGameplayAbility* Ability);	
   void CancelAbilityHandle(const FGameplayAbilitySpecHandle& AbilityHandle);
   void CancelAbilities(const FGameplayTagContainer* WithTags=nullptr, const FGameplayTagContainer* WithoutTags=nullptr, UGameplayAbility* Ignore=nullptr);
   void CancelAllAbilities(UGameplayAbility* Ignore=nullptr);
   ```

   - *入参：*
     - *WithTags：匹配这些标签的技能将被关闭。*
     - *WithoutTags：不包含这些标签的技能才会被关闭。*
     - *Ignore：不会关闭的技能*
   - *核心逻辑：用于实现硬控逻辑，比如眩晕时取消所有正在读条的技能。*

9. *获取技能相关的 Actor 信息。*

   `FGameplayAbilityActorInfo GetActorInfo() const;`

   - *核心逻辑：在技能内部使用。通过它可以直接获取到 AvatarActor（当前的肉体小人）、OwnerActor（逻辑上的所有者）以及 ASC 组件。这是技能内部寻找“我是谁”的最快方式。*

10. *发送游戏事件。*

   `void SendGameplayEvent(FGameplayTag EventTag, FGameplayEventData Payload);`

   - *入参：*
     - *EventTag：标识事件的标签。*
     - *Payload：携带的各种数据包。*
   - *核心逻辑：用于技能内部的主动通信。比如你的技能在某一时刻达成了一个特殊条件，可以发送一个事件来触发角色身上的其他被动技能或特定的特效表现。*

11. *ASC里的函数：发送/触发一个游戏事件。*

    `virtual int32 HandleGameplayEvent(FGameplayTag EventTag, const FGameplayEventData* Payload);`

    - *入参：*
      - *EventTag：触发事件的标签，比如 Event.OnHit。*
      - *Payload：携带的数据包，包含受击者、攻击者、位置等丰富信息。*
    - *核心逻辑：用于触发那些不需要按键、而是由特定行为激发的技能（例如受击反击、暴击回血）。*

### ***重要接口里面的参数***

#### ***FGameplayAbilitySpec***

*技能的“说明书实例”。*

*当你调用 GiveAbility 时，系统会把 GA 类封装成一个 Spec。它存储了技能的等级、输入绑定 ID、以及谁是这个技能的原始所有者。它是技能在内存中真实存在的“户口本”。*

#### ***FGameplayAbilitySpecHandle***

*技能在内存中的“唯一身份证”。*

- ***定义**：一个全局唯一的整数标识符，指向 ASC 中的 FGameplayAbilitySpec。*
- ***应用场景**：当你需要手动结束技能（EndAbility）或者取消特定技能（CancelAbilityHandle）时，系统会认这个 ID。*

*它的存在主要有以下两个核心目的：*

- ***性能优化**：避免我们在蓝图中不得不频繁地拷贝整个完整的目标数据结构体。*
- ***支持多态性**：允许我们在目标数据结构中使用多态特性（即一个句柄可以指向不同类型的具体子类数据）。*
- ***网络同步支持**：允许我们实现 NetSerialize（网络序列化），从而在客户端和服务器之间实现“按值同步（Replication by value）”。*

#### ***FGameplayAbilityActivationInfo***

*技能的“网络状态快照”。*

- ***定义**：存储在技能实例中的结构体，记录了当前技能是如何启动的。*
- ***作用**：它是处理网络同步（Replication）和预测（Prediction）时的重要依据。*
- ***核心状态**：*
  - *ActivationMode：标识当前是“本地预测（Predicting）”、“服务器权威（Authority）”还是“远程非权威（Non-Authoritative）”。*
- ***价值**：在技能内部逻辑中，你可以通过它判断：“我现在是在客户端先行播放特效吗？”或者是“我现在是在服务器做最后的数值结算吗？”*

#### ***FGameplayAbilityActorInfo***

*技能的“环境快照”。*

*记录了技能相关的 Owner、Avatar（化身）、SkeletalMesh、甚至 MovementComponent。它能让技能内部快速获取这些组件，避免频繁调用 GetComponent 造成的性能损耗。*

#### ***FGameplayAbilityTargetData***

*这是一个用于处理目标数据的通用结构体。目标是让通用的函数能够生成这些数据，并由其他通用函数来消费/处理这些数据。*

*该结构能够同时持有特定的 Actor/对象引用，以及通用的位置（Location）/ 方向（Direction）/ 源点（Origin）信息。*

#### ***FGameplayAbilityTargetDataHandle***

*用来包装 FGameplayAbilityTargetData 的句柄*

*它的存在主要有以下两个核心目的：*

- ***性能优化**：避免我们在蓝图中不得不频繁地拷贝整个完整的目标数据结构体。*
- ***支持多态性**：允许我们在目标数据结构中使用多态特性（即一个句柄可以指向不同类型的具体子类数据）。*
- ***网络同步支持**：允许我们实现 NetSerialize（网络序列化），从而在客户端和服务器之间实现“按值同步（Replication by value）”。*

### ***一些其他的杂项***

#### ***UAbilityTask***

*异步处理机。技能是瞬时启动的，Task 允许技能“等待”某事发生（如：等待动画结束、等待一个碰撞、等待一段时间）*

#### ***AGameplayAbilityTargetActor***

*3D 世界的“选择器”。*

*用于在场景中生成视觉辅助（如圆形范围、直线射线）。它负责捕捉玩家选中的目标，并将其打包成 TargetData 发送给服务器。*

*警告：*

* *这些角色在每次能力激活时都会生成一次，并且在默认形式下效率不高*

* *对于大多数游戏，您需要子类化并大量修改此 actor，或者您需要在特定于游戏的 actor 或蓝图中实现类似的功能，以避免 actor 生成成本*

* *这个类没有经过内部游戏的充分测试，但它是一个有用的类，可以用来了解目标复制是如何发生的*

#### ***UGameplayAbilityWorldReticle***

*目标的“反馈准心”。*

*专门用于显示目标确认的反馈效果，比如选中的敌人脚下的红圈，或者技能预判的虚影。*

## ***效果系统***

### ***UGameplayEffect***

*数据容器。它不包含逻辑代码，只包含“修改什么属性、持续多久、有什么标签”。*

*GE 表达了 GAS 架构中一个极其重要的原则：数据与逻辑的彻底分离。*

1. *“驱动一切”的数据资产：*

   *在 GAS 中，UGameplayEffect 本质上是一个“复杂的结构体”。它就像一张处方单，上面写着：加多少血、持续多久、带什么标签。所有的属性改变（Attributes）和状态切换（Tags）都必须通过它来驱动。*

2. *为什么不让写蓝图逻辑？*

   - *性能开销：GE 在游戏中会被高频触发、叠加和同步。如果每个 GE 内部都有复杂的蓝图逻辑，性能会迅速崩溃。*
   - *可预测性：GE 的作用应该是确定的。逻辑应该写在 Ability (GA) 里，或者写在 Execution Calculation（重型计算器）里，而不是写在 GE 这个“数据包”里。*
   - *网络优化：纯数据的同步比同步一段逻辑要高效得多。*

3. *所谓的“模板”：*

   *你在编辑器里右键创建 Gameplay Effect 蓝图时，你其实只是在利用蓝图的界面来填表（设置数值）。这被称为“模板化”。你只是在给这个资产设置初始值，而不是在给它写程序。*

### ***GE 蓝图属性相关***

***Status（状态）***

- *Editor Status Text*
  - *说明: 纯编辑器的备注文本。*
  - *作用: 不影响游戏逻辑，仅用于开发者在蓝图列表中写备注，方便识别这个 GE 的作用。*

***Duration（持续时间策略）***

- *Duration Policy: 定义该效果如何在时间维度上存在。*
  - *Instant (瞬时) 效果应用后立即修改属性的“基础值”（Base Value），然后立即销毁。常用于扣血、加金钱。*
  - *Infinite (永久) 效果永久挂在目标身上，除非手动移除或被标签（Tags）清除。常用于被动技能。*
  - *Has Duration (限时): 效果持续一段时间后自动移除。选择此项后会多出一个 Duration Magnitude 用于设置具体的秒数。常用于眩晕 3 秒、增加攻击力 10 秒。*

*如果选择 永久或者限时 则就会弹出来一个Period (周期)：每多少秒 GE效果就会触发一次*

- *Execute Periodic Effect on Application (应用时立即执行周期效果)*
- Periodic Inhibition Policy (周期抑制策略)
  - *“抑制（Inhibition）”是指 GE 因为某些原因暂时失效了（比如目标获得了免疫某种属性的 Tag）。*
  - *各选项含义：*
    - *Never Reset：*
      - *过程：*
        - *在 0.5 秒时，敌人进入了“无敌/抑制”状态。*
        - *在 1.2 秒时，敌人脱离了“无敌/抑制”状态。*
        - *结果： 闹钟在后台跳过了 1.0 秒那个点。敌人没领到 1.0 秒的那次伤害。 敌人需要等到 2.0 秒那个点才能领下一次伤害。*
    - *Reset Timer：当 GE 从“被抑制”恢复为“正常”时，重新开始 1 秒的倒计时。*
    - *Execute and Reset： 当从“被抑制”恢复时，立刻跳一次伤害，并重新开始 1 秒倒计时。*

***Gameplay Effect（核心变更逻辑）***

- *Components (组件)*
  - *说明: UE5.3之后 新增的模块化功能。点击 + 号可以添加不同的逻辑块。*
  
  - *可选组件举例 (你也可以自定义组件)*
    
    1. *Apply Additional Effects (应用额外效果)*
    
       - **功能：** 当这个 GE 应用到目标身上时，自动触发其他的 GE。
    
       - **例子：** “火球术” GE 在造成伤害的同时，通过这个组件额外触发一个“点燃（持续掉血）”的 GE。
    
    2. *Block Abilities with Tags (通过标签阻断技能)*
    
       - **功能：** 只要这个 GE 还在目标身上，目标就无法激活带有特定标签的技能。
    
       - **例子：** “沉默”状态。给目标一个 GE，组件里填入 Ability.Active。此时目标所有带有该标签的技能都按不出来。
    
    3. *Chance To Apply This Effect (应用概率)*
    
       - ***功能：** 给这个 GE 设置一个触发几率。*
    
       - ***例子：** “暴击”或“眩晕”。设置 20% 的几率，只有运气好时，这个 GE 才会真正挂到目标身上。*
    
    4. *Custom Can Apply This Effect (自定义应用条件)*
    
       - ***功能：** 允许程序员编写复杂的 C++ 逻辑来判断“该 GE 是否能应用”。*
    
       - ***例子：** 只有当目标生命值低于 30% 时，这个“斩杀” GE 才能生效。*
    
    5. *Grant Gameplay Abilities (授予技能)*
    
       - ***功能：** 当这个 GE 存在时，目标会临时获得某些技能；GE 消失，技能回收。*
    
       - ***例子：** 捡到一个“喷气背包”道具（GE），玩家临时获得了“飞行”技能。*
    
    6. *Grant Tags to Target Actor (授予目标标签)*
    
       - **功能：** 当 GE 存在时，给目标打上特定的 Gameplay Tags。
    
       - **例子：** “眩晕”状态。GE 存在期间，目标拥有 State.Stunned 标签。这常用于配合其他的逻辑判断（如：不能移动）。
    
    7. *Immunity to Other Effects (效果免疫)*
    
       - **功能：** 让目标免疫特定类型的 GE。
    
       - **例子：** “圣盾术”。添加此组件并设置 Debuff 标签。此时任何带有 Debuff 标签的 GE 都无法应用到目标身上。
    
    8. Remove Other Effects (移除其他效果)
    
       - **功能：** 当这个 GE 应用时，清除目标身上已有的特定 GE。
    
       - **例子：** “清心术”或“驱散”。应用这个 GE 时，自动移除目标身上所有的 Type.Poison（毒） 标签的效果。
    
    9. Require Tags to Apply/Continue This Effect (应用/持续标签需求)
    
       - **功能：** 只有目标拥有（或没有）某些标签时，这个 GE 才能开始或继续。
    
       - **例子：** “水下呼吸”。要求目标必须拥有 State.InWater 标签。如果目标离开了水，这个 GE 会自动失效。
    
    10. Tags This Effect Has (Asset Tags) (效果自身拥有的标签)
    
        - **功能：** 给这个 GE 资产本身打标签。
    
        - **例子：** 将一个 GE 标记为 Type.Buff.Positive。这样其他的系统（比如 UI 或驱散系统）可以通过标签找到并处理它。
    
    11. UI 相关组件 (UI Data)
    
        - **UI Data (Text Only):** 纯文本的 UI 数据，通常存放 Buff 的名称、描述。
  
- *Modifiers (属性修改器)*
  
  - *说明: 最常用的功能，通过数学运算直接改属性。*
  
  - *内部选项*
    
    - *Attribute: 选择你要修改的属性（如 Health, Mana）。*
    
    - *Modifier Op: 运算方式，包括 Add (加减)、Multiply (乘)、Divide (除)、Override (覆盖/强行设为某值)。*
    
      最终值 = ((基础值 + 所有加法总和) \* 所有乘法总和 / 所有除法总和) -> 最后执行 Override
    
      | *操作符 (ModOp)*                | *数学公式*             | *典型使用场景*                  | *备注*                                                      |
      | ------------------------------- | ---------------------- | ------------------------------- | ----------------------------------------------------------- |
      | ***Additive (Add)***            | *新值 = 旧值 + 修正值* | *生命值上限 +100，攻击力 +5*    | *最常用的修改方式。*                                        |
      | ***Multiplicitive (Multiply)*** | 新值 = 旧值 * 修正值   | *攻击力提升 20% (修正值为 1.2)* | *多个乘法通常是加法堆叠。*（100 * (1.0 + 0.1 + 0.1) = 120） |
      | ***Division (Divide)***         | *新值 = 旧值 / 修正值* | *技能冷却缩减，防御力减半*      | *通常用于负面效果或特殊缩放。*                              |
      | ***Override (Override)***       | *新值 = 修正值*        | *强制移动速度变为 0，强制无敌*  | *霸道总裁。无视之前的任何计算，直接覆盖*                    |
    
    - *Magnitude: 数值来源。可以是固定值，也可以是根据另一个属性计算出的值（Attribute Based）。*
  
- *Executions (执行计算)*
  
  - *说明: 关联 C++ 或蓝图编写的 GameplayEffectExecutionCalculation 类。*
  - 作用: 用于处理极其复杂的伤害公式（例如：伤害 = 攻击力 * 技能倍率 - 敌方护甲，且涉及暴击随机数时)

***Gameplay Cues（视觉与音效表现）***

- *Require Modifier Success to Trigger Cues*:
  - *作用: 如果勾选，只有当上面的 Modifiers 成功修改了属性（没被免疫或失效）时，才会播放特效。*
- *Suppress Stacking Cues*
  - *作用: 如果该 GE 正在堆叠（Stacking），勾选此项后，增加层数时不会重复播放特效，只在第一层播一次。*
- *Gameplay Cues*
  - *说明: 特效标签数组。*
  - *作用: 在这里填入标签（如 GameplayCue.Character.Hit），系统会自动寻找对应的 GameplayCueNotify 蓝图并播放。*
  - *Magnitude Attribute 数值来源属性：在特效蓝图（Gameplay Cue Notify）中，你可以获取这个数值。例如，你可以根据这个数值的大小来缩放爆炸粒子的大小，或者改变音效的音量。如果设为 None，则只发送基础的触发信号*
  - *Min Level 最小等级限制：如果当前施放的 Gameplay Effect 等级低于这个值，该特效将不会播放。这允许你为同一个技能在不同等级配置不同的表现。例如：1-5 级播放小火球特效，6 级以上播放大火球特效。*
  - *Max Level 最大等级限制：配合 Min Level 使用。如果GE等级超过了这个值，该特效将停止触发。如果设置为 0.0 且 Min Level 也是 0.0，通常表示不进行等级过滤，全等级触发*

***Stacking（堆叠策略）***

- *Stacking Type*
  - *None: 不堆叠。每次应用都会创建一个新的独立 GE 实例。*
  - *Aggregate by Source (按来源堆叠): 记录施法者。如果 A 给目标上两次 GE，层数增加；如果 A 给一次，B 给一次，目标身上会有两个独立的 GE。*
  - *Aggregate by Target (按目标堆叠): 不管是谁施放的，只要是同类 GE，在目标身上就只算一个，层数往上加*

*注意： 一旦你将 Stacking Type 设置为非 None，下方会出现更多详细枚举：*

- *Stack Limit Count: 最大堆叠层数。*
- *Stack Duration Refresh Policy (时长刷新策略)*:
  - *Refresh on Successful Application: 每次叠新层数，持续时间重置。*
  - *Never Refresh: 叠层不影响计时，时间到期全消失。*
- *Stack Period Reset Policy (周期重置策略)*
  - *如果 GE 是周期性触发的（比如每 1 秒扣一次血），新层到来时，这个 1 秒的间隔要不要重置？*
  - *Reset on Successful Application: 重置。如果你快要掉血了，此时中了一箭，掉血的时机往后推 1 秒。*
  - *Never Reset: 不重置。无论中多少箭，掉血的频率始终固定。*
- *Stack Expiration Policy (到期策略)*
  - *Clear Entire Stack: 时间到，所有层数瞬间全没。*
  - *Remove Single Stack and Refresh Duration: 时间到，只减一层，然后重新开始倒计时（逐层掉落）。*
  - *Refresh Duration：这个 GE 永远不会因为时间到期而自动消失。使用场景：在某些 UI 设计中，你希望玩家看到一个进度条不断地转圈（比如“战斗状态”），而不是消失*
- *Overflow Effects (溢出效果)*
  - *这是一个 GE 数组。当层数已满且再次触发该 GE 时，系统会额外触发这里配置的 GE。*
  - *典型应用：比如“灼烧”堆满 10 层后，触发一个“爆炸” GE。*
- *Deny Overflow Application (拒绝溢出应用)*
  - *如果不打勾（默认）：即使层数满了，新的应用虽然不加层数，但依然会触发上面的“刷新时长”或“重置周期”逻辑。*
  - *如果打勾：层数满了以后，后续的所有应用直接被忽略，不会刷新时间，也不会触发溢出效果。*

#### ***FAttributeBasedFloat***

*1. BackingAttribute（支撑属性捕获定义）*
*数据类型：FGameplayEffectAttributeCaptureDefinition*
*介绍：这是整个计算的“传感器”。它决定了我们要去抓取谁的属性。*
*它的数据包含：*

- *AttributeToCapture：具体的属性指针（比如攻击力、蓝量）。*
- *Source / Target：是从施法者身上抓，还是从受击者身上抓。*
- *bSnapshot：是否快照。如果设为 true，则记录放技能那一刻的属性；如果为 false，则在效果结算那一刻读取实时属性。*

*2. Coefficient（系数）*
*数据类型：FScalableFloat*
*介绍：公式里的乘数。注意，源码里显示它是一个 FScalableFloat。*
*深度含义：这意味着你的“系数”本身也可以随着 GE 的等级（Level）而变化。*
*游戏场景：比如“法力加成”。1 级 GE 时，伤害是蓝量的 1.0 倍；到了 5 级 GE，伤害可能变成蓝量的 1.5 倍。这就是靠这个系数的 FScalableFloat 曲线来实现的。*

*3. PreMultiplyAdditiveValue（预加值）*
*数据类型：FScalableFloat*
*介绍：在乘法执行前，先给捕获到的属性值加上的底数。*
*逻辑顺序：计算器会先执行 (属性值 + 预加值)。*
*游戏场景：比如“保底计算”。即便你的蓝量（属性）是 0，你可以设预加值为 10，保证后面乘以系数时不会算出 0。*

*4. PostMultiplyAdditiveValue（后加值）*
*数据类型：FScalableFloat*
*介绍：在乘法执行完毕后，最后加上的一个固定偏移量。*
*逻辑顺序：(系数 \* 前面的结果) + 这个值。*
*游戏场景：比如“基于蓝量的额外固伤”。你的一招基础伤害是 100（后加值），然后再额外加上蓝量的 20%。*

*5. AttributeCurve（属性曲线表）*
*数据类型：FCurveTableRowHandle*
*介绍：这是源码里最精彩的部分。如果这里填了数据，系统就不直接使用属性数值了，而是把属性数值当做 X 轴坐标，去这张表里查对应的 Y 轴结果。*
*游戏场景：比如“非线性属性收益”。你的力量达到 100 之后，每点力量带来的伤害提升会越来越少（边际递减），这种复杂的数学曲线不需要写代码，直接把这张表填上就行。*

*6. AttributeCalculationType（属性计算策略）*
*数据类型：EAttributeBasedFloatCalculationType*
*介绍：它决定了计算器怎么看待抓到的那个属性。*
*常用选项：*

- *AttributeMagnitude：直接拿属性的当前值（算完 Buff 的结果）。*
- *AttributeBaseValue：只看属性的基础值（不看任何临时 Buff）。*
- *AttributeEvaluatedUpToChannel：这是一个很深的功能，允许你计算属性时只计算到特定的“层级/通道”，比如无视某些高级别的 Buff。*

*7. SourceTagFilter / TargetTagFilter（标签过滤器）*
*数据类型：FGameplayTagContainer*
*介绍：这是给计算过程加的“前置滤网”。*
*作用：只有当来源（Source）或目标（Target）拥有这些特定的标签时，这个属性修改才会生效。如果标签不匹配，这一整套公式可能会直接返回 0。*

#### ***UGameplayModMagnitudeCalculation***

*该类用于通过蓝图或原生代码（C++）执行自定义的玩法效果（Gameplay Effect）修饰符计算。*

*MMC 的核心定位是：为 Gameplay Effect (GE) 里的某一个属性修改提供动态数值。*

- *单一属性修改：如果你只需要算出一个数值（比如“伤害值”或“加血量”），并填入 GE 的某一个 Modifier 里。*
- *需要客户端预测 (Prediction)：这是最关键的一点。MMC 在客户端和服务器都会运行。如果你希望玩家按下按键，UI 上的数值（如蓝条、体力）立即顺滑减少，必须用 MMC*。
- *公式相对独立：公式只涉及基础的数学运算。例如：*
  - 加血量 = 等级 * 20 + 基础值
  - 技能消耗 = 基础消耗 * (1 - 冷却缩减属性)
- *逻辑简单：它只能返回一个 float，不能直接修改标签（Tags）或执行复杂的逻辑分支。*

#### ***UGameplayEffectExecutionCalculation***

*重型计算器。用于处理最复杂的伤害公式（涉及多属性对比，如：攻击者的破甲 vs 目标的护甲）。*

*ExecCalc 的核心定位是：处理涉及多个属性交互、复杂逻辑的最终结算。*

- *多属性交互（经典伤害公式）：如果你的计算需要同时读取来源（Source）和目标（Target）的大量属性。例如：*
  - 最终伤害 = (攻击者攻击力 * 暴击倍率 - 目标防御力) * (1 - 目标的免伤率) * 属性克制系数。
- *修改多个属性：ExecCalc 一次执行可以同时修改多个属性。例如：*
  - *一个“吸血”效果：同时减少目标的 Health，增加来源的 Health。*
  - *一个“破甲攻击”：同时扣除目标的 Shield 和 Health。*
- *复杂逻辑判断：内部可以写 if-else。例如：*
  - *“如果目标生命值低于 20%，则触发斩杀，伤害翻倍”。*
  - *“如果目标有‘护盾’标签，则伤害先扣除护盾”。*
- *不需要/不建议预测：ExecCalc 通常只在服务器运行。因为伤害结算涉及跨对象的数据交换，客户端预测极易产生“血条回跳”现象。*

#### ***UGameplayEffectComponent***

*UE5.3 引入的模块化设计。将以前杂乱的 GE 设置（如持续时间、几率等）解耦成组件，提高性能。*

### ***GE 重要函数***

1. *ASC里的函数：创建效果上下文句柄。*

   `virtual FGameplayEffectContextHandle MakeEffectContext() const;`

   - *核心逻辑：这个函数用于生成一个空的、但已经初始化了基础信息的容器。它会自动把当前的 OwnerActor（所有者，如 PlayerState）和 AvatarActor（表现肉体，如 Character）填进去。这个上下文（Context）就像是一张空白的身份证，记录了这一发效果到底是谁发出来的。*
   - *开发提示：这是一个虚函数，你可以重写它来携带更多自定义数据。比如在射击游戏中，你可以在重写的 Context 里加入是否爆头、子弹飞行距离等信息。这个生成的句柄随后会被传入 MakeOutgoingSpec，确保这些背景数据能一路跟随 GE 传递到目标的伤害计算（ExecCalc）逻辑中，让目标知道自己是被谁、从哪、用什么方式打中的。*

2. *ASC里的函数：创建一个待发送的 GE 规范句柄。*

   `virtual FGameplayEffectSpecHandle MakeOutgoingSpec(TSubclassOf<UGameplayEffect> GameplayEffectClass, float Level, FGameplayEffectContextHandle Context) const;`

   - *入参：*
     - *GameplayEffectClass：你想使用的 GE 类。*
     - *Level：等级。*
     - *Context：施法上下文。*
   - *核心逻辑：这是配合下一个函数使用的前提。它就像是先填好一张“发货单”但还没发货。你会拿到一个 Handle，通过这个 Handle 你可以调用 SetSetByCallerMagnitude 等函数来修改这批货的“重量”或“属性”，改完后再用 Apply 接口发出去。*

3. *ASC里的函数：应用已经配置好的 GE 规范（Spec）至目标或自身。*

   ```cpp
   virtual FActiveGameplayEffectHandle ApplyGameplayEffectSpecToTarget(const FGameplayEffectSpec& GameplayEffect, UAbilitySystemComponent *Target, FPredictionKey PredictionKey=FPredictionKey());
   
   virtual FActiveGameplayEffectHandle ApplyGameplayEffectSpecToSelf(const FGameplayEffectSpec& GameplayEffect, FPredictionKey PredictionKey = FPredictionKey());
   ```

   - *入参：*
     - *Spec：这是一个已经填好了所有数据的“全家桶”对象（可以通过 FGameplayEffectSpecHandle 获取）。它里面已经包含了等级、上下文、甚至你手动设置的动态数值（SetByCaller）。*
     - *Target：仅在 ToTarget 中使用，指定效果的接收方。*
     - *PredictionKey：网络预测键，用于消除客户端的操作延迟感。*
   - *核心逻辑：这是 GAS 中最高级的应用方式。与之前直接传 UClass 不同，这种方式允许你在效果真正发出去之前，对它进行最后的修改。*

4. *ASC里的函数：将状态效果（GE）应用至目标或自身。*

   ```cpp
   FActiveGameplayEffectHandle ApplyGameplayEffectToTarget(UGameplayEffect *GameplayEffect, UAbilitySystemComponent *Target, float Level = UGameplayEffect::INVALID_LEVEL, FGameplayEffectContextHandle Context = FGameplayEffectContextHandle(), FPredictionKey PredictionKey = FPredictionKey());
   
   FActiveGameplayEffectHandle ApplyGameplayEffectToSelf(const UGameplayEffect *GameplayEffect, float Level, const FGameplayEffectContextHandle& EffectContext, FPredictionKey PredictionKey = FPredictionKey());
   ```

   - *入参（综合介绍）：*
     - *GameplayEffect：想要施加的 GE 类（UClass）。它决定了效果的类型，比如是回血、扣血还是增加防御。*
     - *Target：仅在 ToTarget 中使用。指定谁来接收这个效果。如果是 ToSelf，则默认接收者就是调用者自己。*
     - *Level：效果的等级。用于缩放 GE 内部的数值。比如 1 级技能伤害是 100，2 级可能是 200，就靠这个参数传递。*
     - *Context / EffectContext：上下文句柄。它记录了这次施法的完整背景，比如谁发起的、通过哪个技能发的、命中了哪个点。它是追溯伤害来源（KillCam 或战斗日志）的关键数据。*
     - *PredictionKey：网络预测键。用于处理客户端延迟。它能让玩家在点击技能的瞬间，本地先看到血条变动或 Buff 出现，而不需要等待服务器的往返确认，保证了游戏手感的流畅。*
   - *核心逻辑：这是 GAS 修改属性的终极入口。无论技能逻辑多么复杂，最后一步通常都是通过这两个函数把效果实实在在地挂到角色身上。调用后会返回一个 FActiveGameplayEffectHandle，你可以拿着这个句柄来查询该 Buff 的剩余时间或手动将其移除。*

### ***GE 重要函数里的参数***

#### ***FGameplayEffectSpec***

*这个结构体他告诉了我们*

- *使用了哪个 UGameplayEffect（引用的是不可变的常量数据资产）。*
- *等级是多少（Level）。*
- *谁发起的（Instigator）。*

#### ***FGameplayEffectSpecHandle***

*允许蓝图仅生成一次 GameplayEffectSpec，随后通过句柄对其进行引用，从而实现将其多次应用或应用给多个不同的目标。*

#### ***FActiveGameplayEffect***

*正在运行的“效果快照”。*

*存储在 ASC 内部的结构体。它代表了一个正在生效的持续性（Duration）或永久性（Infinite）的 GE。你想知道自己身上有多少层 Buff，看的就是它。*

#### ***FActiveGameplayEffectHandle***

*效果的“遥控器”。*

*一个唯一的 ID。当你应用一个 Buff 后，系统返回这个句柄。你想手动移除某个 Buff（比如驱散效果），必须通过这个句柄来操作。*

#### ***FGameplayEffectContext***

*这是一个用于存储 “发起者”及相关数据（例如位置和目标）的数据结构。*

*开发者可以派生（子类化）该结构，以添加特定于游戏的自定义信息。*

*由于该结构贯穿于整个效果执行的全过程，因此它是追踪单次执行过程中“瞬态信息（Transient Information）”的绝佳位置。*

#### ***FGameplayEffectContextHandle***

*包装 FGameplayEffectContext 或子类的句柄，以允许其具有多态性并正确复制*

#### ***UGameplayEffectContextPayloadBase***

*它是存储在 FGameplayEffectContext 中的自定义动态数据载体。*

*你可以写一个 UHeadshotPayload 继承自这个 Base，里面存一个 `float HeadshotMultiplier`。在应用 GE 前，把这个 Payload 塞进 Context，后续的伤害计算类（ExecCalc）就能精准地把它取出来。*

## ***表现层***

*处理非数值的表现（特效、音效、震动）。*

*通过 GameplayTag 触发。不参与服务器的回滚，只在客户端执行。*

### ***核心路由：UGameplayCueManager***

- **分发中心（The Router）：** 它是表现层的“神经中枢”。它在项目启动时扫描指定的路径，建立 **GameplayTag → 表现类（Notify）** 的映射表。
- **性能优化：** 负责维护对象池（Recycle Pool），避免频繁创建和销毁 Actor 带来的开销。当一个 Tag 触发时，它负责寻找最合适的处理器来展示效果。

### ***静态处理器：UGameplayCueNotify_Static***

- **本质： 继承自 UObject 的轻量级单例。*
- *特性：“火后即焚” (Fire and Forget)。它不在场景中产生持久实体。*
- *适用场景：处理瞬时状态。如命中时的火花、挥刀的音效、短暂的屏幕震动。*
- *优势： 极高的性能，不参与复杂的生命周期管理，是处理大量爆发性特效的首选。*

### ***动态载体：AGameplayCueNotify_Actor***

- *本质： 实现了 GC 协议函数的 AActor。*
- *特性：“状态伴随” (Lifecycle Managed)。它作为一个真实的实体存在于 3D 世界中，支持挂载组件（Niagara、声音、模型）。*
- *适用场景： 处理持续性状态（WhileActive）。如身上的中毒绿烟、护盾光圈、持续的喷泉特效。*
- *设计逻辑： 之所以继承自 Actor，是为了利用引擎成熟的 Transform（空间变换）、Component（组件化）以及 Tick（帧更新）系统，从而实现随角色移动或随时间渐变的复杂效果。*

### ***数据包：FGameplayCueParameters***

- *本质： 表现层的“任务简报” (Payload)。*

- *核心作用： 解决“表现如何根据上下文变化”的问题。*

- *装载信息：*

  1. ***强度与规模 (Magnitude)***

     * ***Normalized Magnitude (归一化强度):***

       *通常是一个 0.0 到 1.0 之间的值。常用于设置特效的比例（比如蓄力越久，光圈越大）。*

     - ***Raw Magnitude (原始强度):***

       *传递过来的原始数值。比如在伤害 GE 中，这通常是最终计算出的伤害数字。你可以根据这个数字决定播放轻微的出血还是喷涌的出血特效。*

  2. ***标签情报 (Tags)***

     - ***Matched Tag Name (匹配标签):***

       *当前触发这个 Notify 的具体标签。如果你一个 Notify 关联了多个 Tag（如 Abiltiy.Fire.Small 和 Ability.Fire.Big），这个引脚会告诉你到底是哪一个命中了。*

     - ***Original Tag (原始标签):***

       *最开始触发 Cue 的那个标签，不受“标签剥离”或“通配符匹配”的影响。*

     - ***Aggregated Source/Target Tags (来源/目标聚合标签):***

       *包含了攻击方和受击方在这一瞬间**所有的** Gameplay Tags。这非常强大，例如：你可以根据目标是否有 State.InWater 标签，决定火球术击中时是产生爆炸还是产生水蒸气。*

  3. ***物理与位置 (World Info)***

     - **Location (位置):**

       *特效发生的 3D 坐标。这是爆炸或火花生成的精准点。*

     - ***Normal (法线):***

       *碰撞表面的朝向。用于让火花或者弹孔贴图贴在墙面上时角度是正确的。*

     - ***Physical Material (物理材质):***

       *打到了什么材质。是金属、木头还是布料？你可以据此切换音效（叮当声 vs 噗噗声）。*

  4. ***角色关系 (Actors)***

     - ***Instigator (扇动者/主使人):***

       *通常是释放技能的那个 **Controller** 或 **Pawn**。用于追踪“谁”干的。*

     - ***Effect Causer (效果产生物):***

       *具体的物理媒介。例如：发射出的子弹 Actor 或者手里的那把枪。*

     - ***SourceObject (源对象):***

       *通常是一个静态数据对象（如 WeaponData 资产），让你知道这次表现是基于哪种武器配置。*

     - ***Target Attach Component (目标挂载组件):***

       *如果特效需要粘在角色身上，这个引脚告诉你应该挂在哪个组件上（通常是 Mesh）。*

  5. ***等级与深度信息 (Levels & Context)***

     - ***Gameplay Effect Level (GE 等级):***

       *触发此特效的 GE 等级。等级越高，特效可以设计得更华丽。*

     - ***Ability Level (技能等级):***

       *触发此特效的技能等级。*

     - ***Effect Context (效果上下文句柄):***

       *这是最有用的“黑盒子”。它包含了完整的溯源链。在 C++ 或蓝图中，你可以进一步从这里提取出“击中结果（HitResult）”或者自定义的数据（如 Lyra 项目中利用它传递团队 ID）。*

  6. ***技术参数 (Technical)***

     - ***bReplicateLocationWhenUsingMinimalRepProxy (最小化同步代理位置同步):***

       *这是一个底层优化开关。在极简的网络同步模式下，决定是否需要同步具体的地理位置信息，通常开发者不需要手动修改它。*

  *可以将 FGameplayCueParameters 想象成一份“快递单”。有时候系统会自动帮你填好发件人信息，但有时候（比如具体的撞击坐标）必须由你亲手填上去。*

#### ***哪些参数会被自动传进去***

1. *通过 Gameplay Effect (GE) 触发 —— 大部分自动填充*

   *这是最常用的场景。如果你在 GE 的 GameplayCues 栏位里添加了一个 Tag，那么：*

   - *GAS 自动填充的项目：*
     - *Instigator / EffectCauser / SourceObject： 从产生这个 GE 的 EffectContext 中自动提取。*
     - *EffectContext：自动携带触发该 GE 的完整上下文。*
     - *GameplayEffectLevel： 自动填充为 GE 的等级。*
     - *AbilityLevel： 如果 GE 是由技能产生的，自动填充技能等级。*
     - *RawMagnitude： 自动填充为该 GE 的 Modifier 数值（例如：如果你有一个伤害 GE 扣了 50 血，这个 50 会自动塞进 RawMagnitude）。*

   - *需要你手动处理的项目：*
     - *Location / Normal： GE 本身逻辑上并不一定知道“物理撞击点”。如果你需要位置信息，你需要在产生 GE 之前，通过 EffectContext 塞入一个 HitResult。GAS 发现有 HitResult 时，会自动把里面的位置和法线转填到 Cue 参数里。*

2. *在 Ability (GA) 中通过节点手动触发 —— 全手动填充*

   *如果你在蓝图里使用 Execute Gameplay Cue with Params 或者 Add Gameplay Cue 节点：*

   - *情况：系统此时只是一张白纸。*

   - *你的工作： 你必须自己创建一个 MakeGameplayCueParameters 结构体，并把值连上去。*

   - *为什么要手动？ 因为此时系统不知道你触发这个特效的意图。比如你做一个“原地回血”的特效，你可能需要手动把 Location 连成玩家的位置。*

3. *“半自动”：通过 EffectContext 传递*

   *这是高阶开发者最常用的技巧。*

   1. *你在 Ability 开始时，使用 MakeEffectContext 创建上下文。*

   2. *你调用 **AddHitResult** 把射线检测的结果（包含位置、法线、物理材质）塞进去。*

   3. *魔法发生了： 只要这个 Context 被用来产生 GE 或直接触发 Cue，GAS 就会自动从这个“公文包”里拿出位置、法线、物理材质、Instigator 等信息，填满 FGameplayCueParameters。*

## ***UAttributeSet***

*属性仓库。存储 Health, Mana, Attack 等浮点数。*

### ***几个重要的宏***

#### ***ATTRIBUTE_ACCESSORS***

```cpp
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
```

- *作用：属性访问器自动化宏。*
- *核心逻辑：这是一个“全家桶”宏，它会自动为你定义的每一个属性（如血量、攻击力）生成四个标准函数。如果没有这个宏，你需要为每一个属性手动写这四个函数，代码量会非常冗余。*
- *ATTRIBUTE_ACCESSORS 这个是自己手写的 用来整合下面四个宏，下面四个宏才是GAS自带的*

1. *生成的四个函数具体是什么（以属性名 Health 为例）*

   * *GetHealthAttribute()：获取属性的元数据。这在调用效果、监听属性变化（Delegate）或者在计算逻辑（ExecCalc）中标识属性时非常有用。*
   * *GetHealth()：获取当前血量的具体数值（float）。*
   * *SetHealth(float NewVal)：直接设置血量的当前值。*
   * *InitHealth(float NewVal)：初始化血量的基础值。通常只在角色刚刚出生或等级提升重置属性时使用。*

2. *为什么必须用这个宏*

   - *效率：在 AttributeSet 中，如果你有 20 个属性，手动写就是 80 个函数，而用这个宏只需要在每个属性定义下加一行代码。*
   - *注意这个只能在UAttributeSet中使用*
   
3. *老司机的建议*

   * *配合宏定义：这个宏通常不是引擎自带的（虽然有些版本提供了类似的），绝大多数项目都会在项目的头文件里手动定义它。如果你发现代码报错找不到这个宏，记得去把这段定义贴到你的公共头文件（如 ProjectName.h）里。*

   * *属性声明范例：*

     ```cpp
     UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Health, Category = "Attributes")
     FGameplayAttributeData Health;
     ATTRIBUTE_ACCESSORS(UMyAttributeSet, Health)
     ```

#### ***DOREPLIFETIME_CONDITION_NOTIFY***

`DOREPLIFETIME_CONDITION_NOTIFY(ULyraHealthSet, Health, COND_None, REPNOTIFY_Always);`

- *作用：带条件和通知规则的生命周期同步注册宏。*
- *核心逻辑：这个宏写在 GetLifetimeReplicatedProps 函数里。它是虚幻引擎网络底层用来注册变量同步的“最强版本”，比标准的 DOREPLIFETIME 多了两个控制维度。*
- *参数介绍：*
  - *ULyraHealthSet：类名。*
  - *Health：变量名。*
  - *COND_None：同步条件枚举（下文详细介绍）。*
  - *REPNOTIFY_Always：通知触发规则枚举（下文详细介绍）。*

*同步条件枚举（ELifetimeCondition）常用值介绍*

- *COND_None：无条件同步。只要变量变了，服务器就会发给所有客户端。这是属性同步最常用的设置。*
- *COND_OwnerOnly：只同步给拥有者。比如玩家的体力值，其他玩家不需要知道，只需要同步给控制这个角色的玩家即可。*
- *COND_SkipOwner：同步给除拥有者以外的所有人。常用于某些只在他人表现上存在的特效逻辑。*
- *COND_SimulatedOnly：只同步给模拟客户端。即不发给服务器，也不发给主控客户端。*
- *COND_AutonomousOnly：只同步给主控客户端。*

*通知触发规则枚举（ERepNotifyCondition）常用值介绍*

- *REPNOTIFY_OnChanged：只有当值发生变化时才触发 OnRep 函数。这是虚幻引擎默认的行为。*
- *REPNOTIFY_Always：无论值是否发生变化，只要服务器发送了同步包，客户端就必须触发一次 OnRep 函数。*
- *为什么 GAS 喜欢用 Always：因为在 GAS 中，有时候基础值（BaseValue）没变，但内部的计算状态（比如 Buff 叠加）可能需要刷新。使用 Always 可以确保客户端的属性状态始终与服务器保持高度一致，即便数字看起来没变，逻辑也会重新检查一遍。*

#### ***GAMEPLAYATTRIBUTE_REPNOTIFY***

*此宏的作用是确保客户端在收到新数值后，能够正确地通知技能系统组件（ASC）去更新内部的数值状态（包括基础值和当前值），并触发相关的监听回调。*

### ***FGameplayAttribute***

*属性的“门牌号”。*

*它不仅仅是一个名字，而是一个结构体，包装了对 UAttributeSet 中特定字段的反射引用。你在代码中指定“修改生命值”时，传的就是这个。*

### ***FGameplayAttributeData***

*属性的“保险箱”。*

*这是你在 AttributeSet 中定义的每一个属性的真实数据类型。它不像普通的 float，它内部包含两个核心数值：*

- *BaseValue（基础值）：你的永久属性。*

- *CurrentValue（当前值）：算上所有临时 Buff/Debuff 后的最终数值。*

### ***FGameplayAttribute***

*属性的“门牌号”。*

*它不仅仅是一个名字，而是一个结构体，包装了对 UAttributeSet 中特定字段的反射引用。你在代码中指定“修改生命值”时，传的就是这个。*

### ***FGameplayAttributeData***

*属性的“保险箱”。*

*这是你在 AttributeSet 中定义的每一个属性的真实数据类型。它不像普通的 float，它内部包含两个核心数值：*

- *BaseValue（基础值）：你的永久属性。*

- *CurrentValue（当前值）：算上所有临时 Buff/Debuff 后的最终数值。*

### ***重要函数***

1. *GameplayEffect 执行前会触发此函数*

   `virtual bool PreGameplayEffectExecute(FGameplayEffectModCallbackData& Data) override;`

   - *核心逻辑：在一个 GE（状态效果）真正修改属性数值之前触发。*
   - *应用场景：你可以通过返回 false 来取消这次修改。例如，如果你身上有一个“免疫伤害”的标签，你可以在这里拦截所有伤害类的 GE。*

2. *GameplayEffect 执行后会触发此函数*

   `virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;`

   - *核心逻辑：这是 GAS 中最频繁使用的函数。它在 GE 修改完属性后触发，此时你可以拿到修改后的最终数值。*
   - *应用场景：处理核心战斗逻辑。比如：检查血量是否归零并触发死亡、根据受到的伤害数值触发受击动效、将溢出的伤害转化为护盾等。也可以用于数值钳制（Clamping）。*

3. *属性基础值变更前会触发此函数*

   `virtual void PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const override;`

   - *核心逻辑：当属性的“基础值”（BaseValue，不含 Buff 加成）即将发生永久性改变时触发。*
   - *应用场景：用于数值钳制（Clamping）。例如，确保你的基础血量永远不会设置到 0 以下，或者不会超过基础血量的上限。*

4. *属性当前值变更前会触发此函数*

   `virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;`

   - *核心逻辑：当属性的“当前值”（CurrentValue，包含所有 Buff 修正后的值）即将发生改变时触发。*
   - *应用场景：这是处理数值限制的最常见地方。比如：当你通过一个 Buff 临时增加最大血量时，在这里确保当前血量不会超过新的最大血量。*

5. *属性变更后会触发此函数*

   `virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;`

   - *核心逻辑：无论是什么原因导致的属性变化，在变化完成后都会触发。*
   - *应用场景：同步 UI 或者触发简单的被动反应。因为它不提供 GE 的上下文（不知道是谁改的），所以它比 PostGameplayEffectExecute 更纯粹，只关注数字变了这件事。*

6. *注册属性网络同步*

   `virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;`

   *核心逻辑：如果你在做多人游戏，这个函数是必须重写的。你需要在里面通过 DOREPLIFETIME 宏把每一个属性注册到同步列表里。没有这一步，客户端永远拿不到服务器更新的血量。*

7. *ASC里的函数：获取属性的当前最终数值。*

   `float GetNumericAttribute(const FGameplayAttribute& Attribute) const;`

   - *入参：*
     - *Attribute：目标属性，通常通过 AttributeSet 的宏获取。*
   - *核心逻辑：它返回的是经过所有 Buff 修正后的最终数字。比如基础血量 100，身上有加 20 血的 Buff，这里就会返回 120。*

8. *ASC里的函数：获取属性值变化的监听委托。*

   `FOnGameplayAttributeValueChange& GetGameplayAttributeValueChangeDelegate(FGameplayAttribute Attribute);`

   - *入参：*
     - *Attribute：想要监听的属性。*
   - *核心逻辑：这是做 UI 界面联动最核心的接口。当血量或能量变化时，它会自动广播，让你的 UI 及时更新。*

