Unity 技术博客:构建高效的技能管理系统

在游戏开发中,技能管理系统是一个关键组件,它控制了技能的创建、释放以及回收。本文将介绍一个基于 Unity 的技能管理系统,包括技能数据、技能释放器、技能效果、选区管理以及运动曲线的实现。 以及对于优化的想法记录等等。

对于这个系统主要分为:技能管理,数据,效果,选区,运动(事件,工具,配置)等等

技能管理器 SkillManager

SkillManager 负责管理技能的添加、移除以及使用。在系统设计中,我们使用了队列来存储即将释放的技能,同时通过字典存储技能数据。

public class SkillManager
{
    public List<SkillReleaser> CurUseSkill;
    private Dictionary<int, SkillData> skillDataDictionary;
    private Queue<SkillReleaser> skillQueue;

    public SkillManager()
    {
        skillDataDictionary = new Dictionary<int, SkillData>();
        CurUseSkill = new List<SkillReleaser>();
        skillQueue = new Queue<SkillReleaser>();
    }

    public void AddSkillID(int skillID)
    {
        if (!skillDataDictionary.ContainsKey(skillID))
        {
            SkillData skillData = new SkillData { SkillID = skillID };
            skillDataDictionary.Add(skillData.SkillID, skillData);
        }
    }

    public void RemoveSkillID(int skillID)
    {
        if (skillDataDictionary.ContainsKey(skillID))
        {
            skillDataDictionary.Remove(skillID);
        }
    }

    public void UseSkill(int skillID)
    {
        if (skillDataDictionary.ContainsKey(skillID))
        {
            SkillData skillData = skillDataDictionary[skillID];
            SkillReleaser skillReleaser = new SkillReleaser(skillData);
            skillQueue.Enqueue(skillReleaser);
        }
    }

    public void Update()
    {
        if (skillQueue.Count > 0)
        {
            SkillReleaser skillReleaser = skillQueue.Dequeue();
            if (skillReleaser.CanBeReleased)
            {
                skillReleaser.Release();
                CurUseSkill.Add(skillReleaser);
            }
        }
        
        for (int i = CurUseSkill.Count - 1; i >= 0; i--)
        {
            SkillReleaser releaser = CurUseSkill[i];
            if (!releaser.IsTheReleaseCompleted())
                CurUseSkill[i].Update();
            else
                CurUseSkill.Remove(releaser);
        }
    }
}

技能数据 SkillData

这个可以进行拆分:分为SkillInfo 和 SkillData

SkillData 类存储了技能的各种属性,包括伤害、攻击类型、效果、持续时间等。

这个可以后期读取表格创建数据或者引用数据

也可以继承ScriptableObject:手动创建数据使用

namespace Skill
{
    
    [Serializable]
    public class SkillData
    {
        public int SkillID;
        public int Damage;
        public AttackType AttackType;
        public ConstituencyForm constituencyForm;
        public List<SkillEffect> skillEffects;
        public float AngleAttack;
        public AttackTag[] attackTag;
        public GameObject SkillReleaser;
        public Transform[] Targets;
        public float AttackRadius;
        //-- 可以单独设置数据
        public float MoveTime;
        public float MoveSpeed;
        public MotionCurve motionCurve;
        //-- end
        public float Duration;
        public float ComboCount;
        public float SkillCD;        
        public SkillData()
        {
        }
    }

    public enum AttackType
    {
        Individual = 1,
        AOE = 2

    }

    public enum AttackTag
    {
        Player,
        Enemy
    }

    /// <summary>
    /// 选取类型
    /// </summary>
    public enum ConstituencyForm
    {
        ClickAhead,
        Sector,
        Circular,
        Square,
    }

    public enum SkiiType
    {


    }

    public enum SkillEffect
    {
        DMG,//- 伤害数值 持续 掉血
        KB,//- 击退效果 (Knockback)
        Freeze,// - 冰冻效果
        Ignite,// - 着火效果
    }
    //DMG - 伤害数值
    //KB - 击退效果(Knockback)
    //Stun - 击晕效果
    //Freeze - 冰冻效果
    //Ignite - 着火效果
    //Poison - 中毒效果
    //Slow - 减速效果
    //Heal - 恢复效果
    //Shield - 护盾效果
    //Cleanse - 消除效果
    //Counter - 反击效果
    //Heal Reduction - 治疗削减效果

    public enum MotionCurve
    {
        RectilinearMotion,
        CurvedMotion,
    }
}

技能释放器 SkillReleaser

SkillReleaser 负责技能的初始化、更新以及释放操作。

namespace Skill
{
    public class SkillReleaser
    {
        //-- 初始化效果
        //--选择目标
        //--技能位移方式
        //释放技能
        public enum SkillReleaseStatus
        {
            WaitingForRelease,
            Displacement,
            Releasing,
            ReleaseEnd
        }
        private SkillData SkillData;
        private SelectionBase releaseConstituency;
        private List<SkillEffectBase> SkillEffects;
        private MoveBase MoveBase;
        private float LastUseSkillTime;
        private bool skillReleased;
        public SkillReleaser(SkillData skillData)
        {
            this.SkillData = skillData;
            Initialization();
        }
        public void Initialization()
        {
            SkillEffects = new List<SkillEffectBase>();
            skillReleased = false;
            LastUseSkillTime = Time.time- SkillData.SkillCD;
            releaseConstituency = SelectionManager.Instance.GetMappingMethod<SelectionBase>(SkillData.constituencyForm);
            releaseConstituency.Realization(SkillData);
            MoveBase = MotionCurveManager.Instance.GetMappingMethod<MoveBase>(SkillData.motionCurve);
            foreach (var SkillEffect in SkillData.skillEffects)
            {
                SkillEffectBase SkillEffectClass = SkillEffectManager.Instance.GetMappingMethod<SkillEffectBase>(SkillEffect);
                if (SkillEffects != null)
                {
                    SkillEffectClass.Realization((int)(SkillEffect), SkillData);
                    SkillEffects.Add(SkillEffectClass);
                }
            }           
            RefreshSelectionData();
            MoveBase.Realization(SkillData);
        }
        public void RefreshSelectionData()
        {
            SkillData.Targets = releaseConstituency.GetTargets();
        }
        public void Update()
        {
            if (!skillReleased) return;
            RefreshSelectionData();
            if (MoveBase.ISMoveEnd)
                foreach (var item in SkillEffects)
                    item?.Update();
            else
                MoveBase.Update();
        }
        public void Release()
        {
            skillReleased = true;
            LastUseSkillTime = Time.time;
        }
        public bool CanBeReleased { get { return !skillReleased && (Time.time - LastUseSkillTime) >= SkillData.SkillCD; } }
        public SkillData GetSkillData { get { return SkillData; } }
        public bool IsTheReleaseCompleted()
        {
            bool IsTheReleaseExit = false;
            foreach (var item in SkillEffects)
            {
                IsTheReleaseExit = item.IsExit;
            }
            return IsTheReleaseExit;
        }
    }

    //状态基础类
    public interface BasicSkillStatus
    {
        abstract void Realization(SkillData skillData);
        abstract void Update();
        abstract void Exit();
    }
}

技能效果 SkillEffect

技能效果类实现了不同的技能效果,如伤害、击退、冰冻等。

/// <summary>
/// 技能释放效果管理
/// </summary>
namespace Skill
{
    public class SkillEffectManager : DataRegistrationClass<SkillEffectManager>
    {
        public override void Info()
        {
            Register<DMG>(SkillEffect.DMG);
            Register<KB>(SkillEffect.KB);
            Register<Freeze>(SkillEffect.Freeze);
        }
    }

    public class SkillEffectBase : BasicSkillStatus
    {
        protected int SkillEffectID;
        protected SkillData SkillData;                
        protected float RefreshTime;
        protected float EndTime;
        public bool IsExit { get; set; }
        public void Realization(int EffectID, SkillData skilldata)
        {            
            this.SkillEffectID = EffectID;
            this.SkillData = skilldata;
            RefreshTime = Time.time;
            EndTime = Time.time + skilldata.Duration;
            Realization(skilldata);
        }

        //可以实现loop 效果 比如加几次血量
        //击退效果(Knockback) 等
        public virtual void Update() { }
        public virtual void Exit() { }
        public virtual void Realization(SkillData skillData) { }
    }
    
    //持续掉血 持续10秒 掉10次血
    public class DMG : SkillEffectBase
    {
        public override void Update()
        {            
            if (Time.time <= EndTime)
            {
                if (Time.time >= RefreshTime) 
                {
                    RefreshTime = Time.time + (SkillData.Duration / SkillData.ComboCount);
                    Debug.Log("掉血");
                }
            }
            else
            {
                Exit();
            }
        }

        public override void Exit()
        {
            IsExit = true;
            base.Exit();
            Debug.LogError("DMG-------------END");
        }
    }
    public class KB : SkillEffectBase
    {
        public override void Update()
        {

        }
    }
    public class Freeze : SkillEffectBase
    {
        public override void Update()
        {

        }
    }
    public class Ignite : SkillEffectBase
    {
        public override void Update()
        {

        }
    }
}

选区管理 SelectionManager

选区管理类负责技能作用范围的管理,实现了不同形状的选区,如扇形、圆形、方形等。

/// <summary>
///选区管理
/// </summary>
namespace Skill
{
    public class SelectionManager : DataRegistrationClass<SelectionManager>
    {
        public override void Info()
        {
            Register<ClickAhead>(ConstituencyForm.ClickAhead);
            Register<Sector>(ConstituencyForm.Sector);
            Register<Sector>(ConstituencyForm.Circular);
            Register<Square>(ConstituencyForm.Square);
        }
    }
    public abstract class SelectionBase : BasicSkillStatus
    {
        protected SkillData SkillData;
        protected Transform Releaser;
        protected LayerMask LayerMask;
        protected List<Transform> Targets;
        public virtual void Exit() { }

        public virtual void Realization(SkillData skillData)
        {
            Targets = new List<Transform>();
            this.SkillData = skillData;
            Releaser = skillData.SkillReleaser.transform;
            string[] masks = new string[skillData.attackTag.Length];
            for (int i = 0; i < skillData.attackTag.Length; i++)
            {
                masks[i] = skillData.attackTag[i].ToString();
            }
            LayerMask = LayerMask.GetMask(masks);            
        }

        public virtual void Update() { }
        public abstract Transform[] GetTargets();
    }
    public class ClickAhead : SelectionBase
    {
        public override Transform[] GetTargets()
        {
            List<Transform> trans = new List<Transform>();
            Collider[] colliders = Physics.OverlapSphere(Releaser.position, SkillData.AttackRadius, LayerMask);
            float minDistance = SkillData.AttackRadius;
            Transform target = null;
            foreach (Collider col in colliders)
            {
                float DisTarget = Vector3.Distance(Releaser.position, col.transform.position);
                if (DisTarget < minDistance)
                {
                    minDistance = DisTarget;
                    target = col.transform;
                }
            }
            return new Transform[] { target };
        }
    }
    public class Sector : SelectionBase
    {
        public override Transform[] GetTargets()
        {
            Targets.Clear();
            RaycastHit[] hits = Physics.SphereCastAll(Releaser.position, SkillData.AttackRadius, Releaser.transform.forward, 0f, LayerMask);
            foreach (RaycastHit hit in hits)
            {
                Transform target = hit.transform;
                Vector3 directionToTarget = (target.position - Releaser.position).normalized;
                Vector3 forwardDirection = Releaser.forward;
                float angle = Vector3.Angle(forwardDirection, directionToTarget);
                // 如果目标在攻击角度内
                if (angle <= SkillData.AngleAttack / 2f)
                {
                    if (!Targets.Contains(hit.collider.transform))
                        Targets.Add(hit.collider.transform);
                }
            }
            return Targets.ToArray();
        }
    }
    public class Square : SelectionBase
    {
        Vector3[] Squarepos;
        public override void Realization(SkillData skillData)
        {
            Squarepos = new Vector3[4];
            Vector3 topLeft = Releaser.position + Vector3.forward * skillData.AttackRadius / 2 + Vector3.left * skillData.AttackRadius / 2;
            Vector3 topRight = Releaser.position + Vector3.forward * skillData.AttackRadius / 2 + Vector3.right * skillData.AttackRadius / 2;
            Vector3 bottomLeft = Releaser.position + Vector3.back * skillData.AttackRadius / 2 + Vector3.left * skillData.AttackRadius / 2;
            Vector3 bottomRight = Releaser.position + Vector3.back * skillData.AttackRadius / 2 + Vector3.right * skillData.AttackRadius / 2;
            Squarepos[1] = topLeft;
            Squarepos[2] = topRight;
            Squarepos[3] = bottomLeft;
            Squarepos[4] = bottomRight;
            base.Realization(skillData);
        }
        public override Transform[] GetTargets()
        {
            Targets.Clear();
            Collider[] collidersInCircle = Physics.OverlapSphere(Releaser.position, SkillData.AttackRadius, LayerMask);
            foreach (Collider collider in collidersInCircle)
            {
                Vector3 playerPos = collider.transform.position;
                if (playerPos.x >= Squarepos[1].x && playerPos.x <= Squarepos[4].x &&
                    playerPos.z >= Squarepos[3].z && playerPos.z <= Squarepos[2].z)
                {
                    Targets.Add(collider.transform);
                }
            }
            return Targets.ToArray();
        }
    }
}

运动曲线 MotionCurve

运动曲线类实现了技能的运动轨迹,如直线运动和曲线运动。

/// <summary>
/// 运动曲线
/// </summary>
/// <typeparam name="T"></typeparam>
namespace Skill
{
    public class MoveBase : BasicSkillStatus
    {
        protected Transform EffectsTran;
        protected SkillData SkillData;
        protected float MoveTime;
        protected float StartTime;
        protected Vector3 StartPos;
        protected Vector3 EndPos;
        protected float Dis;
        protected bool IsMoveEnd;
        public virtual void Realization(SkillData skillData)
        {
            this.SkillData = skillData;
            MoveTime = SkillData.MoveTime;
            StartTime = Time.time;
            StartPos = SkillData.SkillReleaser.transform.position;
            EndPos = skillData.Targets[0].transform.position;
            Dis = Vector3.Distance(StartPos, EndPos);
            IsMoveEnd = false;
            if (EffectsTran == null)
                IsMoveEnd = true;
        }
        public virtual void Update()
        {            
            if (IsMoveEnd&& EffectsTran!=null) return;
            float timeElapsed = Time.time - StartTime;
            float t = timeElapsed * SkillData.MoveSpeed / Dis;
            if (t > 1)
            {
                Exit();
            }
            switch (SkillData.motionCurve)
            {
                case MotionCurve.RectilinearMotion:
                    EffectsTran.position = Vector3.Lerp(StartPos, EndPos, t);
                    break;
                case MotionCurve.CurvedMotion:
                    EffectsTran.position = CalculateBezierCurve(StartPos, EndPos, Vector3.up * 5f, t);
                    break;
            }   
        }
        public virtual void Exit()
        {
            IsMoveEnd = true;
        }
        public bool ISMoveEnd { get { return IsMoveEnd; } }
        public Vector3 CalculateBezierCurve(Vector3 p0, Vector3 p1, Vector3 p2, float t)
        {
            float u = 1 - t;
            float tt = t * t;
            float uu = u * u;
            Vector3 p = uu * p0;
            p += 2 * u * t * p1;
            p += tt * p2;
            return p;
        }
    }
    public class MotionCurveManager : DataRegistrationClass<MotionCurveManager>
    {
        public override void Info()
        {
            Register<RectilinearMotion>(MotionCurve.RectilinearMotion);
            Register<CurvedMotion>(MotionCurve.CurvedMotion);
        }
    }
    public class RectilinearMotion : MoveBase { }
    public class CurvedMotion : MoveBase { }
}

DataRegistrationClass 实现

DataRegistrationClass 负责注册和获取技能相关的各种类实例

public abstract class DataRegistrationClass<T> where T : new()
{
    private static T _instance;
    public static T Instance
    {
        get { if (_instance == null) _instance = new T(); return _instance; }
    }
    protected Dictionary<object, Func<object>> MethodMappingTable;
    public DataRegistrationClass()
    {
        MethodMappingTable = new Dictionary<object, Func<object>>();
        Info();
    }
    public abstract void Info();
    public void Register<T>(object arg) where T : new()
    {
        MethodMappingTable[arg] = () => new T();
    }
    public T GetMappingMethod<T>(object arg) where T : class
    {
        if (MethodMappingTable.ContainsKey(arg))
            return MethodMappingTable[arg]?.Invoke() as T;
        return default(T);
    }
}

这个技能管理系统已经具备了基本的功能,但还有一些可以进一步优化的地方,以提升其性能、可扩展性和可维护性等等 。。。。。 实现伪代码和建议

泛型改进

1. 泛型改进

为了使 DataRegistrationClass 更加通用和类型安全

public class DataRegistrationClass<TKey, TValue> where TValue : class
{
    private Dictionary<TKey, Func<TValue>> _mappingDictionary;

    public DataRegistrationClass()
    {
        _mappingDictionary = new Dictionary<TKey, Func<TValue>>();
    }

    public void Register<T>(TKey key) where T : TValue, new()
    {
        if (!_mappingDictionary.ContainsKey(key))
        {
            _mappingDictionary[key] = () => new T();
        }
    }

    public TValue GetMappingMethod(TKey key)
    {
        if (_mappingDictionary.TryGetValue(key, out var createInstance))
        {
            return createInstance();
        }
        throw new KeyNotFoundException($"Key {key} not found in the registration class.");
    }
}

2. 缓存机制

可以引入对象池或缓存机制。例如,使用对象池来管理技能释放器和技能效果实例。

伪效果实现

public class ObjectPool<T> where T : new()
{
    private Stack<T> _pool;

    public ObjectPool(int initialCapacity = 10)
    {
        _pool = new Stack<T>(initialCapacity);
        for (int i = 0; i < initialCapacity; i++)
        {
            _pool.Push(new T());
        }
    }

    public T Get()
    {
        return _pool.Count > 0 ? _pool.Pop() : new T();
    }

    public void Release(T obj)
    {
        _pool.Push(obj);
    }
}

3. 事件系统

使用事件系统来处理技能的释放和效果,可以使代码更解耦。

public class SkillManager
{
    public event Action<SkillReleaser> OnSkillRelease;

    // 其他......

    public void Update()
    {
        if (skillQueue.Count > 0)
        {
            var skillReleaser = skillQueue.Dequeue();
            if (skillReleaser.CanBeReleased)
            {
                skillReleaser.Release();
                OnSkillRelease?.Invoke(skillReleaser);
                CurUseSkill.Add(skillReleaser);
            }
        }

        // 其他。。。。。。。
    }
}

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity技能系统架构设计可以根据具体需求和游戏类型的不同而有所差异,但一般可以包括以下几个关键组件和设计思路: 1. 技能管理器(Skill Manager):负责管理所有技能的创建、初始化、释放和销毁等操作。它可以是一个单例对象,用于全局管理技能系统。 2. 技能数据表(Skill Data Table):用于存储技能的配置数据,包括技能的名称、图标、描述、冷却时间、消耗资源等信息。可以使用ScriptableObject或者自定义的数据结构来实现。 3. 技能触发器(Skill Trigger):用于检测技能的触发条件,例如按下特定按键、达到一定的角色状态等。可以使用Unity的Input系统或者自定义的触发器组件来实现。 4. 技能效果(Skill Effect):定义技能的具体效果,例如造成伤害、治疗、状态改变等。可以使用脚本组件或者自定义的效果组件来实现。 5. 技能动画(Skill Animation):用于播放技能相关的动画效果,例如释放技能时的特效、角色动作等。可以使用Animator组件或者自定义的动画控制器来实现。 6. 技能UI(Skill UI):用于显示玩家当前可用的技能列表,并提供交互操作。可以使用UGUI或者自定义的UI组件来实现。 7. 技能升级系统(Skill Upgrade System):用于实现技能的升级和进阶功能,例如提升技能的威力、减少冷却时间等。可以使用经验值、技能点或者其他资源来实现。 8. 技能CD管理器(Skill Cooldown Manager):用于管理技能的冷却时间,防止玩家连续使用同一技能。可以使用计时器或者自定义的冷却管理器来实现。 9. 技能触发事件(Skill Event):用于处理技能触发后的逻辑,例如播放音效、触发特殊效果等。可以使用事件系统或者自定义的消息机制来实现。 以上是一个基本的Unity技能系统架构设计,具体实现方式可以根据游戏需求和开发团队的技术栈进行调整和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值