Unity技能系统架构设计:基于GAS框架实现数据驱动与模块化解耦
1. 项目概述为什么我们需要一个专业的技能系统做游戏尤其是带有角色扮演、动作战斗元素的游戏技能系统往往是核心玩法的心脏。从《魔兽世界》里法师搓一个寒冰箭到《英雄联盟》里亚索的“狂风绝息斩”再到《原神》里角色切换释放的元素战技这些看似简单的“按个键出个效果”背后是一套极其复杂的逻辑在支撑。我见过太多项目初期为了快速出Demo技能逻辑直接写在角色的MonoBehaviour脚本里if-else满天飞特效、伤害、冷却、资源消耗的代码搅在一起。当技能数量从5个膨胀到50个当需要支持“技能连招”、“状态免疫”、“属性加成”、“装备附带技能”时代码就变成了一团理不清的“意大利面条”修一个Bug能引出三个新Bug。这就是为什么我们需要一个像Unity Gameplay Ability System (GAS)这样的框架。它不是一个具体的技能而是一套架构一种设计范式。它的核心思想借鉴自虚幻引擎的Gameplay Ability System将技能Ability、属性Attribute、状态标签Gameplay Tag和效果Gameplay Effect进行解耦和标准化管理。简单来说它把“释放火球术”这个行为拆解成条件检查法力值够吗技能在冷却中吗角色是否被沉默消耗与冷却扣除法力值为技能添加一个“冷却中”的标签。效果应用创建一个“火球术伤害”效果这个效果包含了“造成100点火焰伤害”的逻辑。目标解析与执行判断命中目标将伤害效果应用到目标角色的属性上减少生命值并可能附加一个“燃烧”的持续伤害效果。这套架构让技能的复杂度从代码逻辑转移到了数据配置上。策划可以通过创建不同的ScriptableObject资产属性、标签、效果、技能来组合出千变万化的技能而程序员只需要维护这套稳定、可扩展的底层系统。对于中小团队或者独立开发者而言掌握这样一套系统意味着能用更少的时间处理底层架构问题将更多精力投入到玩法创新和内容制作上。接下来我将带你从零开始深入这套系统的每一个模块并分享我在实际项目中应用和改造它的经验。2. 核心架构拆解GAS的四大支柱要理解GAS必须吃透它的四个核心组成部分属性系统Attribute System、游戏标签系统Gameplay Tags、游戏效果系统Gameplay Effects和技能系统Ability System。它们之间的关系可以用一个简单的战斗流程来串联。2.1 属性系统角色的数字化基石属性是角色一切数值的基础。在GAS框架中属性不仅仅是Health、Mana这样的简单变量。它被设计为包含基础值Base Value、**当前值Current Value和修饰器Modifiers**的复合结构。基础值通常代表角色的永久性属性比如升级、装备永久加成所影响的数值。当前值则是实时计算的结果它会受到各种临时效果Buff/Debuff的影响。计算规则通常是当前值 (基础值 所有加法修饰器之和) * 所有乘法修饰器之积这里的“修饰器”就来自游戏效果Gameplay Effect。例如一个“力量药水”效果可能包含一个对Strength属性的“加法修饰器”值为10。而一个“虚弱诅咒”效果可能包含一个对AttackDamage属性的“乘法修饰器”值为0.7即攻击力变为70%。实操心得属性定义的艺术不要一上来就定义几十个属性。从最核心的开始Health生命值、MaxHealth最大生命值、Mana法力值、MaxMana最大法力值、AttackDamage攻击力、Armor护甲。对于派生属性比如“攻击速度”可能由“敏捷”属性推导而来GAS框架提供了LinearDerivedAttributeScriptableObject这样的类来方便地建立线性关系。例如你可以定义AttackSpeed 1.0 0.02 * Agility这样当角色的Agility属性变化时AttackSpeed会自动更新。这极大地减少了手动同步数值的代码量。2.2 游戏标签系统状态管理的瑞士军刀游戏标签Gameplay Tags是一个看似简单但威力巨大的工具。它本质上是一个字符串标识系统支持层级结构例如State.StunnedBuff.Damage.Physical。你可以把它想象成给角色或效果贴“便利贴”。标签的核心作用是进行快速的、声明式的状态查询和过滤避免了复杂的bool变量枚举。例如一个技能可以要求施法者不能拥有State.Silenced沉默标签。一个眩晕效果在应用时会给目标添加State.Stunned标签。另一个技能的效果是移除目标身上所有带有Buff.Damage前缀的标签即移除所有伤害类增益。踩过的坑标签的命名与组织初期我们随意创建标签导致出现了Stun、IsStunned、State.Stun等多个表示同一状态的标签管理混乱。后来我们强制规定了一套命名规范使用英文点号.分隔层级如State.Debuff.Movement.Slow。状态类以State.开头增益类以Buff.开头减益类以Debuff.开头。在项目初期就建立一个Tag资产目录并禁止随意创建必须经过审核。这虽然增加了前期的一点管理成本但彻底杜绝了后期标签爆炸和冲突的问题。2.3 游戏效果系统一切变化的发动机游戏效果Gameplay Effect, GE是GAS中最核心的数据驱动单元。任何对属性的修改、标签的添加移除都是通过GE来描述的。GE本身不包含逻辑它只描述“是什么”。一个GE资产主要配置以下几部分持续时间策略Duration PolicyInstant瞬时立即修改属性的基础值用于永久性改变如升级增加力量。Infinite无限修改属性的当前值效果永久持续直到被手动移除如装备提供的攻击力加成。Durational持续修改属性的当前值效果在指定持续时间后自动移除如一个持续30秒的攻击力Buff。修饰器Modifiers这是GE影响属性的具体方式。你需要指定目标属性如Health、操作符加、乘、覆盖和数值大小。数值大小可以是一个固定值也可以是一个复杂的计算比如基于施法者的Intelligence属性动态计算。标签Tags资产标签Asset TagGE的唯一标识用于区分不同的效果。授予标签Granted Tags当这个GE生效时会自动给目标添加这些标签。应用/持续/移除需求标签这些标签定义了GE生效的条件。例如一个“魔法护盾”效果可以设置“持续需求标签”为State.Shielded如果这个标签被移除了护盾被打破那么该GE会自动失效。一个关键技巧用GE实现冷却和消耗这是GAS设计精妙的地方。技能的冷却Cooldown和资源消耗Cost本身也被视为一种游戏效果。例如一个“火球术”技能在激活时会同时应用两个GECost GE一个Instant效果将Mana属性减去50点。Cooldown GE一个Durational效果持续5秒并为角色添加一个Ability.Fireball.Cooldown标签。在技能逻辑的“检查”阶段会检测角色是否拥有这个标签如果有则技能无法施放。2.4 技能系统协调一切的指挥家技能系统Ability System是顶层逻辑它把属性、标签和效果串联起来形成一个完整的技能激活流程。在GAS框架中一个技能由一对类定义AbstractAbilityScriptableObject技能资产和AbstractAbilitySpec技能规格实例。AbstractAbilityScriptableObject这是一个ScriptableObject用于在编辑器中配置技能的静态数据。比如这个技能关联的Cost GE、Cooldown GE和主要的Gameplay Effect GE以及技能需要的各种标签条件。这个资产是所有拥有该技能的角色共享的。AbstractAbilitySpec这是技能的运行时实例。每个拥有该技能的角色都会有一个自己的Spec对象。这里可以保存技能独有的状态数据比如充能层数、自定义的目标数据等。最重要的是它包含了技能激活的核心协程逻辑ActivateAbility()。技能激活的标准流程如下授予Grant将技能的Spec实例添加到角色的AbilitySystemCharacter组件中。尝试激活TryActivate当玩家按下技能键时调用TryActivateAbility()。检查CheckSpec会检查所有条件是否满足标签条件如无沉默、无冷却、属性条件如法力值足够、自定义逻辑条件如目标在范围内。预激活PreActivate执行激活前逻辑如播放起手动画、寻找目标。激活Activate核心协程。依次应用Cost GE和Cooldown GE然后应用主要的Gameplay Effect GE如伤害、治疗、位移效果。结束或取消End/Cancel技能正常结束或被外部事件如被击晕中断。这套流程将技能的逻辑清晰地分阶段使得网络同步、技能打断、客户端预测等高级功能的实现有了清晰的切入点。3. 从零搭建一个可运行的GAS实战项目理论讲得再多不如动手做一遍。我们抛开Demo从头构建一个极简但完整的功能一个英雄拥有“普通攻击”和“治疗术”两个技能。3.1 环境准备与框架导入首先你需要一个Unity项目建议2020.3或更新版本。GAS框架可以通过Unity的Package Manager从Git URL安装这是最干净的方式。打开Unity进入Window - Package Manager。点击左上角的号选择Add package from git URL...。输入框架的Git仓库地址https://github.com/sjai013/unity-gameplay-ability-system.git#upm点击Add。Unity会自动下载并导入这个包。导入后你会在项目的Packages目录下看到它并且在Assets/Create菜单中会出现Gameplay Ability System的子菜单。注意原仓库已被作者归档但这不影响我们将其作为包导入和使用。它依然是一个功能完整、学习价值极高的框架。3.2 定义核心属性与标签在动手做技能前我们先搭建底层数据。步骤1创建基础属性在Project窗口右键Create - Gameplay Ability System - Attribute asset。创建Attribute_Strength 代表力量。创建Attribute_Agility 代表敏捷。创建Attribute_Intelligence 代表智力。创建Attribute_MaxHealth 代表最大生命值。我们将让它由Strength派生。选中这个资产在Inspector中点击Change Type按钮将其类型从AttributeScriptableObject改为LinearDerivedAttributeScriptableObject。然后设置Base Value: 200Base Attribute: 选择Attribute_StrengthMultiplier: 20这意味着MaxHealth 200 20 * Strength同理创建Attribute_MaxMana 类型改为LinearDerivedAttributeScriptableObject 基于Intelligence公式为MaxMana 75 12 * Intelligence。创建Attribute_Health和Attribute_Mana 它们就是普通的属性用于记录当前值。步骤2创建关键的游戏标签在Project窗口右键Create - Gameplay Ability System - Tag asset。创建State.Silenced沉默无法施法创建State.Stunned眩晕无法行动创建Ability.Attack.Cooldown普通攻击冷却创建Ability.Heal.Cooldown治疗术冷却创建Buff.Healing治疗增益效果标签3.3 构建游戏效果伤害、治疗与冷却现在我们来创建技能会用到的“效果”。步骤3创建“普通攻击伤害”效果Instant右键Create - Gameplay Ability System - Gameplay Effect Definition 命名为GE_AttackDamage。Duration Policy选择Instant。瞬时效果因为伤害是一次性的。在Modifiers列表下点击新增一个修饰器。Attribute: 选择Attribute_Health。Modifier Operator: 选择Add。注意对Health的Add操作负值就是伤害正值就是治疗。Modifier Magnitude: 点击Change Type选择ScalableFloatModifierMagnitude。这是一个常用的类型允许你设置一个基础值和一个随等级增长的系数。我们简单点设置Base Value为-30造成30点伤害。Multiplier: 保持为1。这个GE不需要配置标签因为它瞬间就结束了。步骤4创建“治疗术”效果Durational创建GE_HealOverTime。Duration Policy选择DurationalDuration Modifier设为5持续5秒。新增一个修饰器Attribute:Attribute_Health。Modifier Operator:Add。Modifier Magnitude: 选择ScalableFloatModifierMagnitudeBase Value设为10每秒治疗10点。Multiplier: 1。关键点设置周期Period。在Period部分将Period勾选上并设置Period Modifier为1。这表示每1秒触发一次修饰器。于是这个效果在5秒内会触发5次每次回复10点生命值总计50点。在Granted Tags中添加Buff.Healing 表示目标正在被治疗。步骤5创建“法力消耗”与“冷却”效果创建GE_Cost_10Mana。Duration Policy:Instant。修饰器Attribute选Attribute_ManaOperator选AddMagnitude设为-10。创建GE_Cooldown_2s。Duration Policy:DurationalDuration Modifier设为2。在Granted Tags中添加Ability.Heal.Cooldown。这样在2秒内角色会拥有这个标签技能检查时会因此失败。3.4 实现自定义技能治疗术框架自带了一个SimpleAbility示例但为了彻底理解我们从头实现一个自定义的HealAbility。步骤6创建HealAbilityScriptableObjectusing UnityEngine; using GameplayAbilitySystem; [CreateAssetMenu(menuName Gameplay Ability System/Abilities/Heal Ability)] public class HealAbilityScriptableObject : AbstractAbilityScriptableObject { // 配置项治疗效果的GE public GameplayEffectScriptableObject HealEffect; // 配置项法力消耗的GE public GameplayEffectScriptableObject CostEffect; // 配置项冷却的GE public GameplayEffectScriptableObject CooldownEffect; public override AbstractAbilitySpec CreateSpec(AbilitySystemCharacter owner) { var spec new HealAbilitySpec(this, owner); spec.Level owner.Level; return spec; } public class HealAbilitySpec : AbstractAbilitySpec { public HealAbilitySpec(AbstractAbilityScriptableObject abilitySO, AbilitySystemCharacter owner) : base(abilitySO, owner) { } public override void CancelAbility() { // 治疗术一般不可取消这里可以播放取消动画或音效 Debug.Log(Heal ability cancelled.); } protected override IEnumerator ActivateAbility() { // 1. 应用消耗扣蓝 if (Ability.Cost ! null) { var costSpec Owner.MakeOutgoingSpec(Ability.Cost); Owner.ApplyGameplayEffectSpecToSelf(costSpec); } // 2. 应用冷却 if (Ability.Cooldown ! null) { var cdSpec Owner.MakeOutgoingSpec(Ability.Cooldown); Owner.ApplyGameplayEffectSpecToSelf(cdSpec); } // 3. 应用主要治疗效果对自己 var healAbilitySO Ability as HealAbilityScriptableObject; if (healAbilitySO.HealEffect ! null) { var healSpec Owner.MakeOutgoingSpec(healAbilitySO.HealEffect); Owner.ApplyGameplayEffectSpecToSelf(healSpec); } // 这里可以加入特效播放、音效等视觉听觉反馈 Debug.Log(Heal ability activated!); yield return null; } public override bool CheckGameplayTags() { // 检查冷却标签 if (Owner.HasTag(GameplayTags.Ability.Heal.Cooldown)) { Debug.Log(Heal is on cooldown!); return false; } // 检查沉默标签 if (Owner.HasTag(GameplayTags.State.Silenced)) { Debug.Log(Cant heal while silenced!); return false; } // 检查法力值是否足够这里简化实际应在Cost GE的检查阶段处理 // 更严谨的做法是在Cost GE的Application Tag Requirements里设置需求属性值。 return true; } protected override IEnumerator PreActivate() { // 激活前摇比如播放举起法杖的动画 yield return new WaitForSeconds(0.5f); // 模拟一个施法前摇 } } }步骤7配置治疗术技能资产在Project窗口右键Create - Gameplay Ability System - Abilities - Heal Ability 命名为AB_Heal。在Inspector中将之前创建的GE_HealOverTime拖入Heal Effect字段。将GE_Cost_10Mana拖入Cost Effect字段。将GE_Cooldown_2s拖入Cooldown Effect字段。3.5 组装角色与测试步骤8创建英雄角色在场景中创建一个Cube或导入一个角色模型命名为Hero。为其添加Ability System Character组件。这个组件是GAS框架的核心负责管理角色的所有属性、标签和已授予的技能。我们需要初始化它的属性。创建一个新的C#脚本HeroInitializer 挂载到Hero上。using UnityEngine; using GameplayAbilitySystem; public class HeroInitializer : MonoBehaviour { public AttributeScriptableObject Strength; public AttributeScriptableObject Agility; public AttributeScriptableObject Intelligence; public AttributeScriptableObject MaxHealth; public AttributeScriptableObject MaxMana; public AttributeScriptableObject Health; public AttributeScriptableObject Mana; public HealAbilityScriptableObject HealAbility; private AbilitySystemCharacter asc; void Start() { asc GetComponentAbilitySystemCharacter(); // 1. 初始化基础属性 asc.SetAttributeBaseValue(Strength, 10); asc.SetAttributeBaseValue(Agility, 10); asc.SetAttributeBaseValue(Intelligence, 15); // 法师智力高一些 // 派生属性MaxHealth, MaxMana会根据公式自动计算 // 但我们仍需为当前值属性Health, Mana设置初始值 // 通常初始为满值 float initialMaxHealth asc.GetAttributeCurrentValue(MaxHealth); float initialMaxMana asc.GetAttributeCurrentValue(MaxMana); asc.SetAttributeBaseValue(Health, initialMaxHealth); asc.SetAttributeBaseValue(Mana, initialMaxMana); // 2. 授予技能 if (HealAbility ! null) { var healSpec HealAbility.CreateSpec(asc); asc.GrantAbility(healSpec); } // 3. 打印初始状态用于调试 Debug.Log($Hero Initialized. Health: {asc.GetAttributeCurrentValue(Health)}/{initialMaxHealth}, Mana: {asc.GetAttributeCurrentValue(Mana)}/{initialMaxMana}); } }在Hero的Inspector中将HeroInitializer脚本里对应的字段拖入我们之前创建的所有属性资产和AB_Heal技能资产。步骤9创建简单的UI控制器创建一个HeroController脚本用于接收输入并触发技能。using UnityEngine; public class HeroController : MonoBehaviour { private AbilitySystemCharacter asc; private AbstractAbilitySpec healAbilitySpec; void Start() { asc GetComponentAbilitySystemCharacter(); // 假设我们只授予了一个技能这里简单获取第一个。实际项目需要更精确的查找逻辑。 var abilities asc.GetGrantedAbilities(); if (abilities.Count 0) { healAbilitySpec abilities[0]; } } void Update() { if (Input.GetKeyDown(KeyCode.H) healAbilitySpec ! null) { StartCoroutine(healAbilitySpec.TryActivateAbility()); } } }将HeroController也挂载到Hero上。步骤10运行与测试运行游戏按下H键。你应该能在Console中看到“Heal ability activated!”的日志。观察角色的Health和Mana属性可以通过在HeroInitializer的Update中打印或编写一个简单的UI来显示应该能看到法力值减少了10点生命值在5秒内每秒回复10点并且在2秒内再次按H键会提示“Heal is on cooldown!”。至此一个基于GAS框架的、数据驱动的、可配置的治疗术技能就完整实现了。你可以通过修改GE_HealOverTime的数值、持续时间、周期或者修改HealAbility的配置轻松地调整这个技能而无需修改任何代码。4. 进阶应用与避坑指南掌握了基础搭建后我们来看看如何应对更复杂的需求以及在实际项目中容易踩的坑。4.1 实现目标型技能火球术上面的治疗术是作用于自身的。如何做一个需要选择目标如敌人的火球术呢关键在于AbilitySpec在激活时需要知道目标是谁。修改HealAbilitySpec以支持目标 我们可以在TryActivateAbility方法中传入一个目标参数。更GAS的方式是利用GameplayEvent或自定义的上下文数据。这里我们用一个简化的方法在AbilitySpec中增加一个Target字段在激活前设置它。修改HealAbilityScriptableObject 在其HealAbilitySpec类中添加一个公共的AbilitySystemCharacter Target属性。创建目标选择逻辑 在实际项目中这通常由玩家的点击、技能指示器或AI逻辑来设置。我们可以创建一个临时的TargetPicker组件来模拟。public class TargetPicker : MonoBehaviour { public AbilitySystemCharacter ownerASC; public HealAbilityScriptableObject.HealAbilitySpec healSpec; // 需要引用到具体的Spec实例 public LayerMask enemyLayer; void Update() { if (Input.GetMouseButtonDown(0)) { Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 100, enemyLayer)) { var targetASC hit.collider.GetComponentAbilitySystemCharacter(); if (targetASC ! null) { healSpec.SetTarget(targetASC); // 假设我们在Spec里添加了SetTarget方法 StartCoroutine(healSpec.TryActivateAbility()); } } } } }修改ActivateAbility逻辑 在ActivateAbility协程中将治疗效果应用到Target而不是Owner。// 修改应用治疗效果的部分 if (healAbilitySO.HealEffect ! null Target ! null) { var healSpec Owner.MakeOutgoingSpec(healAbilitySO.HealEffect); Target.ApplyGameplayEffectSpecToSelf(healSpec); // 对目标应用 }注意事项 这里引出了一个关键问题——网络同步。在多人游戏中目标的选择、技能的激活必须在服务器进行权威验证。GAS框架本身不处理网络但它清晰的职责分离Spec持有状态ASC管理属性和效果为网络同步提供了良好的基础。通常的做法是客户端预测技能激活播放动画、消耗资源将激活请求和目标的网络ID发送给服务器服务器在权威的ASC上执行相同的逻辑并广播结果。4.2 处理复杂属性修饰护甲与伤害减免假设我们有一个Armor护甲属性它按百分比减免物理伤害。如何让一个“物理伤害”的GE受到护甲影响这需要自定义ModifierMagnitude的计算逻辑。创建自定义的伤害修饰器[CreateAssetMenu(menuName Gameplay Ability System/Modifier Magnitude/Armor Reducing Damage)] public class ArmorReducingDamageMagnitude : ModifierMagnitudeScriptableObject { public AttributeScriptableObject ArmorAttribute; public override float? GetMagnitude(AbilitySystemCharacter source, AbilitySystemCharacter target, GameplayEffectSpec spec) { if (target null) return BaseValue; float baseDamage BaseValue; // 从GE配置中读取的基础伤害值 float targetArmor target.GetAttributeCurrentValue(ArmorAttribute); // 一个简单的伤害减免公式每点护甲减少1%伤害最多减少70% float damageReductionPercent Mathf.Min(targetArmor * 0.01f, 0.7f); float finalDamage baseDamage * (1 - damageReductionPercent); return finalDamage; } }在伤害GE中使用它 创建一个新的GE_PhysicalDamage 在它的修饰器的Modifier Magnitude字段选择我们刚创建的ArmorReducingDamageMagnitude资产并设置其ArmorAttribute为Attribute_Armor。这样每次应用这个伤害效果时都会动态计算最终伤害。4.3 性能优化与常见问题排查GAS框架在大量动态效果和属性计算时可能会成为性能瓶颈。以下是一些优化和调试技巧1. 属性监听与UI更新频繁通过GetAttributeCurrentValue来更新UI如血条是低效的。更好的做法是使用属性变化事件。AbilitySystemCharacter提供了属性值变化的事件OnAttributeChanged。你可以在UI脚本中订阅这个事件只在值真正变化时更新显示。void Start() { asc.OnAttributeChanged OnHealthChanged; } void OnHealthChanged(AttributeScriptableObject attribute, float oldValue, float newValue) { if (attribute HealthAttribute) { healthSlider.value newValue / asc.GetAttributeCurrentValue(MaxHealthAttribute); } }2. 游戏效果堆叠与刷新默认情况下同一个GE多次应用到同一个目标效果是叠加的。但有时我们需要“刷新”持续时间而不是叠加。例如一个持续5秒的加速Buff再次获得时应该刷新到5秒而不是变成10秒。这需要在GE的配置中利用Asset Tag和Remove Gameplay Effects With Tag功能。为加速Buff GE设置一个唯一的Asset Tag比如Buff.Speed。在这个GE的Remove Gameplay Effects With Tag列表中也添加Buff.Speed。这样当应用新的加速Buff时会自动移除旧的同名Buff从而实现刷新而非叠加。3. 调试与可视化当技能不生效时按以下步骤排查检查标签 在CheckGameplayTags方法中打日志或使用调试工具查看角色当前拥有的所有标签。最常见的错误就是冷却或状态标签没被正确添加或移除。检查属性值 确保Cost GE消耗的属性如Mana当前值足够。Instant效果的消耗是直接修改Base Value要确保逻辑正确。检查GE应用 在ApplyGameplayEffectSpecToSelf前后打印属性值确认效果是否被应用。注意Infinite和Durational效果是修改Current Value。检查Spec状态 确保AbilitySpec被正确创建和授予GrantAbility并且你在尝试激活的是正确的Spec实例。4. 与现有项目集成的心得如果你在一个已有角色的项目中使用GAS最大的挑战往往是数据迁移。不要试图一夜之间重写所有技能。建议从一个新角色或一个新技能类型开始试点。将原有的Health、Mana等数值变量逐步替换为GAS的属性并通过适配器代码让新旧系统暂时共存。优先用GAS实现新的、复杂的技能用其强大的数据驱动能力证明价值再逐步重构旧技能。GAS框架提供了一套强大、优雅的解决方案来处理游戏中最复杂的技能和状态系统。它的学习曲线确实存在但一旦掌握你将获得无与伦比的开发效率、可维护性和扩展性。它迫使你以数据驱动的方式思考问题这本身就是一次宝贵的设计能力提升。记住框架是工具理解其思想比死记硬背API更重要。在实际项目中你很可能需要根据具体需求对框架进行扩展或修改但只要你抓住了“属性-标签-效果-技能”这个核心链条任何调整都将有迹可循。