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

Unity Editor: Preview window,繪製自定組件的貼圖預覽編輯器介面

Edit icon 沒有留言

驗證需求,最近在專案中,我們建立能根據語言設定,自動切換不同語系貼圖的功能。在這些組件 (Component) 中,我們必須預先設定多張貼圖,怎麼在 Unity 編輯器中檢查這些貼圖設定是否正確,變成一個待解的問題。

在本文章中,紀錄如何使用 Unity Preview 機制,建立自訂該組件專屬的預覽視窗,讓設計人員能在編輯器中確認資料是否正確。

預覽介面,使用 ObjectPreview 或是 Editor

在 Unity 中可以使用 Editor 或者是 ObjectPreview 來建置客製化預覽,Editor 通常用於其組件 (Component) 在 inspector 編輯器的樣子,客製化讓操作者更容易調整組件參數。ObjectPreview 僅僅針對組件建立預覽,而不能修改其編輯器。

由於沒計畫修改組件的編輯器,僅要客製化預覽功能,故使用 ObjectPreview,後續範例都是用 ObjectPreview。

關於範例資料結構

自訂組件的資料結構,建立 private struct config,放置對應語言切換哪張 sprite:

using UnityEngine;
using UnityEngine.UI;

[RequireComponent(typeof(Image))]
public partial class LanguageImageSwitcher : MonoBehaviour
{
   [SerializeField]
   Config[] configs = new Config[0];

   public Config[] Configs
   {
      get
      {
         return this.configs;
      }

      set
      {
         if (value == null)
         {
            throw new System.ArgumentNullException();
         }
         this.configs = value;
      }
   }

   [System.Serializable]
   public struct Config
   {
      [SerializeField]
      Language language;

      [SerializeField]
      Sprite sprite;

      public Language Language
      {
         get { return this.language; }
         set { this.language = value; }
      }

      public Sprite Sprtie
      {
         get { return this.sprite; }
         set { this.sprite = value; }
      }
   }
}

public enum Language
{
   English,
   TraditionalChinese,
   Japanese,
}

預設編輯頁面:

Inspector

資料編輯預設的介面

使用 ObjectPreview 建立客製化預覽介面

參考範例以及官方文件後,完成第一版本的預覽:

Custom preview v1

第一版本的預覽介面

該編輯器程式碼以及註解:

using UnityEngine;
using UnityEditor;

[CustomPreview(typeof(LanguageImageSwitcher))]
public class LanguageImageSwitcherPreview : ObjectPreview
{
   Language displayLanguage;

   public override bool HasPreviewGUI()
   {
      return true;
   }

   public override GUIContent GetPreviewTitle()
   {
      return new GUIContent("Language Images");
   }

   public override void OnPreviewSettings()
   {
      base.OnPreviewSettings();
      this.displayLanguage = (Language)EditorGUILayout.EnumPopup(this.displayLanguage);
   }

   public override void OnInteractivePreviewGUI(Rect r, GUIStyle background)
   {
      var target = this.target as LanguageImageSwitcher;
      var configs = target.Configs;

      foreach (var config in configs)
      {
         if (config.Language == this.displayLanguage)
         {
            if (config.Sprtie != null)
            {
               EditorGUI.DrawTextureTransparent(r, config.Sprtie.texture, ScaleMode.ScaleToFit);
            }
            return;
         }
      }
   }
}
  • Line 4: CustomPreview 標示此預覽是為哪個類型
  • Line 9: HasPreviewGUI 回傳是否有預覽,可以根據參數進行判斷,在這個範例中永遠都會有
  • Line 14: GetPreviewTitle 回傳該預覽標題內容
  • Line 22: OnPreviewSettings 繪製預覽功能列,在這個範例中畫出語言選擇 (EnumPopup)
    Enumpop

    Enumpop 選取顯示語言的貼圖

  • Line 25: 有兩種模式 OnPreviewGUI 以及 OnInteractivePreviewGUI,後者能支援使用者操作互動 (例如滑鼠右建額外的 Context-menu),雖然我們沒有額外的互動機制,但我們還是使用 OnInteractivePreviewGUI
  • Line 27: target 表示目前 inspector 選擇的目標
  • Line 36: 取得目前語言所對應 sprite,並且畫在預覽畫面區域 Rect r

再調整預覽畫面,自行計算繪製區域

但這個版本不怎麼方便除錯,檢查所有語言需要一個一個點來看,實在是太麻煩。因此我們修改一下 Preview,把所有語言對應的貼圖,直接塞在同一個預覽畫面中:

Custom preview v2

第二版本的預覽介面

其程式碼修改如下,根據資料以及參數計算每個設定 Rect 大小:

using UnityEngine;
using UnityEditor;

[CustomPreview(typeof(LanguageImageSwitcher))]
public class LanguageImageSwitcherPreview : ObjectPreview
{
   Language displayLanguage;
   public override bool HasPreviewGUI()
   {
      return true;
   }

   public override GUIContent GetPreviewTitle()
   {
      return new GUIContent("Language Images");
   }

   public override void OnInteractivePreviewGUI(Rect r, GUIStyle background)
   {
      var target = this.target as LanguageImageSwitcher;
      var configs = target.Configs;
      var space = 4f;
      var texturePadding = 4f;
      var rowHeight = (r.height - ((configs.Length - 1) * space)) / configs.Length;
      var labelHeight = EditorGUIUtility.singleLineHeight;
      var textureHeight = rowHeight - labelHeight - texturePadding;
      var rect = r;

      foreach (var config in configs)
      {
         // Draw label
         rect.height = labelHeight;
         EditorGUI.DropShadowLabel(rect, config.Language.ToString());
         rect.y += rect.height + texturePadding;

         // Draw texture or warning
         rect.height = textureHeight;
         if (config.Sprtie != null)
         {
            EditorGUI.DrawTextureTransparent(rect, config.Sprtie.texture, ScaleMode.ScaleToFit);
         }
         else
         {
            EditorGUI.HelpBox(rect, "No data", MessageType.Warning);
         }
         rect.y += rect.height + space;
      }
   }
}

小感

寫一個簡單的預覽工具不難,要不是這次美術圖片檔案命名一樣 (放置在不同資料夾),且還有另外一個更複雜要設定更多貼圖的組件,不然也不會有如此起心動念要製作這個工具啊。

只是…Unity 編輯器在切換選擇不同的 GameObject 時,其 Preview 都會重新設定,如此檢查也是挺不方便的。後來才注意到可以選擇多個 GameObject,其預覽介面能顯示多個物件。

Multiple object preview

選取多物件的預覽介面

另外好奇搜尋一下如何在 Preview 繪製 3D 物件,注意到這篇文章介紹所提到的 PreviewRenderUtility,從 ILSpy 中反編譯 UnityEngine 得知這個是 public class,但搜尋官方文件確沒有說明,以後如果有機會再來嘗試吧。

Reference

沒有留言: