終於抽出時間來做些方便的小工具,其實也是為了要幫助自己快速優化專案的東西。
功能:可以對所有物件上所指定的script 同時替換成另一個script。但是限制是繼承同一個script~(例如NGUISPritePlus(繼承NGUISprite)可以取代NGUISprite)
功能簡介:
1.確認要置換的Script跟物件後,會讀出符合同一Type/Class的script物件,並列表。
2.可勾選需要轉換/替代的物件scrip。
3.列表內列舉的物件名稱都可以點選,點選後,會在Hierarchy內指向該物件。
希望改進的地方:
1.可以拉入unity原生的component。(因為我常常勾錯物件……想要復原覺得頭痛)
2.加入復原(Undo)的功能。(需要思考一下架構)
本文會一一解釋製作的思路,如果不想看這麼多廢言,可以直接拉到最後下載script,如果要轉載,請務必注明來自於熊阿寶的工作MEMO哦。
希望這個小工具可以幫助到大家~有覺得可以優化的部分也歡迎大家給予意見!
第一步:建立Window
[MenuItem("Tools/ConvertComponents")]
public static void ShowWindow()
{
EditorWindow.GetWindow(typeof(ComponentConvertTool), false, "Component Convert Tool");
}
1.在選單中建立這個工具的選項。2.創建這個工具的視窗。
第二步:宣告變數、建立函式
private MonoScript m_TargetScript;
private GameObject m_TargetGameObject;
private string[] m_GameObjectStrAry;
private bool[] m_GameObjectToogleAry;
private Object[] m_ObjectAry;
private int[] m_ObjectHashCodeAry;
private string m_LogStr;
private int m_GameObjectCount = 0;
private GUIStyle m_ObjectUnchoosedStyle, m_ObjectChoosedStyle;
private GUIStyle m_ObjectListStyle;
private void OnGUI()
{
//TO DO...
}
private void DoSomething(string MethodName)
{
//TO DO...
}
public void ShowTargetObject()
{
//TO DO...
}
public void DoConvert()
{
//TO DO...
}
變數:- m_TargetScript:取代的Script
- m_TargetGameObject:要被取代Script的物件,可以是父層物件
- m_GameObjectStrAry:準備拿來存物件名稱
- m_GameObjectToogleAry:拿來存是否勾選物件
- m_ObjectAry:準備拿來存物件
- m_ObjectHashCodeAry:準備拿來存物件的Hash Code,方便在Hierarchy內跳到指定物件位置
- m_LogStr:顯示LOG用
- m_GameObjectCount:物件數量,我習慣存起來用
- m_ObjectUnchoosedStyle, m_ObjectChoosedStyle, m_ObjectListStyle:元件樣式
- OnGUI():繪製視窗內的元件
- DoSomething(string MethodName):取得指定Script Base Type後,接下來要使用哪種功能,算是一種轉運站
- ShowTargetObject
():顯示有哪些物件掛著的Script符合指定的Type - DoConvert
():改變Script
第二步:繪製視窗元件
OnGUI()內的script:
if (m_ObjectListStyle == null)
{
m_ObjectListStyle = new GUIStyle(GUI.skin.box);
}
if (m_ObjectUnchoosedStyle == null)
{
m_ObjectUnchoosedStyle = new GUIStyle(GUI.skin.label);
m_ObjectUnchoosedStyle.alignment = TextAnchor.MiddleLeft;
m_ObjectUnchoosedStyle.normal.textColor = Color.gray;
}
if (m_ObjectChoosedStyle == null)
{
m_ObjectChoosedStyle = new GUIStyle(GUI.skin.label);
m_ObjectChoosedStyle.alignment = TextAnchor.MiddleLeft;
m_ObjectChoosedStyle.normal.textColor = Color.white;
}
已經建立過一次的樣式我習慣就不要重複建立了,因此會做條件式處理樣式。
EditorGUI.BeginChangeCheck();
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Target script:");
m_TargetScript = EditorGUILayout.ObjectField(m_TargetScript, typeof(MonoScript), true) as MonoScript;
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
GUILayout.Label("Be Convert GameObject/Group: ");
m_TargetGameObject = EditorGUILayout.ObjectField(m_TargetGameObject, typeof(GameObject), true) as GameObject;
EditorGUILayout.EndHorizontal();
顯示必要的兩個項目:替換的Script、要被替換的GameObject。
if (EditorGUI.EndChangeCheck())
{
if (m_TargetScript != null)
{
if (m_TargetGameObject != null)
{
DoSomething("ShowTargetObject");
}
else
{
m_LogStr = "";
m_GameObjectStrAry = null;
m_GameObjectCount = 0;
}
}
else
{
m_LogStr = "";
m_GameObjectStrAry = null;
m_GameObjectCount = 0;
}
}
DoSomething("ShowTargetObject");希望可以顯示,在指定的GameObject下含有可以被替換的Script的子物件列表。
並且進行數量的計算。
GUILayout.Label("Include GameObject List:");
if (m_GameObjectCount > 0)
{
if (m_GameObjectToogleAry == null || m_GameObjectToogleAry.Length != m_GameObjectCount)
{
m_GameObjectToogleAry = new bool[m_GameObjectCount];
for (int i = 0; i < m_GameObjectCount; i++)
m_GameObjectToogleAry[i] = true;
}
EditorGUILayout.BeginVertical(m_ObjectListStyle);
for (int i = 0; i < m_GameObjectCount; i++)
{
EditorGUILayout.BeginHorizontal();
m_GameObjectToogleAry[i] = EditorGUILayout.Toggle(m_GameObjectToogleAry[i], GUILayout.Width(20));
if (GUILayout.Button(m_GameObjectStrAry[i], m_GameObjectToogleAry[i] ? m_ObjectChoosedStyle : m_ObjectUnchoosedStyle))
{
Selection.activeInstanceID = m_ObjectHashCodeAry[i];
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
if (GUILayout.Button("Convert"))
{
m_LogStr = "";
if (m_TargetScript == null || m_TargetGameObject == null)
{
GUILayout.Label("ERROR! Targets are NULL", EditorStyles.boldLabel);
}
else
{
DoSomething("DoConvert");
}
}
}
當有同樣/繼承指定script的物件時,就會顯示列表。m_GameObjectToogleAry[i] = EditorGUILayout.Toggle(m_GameObjectToogleAry[i], GUILayout.Width(20));項目可以被選擇要置換或是不置換。
GUILayout.Button(m_GameObjectStrAry[i], m_GameObjectToogleAry[i] ? m_ObjectChoosedStyle : m_ObjectUnchoosedStyle)加上「點擊項目即可在Hierarchy內跳到該物件」、「被選擇時項目顏色不同」的要素,將項目顯示設置成button。
Selection.activeInstanceID = m_ObjectHashCodeAry[i];利用物件的HashCode來找到Hierarchy內的物件。
GUILayout.Button("Convert")增加一個叫做Convert的Button,並且在按下時執行行為。
第三步:開始DoSomething
private void DoSomething(string MethodName)
{
System.Type t = m_TargetScript.GetClass().BaseType;
var method = typeof(ComponentConvertTool).GetMethod(MethodName);
if (method != null)
{
var fooRef = method.MakeGenericMethod(t);
fooRef.Invoke(this, null);
}
else
{
Debug.Log("ERROR!Can't Find this Method:" + MethodName);
}
}
這邊比較麻煩的是呼叫泛型函式,由於我們取得的Type變數沒有辦法直接放進泛型內,因此要使用彈性的方是來進行呼叫,使用的是Reflection技術,生成動態物件後再利用此物件呼叫。- 需要先得到指定Script的BaseType,才能去取得替換物件內所有子物件有沒有相關的script/Component。
- 為了可以使用泛型函式,需要使用GetMethod(string)來生成此函式的物件。
- 確認該物件存在,利用此物件來呼叫泛型函式。
顯示物件列表
public void ShowTargetObject()
{
T[] oAry = m_TargetGameObject.GetComponentsInChildren(true);
m_GameObjectCount = oAry.Length;
m_GameObjectStrAry = new string[m_GameObjectCount];
m_ObjectAry = new Object[m_GameObjectCount];
m_ObjectHashCodeAry = new int[m_GameObjectCount];
for (int i = 0; i < m_GameObjectCount; i++)
{
m_ObjectAry[i] = oAry[i] as Object;
m_ObjectHashCodeAry[i] = oAry[i].GetHashCode();
m_GameObjectStrAry[i] += oAry[i].ToString();
}
if (oAry.Length == 0)
{
m_LogStr = "NO Script Match BaseType("+typeof(T).ToString()+")";
}
}
這邊的code相對簡單,不多贅述。進行置換
public void DoConvert()
{
for (int i = 0;i < m_GameObjectCount; i++)
{
if (m_GameObjectToogleAry[i])
{
Object o = m_ObjectAry[i];
var so = new SerializedObject(o);
so.Update();
so.FindProperty("m_Script").objectReferenceValue = m_TargetScript;
so.ApplyModifiedProperties();
}
}
DoSomething("ShowTargetObject");
}
- 取得物件,並且將其宣告成一個SerializedObject,方便作FindProperty。
- Update()可作可不作。
- 尋找該物件名為m_Script的變數,這個可以在Inspector上看到,指定Component是哪個script。接著就是替換這個變數的Reference。
- 最後,一定要使用ApplyModifiedProperties()進行資料更動進行寫入。
以上,其實並不是很複雜的code,不過在呼叫泛型函式時確實遇到了瓶頸,這還是第一次用Reflection來呼叫泛型函式XD
Script 位置:ComponentConvertTool
沒有留言:
張貼留言
歡迎大家留言提問,我會答的都會盡力回答!
如果太久沒出現回應就是我又忘記回來看留言了TAT