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

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

上述数据其实很简单但是对于我这种初学者来说就有点难度,首先因为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. [转]Unity3D Editor 编辑器简易教程

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

  7. Web Essentials之Markdown和自定义编辑器(Web Essentials完结)

    返回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. python程序生成平均脸

    简介 项目代码https://github.com/LiuRoy/pokerface 原文链接http://www.cnblogs.com/lrysjtu/p/5492547.html 写这个项目的本 ...

  2. AJAX 大全

    本章内容: 简介 伪 AJAX 原生 AJAX XmlHttpRequest 的属性.方法.跨浏览器支持 jQuery AJAX 常用方法 跨域 AJAX JsonP CORS 简单请求.复制请求.请 ...

  3. npm 使用小结

    本文内容基于 npm 4.0.5 概述 npm (node package manager),即 node 包管理器.这里的 node 包就是指各种 javascript 库. npm 是随同 Nod ...

  4. LINQ to SQL Select查询

    1. 查询所有字段 using (NorthwindEntities context = new NorthwindEntities()) { var order = from n in contex ...

  5. ASP.NET SignaiR 实现消息的即时推送,并使用Push.js实现通知

    一.使用背景 1. SignalR是什么? ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程.实时 Web 功能是指 ...

  6. 封装集合(Encapsulate Collection)

    封装就是将相关的方法或者属性抽象成为一个对象. 封装的意义: 对外隐藏内部实现,接口不变,内部实现自由修改. 只返回需要的数据和方法. 提供一种方式防止数据被修改. 更好的代码复用. 当一个类的属性类 ...

  7. 分享一个MySQL分库分表备份脚本(原)

    分享一个MySQL分库备份脚本(原) 开发思路: 1.路径:规定备份到什么位置,把路径(先判断是否存在,不存在创建一个目录)先定义好,我的路径:/mysql/backup,每个备份用压缩提升效率,带上 ...

  8. chattr用法

    [root@localhost tmp]# umask 0022 一.chattr用法 1.创建空文件attrtest,然后删除,提示无法删除,因为有隐藏文件 [root@localhost tmp] ...

  9. MyBatis3.2从入门到精通第一章

    第一章一.引言mybatis是一个持久层框架,是apache下的顶级项目.mybatis托管到goolecode下,再后来托管到github下.(百度百科有解释)二.概述mybatis让程序将主要精力 ...

  10. NOIP2016纪录[那些我所追求的]

    人生第一场正式OI [序] 2016-12-04 见底部 [Day -1] 2016-11-17 期中考试无心插柳柳成荫,考了全市第2班里第1(还不是因为只复习了不到两天考试),马上请了一个周的假准备 ...