在游戏开发中,技能管理系统是一个关键组件,它控制了技能的创建、释放以及回收。本文将介绍一个基于 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);
}
}
// 其他。。。。。。。
}
}