關於 web service, unity, blogger 等軟體工程筆記

Unity5 Editor ReorderableList

Edit icon 沒有留言
Unity

ReorderableList

整理使用 Unity ReorderableList 筆記,希望以後有範本可以直接複製貼上,不用每次都要重新找文章,依樣畫葫蘆重做一次。

// Path: /Scripts/ShopMenu.cs
using UnityEngine;

public class ShopMenu : MonoBehaviour
{
   public Drink[] Drinks = new Drink[0];

   [System.Serializable]
   public struct Drink
   {
     public string Name;
     public float Price;
     public Color Color;
   }
}

參考上述範例程式碼,該程式碼在 Unity Editor 中,預設的呈現編輯器如下圖:

預設的編輯器

預設的編輯器

這樣的編輯器不是不好,只是很陽春且不好瀏覽編輯,此外也不容易修改各個元素順序。 在 Unity 4.5 後,在 UnityEditor.Internal 提供一個非常好的 class,Reorderablelist,讓我們可以修改其陽春編輯器如下圖,使用近似於表格編輯方式,而且可以輕易的調整順序:

近表格式的編輯器

近表格式的編輯器

Reorderablelist 屬於 UnityEngineInternal namespace,該 class 並沒有任何的使用說明文件,不過總是有人分享其使用方法 [0]。該編輯器程式碼如下,記得 Drinks 欄位(Field)要加上 [HideInInspector],來隱藏預設的編輯器。

// Path: /Editor/ShopMenuEditor.cs
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;

[CustomEditor(typeof(ShopMenu))]
[CanEditMultipleObjects]
public class ShopMenuEditor : Editor
{
   ReorderableList list;

   void OnEnable()
   {
      var serProp = this.serializedObject.FindProperty("Drinks");
      this.list = new ReorderableList(serProp.serializedObject, serProp, true, true, true, true);
      this.list.drawElementCallback = this.DrawListElement;
      this.list.drawHeaderCallback = this.DrawListHeader;
   }

   void DrawListHeader(Rect rect)
   {
      var spacing = 10f;
      var arect = rect;
      arect.height = EditorGUIUtility.singleLineHeight;

      arect.x += 15;
      arect.width = 100;
      EditorGUI.LabelField(arect, "Name");
      arect.x += arect.width + spacing ;

      arect.width = 100;
      EditorGUI.LabelField(arect, "Price");
      arect.x += arect.width + spacing;

      arect.width = 100;
      EditorGUI.LabelField(arect, "Color");

   }

   void DrawListElement(Rect rect, int index, bool isActive, bool isFocused)
   {
      var spacing = 10f;
      var arect = rect;
      var serElem = this.list.serializedProperty.GetArrayElementAtIndex(index);
      arect.height = EditorGUIUtility.singleLineHeight;

      arect.width = 100;
      EditorGUI.PropertyField(arect, serElem.FindPropertyRelative("Name"), GUIContent.none);
      arect.x += arect.width + spacing;

      arect.width = 100;
      EditorGUI.PropertyField(arect, serElem.FindPropertyRelative("Price"), GUIContent.none);
      arect.x += arect.width + spacing;

      arect.width = 100;
      EditorGUI.PropertyField(arect, serElem.FindPropertyRelative("Color"), GUIContent.none);
   }

   public override void OnInspectorGUI()
   {
      base.OnInspectorGUI();

      this.serializedObject.Update();

      var property = list.serializedProperty;
      property.isExpanded = EditorGUILayout.Foldout(property.isExpanded, property.displayName);
      if (property.isExpanded)
      {
         this.list.DoLayoutList();
      }

      this.serializedObject.ApplyModifiedProperties();
   }
}
  • Line 6: 標示此為 ShopMenu 物件客製化的編輯器
  • Line 7: 允許多物件同時編輯
  • Line 14: 使用 SerializeObject 取得 SerializedProperty,自訂編輯器必要使用這一個方便的物件
  • Line 24: Unity 編輯器預設的單行高度
  • Line 44: GetArrayElementAtIndex 取得第 n 個元素的 SerializedProperty,再使用該 property 去編輯該元素的 SerializedProperty
  • Line 48: 使用 EditorGUI.PropertyField 來繪製該欄位的編輯功能
  • Line 59: 自訂編輯器的起手式,Override 實作自己的編輯器
  • Line 61: 繪製預設的編輯器
  • Line 63: 開始繪製編輯器前,先更新 SerializeObject 的資料
  • Line 72: 結束繪製編輯器後,將編輯器所做的修改,寫回 SerializeObject

ReorderableList 實際上提供許多 Callback functions 可以自訂更多編輯器項目,以上範例僅用到 drawElementCallback 以及 drawHeaderCallback 來繪製,其他更進一步說明,請參考 [0]

更簡單的使用方法

ReorderableList 協助建立可重新排序的編輯器,功能是非常好用。但對於簡單的結構來說,要寫這麼多程式碼來達成這樣的編輯器,要處理排版(Layout)設定欄位寬度就一個麻煩,該些程式碼顯得又臭很長。只是希望改成行列(Rows)顯示,外加一個標頭列(Header),像是表格方式編輯方式而已。而且每增加一個參數就得要修改編輯器程式碼一次,有沒有自動化的方法?

思考半天,總算是弄出一個 Utility,用於建立能夠自動排版的 ReorderableList,程式碼以及使用方法請參考放在 Github 的 ReorderableListUtility 頁面。

透過 ReorderableListUtility,現在只要幾行就可以建立表格式編輯頁面。

// Path: /Editor/ShopMenuEditor.cs
using UnityEditor;
using UnityEditorInternal;

[CustomEditor(typeof(ShopMenu))]
[CanEditMultipleObjects]
public class ShopMenuEditor : Editor
{
   private ReorderableList list;

   public override void OnInspectorGUI()
   {
      base.OnInspectorGUI();

      this.serializedObject.Update();
      ReorderableListUtility.DoLayoutListWithFoldout(this.list);
      this.serializedObject.ApplyModifiedProperties();
   }

   private void OnEnable()
   {
      this.list = ReorderableListUtility.CreateAutoLayout(this.serializedObject.FindProperty("Drinks"));
   }
}

但這還是有點麻煩,如果 PropertyDrawer 能夠針對 Array 進行繪製處理,那也許可以改用 PropertyAttribute 來完成…,像以下這個樣子:

// Path: /Scripts/ShopMenu.cs
using UnityEngine;

public class ShopMenu : MonoBehaviour
{
   [TableStyleArrayEditor]
   public Drink[] Drinks = new Drink[0];

   // ...
}

Reference

沒有留言: