Unity3D之Editor扩展学习——Unity3D编辑器

Unity3D提供了强大的编辑器扩展机制,在项目开发中,如果可以将一些繁琐的工作放在编辑器扩展中进行,则会大大提高效率。本文对编辑器扩展进行了一些总结,希望对有兴趣编写编辑器扩展的开发人员有所帮助。当我们编写一个编辑器扩展时,一般可以从以下四个类继承:

1 . ScriptableObject  

最常见的小功能扩展,一般不用窗口的编辑扩展,可以从这个类中继承,如以下代码所示:

 

[csharp] view plain copy

[csharp] view plain copy

  1. <span style=”font-size:18px;”>using UnityEngine;
  2. using UnityEditor;
  3. using System.Collections;
  4. public class AddChild : ScriptableObject
  5. {
  6.     [MenuItem ("GameObject/Add Child ^n")]
  7.     static void MenuAddChild()
  8.     {
  9.         Transform[] transforms = Selection.GetTransforms(SelectionMode.TopLevel | SelectionMode.OnlyUserModifiable);
  10.         foreach(Transform transform in transforms)
  11.         {
  12.             GameObject newChild = new GameObject(“_Child”);
  13.             newChild.transform.parent = transform;
  14.         }
  15.     }
  16. }</span>

这个扩展脚本从菜单的“GameObject->Add Child”启动,功能是给Hierarchy窗口中选中的对GameObject添加一个名字为“_Child”的子GameObject,这样可以免去从Hierarchy窗口的根节点拖拽新创建的GameObject到当前选中节点的麻烦,因为在Unity3D编辑器中,创建一个EmptyObject会在Hierarchy窗口的根节点出现,无论当前选中的节点对象是哪个。

 

 

2 .ScriptableWizard      

需要对扩展的参数进行设置,然后再进行功能触发的,可以从这个类进行派生。它已经定制好了四个消息响应函数,开发者对其进行填充即可。

(1) OnWizardUpdate

当扩展窗口打开时或用户对窗口的内容进行改动时,会调用此函数。一般会在这里面显示帮助文字和进行内容有效性验证;

(2)OnWizardCreate

这是用户点击窗口的Create按钮时进行的操作,从ScriptableWizard的名字可以看出,这是一种类似向导的窗口 ,而这种窗口我们在Visual Studio中经常会使用到,如下图:

 

 

 

只不过Unity3D中的ScriptableWizard窗口只能进行小于或等于两个按钮的定制,一个就是所谓的Create按钮,另外一个则笼统称之为Other按钮。ScriptableWizard.DisplayWizard这个静态函数用于对ScriptableWizard窗口标题和按钮名字的定制。

(3) OnDrawGizmos

在窗口可见时,每一帧都会调用这个函数。在其中进行Gizmos的绘制,也就是辅助编辑的线框体。Unity的Gizmos类提供了DrawRayDrawLine ,DrawWireSphere ,DrawSphere ,DrawWireCube ,DrawCubeDrawIcon ,DrawGUITexture 功能。这个功能在Unity3D 的3.4版本中测试了一下,发现没有任何Gizmos绘制出来难过

(4) OnWizardOtherButton

本文在(2) 中已经提及,ScriptableWizard窗口最多可以定制两个按钮,一个是Create,另外一个称之为Other,这个函数会在other按钮被点击时调用。下面是一个使用ScriptableWizard进行编辑扩展的例子:

 

[csharp] view plain copy

  1. <span style=”font-size:18px;”>using UnityEditor;
  2. using UnityEngine;
  3. using System.Collections;
  4. /// <summary>
  5. /// 对于选定GameObject,进行指定component的批量添加
  6. /// </summary>
  7. public class AddRemoveComponentsRecursively : ScriptableWizard
  8. {
  9.     public string componentType = null;
  10.     /// <summary>
  11.     /// 当没有任何GameObject被选中的时候,将菜单disable(注意,这个函数名可以随意取)
  12.     /// </summary>
  13.     /// <returns></returns>
  14.     [MenuItem("GameObject/Add or remove components recursively...", true)]
  15.     static bool CreateWindowDisabled()
  16.     {
  17.         return Selection.activeTransform;
  18.     }
  19.     /// <summary>
  20.     /// 创建编辑窗口(注意,这个函数名可以随意取)
  21.     /// </summary>
  22.     [MenuItem("GameObject/Add or remove components recursively...")]
  23.     static void CreateWindow()
  24.     {
  25.         // 定制窗口标题和按钮,其中第二个参数是Create按钮,第三个则属于other按钮
  26.         // 如果不想使用other按钮,则可调用DisplayWizard的两参数版本
  27.         ScriptableWizard.DisplayWizard<AddRemoveComponentsRecursively>(
  28.             ”Add or remove components recursivly”,
  29.             ”Add”, ”Remove”);
  30.     }
  31.     /// <summary>
  32.     /// 窗口创建或窗口内容更改时调用
  33.     /// </summary>
  34.     void OnWizardUpdate()
  35.     {
  36.         helpString = ”Note: Duplicates are not created”;
  37.         if (string.IsNullOrEmpty(componentType))
  38.         {
  39.             errorString = ”Please enter component class name”;
  40.             isValid = false;
  41.         }
  42.         else
  43.         {
  44.             errorString = ”";
  45.             isValid = true;
  46.         }
  47.     }
  48.     /// <summary>
  49.     /// 点击Add按钮(即Create按钮)调用
  50.     /// </summary>
  51.     void OnWizardCreate()
  52.     {
  53.         int c = 0;
  54.         Transform[] ts = Selection.GetTransforms(SelectionMode.Deep);
  55.         foreach (Transform t in ts)
  56.         {
  57.             if (t.gameObject.GetComponent(componentType) == null)
  58.             {
  59.                 if (t.gameObject.AddComponent(componentType) == null)
  60.                 {
  61.                     Debug.LogWarning(“Component of type ” + componentType + ” does not exist”);
  62.                     return;
  63.                 }
  64.                 c++;
  65.             }
  66.         }
  67.         Debug.Log(“Added ” + c + ” components of type ” + componentType);
  68.     }
  69.     /// <summary>
  70.     /// 点击Remove(即other按钮)调用
  71.     /// </summary>
  72.     void OnWizardOtherButton()
  73.     {
  74.         int c = 0;
  75.         Transform[] ts = Selection.GetTransforms(SelectionMode.Deep);
  76.         foreach (Transform t in ts)
  77.         {
  78.             if (t.GetComponent(componentType) != null)
  79.             {
  80.                 DestroyImmediate(t.GetComponent(componentType));
  81.                 c++;
  82.             }
  83.         }
  84.         Debug.Log(“Removed ” + c + ” components of type ” + componentType);
  85.         Close();
  86.     }
  87. }</span>

其运行窗口如下所示:

 

 

3 . EditorWindow

较复杂的功能,需要多个灵活的控件,实现自由浮动和加入其他窗口的tab,可以从这个类派生,这种窗口的窗体功能和Scene,Hierarchy等窗口完全一致。下面这个例子实现了GameObject的空间对齐和拷贝(也就是将GameObject A作为基准,选中其他的GameObject进行对准或空间位置拷贝),对齐和拷贝提高了了开发者摆放物件的效率;另外还有随机和噪声,后两者用于摆放大量同类物件的时候可以使用,比如一大堆散落的瓶子。

 

[csharp] view plain copy

  1. <span style=”font-size:18px;”>// /////////////////////////////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Transform Utilities.
  4. //
  5. // This window contains four useful tools for asset placing and manipulation: Align, Copy, Randomize and Add noise.
  6. //
  7. // Put this into Assets/Editor and once compiled by Unity you find
  8. // the new functionality in Window -> TransformUtilities, or simply press Ctrl+t (Cmd+t for Mac users)
  9. //
  10. // Developed by Daniel
  11. // http://www.silentkraken.com
  12. // e-mail: seth@silentkraken.com
  13. //
  14. // /////////////////////////////////////////////////////////////////////////////////////////////////////////
  15. using UnityEngine;
  16. using UnityEditor;
  17. public class TransformUtilitiesWindow : EditorWindow
  18. {
  19.     //Window control values
  20.     public int toolbarOption = 0;
  21.     public string[] toolbarTexts = {“Align”, ”Copy”, ”Randomize”, ”Add noise”};
  22.     private bool xCheckbox = true;
  23.     private bool yCheckbox = true;
  24.     private bool zCheckbox = true;
  25.     private Transform source;
  26.     private float randomRangeMin = 0f;
  27.     private float randomRangeMax = 1f;
  28.     private int alignSelectionOption = 0;
  29.     private int alignSourceOption = 0;
  30.     /// <summary>
  31.     /// Retrives the TransformUtilities window or creates a new one
  32.     /// </summary>
  33.     [MenuItem("Window/TransformUtilities %t")]
  34.     static void Init()
  35.     {
  36.         TransformUtilitiesWindow window = (TransformUtilitiesWindow)EditorWindow.GetWindow(typeof(TransformUtilitiesWindow));
  37.         window.Show();
  38.     }
  39.     /// <summary>
  40.     /// Window drawing operations
  41.     /// </summary>
  42.     void OnGUI ()
  43.     {
  44.         toolbarOption = GUILayout.Toolbar(toolbarOption, toolbarTexts);
  45.         switch (toolbarOption)
  46.         {
  47.             case 0:
  48.                 CreateAxisCheckboxes(“Align”);
  49.                 CreateAlignTransformWindow();
  50.                 break;
  51.             case 1:
  52.                 CreateAxisCheckboxes(“Copy”);
  53.                 CreateCopyTransformWindow();
  54.                 break;
  55.             case 2:
  56.                 CreateAxisCheckboxes(“Randomize”);
  57.                 CreateRandomizeTransformWindow();
  58.                 break;
  59.             case 3:
  60.                 CreateAxisCheckboxes(“Add noise”);
  61.                 CreateAddNoiseToTransformWindow();
  62.                 break;
  63.         }
  64.     }
  65.     /// <summary>
  66.     /// Draws the 3 axis checkboxes (x y z)
  67.     /// </summary>
  68.     /// <param name=”operationName”></param>
  69.     private void CreateAxisCheckboxes(string operationName)
  70.     {
  71.         GUILayout.Label(operationName + ” on axis”, EditorStyles.boldLabel);
  72.         GUILayout.BeginHorizontal();
  73.             xCheckbox = GUILayout.Toggle(xCheckbox, ”X”);
  74.             yCheckbox = GUILayout.Toggle(yCheckbox, ”Y”);
  75.             zCheckbox = GUILayout.Toggle(zCheckbox, ”Z”);
  76.         GUILayout.EndHorizontal();
  77.         EditorGUILayout.Space();
  78.     }
  79.     /// <summary>
  80.     /// Draws the range min and max fields
  81.     /// </summary>
  82.     private void CreateRangeFields()
  83.     {
  84.         GUILayout.Label(“Range”, EditorStyles.boldLabel);
  85.         GUILayout.BeginHorizontal();
  86.         randomRangeMin = EditorGUILayout.FloatField(“Min:”, randomRangeMin);
  87.         randomRangeMax = EditorGUILayout.FloatField(“Max:”, randomRangeMax);
  88.         GUILayout.EndHorizontal();
  89.         EditorGUILayout.Space();
  90.     }
  91.     /// <summary>
  92.     /// Creates the Align transform window
  93.     /// </summary>
  94.     private void CreateAlignTransformWindow()
  95.     {
  96.         //Source transform
  97.         GUILayout.BeginHorizontal();
  98.         GUILayout.Label(“Align to: \t”);
  99.         source = EditorGUILayout.ObjectField(source, typeof(Transform)) as Transform;
  100.         GUILayout.EndHorizontal();
  101.         string[] texts = new string[4] { ”Min”, ”Max”, ”Center”, ”Pivot” };
  102.         //Display align options
  103.         EditorGUILayout.BeginHorizontal();
  104.         EditorGUILayout.BeginVertical();
  105.         GUILayout.Label(“Selection:”, EditorStyles.boldLabel);
  106.         alignSelectionOption = GUILayout.SelectionGrid(alignSelectionOption, texts, 1);
  107.         EditorGUILayout.EndVertical();
  108.         EditorGUILayout.BeginVertical();
  109.         GUILayout.Label(“Source:”, EditorStyles.boldLabel);
  110.         alignSourceOption = GUILayout.SelectionGrid(alignSourceOption, texts, 1);
  111.         EditorGUILayout.EndVertical();
  112.         EditorGUILayout.EndHorizontal();
  113.         EditorGUILayout.Space();
  114.         //Position
  115.         if (GUILayout.Button(“Align”))
  116.         {
  117.             if (source != null)
  118.             {
  119.                 //Add a temporary box collider to the source if it doesn’t have one
  120.                 Collider sourceCollider = source.collider;
  121.                 bool destroySourceCollider = false;
  122.                 if (sourceCollider == null)
  123.                 {
  124.                     sourceCollider = source.gameObject.AddComponent<BoxCollider>();
  125.                     destroySourceCollider = true;
  126.                 }
  127.                 foreach (Transform t in Selection.transforms)
  128.                 {
  129.                     //Add a temporary box collider to the transform if it doesn’t have one
  130.                     Collider transformCollider = t.collider;
  131.                     bool destroyTransformCollider = false;
  132.                     if (transformCollider == null)
  133.                     {
  134.                         transformCollider = t.gameObject.AddComponent<BoxCollider>();
  135.                         destroyTransformCollider = true;
  136.                     }
  137.                     Vector3 sourceAlignData = new Vector3();
  138.                     Vector3 transformAlignData = new Vector3();
  139.                     //Transform
  140.                     switch (alignSelectionOption)
  141.                     {
  142.                         case 0: //Min
  143.                             transformAlignData = transformCollider.bounds.min;
  144.                             break;
  145.                         case 1: //Max
  146.                             transformAlignData = transformCollider.bounds.max;
  147.                             break;
  148.                         case 2: //Center
  149.                             transformAlignData = transformCollider.bounds.center;
  150.                             break;
  151.                         case 3: //Pivot
  152.                             transformAlignData = transformCollider.transform.position;
  153.                             break;
  154.                     }
  155.                     //Source
  156.                     switch (alignSourceOption)
  157.                     {
  158.                         case 0: //Min
  159.                             sourceAlignData = sourceCollider.bounds.min;
  160.                             break;
  161.                         case 1: //Max
  162.                             sourceAlignData = sourceCollider.bounds.max;
  163.                             break;
  164.                         case 2: //Center
  165.                             sourceAlignData = sourceCollider.bounds.center;
  166.                             break;
  167.                         case 3: //Pivot
  168.                             sourceAlignData = sourceCollider.transform.position;
  169.                             break;
  170.                     }
  171.                     Vector3 tmp = new Vector3();
  172.                     tmp.x = xCheckbox ? sourceAlignData.x - (transformAlignData.x - t.position.x) : t.position.x;
  173.                     tmp.y = yCheckbox ? sourceAlignData.y - (transformAlignData.y - t.position.y) : t.position.y;
  174.                     tmp.z = zCheckbox ? sourceAlignData.z - (transformAlignData.z - t.position.z) : t.position.z;
  175.                     //Register the Undo
  176.                     Undo.RegisterUndo(t, ”Align ” + t.gameObject.name + ” to ” + source.gameObject.name);
  177.                     t.position = tmp;
  178.                     //Ugly hack!
  179.                     //Unity needs to update the collider of the selection to it’s new position
  180.                     //(it stores in cache the collider data)
  181.                     //We can force the update by a change in a public variable (shown in the inspector),
  182.                     //then a call SetDirty to update the collider (it won’t work if all inspector variables are the same).
  183.                     //But we want to restore the changed property to what it was so we do it twice.
  184.                     transformCollider.isTrigger = !transformCollider.isTrigger;
  185.                     EditorUtility.SetDirty(transformCollider);
  186.                     transformCollider.isTrigger = !transformCollider.isTrigger;
  187.                     EditorUtility.SetDirty(transformCollider);
  188.                     //Destroy the collider we added
  189.                     if (destroyTransformCollider)
  190.                     {
  191.                         DestroyImmediate(transformCollider);
  192.                     }
  193.                 }
  194.                 //Destroy the collider we added
  195.                 if (destroySourceCollider)
  196.                 {
  197.                     DestroyImmediate(sourceCollider);
  198.                 }
  199.             }
  200.             else
  201.             {
  202.                 EditorUtility.DisplayDialog(“Error”, ”There is no source transform”, ”Ok”);
  203.                 EditorApplication.Beep();
  204.             }
  205.         }
  206.     }
  207.     /// <summary>
  208.     /// Creates the copy transform window
  209.     /// </summary>
  210.     private void CreateCopyTransformWindow()
  211.     {
  212.         //Source transform
  213.         GUILayout.BeginHorizontal();
  214.             GUILayout.Label(“Copy from: \t”);
  215.             source = EditorGUILayout.ObjectField(source, typeof(Transform)) as Transform;
  216.         GUILayout.EndHorizontal();
  217.         EditorGUILayout.Space();
  218.         //Position
  219.         if (GUILayout.Button(“Copy Position”))
  220.         {
  221.             if (source != null)
  222.             {
  223.                 foreach (Transform t in Selection.transforms)
  224.                 {
  225.                     Vector3 tmp = new Vector3();
  226.                     tmp.x = xCheckbox ? source.position.x : t.position.x;
  227.                     tmp.y = yCheckbox ? source.position.y : t.position.y;
  228.                     tmp.z = zCheckbox ? source.position.z : t.position.z;
  229.                     Undo.RegisterUndo(t, ”Copy position”);
  230.                     t.position = tmp;
  231.                 }
  232.             }
  233.             else
  234.             {
  235.                 EditorUtility.DisplayDialog(“Error”, ”There is no source transform”, ”Ok”);
  236.                 EditorApplication.Beep();
  237.             }
  238.         }
  239.         //Rotation
  240.         if (GUILayout.Button(“Copy Rotation”))
  241.         {
  242.             if (source != null)
  243.             {
  244.                 foreach (Transform t in Selection.transforms)
  245.                 {
  246.                     Vector3 tmp = new Vector3();
  247.                     tmp.x = xCheckbox ? source.rotation.eulerAngles.x : t.rotation.eulerAngles.x;
  248.                     tmp.y = yCheckbox ? source.rotation.eulerAngles.y : t.rotation.eulerAngles.y;
  249.                     tmp.z = zCheckbox ? source.rotation.eulerAngles.z : t.rotation.eulerAngles.z;
  250.                     Quaternion tmp2 = t.rotation;
  251.                     tmp2.eulerAngles = tmp;
  252.                     Undo.RegisterUndo(t, ”Copy rotation”);
  253.                     t.rotation = tmp2;
  254.                 }
  255.             }
  256.             else
  257.             {
  258.                 EditorUtility.DisplayDialog(“Error”, ”There is no source transform”, ”Ok”);
  259.                 EditorApplication.Beep();
  260.             }
  261.         }
  262.         //Local Scale
  263.         if (GUILayout.Button(“Copy Local Scale”))
  264.         {
  265.             if (source != null)
  266.             {
  267.                 foreach (Transform t in Selection.transforms)
  268.                 {
  269.                     Vector3 tmp = new Vector3();
  270.                     tmp.x = xCheckbox ? source.localScale.x : t.localScale.x;
  271.                     tmp.y = yCheckbox ? source.localScale.y : t.localScale.y;
  272.                     tmp.z = zCheckbox ? source.localScale.z : t.localScale.z;
  273.                     Undo.RegisterUndo(t, ”Copy local scale”);
  274.                     t.localScale = tmp;
  275.                 }
  276.             }
  277.             else
  278.             {
  279.                 EditorUtility.DisplayDialog(“Error”, ”There is no source transform”, ”Ok”);
  280.                 EditorApplication.Beep();
  281.             }
  282.         }
  283.     }
  284.     /// <summary>
  285.     /// Creates the Randomize transform window
  286.     /// </summary>
  287.     private void CreateRandomizeTransformWindow()
  288.     {
  289.         CreateRangeFields();
  290.         //Position
  291.         if (GUILayout.Button(“Randomize Position”))
  292.         {
  293.             foreach (Transform t in Selection.transforms)
  294.             {
  295.                 Vector3 tmp = new Vector3();
  296.                 tmp.x = xCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.position.x;
  297.                 tmp.y = yCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.position.y;
  298.                 tmp.z = zCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.position.z;
  299.                 Undo.RegisterUndo(t, ”Randomize position”);
  300.                 t.position = tmp;
  301.             }
  302.         }
  303.         //Rotation
  304.         if (GUILayout.Button(“Randomize Rotation”))
  305.         {
  306.             foreach (Transform t in Selection.transforms)
  307.             {
  308.                 Vector3 tmp = new Vector3();
  309.                 tmp.x = xCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.rotation.eulerAngles.x;
  310.                 tmp.y = yCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.rotation.eulerAngles.y;
  311.                 tmp.z = zCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.rotation.eulerAngles.z;
  312.                 Quaternion tmp2 = t.rotation;
  313.                 tmp2.eulerAngles = tmp;
  314.                 Undo.RegisterUndo(t, ”Randomize rotation”);
  315.                 t.rotation = tmp2;
  316.             }
  317.         }
  318.         //Local Scale
  319.         if (GUILayout.Button(“Randomize Local Scale”))
  320.         {
  321.             foreach (Transform t in Selection.transforms)
  322.             {
  323.                 Vector3 tmp = new Vector3();
  324.                 tmp.x = xCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.localScale.x;
  325.                 tmp.y = yCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.localScale.y;
  326.                 tmp.z = zCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : t.localScale.z;
  327.                 Undo.RegisterUndo(t, ”Randomize local scale”);
  328.                 t.localScale = tmp;
  329.             }
  330.         }
  331.     }
  332.     /// <summary>
  333.     /// Creates the Add Noise To Transform window
  334.     /// </summary>
  335.     private void CreateAddNoiseToTransformWindow()
  336.     {
  337.         CreateRangeFields();
  338.         //Position
  339.         if (GUILayout.Button(“Add noise to Position”))
  340.         {
  341.             foreach (Transform t in Selection.transforms)
  342.             {
  343.                 Vector3 tmp = new Vector3();
  344.                 tmp.x = xCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0;
  345.                 tmp.y = yCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0;
  346.                 tmp.z = zCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0;
  347.                 Undo.RegisterUndo(t, ”Add noise to position”);
  348.                 t.position += tmp;
  349.             }
  350.         }
  351.         //Rotation
  352.         if (GUILayout.Button(“Add noise to Rotation”))
  353.         {
  354.             foreach (Transform t in Selection.transforms)
  355.             {
  356.                 Vector3 tmp = new Vector3();
  357.                 tmp.x = xCheckbox ?  t.rotation.eulerAngles.x + Random.Range(randomRangeMin, randomRangeMax) : 0;
  358.                 tmp.y = yCheckbox ?  t.rotation.eulerAngles.y + Random.Range(randomRangeMin, randomRangeMax) : 0;
  359.                 tmp.z = zCheckbox ?  t.rotation.eulerAngles.z + Random.Range(randomRangeMin, randomRangeMax) : 0;
  360.                 Undo.RegisterUndo(t, ”Add noise to rotation”);
  361.                 t.rotation = Quaternion.Euler(tmp);
  362.             }
  363.         }
  364.         //Local Scale
  365.         if (GUILayout.Button(“Add noise to Local Scale”))
  366.         {
  367.             foreach (Transform t in Selection.transforms)
  368.             {
  369.                 Vector3 tmp = new Vector3();
  370.                 tmp.x = xCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0;
  371.                 tmp.y = yCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0;
  372.                 tmp.z = zCheckbox ? Random.Range(randomRangeMin, randomRangeMax) : 0;
  373.                 Undo.RegisterUndo(t, ”Add noise to local scale”);
  374.                 t.localScale += tmp;
  375.             }
  376.         }
  377.     }
  378. }</span>


其窗口如下图所示:


4. Editor

对某自定义组件进行观察的Inspector窗口,可以从它派生。如下代码所示:

 

代码片段1定义了一个名为Star的组件:

 

[csharp] view plain copy

  1. <span style=”font-size:18px;”>using System;
  2. using UnityEngine;
  3. [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
  4. public class Star : MonoBehaviour {
  5.     [Serializable]
  6.     public class Point {
  7.         public Color color;
  8.         public Vector3 offset;
  9.     }
  10.     public Point[] points;
  11.     public int frequency = 1;
  12.     public Color centerColor;
  13.     private Mesh mesh;
  14.     private Vector3[] vertices;
  15.     private Color[] colors;
  16.     private int[] triangles;
  17.     void Start () {
  18.         GetComponent<MeshFilter>().mesh = mesh = new Mesh();
  19.         mesh.name = ”Star Mesh”;
  20.         if(frequency < 1){
  21.             frequency = 1;
  22.         }
  23.         if(points == null || points.Length == 0){
  24.             points = new Point[]{ new Point()};
  25.         }
  26.         int numberOfPoints = frequency * points.Length;
  27.         vertices = new Vector3[numberOfPoints + 1];
  28.         colors = new Color[numberOfPoints + 1];
  29.         triangles = new int[numberOfPoints * 3];
  30.         float angle = -360f / numberOfPoints;
  31.         colors[0] = centerColor;
  32.         for(int iF = 0, v = 1, t = 1; iF < frequency; iF++){
  33.             for(int iP = 0; iP < points.Length; iP += 1, v += 1, t += 3){
  34.                 vertices[v] = Quaternion.Euler(0f, 0f, angle * (v - 1)) * points[iP].offset;
  35.                 colors[v] = points[iP].color;
  36.                 triangles[t] = v;
  37.                 triangles[t + 1] = v + 1;
  38.             }
  39.         }
  40.         triangles[triangles.Length - 1] = 1;
  41.         mesh.vertices = vertices;
  42.         mesh.colors = colors;
  43.         mesh.triangles = triangles;
  44.     }
  45. }</span>

代码片段2定义了对Star组件进行观测的Inspector窗口:

 

 

[csharp] view plain copy

  1. <span style=”font-size:18px;”>using UnityEditor;
  2. using UnityEngine;
  3. [CustomEditor(typeof(Star))]
  4. public class StarInspector : Editor {
  5.     private static GUIContent
  6.         insertContent = new GUIContent(“+”, ”duplicate this point”),
  7.         deleteContent = new GUIContent(“-”, ”delete this point”),
  8.         pointContent = GUIContent.none;
  9.     private static GUILayoutOption
  10.         buttonWidth = GUILayout.MaxWidth(20f),
  11.         colorWidth = GUILayout.MaxWidth(50f);
  12.     private SerializedObject star;
  13.     private SerializedProperty
  14.         points,
  15.         frequency,
  16.         centerColor;
  17.     void OnEnable () { … }
  18.     public override void OnInspectorGUI () {
  19.         star.Update();
  20.         GUILayout.Label(“Points”);
  21.         for(int i = 0; i < points.arraySize; i++){
  22.             EditorGUILayout.BeginHorizontal();
  23.             SerializedProperty point = points.GetArrayElementAtIndex(i);
  24.             EditorGUILayout.PropertyField(point.FindPropertyRelative(“offset”), pointContent);
  25.             EditorGUILayout.PropertyField(point.FindPropertyRelative(“color”), pointContent, colorWidth);
  26.             if(GUILayout.Button(insertContent, EditorStyles.miniButtonLeft, buttonWidth)){
  27.                 points.InsertArrayElementAtIndex(i);
  28.             }
  29.             if(GUILayout.Button(deleteContent, EditorStyles.miniButtonRight, buttonWidth)){
  30.                 points.DeleteArrayElementAtIndex(i);
  31.             }
  32.             EditorGUILayout.EndHorizontal();
  33.         }
  34.         EditorGUILayout.PropertyField(frequency);
  35.         EditorGUILayout.PropertyField(centerColor);
  36.         star.ApplyModifiedProperties();
  37.     }
  38. }</span>

其Inspector窗口如下图所示:

 

说到这里,大家对ScriptableObject, ScriptableWizard, EditorWindow和Editor应该都有应有了一定了解。其中EditorWindow和Editor都继承了ScriptableObject,而ScritableWizard则继承了EditorWindow派。在实际开发应用中,应该根据需求的特点,灵活使用这四个类进行编辑器扩展。

参考资料:

1. http://catlikecoding.com/unity/tutorials/star/

2. http://www.unifycommunity.com/wiki

3. http://www.blog.silentkraken.com/2010/02/06/transformutilities/

4.http://unity3d.com/support/documentation/ScriptReference



发表评论

电子邮件地址不会被公开。 必填项已用 * 标注

*

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

(Spamcheck Enabled)

最新评论