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

    刚接手这项工作时觉得没概念,没想法,不知道。后来就去看<<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. Mapreduce的文件和hbase共同输入

    Mapreduce的文件和hbase共同输入 package duogemap;   import java.io.IOException;   import org.apache.hadoop.co ...

  2. 说说Golang的使用心得

    13年上半年接触了Golang,对Golang十分喜爱.现在是2015年,离春节还有几天,从开始学习到现在的一年半时间里,前前后后也用Golang写了些代码,其中包括业余时间的,也有产品项目中的.一直 ...

  3. ASP.NET Core的路由[3]:Router的创建者——RouteBuilder

    在<注册URL模式与HttpHandler的映射关系>演示的实例中,我们总是利用一个RouteBuilder对象来为RouterMiddleware中间件创建所需的Router对象,接下来 ...

  4. .Net 分布式云平台基础服务建设说明概要

    1)  背景 建设云平台的基础框架,用于支持各类云服务的业务的构建及发展. 2)  基础服务 根据目前对业务的理解和发展方向,总结抽象出以下几个基础服务,如图所示 3)  概要说明 基础服务的发展会根 ...

  5. Gradle 实现 Android 多渠道定制化打包

    Gradle 实现 Android 多渠道定制化打包 版权声明:本文为博主原创文章,未经博主允许不得转载. 最近在项目中遇到需要实现 Apk 多渠道.定制化打包, Google .百度查找了一些资料, ...

  6. SQL Server 2016白皮书

    随着SQL Server 2016正式版发布日临近,相关主要特性通过以下预览学习: Introducing Microsoft SQL Server 2016 e-bookSQL Server 201 ...

  7. 代码的坏味道(21)——中间人(Middle Man)

    坏味道--中间人(Middle Man) 特征 如果一个类的作用仅仅是指向另一个类的委托,为什么要存在呢? 问题原因 对象的基本特征之一就是封装:对外部世界隐藏其内部细节.封装往往伴随委托.但是人们可 ...

  8. 我理解的MVC

    前言 前一阶段对MVC模式及其衍生模式做了一番比较深入的研究和实践,这篇文章也算是一个阶段性的回顾和总结. 经典MVC模式 经典MVC模式中,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的 ...

  9. Java程序员应该了解的10个面向对象设计原则

    面向对象设计原则: 是OOPS(Object-Oriented Programming System,面向对象的程序设计系统)编程的核心,但大多数Java程序员追逐像Singleton.Decorat ...

  10. (转) 从0开始搭建SQL Server AlwaysOn 第三篇(配置AlwaysOn)

    原文地址: http://www.cnblogs.com/lyhabc/p/4682986.html 这一篇是从0开始搭建SQL Server AlwaysOn 的第三篇,这一篇才真正开始搭建Alwa ...