You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:11:03