1. 程式人生 > >unity編輯器擴充套件#1 自定義視窗、面板、和屬性

unity編輯器擴充套件#1 自定義視窗、面板、和屬性

擼一遍unity官方文件的給的三個案例:https://docs.unity3d.com/2017.4/Documentation/Manual/editor-EditorWindows.html

自定義視窗

首先,要注意和編輯器相關的指令碼都應該把放到Editor資料夾裡,可以有多個Editor資料夾。

然後建立一個繼承自EditorWindow的類

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

//要建立的編輯器視窗類應該繼承EditorWindow;
public class OneEditorWindow : EditorWindow
{

	//MenuItem是一個特性,會在頂部按路徑生成選項,點選就會呼叫下面的方法,也就是開啟視窗
	[MenuItem("Unity編輯器/OneEditorWindow")]
	public static void  OpenWindow()
	{
		//開啟視窗的方法 註釋掉的方法你可以設定開啟的視窗的位置和大小
		//可以直接用沒引數的過載方法,引數用來設定視窗名稱,是否用標籤視窗形式之類的
		EditorWindow.GetWindow<OneEditorWindow>(false,"一個視窗");
		//EditorWindow.GetWindowWithRect<MyFirstWindow>(new Rect(0f, 0f, 500, 500));
	}
	
	
	private Color col;
	private float f;
	
	//OnGUI裡寫你想繪製在窗口裡的內容
	private void OnGUI()
	{
		EditorGUILayout.LabelField("一個label");
		f = EditorGUILayout.FloatField("一個數字框", f);
		col=EditorGUILayout.ColorField("一個顏色選擇框",col);
	}
}

就好了~

  

自定義類和成員的顯示

這裡提到的指令碼不需要放到Editor資料夾裡,因為他只是修改原有的顯示而已

修改類例項的的顯示

        

紅色框的就是我們要修改的類 Ingredient在Inspector面板上的顯示,左邊是Unity預設的顯示,右邊是修改後的。

首先是用於實驗的類

public class Recipe: MonoBehaviour
{
	public Ingredient PotionResult;
	public Ingredient[] PotionIngredients;
}

public enum IngredientUnit {Cup,Bowl,Spoon,Piece}

[Serializable]//這個特性表示該類可以被序列化,但是不加好像也沒關係
public class Ingredient  {
    //需要重新設計屬性面板的類
	public string Name;
	public int Amount;
	public IngredientUnit Unit;
}

接下來就是寫可以修改顯示的類

修改顯示的類應該繼承:PropertyDrawer

用特性[CustomPropertyDrawer(typeof(type))]表明它是用來針對修改哪個類的

和視窗一樣,在OnGUI裡寫繪製的程式碼,

方法提供來三個引數,Rect是位置和寬高;property裡面存著類的相關資訊,可以用FindPropertyRelative(name)來找類成員;label就是名字;

在BeginProperty()和EndProperty()裡寫可以確保佈局的整齊

[CustomPropertyDrawer(typeof(Ingredient))]
public class IngredientDrawer:PropertyDrawer
{
	//重新繪製面板
	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
	{
		EditorGUI.BeginProperty(position, label, property);
		
		
		//每個例項的名字 本來應該是Element0123 ,但這裡顯示了Name字串
		position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
		
		//本來是1層,要縮排,設定成0和他的上級size同級,使之與其對齊;
		var index = EditorGUI.indentLevel;
		EditorGUI.indentLevel = 0;
		
		//給三個屬性安排位置
		var amountRect = new Rect(position.x, position.y, 30, position.height);
		var unitRect = new Rect(position.x + 35, position.y, 50, position.height);
		var nameRect = new Rect(position.x + 90, position.y, position.width - 90, position.height);
		
		//繪製屬性
		EditorGUI.PropertyField(amountRect, property.FindPropertyRelative("Amount"), GUIContent.none);
		EditorGUI.PropertyField(unitRect, property.FindPropertyRelative("Unit"), GUIContent.none);
		EditorGUI.PropertyField(nameRect, property.FindPropertyRelative("Name"), GUIContent.none);
		
		//重新設定為原來的層級
		EditorGUI.indentLevel = index;
		
		EditorGUI.EndProperty();

	}
}

修改類中成員的顯示

修改類成員的顯示通過給成員新增屬性特性(Property Attributes)來實現,像unity自帶的[Range(min,max)]就是一個特性,這裡實現一個自己的Range來作為例子

先定義一個特性類,繼承:PropertyAttribute

public class MyRangeAttribute : PropertyAttribute
{
	public float min;
	public float max;

	public MyRangeAttribute(float min, float max)
	{
		this.min = min;
		this.max = max;
	}
}

然後和修改類的顯示類似,寫一個繼承PropertyDrawer的類,在OnGUI裡寫顯示的程式碼

[CustomPropertyDrawer(typeof(MyRangeAttribute))]
public class RangeDrawer : PropertyDrawer
{
	public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
	{
		MyRangeAttribute range = (MyRangeAttribute) attribute;
		if (property.propertyType == SerializedPropertyType.Float)
			EditorGUI.Slider(position, property, range.min, range.max, label);
		else if (property.propertyType == SerializedPropertyType.Integer)
			EditorGUI.IntSlider(position, property, (int) range.min, (int) range.max, label);
		else
			EditorGUI.LabelField(position, label.text, "Use MyRange with float or int.");
	}
}

attribute是PropertyDrawer裡的成員,儲存了所對應的特性類,強轉出真正的特性。 然後就是讀取attribute和property裡的資料生成slider控制元件了,關於GUI裡的各種控制元件看#2.

自定義編輯器

就是像遊戲中的地形系統啊、布料系統啊,你新增元件到遊戲物體,然後就可以在編輯狀態,在Sence或者哪裡裡做各種修改和操作。

首先是寫一個用於實驗的類,因為要在編輯模式也能執行,所以加了個[ExecuteInEditMode]的特性

[ExecuteInEditMode]
public class LookAtPoint : MonoBehaviour
{

	public Transform lookPoint;
	public Vector3 lookPos;

	public void Update()
	{
		if (lookPoint)
		{
			transform.LookAt(lookPos);
		}
		
	}
}

接下來就是重要的編輯器類了,繼承:Editor,要放在Editor資料夾裡

[CustomEditor(typeof(LookAtPoint))]
[CanEditMultipleObjects]
public class LookAtPointEditor : Editor
{
	private SerializedProperty lookAtPoint;
	private SerializedProperty lookPos;

	private void OnEnable()
	{
		lookAtPoint = serializedObject.FindProperty("lookPoint");
		lookPos = serializedObject.FindProperty("lookPos");
		
	}

	//調整在Inspector上的佈局
	public override void OnInspectorGUI()
	{
		
		serializedObject.Update();//更新面板顯示
		//選擇的transform
		EditorGUILayout.PropertyField(lookPos);
		serializedObject.ApplyModifiedProperties();//將面板值應用到屬性
		
		/*
		//轉化成實際的型別
		Transform a=(Transform)lookAtPoint.objectReferenceValue;
		if (a.position.y > ((LookAtPoint)target).transform.position.y)
		{
			EditorGUILayout.LabelField("(Above this object)");
		}
		if (a.position.y < ((LookAtPoint)target).transform.position.y)
		{
			EditorGUILayout.LabelField("(Below this object)");
		}
		*/

	}
	
	//在Scene場景裡新增繪製一些需要的UI佈局
	public void OnSceneGUI()
	{
		var t = (target as LookAtPoint);
		EditorGUI.BeginChangeCheck();
		//繪製一個圖形(移動物體的那個三箭頭),位置對應lookPos
		Vector3 pos = Handles.PositionHandle(t.lookPos, Quaternion.identity);
		if (EditorGUI.EndChangeCheck())
		{//有調整就修改數值
			Undo.RecordObject(target, "Move point");
			t.lookPos = pos;
			t.Update();
		}
	}

	//protected override void OnHeaderGUI();
	//public override void OnPreviewGUI(Rect r, GUIStyle background)
	//public override void OnInteractivePreviewGUI(Rect r, GUIStyle background)
}

首先是兩個特性,[CustomEditor(typeof(type))]和前面的類似,用來表示你這個Editor管的是哪個元件,這裡就是我們的LookAtPoint,[CanEditMultipleObjects]則表示該編輯器可以支援多個物體。

用serializedObject.FindProperty來找需要的資料

這裡用到了兩個顯示的函式,一個處理Inspector上指令碼的顯示,一個處理Scene場景中的顯示,後面還註釋了幾個沒用到的。

記得把SerializedProperty轉化成正確的型別,一般的資料結構它獨有對應的屬性,像lookAtPoint.doubleValue就會返回double,當然lookAtPoint不是double型別,會報錯,對於引用型別,用.objectReferenceValue,然後再轉一下,像文中註釋的部分一樣