Unity/C#可切换技能系统疑问:如何高效给玩家挂载技能?
问题描述
我正在Unity中使用C#开发一套可切换技能系统,以抽象类Ability作为基类,各具体技能继承该类实现独特效果。目前遇到的问题是,玩家选中技能后,无法高效地将技能挂载到玩家对象上。
现有基类代码如下:
public abstract class Ability : MonoBehaviour { public int id = -1; public string abilityName = string.Empty; public string description = string.Empty; enum AbilityState { Ready, Casting, Cooldown} AbilityState state = AbilityState.Ready; public float cooldown = 0f; public bool castDone = false; public bool castIFrames = false; public bool castLockMove = false; public void TryCast() { if (state == AbilityState.Ready) { StartCoroutine(Cast()); StartCoroutine(UseAbility()); } } IEnumerator Cast() { state = AbilityState.Casting; if (castLockMove) { //disable movement } if (castIFrames) { //disbale hit collider } yield return new WaitUntil(() => castDone = true); //make sure things are back on StartCoroutine(CooldDown()); } IEnumerator CooldDown() { state = AbilityState.Cooldown; yield return new WaitForSeconds(cooldown); state = AbilityState.Ready; } public abstract IEnumerator UseAbility(); public void RemoveThis() { Destroy(this); } }
具体技能实现示例:
public class Test_Dash : Ability { public Test_Dash() { id = 1; abilityName = "Dash"; description = "Quick move forwards"; cooldown = 4f; castIFrames = true; } float dashFactor = 10f; Vector2 direction; public override IEnumerator UseAbility() { castDone = false; yield return null; while (PlayerClass.playerObject.GetComponent<PlayerAndCamera>().moveInfo != Vector2.zero) { yield return null; } direction = PlayerClass.playerObject.GetComponent<PlayerAndCamera>().moveInfo; StartCoroutine(Dash()); yield break; } IEnumerator Dash() { PlayerClass.playerObject.GetComponent<Rigidbody2D>().AddForce(direction * dashFactor * 0.3f); yield return null; PlayerClass.playerObject.GetComponent<Rigidbody2D>().AddForce(direction * dashFactor * 0.9f); yield return null; PlayerClass.playerObject.GetComponent<Rigidbody2D>().AddForce(direction * dashFactor * 1f); castDone = true; } }
我原本计划用如下类管理技能:
public class Abilities { public Ability abl1; public Ability abl2; public Ability abl3; public Ability abl4; }
但不想为每个技能创建单独GameObject,也不想把所有技能都挂载到同一个GameObject上,这两种方式都过于繁琐且难以维护。请问有没有更优的实现方法?
优化实现方案
方案1:对象池+动态组件挂载
核心思路是用对象池缓存技能组件实例,切换时直接从池取出挂载到玩家对象,用完后放回池,避免频繁创建/销毁对象的性能开销。
代码实现
技能对象池工具类
public static class AbilityPool { private static Dictionary<Type, Stack<Ability>> _pool = new Dictionary<Type, Stack<Ability>>(); // 获取指定类型的技能组件 public static T Get<T>() where T : Ability { Type type = typeof(T); if (_pool.ContainsKey(type) && _pool[type].Count > 0) { var ability = _pool[type].Pop(); ability.gameObject.SetActive(true); return ability as T; } // 池为空时创建新实例 var newAbilityObj = new GameObject(typeof(T).Name); return newAbilityObj.AddComponent<T>(); } // 将技能组件放回池 public static void Return(Ability ability) { Type type = ability.GetType(); if (!_pool.ContainsKey(type)) { _pool[type] = new Stack<Ability>(); } // 重置技能状态 ability.castDone = true; ability.gameObject.SetActive(false); _pool[type].Push(ability); } }
玩家技能管理类
public class PlayerAbilityManager : MonoBehaviour { private Ability _currentAbility; // 切换到指定技能 public void SwitchToAbility<T>() where T : Ability { // 移除当前技能 if (_currentAbility != null) { AbilityPool.Return(_currentAbility); Destroy(_currentAbility.gameObject); _currentAbility = null; } // 挂载新技能 _currentAbility = AbilityPool.Get<T>(); _currentAbility.transform.SetParent(transform); _currentAbility.transform.localPosition = Vector3.zero; } // 触发当前技能 public void CastCurrentAbility() { _currentAbility?.TryCast(); } }
方案2:分离数据与逻辑,使用非MonoBehaviour基类
把技能逻辑从MonoBehaviour中剥离,基类改为普通C#类,由玩家对象的管理类统一处理协程和状态,彻底避免组件挂载/移除的操作。
代码实现
重构后的技能基类
public abstract class Ability { public int id = -1; public string abilityName = string.Empty; public string description = string.Empty; public float cooldown = 0f; public bool castIFrames = false; public bool castLockMove = false; protected enum AbilityState { Ready, Casting, Cooldown} protected AbilityState _state = AbilityState.Ready; protected bool _castDone = false; protected MonoBehaviour _owner; // 持有玩家的MonoBehaviour,用于启动协程 public Ability(MonoBehaviour owner) { _owner = owner; } public void TryCast() { if (_state == AbilityState.Ready) { _owner.StartCoroutine(CastCoroutine()); _owner.StartCoroutine(UseAbility()); } } private IEnumerator CastCoroutine() { _state = AbilityState.Casting; // 应用技能状态(锁定移动、无敌帧) if (castLockMove) { _owner.GetComponent<PlayerAndCamera>().LockMovement(true); } if (castIFrames) { _owner.GetComponent<PlayerHealth>().EnableIFrames(true); } yield return new WaitUntil(() => _castDone); // 恢复玩家状态 if (castLockMove) { _owner.GetComponent<PlayerAndCamera>().LockMovement(false); } if (castIFrames) { _owner.GetComponent<PlayerHealth>().EnableIFrames(false); } _owner.StartCoroutine(CooldownCoroutine()); } private IEnumerator CooldownCoroutine() { _state = AbilityState.Cooldown; yield return new WaitForSeconds(cooldown); _state = AbilityState.Ready; } public abstract IEnumerator UseAbility(); protected void SetCastDone() { _castDone = true; } }
玩家技能控制器
public class PlayerAbilityController : MonoBehaviour { private Ability _currentAbility; void Start() { // 初始化默认技能 SwitchToDash(); } public void SwitchToDash() { _currentAbility = new Test_Dash(this); } // 可扩展更多技能切换方法,如SwitchToFireball() void Update() { // 检测输入触发技能 if (Input.GetKeyDown(KeyCode.Space)) { _currentAbility?.TryCast(); } } }
方案3:ScriptableObject配置+动态实例化
用ScriptableObject存储技能的配置数据(冷却时间、名称等),玩家切换技能时,动态从预制体实例化技能组件并挂载到玩家对象,同时加载配置数据。
代码实现
技能数据配置类
[CreateAssetMenu(fileName = "New Ability Data", menuName = "Game/Ability Data")] public class AbilityData : ScriptableObject { public int id; public string abilityName; public string description; public float cooldown; public bool castIFrames; public bool castLockMove; public GameObject abilityPrefab; // 对应技能的MonoBehaviour预制体 }
玩家技能管理类
public class PlayerAbilityManager : MonoBehaviour { private Ability _currentAbility; // 根据配置切换技能 public void SwitchAbility(AbilityData data) { // 移除当前技能 if (_currentAbility != null) { Destroy(_currentAbility); } // 实例化并挂载新技能 _currentAbility = Instantiate(data.abilityPrefab, transform).GetComponent<Ability>(); // 加载配置数据 _currentAbility.id = data.id; _currentAbility.abilityName = data.abilityName; _currentAbility.description = data.description; _currentAbility.cooldown = data.cooldown; _currentAbility.castIFrames = data.castIFrames; _currentAbility.castLockMove = data.castLockMove; } public void CastAbility() { _currentAbility?.TryCast(); } }
内容的提问来源于stack exchange,提问作者RedPandas
相关产品推荐
相关产品推荐

