Ключ к ускорению создания игры - создание пользовательских редакторов для часто используемых компонентов. Для примера мы будем использовать очень простой скрипт, из-за которого объект всегда обращён к определённой точке.
//C# Example (LookAtPoint.cs)
using UnityEngine;
public class LookAtPoint : MonoBehaviour
{
public Vector3 lookAtPoint = Vector3.zero;
void Update()
{
transform.LookAt(lookAtPoint);
}
}
//JS Example (LookAtPoint.js)
#pragma strict
var lookAtPoint = Vector3.zero;
function Update()
{
transform.LookAt(lookAtPoint);
}
This will keep an object oriented towards a world-space point. Currently this script will only become active in play mode, that is, when the game is running. When writing editor scripts it’s often useful to have certain scripts execute during edit mode too, while the game is not running. You can do this by adding an ExecuteInEditMode attribute to it:
//C# Example (LookAtPoint.cs)
using UnityEngine;
[ExecuteInEditMode]
public class LookAtPoint : MonoBehaviour
{
public Vector3 lookAtPoint = Vector3.zero;
void Update()
{
transform.LookAt(lookAtPoint);
}
}
//JS Example (LookAtPoint.js)
#pragma strict
@script ExecuteInEditMode()
var lookAtPoint = Vector3.zero;
function Update()
{
transform.LookAt(lookAtPoint);
}
Now if you move the object which has this script around in the editor, or change the values of “Look At Point” in the inspector - even when not in play mode - the object will update its orientation correspondingly so it remains looking at the target point in world space.
The above demonstrates how you can get simple scripts running during edit-time, however this alone does not allow you to create your own editor tools. The next step is to create a Custom Editor for script we just created.
When you create a script in Unity, by default it inherits from MonoBehaviour, and therefore is a Component which can be placed on a game object. When placed on a game object, the Inspector displays a default interface for viewing and editing all public variables that can be shown - such as integers, floats, strings, Vector3’s, etc.
Here’s how the default inspector looks for our script above:
A custom editor is a separate script which replaces this default layout with any editor controls that you choose.
To begin creating the custom editor for our LookAtPoint script, you should create another script with the same name, but with “Editor” appended. So for our example: “LookAtPointEditor”.
//c# Example (LookAtPointEditor.cs)
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(LookAtPoint))]
[CanEditMultipleObjects]
public class LookAtPointEditor : Editor
{
SerializedProperty lookAtPoint;
void OnEnable()
{
lookAtPoint = serializedObject.FindProperty("lookAtPoint");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(lookAtPoint);
serializedObject.ApplyModifiedProperties();
}
}
//JS Example (LookAtPointEditor.js)
#pragma strict
@CustomEditor(LookAtPoint)
@CanEditMultipleObjects
class LookAtPointEditor extends Editor {
var lookAtPoint : SerializedProperty;
function OnEnable()
{
lookAtPoint = serializedObject.FindProperty("lookAtPoint");
}
function OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(lookAtPoint);
serializedObject.ApplyModifiedProperties();
}
}
Этот класс должен быть производным от класса Editor. Атрибут @CustomEditor сообщает Unity для какого компонента предназначен редактор.
Код в OnInspectorGUI выполняется каждый раз, когда Unity отображает редактор в инспекторе. Сюда можно поместить любой GUI код - он работает точно так же как OnGUI в играх, но выполняется внутри инспектора. Редактор определяет целевое свойство, которое можно использовать для доступа к инспектируемому объекту.
It’s not very interesting because all we have done so far is to recreate the Vector3 field, exactly like the default inspector shows us, so the result looks very similar (although the “Script” field is now not present, because we didn’t add any inspector code to show it).
However now that you have control over how the inspector is displayed in an Editor script, you can use any code you like to lay out the inspector fields, allow the user to adjust the values, and even display graphics or other visual elements. In fact all of the inspectors you see within the Unity Editor including the more complex inspectors such as the terrain system and animation import settings, are all made using the same API that you have access to when creating your own custom Editors.
Here’s a simple example which extends your editor script to display a message indicating whether the target point is above or below the gameobject:
//c# Example (LookAtPointEditor.cs)
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(LookAtPoint))]
[CanEditMultipleObjects]
public class LookAtPointEditor : Editor
{
SerializedProperty lookAtPoint;
void OnEnable()
{
lookAtPoint = serializedObject.FindProperty("lookAtPoint");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(lookAtPoint);
serializedObject.ApplyModifiedProperties();
if (lookAtPoint.vector3Value.y > (target as LookAtPoint).transform.position.y)
{
EditorGUILayout.LabelField("(Above this object)");
}
if (lookAtPoint.vector3Value.y < (target as LookAtPoint).transform.position.y)
{
EditorGUILayout.LabelField("(Below this object)");
}
}
}
//JS Example (LookAtPointEditor.js)
#pragma strict
@CustomEditor(LookAtPoint)
@CanEditMultipleObjects
class LookAtPointEditor extends Editor {
var lookAtPoint : SerializedProperty;
function OnEnable()
{
lookAtPoint = serializedObject.FindProperty("lookAtPoint");
}
function OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(lookAtPoint);
serializedObject.ApplyModifiedProperties();
if (lookAtPoint.vector3Value.y > (target as LookAtPoint).transform.position.y)
{
EditorGUILayout.LabelField("(Above this object)");
}
if (lookAtPoint.vector3Value.y < (target as LookAtPoint).transform.position.y)
{
EditorGUILayout.LabelField("(Below this object)");
}
}
}
So now we have an new element to our inspector which prints a message showing if the target point is above or below the gameobject.
This is just scratching the surface of what you can do with Editor scripting. You have full access to all the IMGUI commands to draw any type of interface, including rendering scenes using a camera within editor windows.
Вы можете добавить дополнительный код к окну Scene View, реализовав OnSceneGUI в вашем пользовательском редакторе. В данном случае мы добавим второй набор “ручек” позиции, позволяя пользователям перетягивать точку Look-at Point по окну Scene view.
OnSceneGUI работает точно также, как и OnInspectorGUI, только OnSceneGUI работает в окне Scene view. Для упрощения создания вашего интерфейса редактирования, вы можете использовать функции, указанные в классе Handles. Все функции оттуда предназначены для работы в 3D окне Scene view.
//C# Example (LookAtPointEditor.cs)
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(LookAtPoint))]
[CanEditMultipleObjects]
public class LookAtPointEditor : Editor
{
SerializedProperty lookAtPoint;
void OnEnable()
{
lookAtPoint = serializedObject.FindProperty("lookAtPoint");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(lookAtPoint);
if (lookAtPoint.vector3Value.y > (target as LookAtPoint).transform.position.y)
{
EditorGUILayout.LabelField("(Above this object)");
}
if (lookAtPoint.vector3Value.y < (target as LookAtPoint).transform.position.y)
{
EditorGUILayout.LabelField("(Below this object)");
}
serializedObject.ApplyModifiedProperties();
}
public void OnSceneGUI()
{
var t = (target as LookAtPoint);
EditorGUI.BeginChangeCheck();
Vector3 pos = Handles.PositionHandle(t.lookAtPoint, Quaternion.identity);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Move point");
t.lookAtPoint = pos;
t.Update();
}
}
}
//JS Example (LookAtPointEditor.js)
#pragma strict
@CustomEditor(LookAtPointJS)
@CanEditMultipleObjects
class LookAtPointEditorJS extends Editor {
var lookAtPoint : SerializedProperty;
function OnEnable()
{
lookAtPoint = serializedObject.FindProperty("lookAtPoint");
}
function OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(lookAtPoint);
serializedObject.ApplyModifiedProperties();
if (lookAtPoint.vector3Value.y > (target as LookAtPointJS).transform.position.y)
{
EditorGUILayout.LabelField("(Above this object)");
}
if (lookAtPoint.vector3Value.y < (target as LookAtPointJS).transform.position.y)
{
EditorGUILayout.LabelField("(Below this object)");
}
}
function OnSceneGUI()
{
var t : LookAtPointJS = (target as LookAtPointJS);
EditorGUI.BeginChangeCheck();
var pos = Handles.PositionHandle(t.lookAtPoint, Quaternion.identity);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(target, "Move point");
t.lookAtPoint = pos;
t.Update();
}
}
}
Если вы хотите поместить 2D объекты интерфейса (GUI, EditorGUI и остальные), вам надо будет их использовать между вызовами к Handles.BeginGUI() и Handles.EndGUI().