项目已经被解散了,但是项目中实现的技能系统真的是一套非常优秀的系统,可以脱离配置表,实现高度灵活配置,只要把基本的行为实现了,就可以让策划自由组合,设计出不同的技能,完全不用程序配合,这里技能系统大概实现一遍,子弹系统和buff系统大同小异,只是事件触发的时机不一样
照例先看技能编辑器效果图
一、设计原理
unity的动画系统在播放的时候在指定的时间触发一些指定的事件,再由这些事件购成整个技能
二、实现过程
首先我们新建一个Skill.cs,让其继承ScriptableObject,这样可以把技能数据保存成asset文件
具体代码如下
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class Skill : ScriptableObject
{
public Skill() :
base()
{
}
public int skillId; //技能id
public string desc = ""; //技能描述
[HideInInspector]
public CenterController centerController;
public List activeEvent = new List(); //技能激活事件
public List animaEvent = new List(); //技能的动画事件
private bool _isExecute = false; //是否在执行
//使用技能
public void Use()
{
//每次使用完都要重置一次
Reset();
_isExecute = true;
for (int i = 0; i < activeEvent.Count; ++i)
{
activeEvent[i].OwerSkill = this;
activeEvent[i].Execute();
}
for (int i = 0; i < animaEvent.Count; ++i)
{
animaEvent[i].OwerSkill = this;
}
}
//更新action的Update逻辑
public void Update()
{
if (_isExecute == false)
{
return;
}
if (centerController.statusController.IsSameStatus())
{
for (int i = 0; i < animaEvent.Count; ++i)
{
if(animaEvent[i].isTrigger)
{
continue;
}
//动画播放到指定的时间,触发事件
if (centerController.statusController.GetNormalizedTime() >= animaEvent[i].normalTime)
{
animaEvent[i].Execute();
}
}
for (int i = 0; i < activeEvent.Count; ++i)
{
if (activeEvent[i].isTrigger)
{
activeEvent[i].Update();
}
}
for (int i = 0; i < animaEvent.Count; ++i)
{
if (animaEvent[i].isTrigger)
{
animaEvent[i].Update();
}
}
}
}
//更新action的FixedUpdate逻辑
public void FixedUpdate()
{
if (_isExecute == false)
{
return;
}
if (centerController.statusController.IsSameStatus())
{
for (int i = 0; i < activeEvent.Count; ++i)
{
if (activeEvent[i].isTrigger)
{
activeEvent[i].FixedUpdate();
}
}
for (int i = 0; i < animaEvent.Count; ++i)
{
if (animaEvent[i].isTrigger)
{
animaEvent[i].FixedUpdate();
}
}
}
}
//正常使用完成
public void Finish()
{
_isExecute = false;
for (int i = 0; i < activeEvent.Count; ++i)
{
activeEvent[i].Finish();
}
for (int i = 0; i < animaEvent.Count; ++i)
{
animaEvent[i].Finish();
}
}
//重置技能数据
public void Reset()
{
_isExecute = false;
for (int i = 0; i < activeEvent.Count; ++i)
{
activeEvent[i].isTrigger = false;
activeEvent[i].Reset();
}
for (int i = 0; i < animaEvent.Count; ++i)
{
animaEvent[i].isTrigger = false;
animaEvent[i].Reset();
}
}
//被打断
public void Interrupt()
{
Finish();
}
}
同理,我们新建一个SkillEvent.cs,具体代码如下
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SkillEvent : ScriptableObject
{
public SkillEvent()
:base()
{
}
public bool isTrigger = false; //是否已经被触发了
public List skillActions = new List(); //action列表
protected Skill _skill; //拥有该事件的技能
public Skill OwerSkill
{
set { _skill = value; }
}
//执行
public virtual void Execute()
{
isTrigger = true;
for (int i = 0; i < skillActions.Count; ++i)
{
skillActions[i].OwerSkill = _skill;
skillActions[i].Execute();
}
}
//Update由skill调用
public virtual void Update()
{
for (int i = 0; i < skillActions.Count; ++i)
{
if (skillActions[i].isDurative)
{
skillActions[i].Update();
}
}
}
//FixedUpdate由skill调用
public virtual void FixedUpdate()
{
for (int i = 0; i < skillActions.Count; ++i)
{
if (skillActions[i].isDurative)
{
skillActions[i].FixedUpdate();
}
}
}
//完成,把所有的技能action都执行完毕
public virtual void Finish()
{
for (int i = 0; i < skillActions.Count; ++i)
{
skillActions[i].Finish();
}
}
//重置数据
public virtual void Reset()
{
for (int i = 0; i < skillActions.Count; ++i)
{
skillActions[i].Reset();
}
}
}
具体代码如下
ActiveEvent.cs
public class ActiveEvent : SkillEvent
{
//激活事件,技能激活马上执行
public ActiveEvent():
base()
{
}
}
AnimaEvent.cs
public class AnimaEvent: SkillEvent
{
//动画事件,动画执行到指定的时间再执行
public AnimaEvent()
: base()
{
}
public float normalTime = 0.0f; //触发的时间
}
具体代码如下
SkillAction.cs
using UnityEngine;
using System.Collections;
public class SkillAction : ScriptableObject
{
public bool isDurative = false; //该action是否是技续性的
protected Skill _skill; //拥有该action的技能
public Skill OwerSkill
{
set { _skill = value; }
}
public virtual void Execute()
{
}
public virtual void Finish()
{
}
public virtual void Update()
{
}
public virtual void FixedUpdate()
{
}
public virtual void Reset()
{
}
}
具体代码如下
PlayAnimator.cs
using UnityEngine;
using System.Collections;
public class PlayAnimator : SkillAction
{
public PlayAnimator()
:base()
{
}
//要播放的动画
public Status name = Status.Idle;
public override void Execute()
{
_skill.centerController.statusController.Play(name);
}
}
FinishSkill.cs
using System.Collections;
public class FinishSkill : SkillAction
{
public FinishSkill():
base()
{
}
//动画播放的时间
public float normaledTime = 1.0f;
private Task _waitingAnimatorStop = null;
public override void Execute()
{
if (_waitingAnimatorStop != null)
{
_waitingAnimatorStop.Stop();
}
_waitingAnimatorStop = new Task(WaitingAnimatorStop());
}
private IEnumerator WaitingAnimatorStop()
{
while (true)
{
if (_skill.centerController.statusController.GetNormalizedTime() >= normaledTime)
{
EndSkill();
break;
}
yield return 0;
}
yield return 0;
}
private void EndSkill()
{
if (_waitingAnimatorStop != null)
{
_waitingAnimatorStop.Stop();
_waitingAnimatorStop = null;
}
_skill.Finish();
//放完技能自动回到idle
_skill.centerController.statusController.Play(Status.Idle);
}
public override void Reset()
{
if (_waitingAnimatorStop != null)
{
_waitingAnimatorStop.Stop();
_waitingAnimatorStop = null;
}
}
}
最后我们再回到技能编辑器
我们的技能编辑器用一个树形插件,叫treeviewcontrol,但是要修改一下它的源码,这里不细说,最后会附上源码,你也可以不用它,编辑器主要是为了方便我们创建技能数据,你用其他的也可以,只要觉得方便就行了
技能编辑器主要是用来创建一些asset文件,然后把技能数据保存进去
具体代码如下
SkillEditor.cs
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.Reflection;
using System;
using System.IO;
using System.Collections.Generic;
public enum ItemType
{
None,
Root,
SkillEvent,
SkillAction
}
public enum SkillEventType
{
ActiveEvent,
AnimaEvent
}
public class SkillBase
{
public ItemType type = ItemType.None;
public string resPath = "";
public int skillId = 0;
}
public class SkillEditor : EditorWindow
{
static TreeViewControl m_treeViewControl = null;
static TreeViewItem _root = null;
static TreeViewItem _curItem = null;
static string FixPath = "Assets/Resources/FightData/Skill/"; //技能数据的位置
static string ResPath = "FightData/Skill/";
static List _skillActionName = new List();
static Dictionary _skillDic = new Dictionary();
[MenuItem("GameEditor/Skill Editor")]
public static void ShowSkillTreeViewPanel()
{
_skillDic.Clear();
GetSkillActionName();
GetSkillData();
CreateTreeView();
RefreshPanel();
}
static SkillEditor m_instance = null;
public static SkillEditor GetPanel()
{
if (null == m_instance)
{
m_instance = EditorWindow.GetWindow(false, "技能编辑器", false);
}
return m_instance;
}
public static void RefreshPanel()
{
SkillEditor panel = GetPanel();
panel.Repaint();
}
static void CreateTreeView()
{
m_treeViewControl = TreeViewInspector.AddTreeView();
m_treeViewControl.DisplayInInspector = false;
m_treeViewControl.DisplayOnGame = false;
m_treeViewControl.DisplayOnScene = false;
m_treeViewControl.X = 600;
m_treeViewControl.Y = 500;
_root = m_treeViewControl.RootItem;
_root.Header = "所有技能";
SkillBase data1 = new SkillBase();
data1.type = ItemType.None;
_root.DataContext = data1;
_curItem = _root;
AddEvents(_root);
CreateSkillItem();
}
static void AddEvents(TreeViewItem item)
{
AddHandlerEvent(out item.Selected);
}
public static void Handler(object sender, System.EventArgs args)
{
_curItem = sender as TreeViewItem;
Selection.activeObject = Resources.Load((_curItem.DataContext as SkillBase).resPath);
}
static void AddHandlerEvent(out System.EventHandler handler)
{
handler = new System.EventHandler(Handler);
}
void OnEnable()
{
wantsMouseMove = true;
}
int skillId = 0; //技能id
int selectIdx = 0; //选择的事件
void OnGUI()
{
if (null == m_treeViewControl)
{
return;
}
if (_curItem == null)
{
return;
}
wantsMouseMove = true;
if (null != Event.current &&
Event.current.type == EventType.MouseMove)
{
Repaint();
}
m_treeViewControl.DisplayTreeView(TreeViewControl.DisplayTypes.USE_SCROLL_VIEW);
if ((_curItem.DataContext as SkillBase).type == ItemType.None)
{
skillId = EditorGUILayout.IntField("技能id:",skillId);
GUILayout.BeginVertical();
if (GUILayout.Button("创建技能"))
{
AddSkill(skillId);
}
GUILayout.EndVertical();
}
else if ((_curItem.DataContext as SkillBase).type == ItemType.Root)
{
GUILayout.BeginHorizontal();
string[] list = new string[] { "-------请选择------", SkillEventType.AnimaEvent.ToString() };
selectIdx = EditorGUILayout.Popup("选择事件", selectIdx, list);
if (GUILayout.Button("添加事件"))
{
AddSkillEventNode(_curItem, list[selectIdx]);
}
GUILayout.EndHorizontal();
}
else if ((_curItem.DataContext as SkillBase).type == ItemType.SkillEvent)
{
GUILayout.BeginHorizontal();
List list = new List();
list.Add("-------请选择------");
list.AddRange(_skillActionName);
selectIdx = EditorGUILayout.Popup("选择行为", selectIdx, list.ToArray());
if (GUILayout.Button("添加行为"))
{
AddSkillAction(_curItem, list[selectIdx]);
}
GUILayout.EndHorizontal();
}
}
///
/// 添加技能
///
/// 技能id
/// 是否创建新的资源
static TreeViewItem AddSkill(int newSkillId, bool isCreateAsset = true)
{
Skill skill = ScriptableObject.CreateInstance();
_skillDic[skill.skillId] = skill;
skill.skillId = newSkillId;
if (isCreateAsset)
{
AssetEditor.CreateAsset(skill, FixPath + newSkillId, "Skill");
}
TreeViewItem skillItem = _root.AddItem(newSkillId.ToString());
SkillBase data = new SkillBase();
data.type = ItemType.Root;
data.resPath = ResPath + newSkillId + "/Skill";
data.skillId = newSkillId;
skillItem.DataContext = data;
AddEvents(skillItem);
if (isCreateAsset)
{
TreeViewItem evtItem = AddSkillEventNode(skillItem, SkillEventType.ActiveEvent.ToString());
if (evtItem != null)
{
AddSkillAction(evtItem, "PlayAnimator");
AddSkillAction(evtItem, "FinishSkill");
}
AddSkillEventNode(skillItem, SkillEventType.AnimaEvent.ToString());
}
return skillItem;
}
///
/// 添加事件
///
/// 父节点
/// 事件名
/// 是否创建新的资源
///
static TreeViewItem AddSkillEventNode(TreeViewItem item, string name, bool isCreateAsset = true)
{
if (name == String.Empty)
{
return null;
}
Skill skill = RegetditSkill(item);
if (skill == null)
{
return null;
}
string evtName = name;
Assembly ass = typeof(SkillEvent).Assembly;
string[] nameList = name.Split('_');
System.Type type = ass.GetType(nameList[0]);
SkillEvent evt = System.Activator.CreateInstance(type) as SkillEvent;
SkillBase evtData = new SkillBase();
if (evt is ActiveEvent)
{
if (isCreateAsset)
{
skill.activeEvent.Add(evt as ActiveEvent);
}
}
else
{
if (skill.animaEvent.Count > 0)
{
evtName = name + "_" + skill.animaEvent.Count;
}
if (isCreateAsset)
{
skill.animaEvent.Add(evt as AnimaEvent);
}
}
if (isCreateAsset)
{
AssetEditor.CreateAsset(evt, FixPath + skill.skillId + "/" + evtName, evtName);
}
else
{
evtName = name;
}
TreeViewItem evtItem = item.AddItem(evtName);
evtData.type = ItemType.SkillEvent;
evtData.skillId = skill.skillId;
evtData.resPath = ResPath + skill.skillId + "/" + evtName + "/" + evtName;
evtItem.DataContext = evtData;
AddEvents(evtItem);
EditorUtility.SetDirty(skill);
return evtItem;
}
///
/// 添加技能action
///
/// 父节点
/// action名
/// 是否创建新资源
static void AddSkillAction(TreeViewItem item, string name, bool isCreateAsset = true)
{
SkillBase data = item.DataContext as SkillBase;
SkillBase actData = new SkillBase();
Skill skill = RegetditSkill(item);
SkillEvent evt = Resources.Load(data.resPath) as SkillEvent;
if (skill == null || evt == null)
{
return;
}
Assembly ass = typeof(SkillAction).Assembly;
string[] nameList = name.Split('_');
System.Type type = ass.GetType(nameList[0]);
SkillAction act = System.Activator.CreateInstance(type) as SkillAction;
string actName = name;
int num = 0;
foreach (var act2 in evt.skillActions)
{
if (act2.name.StartsWith(name))
{
num++;
}
}
if (num > 0)
{
actName = actName + "_" + num;
}
if (isCreateAsset)
{
evt.skillActions.Add(act);
AssetEditor.CreateAsset(act, FixPath + skill.skillId + "/" + evt.name + "/SkillAction", actName);
}
else
{
actName = name;
}
TreeViewItem evtItem = item.AddItem(actName);
actData.type = ItemType.SkillAction;
actData.skillId = skill.skillId;
actData.resPath = ResPath + skill.skillId + "/" + evt.name + "/SkillAction/" + actName;
evtItem.DataContext = actData;
AddEvents(evtItem);
EditorUtility.SetDirty(evt);
EditorUtility.SetDirty(skill);
}
void OnDestroy()
{
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
static Skill RegetditSkill(TreeViewItem item)
{
SkillBase data = item.DataContext as SkillBase;
Skill skill = null;
if (_skillDic.ContainsKey(data.skillId))
{
skill = _skillDic[data.skillId];
}
else
{
skill = Resources.Load(data.resPath) as Skill;
if (skill != null)
{
_skillDic[data.skillId] = skill;
}
}
return skill;
}
static void CreateSkillItem()
{
List skills = new List();
foreach (var node in _skillDic)
{
if (node.Value != null)
{
skills.Add(node.Value);
}
}
for (int i = 0; i < skills.Count; ++i)
{
TreeViewItem skillItem = AddSkill(skills[i].skillId, false);
if (skillItem == null)
{
continue;
}
for (int n = 0; n < skills[i].activeEvent.Count; n++)
{
TreeViewItem evtItem = AddSkillEventNode(skillItem, skills[i].activeEvent[n].name, false);
for (int m = 0; m < skills[i].activeEvent[n].skillActions.Count; m++)
{
AddSkillAction(evtItem, skills[i].activeEvent[n].skillActions[m].name, false);
}
}
for (int n = 0; n < skills[i].animaEvent.Count; n++)
{
TreeViewItem evtItem = AddSkillEventNode(skillItem, skills[i].animaEvent[n].name, false);
for (int m = 0; m < skills[i].animaEvent[n].skillActions.Count; m++)
{
AddSkillAction(evtItem, skills[i].animaEvent[n].skillActions[m].name, false);
}
}
}
}
static void GetSkillData()
{
try
{
DirectoryInfo parentFolder = new DirectoryInfo(FixPath);
//遍历文件夹
foreach (DirectoryInfo folder in parentFolder.GetDirectories())
{
Skill skill = Resources.Load("FightData/Skill/" + folder.Name + "/Skill") as Skill;
if (skill == null)
{
continue;
}
_skillDic[skill.skillId] = skill;
}
}
catch(Exception e)
{
Debug.LogError(e);
}
}
static void GetSkillActionName()
{
try
{
_skillActionName.Clear();
int i = 0;
string _skillActionPath = "Assets/Script/Skill/SkillEvent/SkillAction/";
DirectoryInfo parentFolder = new DirectoryInfo(_skillActionPath);
//遍历文件夹
foreach (var file in parentFolder.GetFiles())
{
if (file.Extension != ".cs" || file.Name == "SkillAction.cs")
{
continue;
}
_skillActionName.Add(file.Name.Replace(file.Extension, ""));
++i;
}
}
catch (Exception e)
{
Debug.LogError(e);
}
}
}
最后附上整个工程源码 https://github.com/caolaoyao/SkillEditor