Editor Scripting
- 前言
- 建立一個自定義Inspector
- 添加按鈕
- 編輯器菜單欄擴充
-
- 添加到頂部菜單欄
- 添加到其他菜單欄
- 添加菜單激活驗證
- 菜單排序
- 擷取選中元件引用
- 為元件添加預設的Inpector 右鍵菜單
- 為元件上的字段添加菜單
- 腳本使用技巧
-
- 自動添加相關依賴元件
- Inspector 菜單上的Reset功能
- 暴露方法,快速測試
- 在SceneView中添加Widget
-
- Handles.PositionHandle
- Handles.RadiusHandle
- DrawGizmo
- 在Scene View 中繪制2D按鈕
前言
歡迎來到英雄 (Unity)聯盟,今年冠狀病毒肆虐正好有理由宅在家學習學習~今天我們來學習自定義編輯器界面。
1.參考的官網教程
2.其它參考
建立一個自定義Inspector
1.建立自己的腳本
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 自己建立的腳本
/// </summary>
public class YourScript : MonoBehaviour
{
[Header("得分")]
public int score;
/// <summary>
/// 等級
/// </summary>
public int Level
{
get
{
return score / 100;
}
}
}
建立好後挂到一個空物體上有個預設的Inspector界面,隻能看到
score
字段看不到
Level
屬性
2.為了能直覺的看到對應得分的
Level
需要在Editor檔案夾下建立一個腳本告訴Unity顯示出
Level
資訊。這個腳本隻在編輯器環境下有效,在Build的時候是會被自動忽略的!!腳本需要引用UnityEditor命名空間并且繼承Editor這個類;我們主要工作就是覆寫
OnInspectorGUI()
這個父類方法。
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(YourScript))]
public class YourScriptEditor : Editor
{
public override void OnInspectorGUI()
{
//保留已有的樣式
DrawDefaultInspector();
//腳本執行個體擷取
YourScript ys = (YourScript)target;
//添加Level 顯示
EditorGUILayout.LabelField("Level",ys.Level.ToString());
}
}
這時就可以看到Inpector上的
Level
資訊了,是不是很好玩啊,哈哈哈
添加按鈕
接下來添加像Image控件上的Set Native Size 按鈕,可以在編輯器未運作時執行方法。
方法如下在Editor腳本中添加
GUILayout.Button
即可:
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(YourScript))]
public class YourScriptEditor : Editor
{
public override void OnInspectorGUI()
{
//保留已有的樣式
DrawDefaultInspector();
//腳本執行個體擷取
YourScript ys = (YourScript)target;
//添加Level 顯示
EditorGUILayout.LabelField("Level",ys.Level.ToString());
//按鈕示例
if (GUILayout.Button("執行個體化一個cube"))
{
ys.SpawnCube();
}
}
}
編輯器菜單欄擴充
添加到頂部菜單欄
1.建立一個腳本YourMenuItems
(必須放在Editor檔案夾下)
,使用
MenuItem
特性标記你所寫的靜态方法,它就會自動在頂部菜單欄添加對應的菜單,點選後會執行對應的方法。
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
public class YourMenuItems
{
[MenuItem("YourMenuItems/Clear PlayerPrefs")]
public static void ClearPlayerPrefs() {
PlayerPrefs.DeleteAll();
Debug.Log("Clear PlayerPrefs");
}
}
2.給菜單添加快捷鍵直接在路徑後面空一格,然後寫上快捷鍵組合符号
快捷鍵對照表
% 代表 Windows中的 CTRL,OSX中的 CMD按鍵
# 代表 Shift
& 代表 Alt
LEFT/RIGHT/UP/DOWN 代表方向鍵
F1…F2 代表 頂部的F鍵
HOME, END, PGUP, PGDN
如果是當個字母的快捷鍵需要在前面加下劃線: _g
示例如下:
/// <summary>
/// 快捷鍵 CTRL + Shift + j
/// </summary>
[MenuItem(path+"/Option_1 %#j") ]
public static void MenuItem_A() {
Debug.Log("快捷鍵 CTRL + Shift + j");
}
/// <summary>
/// 單字母快捷鍵 j
/// </summary>
[MenuItem(path+"/Option_2 _j") ]
public static void MenuItem_B() {
Debug.Log("單字母快捷鍵 j");
}
添加到其他菜單欄
我們可以在寫路徑時添加特殊的根路徑(
Assets/ 或者 Assets/Create/
)實作把菜單放到Project/Assets 的右鍵菜單欄上:
也可以用根路徑(
CONTEXT/ComponentName
)把菜單放到名字為“ComponentName”的元件右鍵菜單欄上:
/// <summary>
/// Project/Assets 右鍵菜單
/// </summary>
[MenuItem("Assets/Option_1")]
public static void MenuItem_C()
{
Debug.Log("Project/Assets 右鍵菜單 點選");
}
/// <summary>
/// 元件YourScript,Inspector上的 右鍵菜單
/// </summary>
[MenuItem("CONTEXT/YourScript/Option_2")]
public static void MenuItem_D()
{
Debug.Log("元件YourScript,Inspector上的 右鍵菜單 點選");
}
添加菜單激活驗證
有的菜單需要在一定的條件下才允許使用,這時可以添加驗證參數和方法自動驗證并控制菜單的激活狀态。
1.給
MenuItem_C菜單
新增一個驗證方法
MenuItem_C_Validation
,設定它的validate=true,傳回一個布爾值告之目前是否可以激活菜單。路徑參數(菜單名itemName)必須一緻。
/// <summary>
/// Project/Assets 右鍵菜單
/// </summary>
[MenuItem(itemName:"Assets/Option_1")]
public static void MenuItem_C()
{
Debug.Log("Project/Assets 右鍵菜單 點選");
}
/// <summary>
/// Project/Assets 右鍵菜單 驗證方法
/// </summary>
[MenuItem(itemName:"Assets/Option_1",validate =true)]
public static bool MenuItem_C_Validation()
{
//判斷目前選中的是否是場景資源
return Selection.activeObject.GetType() == typeof(SceneAsset);
}
下面所示如果目前不是場景檔案就把菜單變為不可點狀态:
選中場景檔案菜單就可點了:
菜單排序
菜單排序直接在特性中添加參數priority,每五十個為一組。
priority 都在[0,49]之間時為一組,它們沒有分隔線
const string path = "YourMenuItems";
[MenuItem(path+"/Clear PlayerPrefs",priority = 0)]
public static void ClearPlayerPrefs() {
PlayerPrefs.DeleteAll();
Debug.Log("Clear PlayerPrefs");
}
/// <summary>
/// 快捷鍵 CTRL + Shift + j
/// </summary>
[MenuItem(path+"/Option_1 %#j",priority =1) ]
public static void MenuItem_A() {
Debug.Log("快捷鍵 CTRL + Shift + j");
}
/// <summary>
/// 單字母快捷鍵 j
/// </summary>
[MenuItem(path+"/Option_2 _j",priority =2) ]
public static void MenuItem_B() {
Debug.Log("單字母快捷鍵 j");
}
priority =51時會添加分隔線劃到新的一組
/// <summary>
/// 單字母快捷鍵 e
/// </summary>
[MenuItem(path + "/Option_2 _e", priority = 51)]
public static void MenuItem_E()
{
Debug.Log("單字母快捷鍵 e");
}
擷取選中元件引用
直接在菜單對應的方法内添加
MenuCommand 參數
,系統會自動傳入。
/// <summary>
/// 元件YourScript,Inspector上的 右鍵菜單
/// </summary>
[MenuItem("CONTEXT/YourScript/Option_3")]
public static void MenuItem_F(MenuCommand menucommand)
{
YourScript ys = menucommand.context as YourScript;
Debug.Log($"選中的元件所屬GameObject Name:{ys.gameObject.name}",ys.gameObject);
}
為元件添加預設的Inpector 右鍵菜單
在元件腳本裡添加方法并用特性
ContextMenu
标記,它是元件預設的菜單。而
[MenuItem("CONTEXT/YourScript/MenuName")]
是對元件菜單的擴充,不需要寫在元件腳本裡,這樣可以對系統自帶的元件進行擴充。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 自己建立的腳本
/// </summary>
public class YourScript : MonoBehaviour
{
[Header("得分")]
public int score;
/// <summary>
/// 等級
/// </summary>
public int Level
{
get
{
return score / 100;
}
}
[ContextMenu("随機score")]
public void YourScriptDefaultMenu() {
score = Random.Range(0,1000);
}
}
為元件上的字段添加菜單
在字段上添加特性
ContextMenuItem
,并設定對應的執行參數即可。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 自己建立的腳本
/// </summary>
public class YourScript : MonoBehaviour
{
[Header("得分"),ContextMenuItem(name:"随機得分",function:"YourScriptDefaultMenu")]
public int score;
/// <summary>
/// 等級
/// </summary>
public int Level
{
get
{
return score / 100;
}
}
/// <summary>
/// 生成一個cube
/// </summary>
public void SpawnCube()
{
var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
}
[ContextMenu("随機score")]
public void YourScriptDefaultMenu() {
score = Random.Range(0,1000);
}
}
腳本使用技巧
自動添加相關依賴元件
有時候一個腳本挂到一個GameObject上時需要依賴其他的元件,為了讓不需要關系這些依賴的人使用這個腳本可以給腳本添加依賴資訊(
RequireComponent(typeof(xxx1),typeof(xxx2),...)
),這樣腳本首次挂到GameObject時就會把需要的依賴元件自動加上。
using UnityEngine;
[RequireComponent(typeof(Rigidbody),typeof(Transform))]
public class Projectile : MonoBehaviour
{
}
Inspector 菜單上的Reset功能
在所有Monobehaviour上都有一個Reset菜單,隻要在腳本中覆寫Reset()方法就可以響應該菜單的點選。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class Projectile : MonoBehaviour
{
public new Rigidbody rigidbody;
private void Reset()
{
rigidbody =transform.GetComponent<Rigidbody>();
}
}
暴露方法,快速測試
有時候需要驗證某個方法,這時可以在方法上添加
[ContextMenu]
屬性暴露該方法。這樣就不用另外添加ui按鈕,簡化測試步驟。
using UnityEngine;
public class Lancher : MonoBehaviour
{
public Rigidbody projectile;
public Vector3 offset = Vector3.forward;
[Range(0,10)]
public float velocity = 10;
[ContextMenu("發射")]
public void Fire() {
var body = Instantiate(projectile,transform.TransformPoint(offset),transform.rotation);
body.velocity = Vector3.forward * velocity;
}
}
在SceneView中添加Widget
Handles.PositionHandle
接下來學習給SceneView 中添加輔助Widget,比如用一個坐标軸标記一個Vector3的位置。一般情況下隻有當你選中場景中的GameObject并且點中了工具欄上的坐标軸選項才有坐标軸。學習之後我們也可以在Editor的
OnSceneGUI()
中添加自定義的坐标軸:Handles.PositionHandle。
1.建立一個
LancherEditor
腳本并放到Editor檔案夾下。
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Lancher))]
public class LancherEditor : Editor
{
private void OnSceneGUI()
{
var lancher = target as Lancher;
var transform = lancher.transform;
//轉到世界坐标
var wOffset = transform.TransformPoint(lancher.offset);
//設定坐标軸
wOffset = Handles.PositionHandle(position:wOffset,rotation:transform.rotation);
//更新拖拽坐标軸後的位置
lancher.offset = transform.InverseTransformPoint(position: wOffset);
}
}
2.添加完上面編輯器代碼後就會在SceneView中發現選中Lancher物體就有兩個坐标軸。 其中一個就是
lancher.offset
對應的坐标軸标記
Handles.RadiusHandle
Handles.RadiusHanle
可以标記一個可編輯的球體,讓Radiu更加直覺。其中Undo.RecordObject可以記錄對Projectile的修改操作,這樣可以用Ctrl+Z進行撤銷操作。
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Projectile))]
public class ProjectileEditor : Editor
{
private void OnSceneGUI()
{
var projectile = target as Projectile;
//開始修改檢測
EditorGUI.BeginChangeCheck();
var transform = projectile.transform;
//擷取最新值
var newDamageRadius = Handles.RadiusHandle(rotation: transform.rotation, position: transform.position, radius: projectile.damageRadius);
//結束修改檢測,更新值
if (EditorGUI.EndChangeCheck())
{
//記錄操作,後面可以撤銷目前操作
Undo.RecordObject(projectile,"改變damageRadius的操作");
projectile.damageRadius = newDamageRadius;
}
}
}
DrawGizmo
使用
[DrawGizmo]
能讓一個沒有Renderer元件的物體在Scene View 中渲染出來友善觀察測試,它必須是一個靜态方法參數包含兩個:一個是要渲染的目标元件,一個是GizmoType,這個方法可以放在任何類中但需要該類檔案在
Editor檔案夾
下。
/// <summary>
/// 給沒有Render元件的物體繪制Gizmos
/// </summary>
/// <param name="projectile">目标元件</param>
/// <param name="gizmoType"></param>
[DrawGizmo( GizmoType.Selected | GizmoType.NonSelected)]
static void DrawGizmos(Projectile projectile,GizmoType gizmoType) {
Gizmos.DrawSphere(projectile.transform.position,0.125f);
}
在Scene View 中繪制2D按鈕
在Scene View中繪制按鈕并綁定功能可以友善開發的時候測試功能。
1.在OnSceneGUI()中如果要繪制2D的GUI需要用
Handles.BeginGUI()
切到2D,後面再用
Handles.EndGUI()
切回去;
2.在
GUILayout.BeginArea
和
GUILayout.EndArea()
語句塊内繪制按鈕等…
3.new EditorGUI.DisabledGroupScope(!Application.isPlaying)表示隻在運作時才激活UI。
4.需要注意的是 坐标的轉換,螢幕坐标應用到GUILayout.Button需要做y軸方向的翻轉!!!即(
rectMin.y = SceneView.currentDrawingSceneView.position.height - rectMin.y;
)
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Lancher))]
public class LancherEditor : Editor
{
private void OnSceneGUI()
{
var lancher = target as Lancher;
var transform = lancher.transform;
//轉到世界坐标
var wOffset = transform.TransformPoint(lancher.offset);
//設定坐标軸
wOffset = Handles.PositionHandle(position: wOffset, rotation: transform.rotation);
//更新拖拽坐标軸後的位置
lancher.offset = transform.InverseTransformPoint(position: wOffset);
//繪制2D UI
{
Handles.BeginGUI();
Vector2 rectMin = Camera.current.WorldToScreenPoint(wOffset);
//y軸坐标需要翻轉
rectMin.y = SceneView.currentDrawingSceneView.position.height - rectMin.y;
var rect = new Rect(position: rectMin, size: new Vector2(64, 18));
{
GUILayout.BeginArea(rect);
//激活判斷:隻在運作模式激活
using (new EditorGUI.DisabledGroupScope(!Application.isPlaying))
{
//綁定事件
if (GUILayout.Button("發射"))
{
lancher.Fire();
}
}
GUILayout.EndArea();
}
Handles.EndGUI();
}
}
}