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

    刚接手这项工作时觉得没概念,没想法,不知道。后来就去看<<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. 谈一下关于CQRS架构如何实现高性能

    CQRS架构简介 前不久,看到博客园一位园友写了一篇文章,其中的观点是,要想高性能,需要尽量:避开网络开销(IO),避开海量数据,避开资源争夺.对于这3点,我觉得很有道理.所以也想谈一下,CQRS架构 ...

  2. .NET里简易实现AOP

    .NET里简易实现AOP 前言 在MVC的过滤器章节中对于过滤器的使用就是AOP的一个实现了吧,时常在工作学习中遇到AOP对于它的运用可以说是很熟练了,就是没想过如果自己来实现的话是怎么实现的,性子比 ...

  3. iOS可视化动态绘制八种排序过程

    前面几篇博客都是关于排序的,在之前陆陆续续发布的博客中,我们先后介绍了冒泡排序.选择排序.插入排序.希尔排序.堆排序.归并排序以及快速排序.俗话说的好,做事儿要善始善终,本篇博客就算是对之前那几篇博客 ...

  4. How those spring enable annotations work--转

    原文地址:http://blog.fawnanddoug.com/2012/08/how-those-spring-enable-annotations-work.html Spring's Java ...

  5. android Notification介绍

    如果要添加一个Notification,可以按照以下几个步骤 1:获取NotificationManager: NotificationManager m_NotificationManager=(N ...

  6. Git的四个基本概念及 git的工作流程

  7. Mysql - 性能优化之子查询

    记得在做项目的时候, 听到过一句话, 尽量不要使用子查询, 那么这一篇就来看一下, 这句话是否是正确的. 那在这之前, 需要介绍一些概念性东西和mysql对语句的大致处理. 当Mysql Server ...

  8. Mysql - 查询之关联查询

    查询这块是重中之重, 关系到系统反应时间. 项目做到后期, 都是要做性能测试和性能优化的, 优化的时候, 数据库这块是一个大头. sql格式: select 列名/* from 表名 where 条件 ...

  9. Linux基础介绍【第六篇】

    定时任务crond介绍 crond是什么? crond是linux系统中用来定期执行命令或指定程序任务的一种服务或软件.一般情况下,安装完CentOS5/6 linux操作系统之后,默认便会启动cro ...

  10. 烂泥:redis3.2.3安装与配置

    本文由ilanniweb提供友情赞助,首发于烂泥行天下 想要获得更多的文章,可以关注我的微信ilanniweb 前一段时间写过一篇codis集群的文章,写那篇文章主要是因为当时的项目不支持redis自 ...