Unity3D 之 Editor 扩展

作者:雨辰 发布于:2014-1-10 22:06 Friday 分类:Unity3D

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

1 . ScriptableObject  

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

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

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


3 . EditorWindow

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

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


其窗口如下图所示:


4. Editor

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

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

  1. <span style="font-size:18px;">using System;  
  2. using UnityEngine;  
  3.   
  4. [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]  
  5. public class Star : MonoBehaviour {  
  6.   
  7.     [Serializable]  
  8.     public class Point {  
  9.         public Color color;  
  10.         public Vector3 offset;  
  11.     }  
  12.   
  13.     public Point[] points;  
  14.     public int frequency = 1;  
  15.     public Color centerColor;  
  16.   
  17.     private Mesh mesh;  
  18.     private Vector3[] vertices;  
  19.     private Color[] colors;  
  20.     private int[] triangles;  
  21.   
  22.     void Start () {  
  23.         GetComponent<MeshFilter>().mesh = mesh = new Mesh();  
  24.         mesh.name = "Star Mesh";  
  25.   
  26.         if(frequency < 1){  
  27.             frequency = 1;  
  28.         }  
  29.         if(points == null || points.Length == 0){  
  30.             points = new Point[]{ new Point()};  
  31.         }  
  32.   
  33.         int numberOfPoints = frequency * points.Length;  
  34.         vertices = new Vector3[numberOfPoints + 1];  
  35.         colors = new Color[numberOfPoints + 1];  
  36.         triangles = new int[numberOfPoints * 3];  
  37.         float angle = -360f / numberOfPoints;  
  38.         colors[0] = centerColor;  
  39.         for(int iF = 0, v = 1, t = 1; iF < frequency; iF++){  
  40.             for(int iP = 0; iP < points.Length; iP += 1, v += 1, t += 3){  
  41.                 vertices[v] = Quaternion.Euler(0f, 0f, angle * (v - 1)) * points[iP].offset;  
  42.                 colors[v] = points[iP].color;  
  43.                 triangles[t] = v;  
  44.                 triangles[t + 1] = v + 1;  
  45.             }  
  46.         }  
  47.         triangles[triangles.Length - 1] = 1;  
  48.   
  49.         mesh.vertices = vertices;  
  50.         mesh.colors = colors;  
  51.         mesh.triangles = triangles;  
  52.     }  
  53. }</span>  
代码片段2定义了对Star组件进行观测的Inspector窗口:
  1. <span style="font-size:18px;">using UnityEditor;  
  2. using UnityEngine;  
  3.   
  4. [CustomEditor(typeof(Star))]  
  5. public class StarInspector : Editor {  
  6.   
  7.     private static GUIContent  
  8.         insertContent = new GUIContent("+""duplicate this point"),  
  9.         deleteContent = new GUIContent("-""delete this point"),  
  10.         pointContent = GUIContent.none;  
  11.   
  12.     private static GUILayoutOption  
  13.         buttonWidth = GUILayout.MaxWidth(20f),  
  14.         colorWidth = GUILayout.MaxWidth(50f);  
  15.   
  16.     private SerializedObject star;  
  17.     private SerializedProperty  
  18.         points,  
  19.         frequency,  
  20.         centerColor;  
  21.   
  22.     void OnEnable () { … }  
  23.   
  24.     public override void OnInspectorGUI () {  
  25.         star.Update();  
  26.   
  27.         GUILayout.Label("Points");  
  28.         for(int i = 0; i < points.arraySize; i++){  
  29.             EditorGUILayout.BeginHorizontal();  
  30.             SerializedProperty point = points.GetArrayElementAtIndex(i);  
  31.             EditorGUILayout.PropertyField(point.FindPropertyRelative("offset"), pointContent);  
  32.             EditorGUILayout.PropertyField(point.FindPropertyRelative("color"), pointContent, colorWidth);  
  33.   
  34.             if(GUILayout.Button(insertContent, EditorStyles.miniButtonLeft, buttonWidth)){  
  35.                 points.InsertArrayElementAtIndex(i);  
  36.             }  
  37.             if(GUILayout.Button(deleteContent, EditorStyles.miniButtonRight, buttonWidth)){  
  38.                 points.DeleteArrayElementAtIndex(i);  
  39.             }  
  40.   
  41.             EditorGUILayout.EndHorizontal();  
  42.         }  
  43.   
  44.         EditorGUILayout.PropertyField(frequency);  
  45.         EditorGUILayout.PropertyField(centerColor);  
  46.   
  47.         star.ApplyModifiedProperties();  
  48.     }  
  49. }</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

标签: Unity3D-Editer

发表评论:

雨辰 joyimp|@2011-2018 京ICP备16030765号