【Unity】动作游戏开发实战详细分析-06-技能系统设计
基本思想
不同的技能可以设计为技能模版,当角色释放技能时,会通过模版ID将它进行实例化,这个实例技能类可以是一个挂载的MonoBehaviour组件或者通过上下文传入的独立对象
其次,考虑技能是否共用的问题,如果一个技能,玩家和敌人都可以使用,是否需要设计更为严谨的上下文接口?在动作游戏中,真正需要共用技能的情况相对比价多见。
系统设计
我们首先设计技能基本接口
它包含了:
- 属性-技能ID
- 方法-序列化恢复
- 方法-执行
public interface ISkillBase
{
int ID { get; }//技能ID
void SerializeRecovery(SkillContext skillContext);//序列化恢复
void Execute(Action onFinished);//技能执行
}
其中序列化恢复需要传入技能上下文,它是一个结构体,定义如下,它存储了该技能的技能释放者以及使用按键
public struct SkillContext
{
public GameObject GameObject { get; set; }//技能释放者引用
public KeyCode TriggerKey { get; set; }
}
然后,我们继续设计技能接口
新增接口包含:
- 模版注册回调函数
- 模版注销回调函数
- 技能实例化
public interface ISkill : ISkillBase
{
void OnTemplateRegist();//模板注册回调
void OnTemplateUnregist();//模板反注册回调
ISkillBase Instantiate(SkillContext skillContext);//技能对象实例化
}
最后,定义我们的抽象类技能模版
它包含基本属性和字段
属性:ID
这里有一点需要注意,Skill实现了ISkill接口,它就会有一个公开的ID,这个ID和现在我们定义的ID不是一个东西。其次,模版类中的东西均是不可访问,对技能的访问只能通过ISkill接口进行访问,这里使用了类似的技术使的所有东西被封闭封装
using System;
namespace ACTBook
{
public abstract class Skill : ISkill, ICloneable
{
protected abstract int ID { get; }
protected SkillContext mSkillContext;
protected abstract void Execute(Action onFinished);
protected virtual void OnTemplateRegist()
{
}
protected virtual void OnTemplateUnregist()
{
}
protected virtual void OnInstantiate()
{
}
protected virtual void SerializeRecovery(SkillContext skillContext)
{
}
protected virtual ISkillBase SkillInstantiate(SkillContext skillContext)
{
var instantiateSkill = Clone() as Skill;
instantiateSkill.mSkillContext = skillContext;
instantiateSkill.OnInstantiate();
return instantiateSkill;
}
#region --- ISkillBase Members ---
int ISkillBase.ID { get { return this.ID; } }
void ISkillBase.Execute(Action onFinished)
{
this.Execute(onFinished);
}
ISkillBase ISkill.Instantiate(SkillContext skillContext)
{
return SkillInstantiate(skillContext);
}
void ISkill.OnTemplateRegist()
{
OnTemplateRegist();
}
void ISkill.OnTemplateUnregist()
{
OnTemplateUnregist();
}
void ISkillBase.SerializeRecovery(SkillContext skillContext)
{
SerializeRecovery(skillContext);
}
#endregion
#region --- ICloneable Members ---
public object Clone()
{
return base.MemberwiseClone();
}
#endregion
}
}
仅仅这些,我们还无法实例化技能,我们还需要一个技能管理类
它是一个单例,并且作为一个可用组件
通过技能模版字典存储所有的基本技能模版,通过公共接口对公共技能进行访问获取,这样的设计使的所有的技能都只需要实例化一次,因为它是共用的。
public class SkillManager : MonoBehaviour
{
static bool mIsDestroying;
static SkillManager mInstance;//单例对象
public static SkillManager Instance
{
get
{
if (mIsDestroying) return null;
if (mInstance == null)
{
mInstance = new GameObject("[SkillManager]").AddComponent<SkillManager>();
DontDestroyOnLoad(mInstance.gameObject);
}
return mInstance;
}
}
Dictionary<int, ISkill> mSkillTemplateDict;//技能模板字典
void Awake()
{
mSkillTemplateDict = new Dictionary<int, ISkill>(10);
}
void OnDestroy()
{
mIsDestroying = true;//单例处理
}
public void RegistToTemplate(ISkill skill)//模板注册
{
mSkillTemplateDict.Add(skill.ID, skill);
}
public bool UnregistFromTemplate(int skillID)//模板反注册
{
return mSkillTemplateDict.Remove(skillID);
}
public ISkillBase InstantiateSkill(int skillID, SkillContext skillContext)//技能实例化
{
return mSkillTemplateDict[skillID].Instantiate(skillContext);
}
}
然后我们最后只需要定义一个技能初始化的类,来对我们自己个性化的技能进行模版注册即可
并使用[DefaultExecutionOrder(-100)]
来让它的执行顺序优先,防止出现空异常
[DefaultExecutionOrder(-100)]
public class SkillManagerInitialization : MonoBehaviour
{
void Awake()
{
SkillManager.Instance.RegistToTemplate(new PlayerSkill_Roll());
SkillManager.Instance.RegistToTemplate(new PlayerSkill_SkillAttack1());
SkillManager.Instance.RegistToTemplate(new PlayerSkill_SkillAttack2());
}
}