本章要來實做一些 Custom Inspector 的範例,我們會針對可序列化陣列的 Inspector 做出更人性化的呈現。
序列化 (Serialization) 是 Unity Editor 裡非常核心的程式。當你在使用 Unity 很多方便的功能時,它幾乎都是架構在序列化程式之上的。由其你的程式碼是繼承自
MonoBehaviour ,那些能夠在 Inspector 裡看到的可調整欄位,都是序列化的功勞啊!在 Unity 中,設為 Public 的變數通常都會被設為可序列化的。但有的時候,我們自行設計的類別,因為 Unity 也不知道我們寫了些什麼,它當然就無法將它做序列化的動作。例如,下面這段程式,
List<BrickType> 就算設成 Public,Unity 也不會把它做序列化。它在 Inspector 視窗中當然就看不到可調整欄位的身影啦。但如果在類別前加上 [System.Serializable],告訴 Unity 這個類別是可以做序列化的。那麼,它就會在 Inspector 視窗中出現一個陽春版的可調整欄位。
using UnityEngine;
using System.Collections;
//如果此類別有被拿來做成 Public Array 或 Public 變數
//[System.Serializable] 會叫 Unity 去對此類別做序列化
[System.Serializable]
public class BrickType
{
public string Name;
public Color HitColor;
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class BlockController : MonoBehaviour
{
public List<BrickType> Types = new List<BrickType>();
public string typeName;
public LayerMask whatIsPlayer;
private BoxCollider2D m_boxCollider2D;
// 以下省略......
}
其實本篇的重點不在可序列化,而是這個可序列化陣列
List<BrickType>的 Inspector 欄位。因為它說實在的不太好用。如果你有使用過它,應該知道它必須先填寫 List Size 的大小,填完後才會長出相對應個數的參數集合給你填,使用上很不直覺。而且你如果不小心手殘把已經設定好的 List Size 改成 0,你原本參數集合裡的值通通會消失不見,必須要一個個再把它填回來。那真的是很崩潰!所以,本篇的任務就是要利用 Editor Script 來修改可序列化陣列呈現的方式。
using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Reflection;
[CustomEditor(typeof(BlockController))]
public class BlockControllerEditor : Editor {
BlockController m_Target;
public override void OnInspectorGUI()
{
m_Target = (BlockController)target;
// DrawDefaultInspector() 會將原本 Inspector 上有的東西先畫出來。
// 這麼一來可能會造成 Inspector 中的 Types 欄位被畫兩次(Default一次,下面 Editor Script 又會在畫一次)
// 因此,你可以在原先 public List<BrickType> Types 的欄位前加入 [HideInInspector],把 Default Inspector 關掉
DrawDefaultInspector();
DrawTypesInspector();
}
void DrawTypesInspector()
{
GUILayout.Space(5);
GUILayout.Label("State", EditorStyles.boldLabel);
for(int i=0; i< m_Target.Types.Count; i++)
{
DrawType(i);
}
DrawAddTypeButton();
}
void DrawType(int index)
{
if (index < 0 || index >= m_Target.Types.Count)
return;
GUILayout.BeginHorizontal();
{
GUILayout.Label("Name", EditorStyles.label, GUILayout.Width(50));
// BeginChangeCheck() 用來檢查在 BeginChangeCheck() 和 EndChangeCheck() 之間是否有 Inspector 變數改變
EditorGUI.BeginChangeCheck();
string newName = GUILayout.TextField(m_Target.Types[index].Name, GUILayout.Width(120));
Color newColor = EditorGUILayout.ColorField(m_Target.Types[index].HitColor);
m_Target.Types[index].Name = newName;
m_Target.Types[index].HitColor = newColor;
// 如果 Inspector 變數有改變,EndChangeCheck() 會回傳 True,才有必要去做變數存取
if (EditorGUI.EndChangeCheck())
{
// 在修改之前建立 Undo/Redo 記錄步驟
Undo.RecordObject(m_Target, "Modify Types");
m_Target.Types[index].Name = newName;
m_Target.Types[index].HitColor = newColor;
// 每當直接修改 Inspector 變數,而不是使用 serializedObject 修改時,必須要告訴 Unity 這個 Compoent 已經修改過了
// 在下一次存檔時,必須要儲存這個變數
EditorUtility.SetDirty(m_Target);
}
if (GUILayout.Button("Remove"))
{
// 系統會 "登" 一聲
EditorApplication.Beep();
// 顯示對話框功能(帶有 OK 和 Cancel 兩個按鈕)
if (EditorUtility.DisplayDialog("Really?", "Do you really want to remove the state '" + m_Target.Types[index].Name + "'?", "Yes", "No") == true)
{
m_Target.Types.RemoveAt(index);
EditorUtility.SetDirty(m_Target);
}
}
}
GUILayout.EndHorizontal();
}
void DrawAddTypeButton()
{
if (GUILayout.Button("Add new State", GUILayout.Height(30)))
{
Undo.RecordObject(m_Target, "Add new Type");
m_Target.Types.Add(new BrickType { Name = "New State" });
EditorUtility.SetDirty(m_Target);
}
}
}
沒有留言:
張貼留言