共计 2659 个字符,预计需要花费 7 分钟才能阅读完成。
背景痛点:传统技能系统的困局
在早期的游戏开发中,我们常常会看到这样的代码:每个技能的逻辑直接写在角色的控制器里,条件判断堆满if-else,特效播放和伤害计算耦合在一起。这种实现方式会带来三个致命问题:

- 牵一发动全身:修改火球术的伤害计算公式可能需要重新测试整个角色系统
- 技能复用困难:同样的冰冻效果要在不同技能中重复编写
- 策划协作低效:数值调整必须依赖程序员重新编译
技术方案:解构与重组
1. 数据与逻辑分离:ScriptableObject 的妙用
Unity 的 ScriptableObject 让我们能将技能属性完全配置化。创建一个基础技能模板:
[CreateAssetMenu(fileName = "NewSkill", menuName = "Skills/SkillData")]
public class SkillData : ScriptableObject {
public string skillName;
public float cooldown;
public Sprite icon;
[SerializeReference] public List<SkillEffect> effects;
}
这样策划可以在不碰代码的情况下,通过 Inspector 窗口配置技能属性,甚至拖拽组合不同效果模块。
2. 事件总线:松耦合的神经系统
采用事件驱动架构解决技能触发问题:
// 事件定义
public struct SkillTriggerEvent {
public int skillID;
public Vector3 castPosition;
}
// 在角色输入系统中
EventBus.Publish(new SkillTriggerEvent { skillID = 101});
// 在技能系统中
EventBus.Subscribe<SkillTriggerEvent>(OnSkillTriggered);
这种设计让施法者和技能执行者完全解耦,特别适合处理骑乘状态、变身状态等特殊场景下的技能覆盖。
3. 模块化效果组件
定义技能效果的通用接口:
public interface ISkillEffect {void Apply(SkillContext context);
float GetDuration();}
// 示例:击退效果
[Serializable]
public class KnockbackEffect : ISkillEffect {
public float force = 5f;
public void Apply(SkillContext ctx) {ctx.target.GetComponent<Rigidbody>()
.AddForce(ctx.direction * force, ForceMode.Impulse);
}
}
代码实现:从抽象到具体
技能基类实现
public abstract class SkillBase : MonoBehaviour {[SerializeField] protected SkillData data;
protected float currentCooldown;
public bool CanCast() {return currentCooldown <= Mathf.Epsilon;}
public virtual void Cast() {if(!CanCast()) return;
currentCooldown = data.cooldown;
StartCoroutine(CooldownRoutine());
foreach(var effect in data.effects) {effect.Apply(CreateContext());
}
}
IEnumerator CooldownRoutine() {while(currentCooldown > 0) {
currentCooldown -= Time.deltaTime;
yield return null;
}
}
}
火球术具体实现
public class FireballSkill : SkillBase {[SerializeField] Projectile projectilePrefab;
[SerializeField] Transform spawnPoint;
protected override void Cast() {base.Cast();
var projectile = Instantiate(projectilePrefab,
spawnPoint.position,
spawnPoint.rotation);
projectile.Init(10f /*speed*/, OnHit);
}
void OnHit(Collider other) {// 处理命中逻辑}
}
性能优化:流畅体验的保障
对象池实战
public class ProjectilePool {private Queue<Projectile> pool = new Queue<Projectile>();
public Projectile Get(Vector3 position) {if(pool.Count > 0) {var obj = pool.Dequeue();
obj.gameObject.SetActive(true);
obj.transform.position = position;
return obj;
}
return Instantiate(prefab, position, Quaternion.identity);
}
public void Return(Projectile obj) {obj.gameObject.SetActive(false);
pool.Enqueue(obj);
}
}
避坑指南
- 技能 ID 管理:使用 GUID 或哈希值代替简单整数
- 网络同步:采用状态快照而非连续事件
- 打断机制:通过取消协程实现
private IEnumerator currentCastRoutine; public void Interrupt() {if(currentCastRoutine != null) {StopCoroutine(currentCastRoutine); currentCastRoutine = null; } }
扩展思考
如何设计支持以下进阶特性:
1. 技能连招组合(如:火球 + 冰箭 = 蒸汽爆炸)
2. 环境交互技能(对水面释放会变成冰霜路径)
3. 基于技能树的动态效果组合
这个框架已经帮我们解决了 70% 的共性问题,剩下的 30% 特殊需求可以通过扩展 SkillEffect 接口来实现。记住好的架构不是一次性设计出来的,而是在不断解决具体问题的过程中迭代完善的。
正文完
