关于Unity3D自定义编辑器的学习

时间:2022-03-15 13:20:26

  被人物编辑器折腾了一个月,最终还是交了点成品上去(还要很多优化都还么做)。

    刚接手这项工作时觉得没概念,没想法,不知道。后来就去看<<Unity5.X从入门到精通>>中有关于自定义编辑器(自定义Inspector和自定义Scene或GUI)的一些例子,还包括看了 雨松的编辑器教程 和 自定义结构显示在Inspector的方法 看完之后也实战了一下就算入了门,就分析自己项目的人物对应的数据,如下图:关于Unity3D自定义编辑器的学习

上述数据其实很简单但是对于我这种初学者来说就有点难度,首先因为Actions 和 Frames(动作对应的帧集合) 需要有类似数组或者链表这些数据结构来存储。就去查了一些资料发现 几篇好的关于序列化和反序列化的博文 , 比如 大表哥的博文 提到 哪些数据能够序列化和反序列化 ,其中我们就可以用List<T>的结构来存储数据集合,关于为什么涉及到序列化和反序列化, 因为我们需要将一些数据保存到本地,而不是仅仅的放在内存中,再从本地取回到内存中就需要反序列化了。

  理解了上述的基础知识 ,便自己定义了特定的数据类(主要的):

     [System.Serializable]
public class CharacterEditorStateData : ISerializationCallbackReceiver
{
public string m_animationName;
public int m_totFrame;
public CharacterStateType m_stateType;
[HideInInspector]
public CharacterStateType m_oldStateType;
[HideInInspector][NonSerialized]
public List<CharacterEditorAttackData> m_attackDatas = new List<CharacterEditorAttackData>();
[HideInInspector][SerializeField]
public List<CharacterEditorBombAttackData> m_attackBmDatas = new List<CharacterEditorBombAttackData>();
[HideInInspector][SerializeField]
public List<CharacterEditorNormalAttackData> m_attackNmDatas = new List<CharacterEditorNormalAttackData>();
public CharacterEditorAttackData IsFrameDataExist(int frame)
{
foreach (CharacterEditorAttackData dt in m_attackDatas)
{
if (frame == dt.m_iFrame)
return dt;
}
return null;
} public bool AddFrameData(int newFrame)
{
CharacterEditorAttackData dt = CharacterEditorAttackData.CreateData(CharacterAttackType.BOMB);
if (dt == null)
return false;
dt.m_iFrame = newFrame;
dt.m_attackType = CharacterAttackType.BOMB;
this.m_attackDatas.Add(dt);
return true;
} public bool RemoveFrameData(int oldFrame)
{
CharacterEditorAttackData dt = this.IsFrameDataExist(oldFrame);
if (dt == null)
return false;
this.m_attackDatas.Remove(dt);
return true;
} public void ChangeFrameData(int index , CharacterAttackType attType)
{
CharacterEditorAttackData dt = this.m_attackDatas[index];
int iFrame = dt.m_iFrame;
if (attType != dt.m_attackType)
{
dt = CharacterEditorAttackData.CreateData(attType);
dt.m_iFrame = iFrame;
dt.m_attackType = attType;
this.m_attackDatas[index] = dt;
}
} public int GetNewFrame()
{
if (m_attackDatas.Count == )
return ;
int frame = -;
foreach (CharacterEditorAttackData dt in m_attackDatas)
{
if (frame <= dt.m_iFrame)
frame = dt.m_iFrame;
}
if (frame == this.m_totFrame)
return -;
return frame + ;
} public bool IsLegalFrame(int frame)
{
if (IsFrameDataExist(frame) != null || frame < || frame > m_totFrame)
return false;
return true;
} public bool UpdateFramesSz()
{
int count = m_attackDatas.Count;
for (int i = count - ; i > m_totFrame - ; i--)
{
this.m_attackDatas.RemoveAt(i);
}
return true;
} public void Init(CharacterStateType state)
{
m_animationName = "";
m_totFrame = ;
m_stateType = state;
m_oldStateType = state;
m_attackDatas.Clear();
m_attackBmDatas.Clear();
m_attackNmDatas.Clear();
} void ISerializationCallbackReceiver.OnBeforeSerialize()
{
if (m_attackBmDatas == null || m_attackNmDatas == null || m_attackDatas == null)
return;
m_attackBmDatas.Clear();
m_attackNmDatas.Clear();
foreach(CharacterEditorAttackData item in m_attackDatas)
{
switch(item.m_attackType)
{
case CharacterAttackType.BOMB:m_attackBmDatas.Add((CharacterEditorBombAttackData)item); break;
case CharacterAttackType.NORMAL: m_attackNmDatas.Add((CharacterEditorNormalAttackData)item);break;
}
}
} void ISerializationCallbackReceiver.OnAfterDeserialize()
{
if (m_attackBmDatas == null || m_attackNmDatas == null || m_attackDatas == null)
return;
m_attackDatas.Clear();
foreach (CharacterEditorAttackData item in m_attackBmDatas)
{
m_attackDatas.Add(item);
}
foreach (CharacterEditorAttackData item in m_attackNmDatas)
{
m_attackDatas.Add(item);
}
}
}

  对于上述的数据结构,可能会有疑问,首先为什么需要实现 ISerializationCallbackReceiver , 和这个接口的作用;为什么需要用到三个List结构。首先,来

理解一下 ISerializationCallbackReceiver 这个接口的作用 。 关于这个 接口介绍的博文 ,其实看完这个博文,我还是没有理解作者想讲的意思,后来自己翻阅

了其他资料,

void ISerializationCallbackReceiver.OnBeforeSerialize()

这个接口的作用就是 序列化快开始了 , 你可以在序列化开始前做些操作 。 比如C#结构中Dict是不能够序列化的,所以在开始前可以将Dict的键和值都保存在List中 这样就达到了序列化的目的。
void ISerializationCallbackReceiver.OnAfterDeserialize()

这个接口的作用就是 反序列化结束了 , 你可以在反序列化后做一些操作 。还是上面的例子,我们可以在反序列化后从list中拿到对应的数据,把List中的键和值存储在对应的Dict中。

  关于两个接口的作用已经讲完了,来解决下为什么要用那么多List的原因,首先先说一下我遇到的问题,之前访问子类和存储都是通过new子类对象后,用父类的指针保存在List上,所以就会存在问题,在序列化时,序列化的是父类而不是子类,在反序列化后,就会出现数据丢失。所以需要在上述的两个接口做一个序列化前和反序列化后的数据操作,保证数据的正确。

  通过上述,我们可以得到可靠的序列化流程,接下来就可以就可以自定义编辑器了,编辑器代码相对简单,由于界面主要在InspectorUI上操作,就写在OnInspector上。其实在写之前对于OnInspectorUI这个函数的调用是有疑问的,后来亲自实践了一下,发现只有有UI发生更改时或者切入切出(调到另一个)显示对象都会调用,代码如下:
 using UnityEngine;
using UnityEditor;
using System.Collections;
using TKGame;
using System.Collections.Generic;
using System; [CustomEditor(typeof(CharacterEditorData))]
public class CharacterEditor : Editor { enum AddState { NONE, NEWSTATE, FULLSTATE };
public const string TAG = "[CharacterEditor]";
public CharacterEditorData m_chaEditData = null;
private AddState m_addState;
public void OnEnable(){
m_chaEditData = target as CharacterEditorData;
if (m_chaEditData == null)
return;
m_addState = AddState.NONE;
} public override void OnInspectorGUI()
{
if (m_chaEditData == null)
{
PrintLog("the chaEditorData is null");
return;
}
m_chaEditData.m_id = EditorGUILayout.IntField("PlayerID: ", m_chaEditData.m_id);
m_chaEditData.m_resID = EditorGUILayout.IntField("PlayerResourceID:", m_chaEditData.m_resID);
m_chaEditData.m_defaultName = EditorGUILayout.TextField("PlayerDefaultName: ", m_chaEditData.m_defaultName);
m_chaEditData.m_scale = EditorGUILayout.FloatField("PlayerScale:", m_chaEditData.m_scale);
m_chaEditData.m_walkSpeedX = EditorGUILayout.IntField("PlayerXSpeed: ", m_chaEditData.m_walkSpeedX);
m_chaEditData.m_walkSpeedY = EditorGUILayout.IntField("PlayerYSpeed:", m_chaEditData.m_walkSpeedY);
m_chaEditData.m_hatred = EditorGUILayout.FloatField("PlayerHatred:", m_chaEditData.m_hatred);
m_chaEditData.m_lowFireAngle = EditorGUILayout.FloatField("PlayerLowFireAngle:", m_chaEditData.m_lowFireAngle);
m_chaEditData.m_higFireAngle = EditorGUILayout.FloatField("PlayerHigFireAngle:", m_chaEditData.m_higFireAngle);
m_chaEditData.m_fireRange = EditorGUILayout.IntField("PlayerFireRange:", m_chaEditData.m_fireRange);
m_chaEditData.m_weaponPosition = EditorGUILayout.Vector2Field("PlayerWeaponPos", m_chaEditData.m_weaponPosition);
m_chaEditData.m_beAttackBoxMinX = EditorGUILayout.IntField("PlayerBAtkBoxMinX:", m_chaEditData.m_beAttackBoxMinX);
m_chaEditData.m_beAttackBoxMinY = EditorGUILayout.IntField("PlayerBAtkBoxMinY:", m_chaEditData.m_beAttackBoxMinY);
m_chaEditData.m_beAttackBoxMaxX = EditorGUILayout.IntField("PlayerBAtkBoxMaxX:", m_chaEditData.m_beAttackBoxMaxX);
m_chaEditData.m_beAttackBoxMaxY = EditorGUILayout.IntField("PlayerBAtkBoxMaxY:", m_chaEditData.m_beAttackBoxMaxY);
if (GUILayout.Button("Add New State"))
{
if (m_chaEditData.IsAllStateExist())
m_addState = AddState.FULLSTATE;
else
m_addState = AddState.NEWSTATE;
}
EditorGUILayout.Space();
if (m_addState == AddState.FULLSTATE)
EditorGUILayout.LabelField("all states is used");
else if (m_addState == AddState.NEWSTATE)
{
CharacterStateType newestState = m_chaEditData.GetNewestState();
m_chaEditData.AddNewState(newestState);
m_addState = AddState.NONE;
} EditorGUILayout.Space();
///Debug.Log("yes");
for (int index = ; index < m_chaEditData.m_lsStates.Count; index++)
{
CharacterEditorData.CharacterEditorStateData chaState = m_chaEditData.m_lsStates[index];
//Debug.Log(EditorGUILayout.EnumPopup("state:", chaState.m_newState));
CharacterStateType state = (CharacterStateType)EditorGUILayout.EnumPopup("state:", chaState.m_stateType);
m_chaEditData.ChangeByState(chaState, state);
chaState.m_animationName = EditorGUILayout.TextField("AnimationName:", chaState.m_animationName);
int totFrame = EditorGUILayout.IntField("TotalFrame:", chaState.m_totFrame);
if (totFrame != chaState.m_totFrame)
{
chaState.m_totFrame = totFrame;
chaState.UpdateFramesSz();
}
if (chaState.m_stateType == CharacterStateType.ATTACK)
{
if (GUILayout.Button("Add Frame Data", GUILayout.MaxWidth(), GUILayout.MaxHeight()))
{
int newFrame = chaState.GetNewFrame();
//Debug.Log(newFrame);
if (newFrame != -)
{
chaState.AddFrameData(newFrame);
}
}
EditorGUILayout.Space();
for (int i = ; i < chaState.m_attackDatas.Count; i++)
{
CharacterEditorData.CharacterEditorAttackData frameData = chaState.m_attackDatas[i];
int frame = EditorGUILayout.IntField("Frame:", frameData.m_iFrame);
if (chaState.IsLegalFrame(frame))
{
//Debug.Log(frame);
frameData.m_iFrame = frame;
}
CharacterAttackType attackType = (CharacterAttackType)EditorGUILayout.EnumPopup("AttackType:", frameData.m_attackType);
chaState.ChangeFrameData(i , attackType);
EditorGUILayout.Space();
if (frameData.m_attackType == CharacterAttackType.BOMB)
{
CharacterEditorData.CharacterEditorBombAttackData bomb = (CharacterEditorData.CharacterEditorBombAttackData)frameData;
bomb.m_bombCofigID = EditorGUILayout.IntField("BombConfigID:", bomb.m_bombCofigID);
bomb.m_damage = EditorGUILayout.IntField("Damge:", bomb.m_damage);
bomb.m_centerDamage = EditorGUILayout.IntField("CenterDamage:", bomb.m_centerDamage);
}
if (GUILayout.Button("Remove this Frame"))
{
chaState.RemoveFrameData(frameData.m_iFrame);
}
EditorGUILayout.Space();
EditorGUILayout.Space();
}
}
if (GUILayout.Button("remove this state"))
{
m_chaEditData.RemoveOldState(index);
}
EditorGUILayout.Space();
EditorGUILayout.Space();
}
EditorUtility.SetDirty(m_chaEditData);
} private void PrintLog(string str)
{
Debug.Log(TAG+" "+ str);
}
}
  最后一句的SetDirty表示当前的数据对象有更改,可以通知UI刷新。

  

关于Unity3D自定义编辑器的学习的更多相关文章

  1. Unity3D自定义编辑器简单实例

    MenuItem:在标题栏自定义菜单.需要在Editor文件夹内创建脚本,无需挂载.但是注意其下的函数必须为静态函数. using UnityEngine; using UnityEditor; pu ...

  2. Unity3d编辑器扩展学习笔记

    编辑器扩展 1.添加菜单栏:把特性应用于静态方法 参数1:菜单名的空格后面是定义快捷键(单符号得用"_"开头,组合键%=Ctrl,#=Shift,&=Alt) 参数2:通过 ...

  3. 【Unity】自定义编辑器窗口——拓展编辑器功能

    最近学习了Unity自定义编辑器窗口,下面简单总结,方便用到时回顾. 新建一个脚本: using UnityEngine; using System.Collections; using UnityE ...

  4. (转)Unity3d UnityEditor编辑器定制和开发插件

    在阅读本教程之前,你需要对Unity的操作流程有一些基础的认识,并且最好了解内置的GUI系统如何使用. 如何让编辑器运行你的代码 Unity3D可以通过事件触发来执行你的编辑器代码,但是我们需要一些编 ...

  5. 【转载】Unity3d UnityEditor编辑器定制和开发插件

    在阅读本教程之前,你需要对Unity的操作流程有一些基础的认识,并且最好了解内置的GUI系统如何使用. 如何让编辑器运行你的代码 Unity3D可以通过事件触发来执行你的编辑器代码,但是我们需要一些编 ...

  6. &lbrack;转&rsqb;Unity3D Editor 编辑器简易教程

    Star 自定义编辑器简易教程 an introduction to custom editors 原文地址 http://catlikecoding.com/unity/tutorials/star ...

  7. Web Essentials之Markdown和自定义编辑器&lpar;Web Essentials完结&rpar;

    返回Web Essentials功能目录 本篇目录 功能 自定义编辑器 开源项目都会在项目的根目录放一个Readme.md文件来告诉读者一些重要的说明,那么就可以在VS中直接编辑Markdown文件. ...

  8. unity3d拓展编辑器MenuItem的使用

    MenuItem是自定义菜单栏显示 比如:[MenuItem("new/My Window")] 这样就会显示菜单new/My Window 把这个放在一个静态方法上就可以了.记住 ...

  9. markdown编辑器的学习

    markdown编辑器的学习 1 标题 一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 2列表 无序列表 1 2 3 4 有序列表 1 2 3 4 3引用 这里是引用,哈哈我也不知道到我引 ...

随机推荐

  1. POS与EPOS区别

    本文分文三个部分来介绍,第一部分是两个概念(POS与EPOS),第三部分是 POS与EPOS的区别. 一.epos ( electronic point of sale ) 俗称电话pos机: .EP ...

  2. 利用ODBC从SQLServer向Oracle中导数据

    1.首先要在Oracle数据库中建对应的表,Oracle数据库中的字段类型和Sql Server 有所不同,Oracle中常用的有varchar2.integer.nchar.date,Sql Ser ...

  3. Excel转换成PDF

    public class Office2Pdf { public bool DOCConvertToPDF(string sourcePath, string targetPath) { //Stre ...

  4. 【C&plus;&plus;】命令行Hangman &num;2015年12月15日 00&colon;20&colon;27

    增加了可以在构造Hangman对象时通过传入参数设定“最大猜测次数”的功能.少量修改.# 2015年12月15日 00:20:22 https://github.com/shalliestera/ha ...

  5. hdu1087 简单DP

    I - 简单dp 例题扩展 Crawling in process... Crawling failed Time Limit:1000MS     Memory Limit:32768KB     ...

  6. 把angular项目整合到&period;net mvc中

    之前的开发选择的是完全舍弃服务端,仅保留最简单web服务器提供angular经打包的静态资源,此外所有的业务与数据请求都访问一个分离的WebApi来实现.不过最近碰到一个需求,有必要使用多个客户端,而 ...

  7. 如何配置Open Live Writer程序以便更好的为博客服务

    Open Live Writer的前身是Windows live Writer 即(WLW)是一个免费的桌面应用程序,您可以使用它轻松发布丰富的内容到您的网络日志.WLW最终版为Windows Liv ...

  8. HBase命令终端测试

    [root@CloudDeskTop ~]# su -l hadoop[hadoop@CloudDeskTop ~]$ cd /software/hbase-1.2.6/bin/ [hadoop@Cl ...

  9. VsCode基本使用

    迫于公司统一编辑器,初次接触VsCode,小白入门笔记 安装插件及其用途: 1. Bracket Pair Colorizer :对括号对进行着色,再也不会搞不清状况了. 2. Git History ...

  10. java多态的具体表现实例和理解

    Java的多态性 面向对象编程有三个特征,即封装.继承和多态. 封装隐藏了类的内部实现机制,从而可以在不影响使用者的前提下改变类的内部结构,同时保护了数据. 继承是为了重用父类代码,同时为实现多态性作 ...