被人物编辑器折腾了一个月,最终还是交了点成品上去(还要很多优化都还么做)。
刚接手这项工作时觉得没概念,没想法,不知道。后来就去看<<Unity5.X从入门到精通>>中有关于自定义编辑器(自定义Inspector和自定义Scene或GUI)的一些例子,还包括看了 雨松的编辑器教程 和 自定义结构显示在Inspector的方法 看完之后也实战了一下就算入了门,就分析自己项目的人物对应的数据,如下图:
上述数据其实很简单但是对于我这种初学者来说就有点难度,首先因为Actions 和 Frames(动作对应的帧集合) 需要有类似数组或者链表这些数据结构来存储。就去查了一些资料发现 几篇好的关于序列化和反序列化的博文 , 比如 大表哥的博文 提到 哪些数据能够序列化和反序列化 ,其中我们就可以用List<T>的结构来存储数据集合,关于为什么涉及到序列化和反序列化, 因为我们需要将一些数据保存到本地,而不是仅仅的放在内存中,再从本地取回到内存中就需要反序列化了。
理解了上述的基础知识 ,便自己定义了特定的数据类(主要的):
1 [System.Serializable] 2 public class CharacterEditorStateData : ISerializationCallbackReceiver 3 { 4 public string m_animationName; 5 public int m_totFrame; 6 public CharacterStateType m_stateType; 7 [HideInInspector] 8 public CharacterStateType m_oldStateType; 9 [HideInInspector][NonSerialized] 10 public List<CharacterEditorAttackData> m_attackDatas = new List<CharacterEditorAttackData>(); 11 [HideInInspector][SerializeField] 12 public List<CharacterEditorBombAttackData> m_attackBmDatas = new List<CharacterEditorBombAttackData>(); 13 [HideInInspector][SerializeField] 14 public List<CharacterEditorNormalAttackData> m_attackNmDatas = new List<CharacterEditorNormalAttackData>(); 15 public CharacterEditorAttackData IsFrameDataExist(int frame) 16 { 17 foreach (CharacterEditorAttackData dt in m_attackDatas) 18 { 19 if (frame == dt.m_iFrame) 20 return dt; 21 } 22 return null; 23 } 24 25 public bool AddFrameData(int newFrame) 26 { 27 CharacterEditorAttackData dt = CharacterEditorAttackData.CreateData(CharacterAttackType.BOMB); 28 if (dt == null) 29 return false; 30 dt.m_iFrame = newFrame; 31 dt.m_attackType = CharacterAttackType.BOMB; 32 this.m_attackDatas.Add(dt); 33 return true; 34 } 35 36 public bool RemoveFrameData(int oldFrame) 37 { 38 CharacterEditorAttackData dt = this.IsFrameDataExist(oldFrame); 39 if (dt == null) 40 return false; 41 this.m_attackDatas.Remove(dt); 42 return true; 43 } 44 45 public void ChangeFrameData(int index , CharacterAttackType attType) 46 { 47 CharacterEditorAttackData dt = this.m_attackDatas[index]; 48 int iFrame = dt.m_iFrame; 49 if (attType != dt.m_attackType) 50 { 51 dt = CharacterEditorAttackData.CreateData(attType); 52 dt.m_iFrame = iFrame; 53 dt.m_attackType = attType; 54 this.m_attackDatas[index] = dt; 55 } 56 } 57 58 public int GetNewFrame() 59 { 60 if (m_attackDatas.Count == 0) 61 return 0; 62 int frame = -1; 63 foreach (CharacterEditorAttackData dt in m_attackDatas) 64 { 65 if (frame <= dt.m_iFrame) 66 frame = dt.m_iFrame; 67 } 68 if (frame == this.m_totFrame) 69 return -1; 70 return frame + 1; 71 } 72 73 public bool IsLegalFrame(int frame) 74 { 75 if (IsFrameDataExist(frame) != null || frame < 0 || frame > m_totFrame) 76 return false; 77 return true; 78 } 79 80 public bool UpdateFramesSz() 81 { 82 int count = m_attackDatas.Count; 83 for (int i = count - 1; i > m_totFrame - 1; i--) 84 { 85 this.m_attackDatas.RemoveAt(i); 86 } 87 return true; 88 } 89 90 public void Init(CharacterStateType state) 91 { 92 m_animationName = "11111"; 93 m_totFrame = 12; 94 m_stateType = state; 95 m_oldStateType = state; 96 m_attackDatas.Clear(); 97 m_attackBmDatas.Clear(); 98 m_attackNmDatas.Clear(); 99 } 100 101 void ISerializationCallbackReceiver.OnBeforeSerialize() 102 { 103 if (m_attackBmDatas == null || m_attackNmDatas == null || m_attackDatas == null) 104 return; 105 m_attackBmDatas.Clear(); 106 m_attackNmDatas.Clear(); 107 foreach(CharacterEditorAttackData item in m_attackDatas) 108 { 109 switch(item.m_attackType) 110 { 111 case CharacterAttackType.BOMB:m_attackBmDatas.Add((CharacterEditorBombAttackData)item); break; 112 case CharacterAttackType.NORMAL: m_attackNmDatas.Add((CharacterEditorNormalAttackData)item);break; 113 } 114 } 115 } 116 117 void ISerializationCallbackReceiver.OnAfterDeserialize() 118 { 119 if (m_attackBmDatas == null || m_attackNmDatas == null || m_attackDatas == null) 120 return; 121 m_attackDatas.Clear(); 122 foreach (CharacterEditorAttackData item in m_attackBmDatas) 123 { 124 m_attackDatas.Add(item); 125 } 126 foreach (CharacterEditorAttackData item in m_attackNmDatas) 127 { 128 m_attackDatas.Add(item); 129 } 130 } 131 }
对于上述的数据结构,可能会有疑问,首先为什么需要实现 ISerializationCallbackReceiver , 和这个接口的作用;为什么需要用到三个List结构。首先,来
理解一下 ISerializationCallbackReceiver 这个接口的作用 。 关于这个 接口介绍的博文 ,其实看完这个博文,我还是没有理解作者想讲的意思,后来自己翻阅
了其他资料,
void ISerializationCallbackReceiver.OnBeforeSerialize()
这个接口的作用就是 序列化快开始了 , 你可以在序列化开始前做些操作 。 比如C#结构中Dict是不能够序列化的,所以在开始前可以将Dict的键和值都保存在List中 这样就达到了序列化的目的。
void ISerializationCallbackReceiver.OnAfterDeserialize()
这个接口的作用就是 反序列化结束了 , 你可以在反序列化后做一些操作 。还是上面的例子,我们可以在反序列化后从list中拿到对应的数据,把List中的键和值存储在对应的Dict中。
关于两个接口的作用已经讲完了,来解决下为什么要用那么多List的原因,首先先说一下我遇到的问题,之前访问子类和存储都是通过new子类对象后,用父类的指针保存在List上,所以就会存在问题,在序列化时,序列化的是父类而不是子类,在反序列化后,就会出现数据丢失。所以需要在上述的两个接口做一个序列化前和反序列化后的数据操作,保证数据的正确。
通过上述,我们可以得到可靠的序列化流程,接下来就可以就可以自定义编辑器了,编辑器代码相对简单,由于界面主要在InspectorUI上操作,就写在OnInspector上。其实在写之前对于OnInspectorUI这个函数的调用是有疑问的,后来亲自实践了一下,发现只有有UI发生更改时或者切入切出(调到另一个)显示对象都会调用,代码如下:
1 using UnityEngine; 2 using UnityEditor; 3 using System.Collections; 4 using TKGame; 5 using System.Collections.Generic; 6 using System; 7 8 [CustomEditor(typeof(CharacterEditorData))] 9 public class CharacterEditor : Editor { 10 11 enum AddState { NONE, NEWSTATE, FULLSTATE }; 12 public const string TAG = "[CharacterEditor]"; 13 public CharacterEditorData m_chaEditData = null; 14 private AddState m_addState; 15 public void OnEnable(){ 16 m_chaEditData = target as CharacterEditorData; 17 if (m_chaEditData == null) 18 return; 19 m_addState = AddState.NONE; 20 } 21 22 public override void OnInspectorGUI() 23 { 24 if (m_chaEditData == null) 25 { 26 PrintLog("the chaEditorData is null"); 27 return; 28 } 29 m_chaEditData.m_id = EditorGUILayout.IntField("PlayerID: ", m_chaEditData.m_id); 30 m_chaEditData.m_resID = EditorGUILayout.IntField("PlayerResourceID:", m_chaEditData.m_resID); 31 m_chaEditData.m_defaultName = EditorGUILayout.TextField("PlayerDefaultName: ", m_chaEditData.m_defaultName); 32 m_chaEditData.m_scale = EditorGUILayout.FloatField("PlayerScale:", m_chaEditData.m_scale); 33 m_chaEditData.m_walkSpeedX = EditorGUILayout.IntField("PlayerXSpeed: ", m_chaEditData.m_walkSpeedX); 34 m_chaEditData.m_walkSpeedY = EditorGUILayout.IntField("PlayerYSpeed:", m_chaEditData.m_walkSpeedY); 35 m_chaEditData.m_hatred = EditorGUILayout.FloatField("PlayerHatred:", m_chaEditData.m_hatred); 36 m_chaEditData.m_lowFireAngle = EditorGUILayout.FloatField("PlayerLowFireAngle:", m_chaEditData.m_lowFireAngle); 37 m_chaEditData.m_higFireAngle = EditorGUILayout.FloatField("PlayerHigFireAngle:", m_chaEditData.m_higFireAngle); 38 m_chaEditData.m_fireRange = EditorGUILayout.IntField("PlayerFireRange:", m_chaEditData.m_fireRange); 39 m_chaEditData.m_weaponPosition = EditorGUILayout.Vector2Field("PlayerWeaponPos", m_chaEditData.m_weaponPosition); 40 m_chaEditData.m_beAttackBoxMinX = EditorGUILayout.IntField("PlayerBAtkBoxMinX:", m_chaEditData.m_beAttackBoxMinX); 41 m_chaEditData.m_beAttackBoxMinY = EditorGUILayout.IntField("PlayerBAtkBoxMinY:", m_chaEditData.m_beAttackBoxMinY); 42 m_chaEditData.m_beAttackBoxMaxX = EditorGUILayout.IntField("PlayerBAtkBoxMaxX:", m_chaEditData.m_beAttackBoxMaxX); 43 m_chaEditData.m_beAttackBoxMaxY = EditorGUILayout.IntField("PlayerBAtkBoxMaxY:", m_chaEditData.m_beAttackBoxMaxY); 44 if (GUILayout.Button("Add New State")) 45 { 46 if (m_chaEditData.IsAllStateExist()) 47 m_addState = AddState.FULLSTATE; 48 else 49 m_addState = AddState.NEWSTATE; 50 } 51 EditorGUILayout.Space(); 52 if (m_addState == AddState.FULLSTATE) 53 EditorGUILayout.LabelField("all states is used"); 54 else if (m_addState == AddState.NEWSTATE) 55 { 56 CharacterStateType newestState = m_chaEditData.GetNewestState(); 57 m_chaEditData.AddNewState(newestState); 58 m_addState = AddState.NONE; 59 } 60 61 EditorGUILayout.Space(); 62 ///Debug.Log("yes"); 63 for (int index = 0; index < m_chaEditData.m_lsStates.Count; index++) 64 { 65 CharacterEditorData.CharacterEditorStateData chaState = m_chaEditData.m_lsStates[index]; 66 //Debug.Log(EditorGUILayout.EnumPopup("state:", chaState.m_newState)); 67 CharacterStateType state = (CharacterStateType)EditorGUILayout.EnumPopup("state:", chaState.m_stateType); 68 m_chaEditData.ChangeByState(chaState, state); 69 chaState.m_animationName = EditorGUILayout.TextField("AnimationName:", chaState.m_animationName); 70 int totFrame = EditorGUILayout.IntField("TotalFrame:", chaState.m_totFrame); 71 if (totFrame != chaState.m_totFrame) 72 { 73 chaState.m_totFrame = totFrame; 74 chaState.UpdateFramesSz(); 75 } 76 if (chaState.m_stateType == CharacterStateType.ATTACK) 77 { 78 if (GUILayout.Button("Add Frame Data", GUILayout.MaxWidth(130), GUILayout.MaxHeight(20))) 79 { 80 int newFrame = chaState.GetNewFrame(); 81 //Debug.Log(newFrame); 82 if (newFrame != -1) 83 { 84 chaState.AddFrameData(newFrame); 85 } 86 } 87 EditorGUILayout.Space(); 88 for (int i = 0; i < chaState.m_attackDatas.Count; i++) 89 { 90 CharacterEditorData.CharacterEditorAttackData frameData = chaState.m_attackDatas[i]; 91 int frame = EditorGUILayout.IntField("Frame:", frameData.m_iFrame); 92 if (chaState.IsLegalFrame(frame)) 93 { 94 //Debug.Log(frame); 95 frameData.m_iFrame = frame; 96 } 97 CharacterAttackType attackType = (CharacterAttackType)EditorGUILayout.EnumPopup("AttackType:", frameData.m_attackType); 98 chaState.ChangeFrameData(i , attackType); 99 EditorGUILayout.Space(); 100 if (frameData.m_attackType == CharacterAttackType.BOMB) 101 { 102 CharacterEditorData.CharacterEditorBombAttackData bomb = (CharacterEditorData.CharacterEditorBombAttackData)frameData; 103 bomb.m_bombCofigID = EditorGUILayout.IntField("BombConfigID:", bomb.m_bombCofigID); 104 bomb.m_damage = EditorGUILayout.IntField("Damge:", bomb.m_damage); 105 bomb.m_centerDamage = EditorGUILayout.IntField("CenterDamage:", bomb.m_centerDamage); 106 } 107 if (GUILayout.Button("Remove this Frame")) 108 { 109 chaState.RemoveFrameData(frameData.m_iFrame); 110 } 111 EditorGUILayout.Space(); 112 EditorGUILayout.Space(); 113 } 114 } 115 if (GUILayout.Button("remove this state")) 116 { 117 m_chaEditData.RemoveOldState(index); 118 } 119 EditorGUILayout.Space(); 120 EditorGUILayout.Space(); 121 } 122 EditorUtility.SetDirty(m_chaEditData); 123 } 124 125 private void PrintLog(string str) 126 { 127 Debug.Log(TAG+" "+ str); 128 } 129 }
最后一句的SetDirty表示当前的数据对象有更改,可以通知UI刷新。