大家先看一下 最后可以实现的效果
极简版技能编辑器效果
注:代码里有详细讲解 有我没有描述清楚的欢迎在下方留言
实现这种技能编辑器需要哪些步骤???
1.Editor文件夹的使用及创建
1.1、两个脚本文件
(1)、用于创建窗口 编辑人物Model 和 技能名称的脚本
(2)、用于编辑人物技能组件的显示存储脚本
2.Scripts脚本的编写
2.1、两个必要使用的脚本文件
(1)、人物实体与组件的绑定使用脚本
(2)、创建人物技能组件所使用的基类脚本
我们大体的说了一下实现以上功能的需求 下面我们用代码与讲解详细的分析一下它的步骤
写代码我们要分清楚主次与流程 知道了主次就知道先去做哪些事情也有了一个合理的有序的思路
1.这里呢我们先去写技能基类与玩家实体
技能基类代码示例(请结合后面的玩家实体代码观看):
using Newtonsoft.Json;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 组件类型
/// </summary>
public enum TypeInfo
{
None,
动画,
声音,
特效,
}
public class PlayerInfo
{
public string skillName;//技能名称
public string icon;//技能图片
public List<SKillBase> skills = new List<SKillBase>();//存放技能的库
}
/// <summary>
/// 技能基类
/// </summary>
public class SKillBase
{
//[JsonIgnore] 忽略序列化,就是当字段在序列化时,被[JsonIgnore]标记了的字段将被忽略序列化
public string clipPath; //元件路径
public float delayTime; //延迟时间
[JsonIgnore] public PlayerEntity playerEntity;//玩家实体
public TypeInfo type;//组件类型
//虚方法 --初始化 --播放 --暂停
public virtual void Init(PlayerEntity player)
{
//初始化时将其与玩家实体进行绑定
playerEntity = player;
}
public virtual void Play()
{
}
public virtual void Stop()
{
}
}
/// <summary>
/// 动画组件 Animator的更改替换
/// </summary>
public class Skill_Anim : SKillBase
{
//动画元件
[JsonIgnore] public AnimationClip clip;
public override void Init(PlayerEntity player)
{
base.Init(player);
//拿到加载对应路径得到的动画元件
clip = Resources.Load<AnimationClip>("Anim/" + clipPath);
}
/// <summary>
/// 切换并播放动画的原理
/// </summary>
public override void Play()
{
base.Play();
playerEntity.m_animOver["Start"] = clip;
playerEntity.m_anim.SetTrigger("Play");
}
/// <summary>
/// 停止动画的原理
/// </summary>
public override void Stop()
{
base.Stop();
playerEntity.m_anim.StopPlayback();
playerEntity.m_animOver["Idle"] = Resources.Load<AnimationClip>("Anim/Idle");
playerEntity.m_anim.SetTrigger("Play");
}
}
/// <summary>
/// 音乐组件 Audio的更改替换
/// </summary>
public class Skill_Audio : SKillBase
{
//声音元件
[JsonIgnore] public AudioClip clip;
public override void Init(PlayerEntity player)
{
base.Init(player);
//拿到加载对应路径得到的音乐元件
clip = Resources.Load<AudioClip>("Audio/" + clipPath);
}
/// <summary>
/// 切换并播放音乐的原理
/// </summary>
public override void Play()
{
base.Play();
playerEntity.m_audio.Play();
}
/// <summary>
/// 停止音乐的原理
/// </summary>
public override void Stop()
{
base.Stop();
playerEntity.m_audio.Stop();
}
}
/// <summary>
/// 特效(GameObject)组件 Effect的更改替换
/// </summary>
public class Skill_Effect : SKillBase
{
//特效元件
[JsonIgnore] public GameObject clip;
//特效实例
[JsonIgnore] public GameObject effect;
public override void Init(PlayerEntity player)
{
base.Init(player);
//拿到加载对应路径得到的特效元件
clip = Resources.Load<GameObject>("Effect/" + clipPath);
}
/// <summary>
/// 切换并播放特效的原理
/// </summary>
public override void Play()
{
base.Play();
effect = GameObject.Instantiate(clip, playerEntity.transform);
effect.GetComponent<ParticleSystem>().Play();
}
/// <summary>
/// 停止特效的原理
/// </summary>
public override void Stop()
{
base.Stop();
effect.GetComponent<ParticleSystem>().Stop();
}
}
人物实体(请结合前面的技能基类代码观看):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 玩家实体
/// </summary>
public class PlayerEntity : MonoBehaviour
{
public string m_playerName;//玩家名称
public PlayerInfo m_Info;//对于技能基类的引用
public List<PlayerInfo> skills = new List<PlayerInfo>();//人物技能列表的存储库
public AudioSource m_audio;//音乐组件
public Animator m_anim;//动画组件
public AnimatorOverrideController m_animOver;//替换使用的特定动画但保留其原始结构的组件
public RuntimeAnimatorController m_runTimeAnim;//人物身上挂载的常见动画控制器 用于运行时动画的操作
public void OnInit(string playerName)
{
m_playerName = playerName;
//获取玩家身上的音乐、动画组件
m_audio = GetComponent<AudioSource>();
m_anim = GetComponent<Animator>();
//拿到动画控制器
m_runTimeAnim = Resources.Load<RuntimeAnimatorController>("Player");
//创建一个新的动画重构器
m_animOver = new AnimatorOverrideController();
//初始时将其为空 (为不为空都一样 因为是新创建的 初始肯定为空 但是保证代码的完整性 还是加上吧)
m_animOver.runtimeAnimatorController = null;
//将拿到的动画控制器放到动画重构器里 代码外会详细讲解 AnimatorOverrideController 和 RuntimeAnimatorController都是什么东西
m_animOver.runtimeAnimatorController = m_runTimeAnim;
//最后添加到玩家身上的动画组件上
m_anim.runtimeAnimatorController = m_animOver;
}
//初始化所有技能库里的技能
public void Init()
{
foreach (var item in m_Info.skills)
{
switch (item.type)
{
case TypeInfo.None:
break;
case TypeInfo.动画:
(item as Skill_Anim).Init(this);
break;
case TypeInfo.声音:
(item as Skill_Audio).Init(this);
break;
case TypeInfo.特效:
(item as Skill_Effect).Init(this);
break;
default:
break;
}
}
}
//播放所有技能库里的技能
public void Play()
{
foreach (var item in m_Info.skills)
{
switch (item.type)
{
case TypeInfo.None:
break;
case TypeInfo.动画:
(item as Skill_Anim).Play();
break;
case TypeInfo.声音:
(item as Skill_Audio).Play();
break;
case TypeInfo.特效:
(item as Skill_Effect).Play();
break;
default:
break;
}
}
}
//暂停所有技能库里的技能
public void Stop()
{
foreach (var item in m_Info.skills)
{
switch (item.type)
{
case TypeInfo.None:
break;
case TypeInfo.动画:
(item as Skill_Anim).Stop();
break;
case TypeInfo.声音:
(item as Skill_Audio).Stop();
break;
case TypeInfo.特效:
(item as Skill_Effect).Stop();
break;
default:
break;
}
}
}
}
AnimatorOverrideController 和 RuntimeAnimatorController 都是什么东西???
2.基类与实体部分已经操作完了 下面开始去编写编辑器里的部分
我们的编辑器代码存放到 Editor 文件夹下
想要在Unity 添加菜单项的话就必须改变继承关系 从MonoBehaviour 变为 EditorWindow 并使用 [ MenuItem("第一个路径 / 子路径") ] 并且放置的位置是在你写的静态方法的上一行
选择人物模型与编辑技能名称界面
如下图:
可不能冤枉我 代码里有详细注释 !!!
using Codice.Client.BaseCommands;
using Codice.Client.Common;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
/// <summary>
/// 选择系列编辑器
/// 注意:改变继承关系 继承EditorWindow
/// </summary>
public class SelectEditor : EditorWindow
{
public List<string> roleList = new List<string>();//存放人物的库
public int roleIndex = 0;//人物的下标
public GameObject model;//实例好的单个人物
public string skillName;//技能名称
public PlayerEntity playerEntity;//人物实体
public bool removeFlag = false;//是否删除
[MenuItem("Tools/编辑器")]
private static void Init()
{
//获取继承EditorWindow的编辑器脚本 可在后方添加窗口名称
SelectEditor win = GetWindow<SelectEditor>("编辑器");
//是否存在
if (win)
{
//打开编辑器窗口
win.Show();
}
}
/// <summary>
/// OnEable 特性:每当显示激活的时候去执行一次
/// </summary>
private void OnEnable()
{
//得到预制体路径
string path = Application.dataPath + "/Resources/Prefab";
//得到类型为.prefab 的文件
var file = Directory.GetFiles(path, "*.prefab", SearchOption.AllDirectories);
roleList.Add("None");
//遍历添加查询到的预制体文件
foreach (var item in file)
{
//在人物集合中添加去除后缀名的遍历
roleList.Add(Path.GetFileNameWithoutExtension(item));
}
}
/// <summary>
/// OnGUI 创建调试工具、创建自定义属性面板、创建新的Editor窗口和工具达到扩展编辑器效果 特性:每帧绘制
/// </summary>
private void OnGUI()
{
//布局
GUILayout.Space(20);
GUILayout.BeginHorizontal();
GUILayout.Label("请选择你的人物:");
//获取到选择的下拉列表的下标
int index = EditorGUILayout.Popup(roleIndex, roleList.ToArray());
if (index != roleIndex)
{
OnDelRole();//删除人物
roleIndex = index;//将下标赋值 不赋值的话就不会选择上
if (index != 0)
{
OnCreate();//创建人物
}
}
GUILayout.EndHorizontal();
GUILayout.Space(20);
if (model)//重要一步 必须添加 不然 没添加人物角色的时候后面也去每帧绘制 后面没有拿到人物信息会报错
{
GUILayout.BeginHorizontal();
//输入文本框
skillName = GUILayout.TextField(skillName);
if (GUILayout.Button("添加"))
{
//文本框内容是否为空
if (string.IsNullOrEmpty(skillName))
{
Debug.Log("技能名称不能为空");
}
else
{
//判断技能列表中是否有相同名称的技能名称
var info = playerEntity.skills.Find((x) => x.skillName == skillName);
if (info == null)
{
//没有就创建并添加进技能列表
Debug.Log("技能创建成功");
PlayerInfo playerinfo = new PlayerInfo();
playerinfo.skillName = skillName;
playerEntity.skills.Add(playerinfo);
}
else
{
Debug.Log("技能名称已存在");
}
}
}
GUILayout.EndHorizontal();
//将所有的技能列表里的技能显示出来
foreach (var item in playerEntity.skills)
{
GUILayout.Space(10);
GUILayout.BeginHorizontal();
if (GUILayout.Button(item.skillName))
{
//点击进入技能配置界面
SkillEditor.Init(item, playerEntity);
}
//这的删除不建议去看 这里的只是效果上删除 后期保存的技能配置表并未删除
if (GUILayout.Button("删除"))
{
playerEntity.skills.Remove(item);
//不能删除后就立马Break掉 不然会出现布局错误
removeFlag = true;
}
GUILayout.EndHorizontal();
if (removeFlag)
{
break;
}
}
}
}
private void OnCreate()
{
model = Instantiate(Resources.Load<GameObject>("Prefab/" + roleList[roleIndex]));
playerEntity = model.AddComponent<PlayerEntity>();//将模型与玩家实体脚本绑定
playerEntity.OnInit(roleList[roleIndex]);
//获取人物技能
GetSkill();
}
/// <summary>
/// 获取人物技能 为什么要按照下方那么去解配置表??? 后面配文有配置表 看完就知道了
/// </summary>
private void GetSkill()
{
string path = Application.dataPath + "/Resources/cfg_folder/" + playerEntity.m_playerName;
var files = Directory.GetFiles(path, "*.txt", SearchOption.AllDirectories);
foreach (var item in files)
{
string com = File.ReadAllText(item);
string[] arr = com.Split("\r\n");
PlayerInfo info = new PlayerInfo();
info.skillName = Path.GetFileNameWithoutExtension(item);//去除后缀名的操作
info.icon = arr[0];
for (int i = 1; i < arr.Length - 1; i += 2)
{
TypeInfo type = (TypeInfo)int.Parse(arr[i]);
switch (type)
{
case TypeInfo.None:
break;
case TypeInfo.动画:
Skill_Anim anim = JsonConvert.DeserializeObject<Skill_Anim>(arr[i + 1]);
info.skills.Add(anim);
break;
case TypeInfo.声音:
Skill_Audio audio = JsonConvert.DeserializeObject<Skill_Audio>(arr[i + 1]);
info.skills.Add(audio);
break;
case TypeInfo.特效:
Skill_Effect effect = JsonConvert.DeserializeObject<Skill_Effect>(arr[i + 1]);
info.skills.Add(effect);
break;
default:
break;
}
}
playerEntity.skills.Add(info);
}
}
private void OnDelRole()
{
if (model)
{
DestroyImmediate(model.gameObject);
}
}
}
配置表:![](https://img-blog.csdnimg.cn/ab35c212b93545c58f47c52f6586cbeb.png)
编辑技能界面:
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
using UnityEngine;
/// <summary>
/// 编辑技能编辑器界面 同样继承EditorWidow
/// </summary>
public class SkillEditor : EditorWindow
{
public static PlayerEntity m_playerEntity;//人物实体
public static PlayerInfo m_Info;//人物信息
public static Texture2D m_texture;//技能贴图
public TypeInfo type;
public static void Init(PlayerInfo skill, PlayerEntity playerEntity)
{
m_Info = skill;//赋值人物信息
m_Info.skillName = skill.skillName;//赋值名称
m_playerEntity = playerEntity;//获取人物实体
playerEntity.m_Info = skill;//给实体附加人物信息
//存在技能图标的话就添加 不存在 就先空着
if (!string.IsNullOrEmpty(playerEntity.m_Info.icon))
{
m_texture = Resources.Load<Texture2D>("Icon/" + playerEntity.m_Info.icon);
}
else
{
m_texture = null;
}
playerEntity.Init();//将已存在的技能列表进行初始化
// 获取继承EditorWindow的编辑器脚本
SkillEditor win = new SkillEditor();
win.Show();
}
private void OnGUI()
{
//添加选择头像
m_texture = EditorGUILayout.ObjectField("请选择需要的图片", m_texture, typeof(Texture2D), true) as Texture2D;
GUILayout.BeginHorizontal();
if (GUILayout.Button("播放"))
{
//播放所有技能(动画 音乐 特效 ...等)
m_playerEntity.Play();
}
if (GUILayout.Button("暂停"))
{
//暂停所有技能(动画 音乐 特效 ...等)
m_playerEntity.Stop();
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
//枚举类型的下拉框
type = (TypeInfo)EditorGUILayout.EnumPopup(type);
//添加对应的类型
//这里只是将他们添加进存放技能的库 还并未有任何显示的操作
if (GUILayout.Button("添加"))
{
switch (type)
{
case TypeInfo.None:
break;
case TypeInfo.动画:
Skill_Anim anim = new Skill_Anim();
anim.type = type;
anim.playerEntity = m_playerEntity;
m_Info.skills.Add(anim);
break;
case TypeInfo.声音:
Skill_Audio audio = new Skill_Audio();
audio.type = type;
audio.playerEntity = m_playerEntity;
m_Info.skills.Add(audio);
break;
case TypeInfo.特效:
Skill_Effect effect = new Skill_Effect();
effect.type = type;
effect.playerEntity = m_playerEntity;
m_Info.skills.Add(effect);
break;
default:
break;
}
}
GUILayout.EndHorizontal();
//遍历存放技能的库 开始准备显示
foreach (var item in m_Info.skills)
{
switch (item.type)
{
case TypeInfo.None:
break;
case TypeInfo.动画:
CreateAnim(item as Skill_Anim);
break;
case TypeInfo.声音:
CreateAudio(item as Skill_Audio);
break;
case TypeInfo.特效:
CreateEffect(item as Skill_Effect);
break;
default:
break;
}
}
GUILayout.Space(50);
if (GUILayout.Button("保存"))
{
OnSave();
}
}
//保存逻辑
private void OnSave()
{
if (m_texture)
{
m_playerEntity.m_Info.icon = m_texture.name;
}
else
{
m_playerEntity.m_Info.icon = string.Empty;
}
StringBuilder sb = new StringBuilder();
sb.AppendLine(m_playerEntity.m_Info.icon);
foreach (var item in m_playerEntity.m_Info.skills)
{
int types = -1;
types = (int)item.type;
sb.AppendLine(types.ToString());
switch (item.type)
{
case TypeInfo.None:
break;
case TypeInfo.动画:
string anim = JsonConvert.SerializeObject(item);
sb.AppendLine(anim);
break;
case TypeInfo.声音:
string audio = JsonConvert.SerializeObject(item);
sb.AppendLine(audio);
break;
case TypeInfo.特效:
string effect = JsonConvert.SerializeObject(item);
sb.AppendLine(effect);
break;
default:
break;
}
}
string path = Application.dataPath + "/Resources/cfg_folder/" + m_playerEntity.m_playerName + "/" + m_playerEntity.m_Info.skillName + ".txt";
File.WriteAllText(path, sb.ToString());
AssetDatabase.Refresh();//实时刷新
}
/// <summary>
/// 创建特效的选择栏
/// </summary>
/// <param name="skill_Effect"></param>
private void CreateEffect(Skill_Effect skill_Effect)
{
GUILayout.Space(20);
skill_Effect.clip = EditorGUILayout.ObjectField(skill_Effect.clip, typeof(GameObject), true) as GameObject;
if (skill_Effect.clip != null)
{
skill_Effect.clipPath = skill_Effect.clip.name;
}
}
/// <summary>
/// 创建音乐的选择栏
/// </summary>
/// <param name="skill_Audio"></param>
private void CreateAudio(Skill_Audio skill_Audio)
{
GUILayout.Space(20);
skill_Audio.clip = EditorGUILayout.ObjectField(skill_Audio.clip, typeof(AudioClip), true) as AudioClip;
if (skill_Audio.clip != null)
{
skill_Audio.clipPath = skill_Audio.clip.name;
}
}
/// <summary>
/// 创建动画的选择栏
/// </summary>
/// <param name="skill_Anim"></param>
private void CreateAnim(Skill_Anim skill_Anim)
{
GUILayout.Space(20);
skill_Anim.clip = EditorGUILayout.ObjectField(skill_Anim.clip, typeof(AnimationClip), true) as AnimationClip;
if (skill_Anim.clip != null)
{
skill_Anim.clipPath = skill_Anim.clip.name;
}
}
}
写到这里我们的技能编辑器就可以正常编辑自己想要的技能配置以及保存了
接下来要做的操作就是我们去实际的使用了
首先是UI方面:
内容分析:有下拉框、确定按钮、三个技能按钮、一个普通攻击的按钮 、摇杆(可写可不写)
上代码:
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using Unity.Collections;
using UnityEngine;
using UnityEngine.UI;
public class AddPlayer : MonoBehaviour
{
public static AddPlayer Instance;
private void Awake()
{
Instance = this;
}
public Dropdown dropdown;//下拉框
int index = 0;//下拉框下标
public GameObject player;//存放玩家的父级 便于切换人物后删除上一个人物
public Button btn_Player;//确定选取
public List<Button> btn_skills = new List<Button>();
PlayerController _playerController;
string folde = string.Empty;//技能文件夹
GameObject playerObj;//玩家实例
void Start()
{
dropdown.onValueChanged.AddListener((x) =>
{
index = x;//下标赋值
});
btn_Player.onClick.AddListener(() =>
{
OnAddPlayer();//生成玩家
});
}
private void OnAddPlayer()
{
//获取对应的玩家名称 当然也有其他写法 我这样灵活性太小
if (index == 0)
{
folde = "Player_0";
_playerController.playerName = "Player_0";
}
if (index == 1)
{
folde = "Player_1";
_playerController.playerName = "Player_1";
}
//删除人物
if (player.transform.childCount > 0)
{
for (int i = 0; i < player.transform.childCount; i++)
{
Destroy(player.transform.GetChild(0).gameObject);
}
}
//实例玩家
playerObj = Instantiate(Resources.Load<GameObject>("Prefab/" + _playerController.playerName),
player.transform);//生成模型
//添加人物实体
_playerController = playerObj.AddComponent<PlayerController>();
//初始化玩家信息
_playerController.Init(_playerController.playerName);
GetSkill();//获取技能这里和选择人物与技能名称里的获取技能 一模一样
OnSkillCall();//这里是将技能与界面按钮进行信息绑定
}
private void OnSkillCall()
{
//遍历所有的按钮
for (int i = 0; i < btn_skills.Count; i++)
{
//按钮个数是否小于人物技能个数 (可写可不写 不写的话可以将那个按钮整成未解锁的状态)
if (i < _playerController.skills.Count)
{
//下面就是对每个按钮的绑定赋值了
btn_skills[i].gameObject.SetActive(true);
btn_skills[i].image.sprite = Resources.Load<Sprite>("Icon/" + _playerController.skills[i].icon);
btn_skills[i].transform.GetChild(0).GetComponent<Text>().text = _playerController.skills[i].skillName;
int indexs = i;
btn_skills[indexs].onClick.AddListener(() =>
{
_playerController.currentSkill = _playerController.skills[indexs];
_playerController.InitSkillData();
_playerController.Play();
});
}
else
{
btn_skills[i].gameObject.SetActive(false);
}
}
}
private void GetSkill()
{
string path = $"{Application.dataPath}/Resources/cfg_data/{folde}";
//string paths = Application.dataPath + "/Resources/cfg_data/" + folde;
var files = Directory.GetFiles(path, "*.txt", SearchOption.AllDirectories);
foreach (var item in files)
{
string content = File.ReadAllText(item);
string[] arr = content.Split("\r\n");
PlayerInfo info = new PlayerInfo();
info.skillName = Path.GetFileNameWithoutExtension(item);
info.icon = arr[0];
for (int i = 1; i < arr.Length - 1; i += 2)
{
SkillType type = (SkillType)int.Parse(arr[i]);
switch (type)
{
case SkillType.动画:
Skill_Animator anima = JsonConvert.DeserializeObject<Skill_Animator>(arr[i + 1]);
info.skills.Add(anima);
break;
case SkillType.声音:
Skill_AudioSource audio = JsonConvert.DeserializeObject<Skill_AudioSource>(arr[i + 1]);
info.skills.Add(audio);
break;
case SkillType.特效:
Skill_Effect effect = JsonConvert.DeserializeObject<Skill_Effect>(arr[i + 1]);
info.skills.Add(effect);
break;
}
}
_playerController.skills.Add(info);
}
}
后期补充(重中之重(非技术)):
以上就是我对于极简版的技能编辑器大体讲解
后期还会有其他更改与补充 十分欢迎大家来分享自己de见解
希望能和大家相互学习 一起提升自己的芝士!!!