Unity extension editor inbound guide

 

One of the better features in Unity I also like is its easy-to-expandable editor.

Generally speaking, expanding the editor is not very helpful to the efficiency of the game, but it helps to improve the efficiency of development.

After all, if you want to do well, you must first sharpen your tools.

This time, I will introduce the following methods to expand the editor:

  • OnDrawGizmos

  • OnInspectorGUI

  • OnSceneGUI

  • MenuItem and EditorWindow

  • ScriptableWizard

  • ScriptObject

  • Attributes

  • AssetProcess

 

OnDrawGizmos

OnDrawGizmos is a method under MonoBehaviour. Through this method, some Gizmos can be drawn to make some of its parameters easy to view in the Scene window.

For example, we have a platform that moves along a waypoint. The general operation may be to generate a bunch of new sub-objects to determine and set the position, but in fact, this is a bit redundant. All we need is a Vector2/Vector3 array. At this time, we can draw these Vector2/Vector3 array points in the editor through the OnDrawGizmos method.

30ffa1c4-cef7-41f9-8707-52be2ca66328_Gizmo.pnguploading.4e448015.gifFailed to export, re-upload canceled

OnDrawGizmos can draw the way out

The complete code is as follows:

 

public class DrawGizmoTest : MonoBehaviour { public Vector2[] poses; private void OnDrawGizmos() { Color originColor = Gizmos.color; Gizmos.color = Color.red; if( poses!=null && poses.Length>0 ) { //Draw Sphere for (int i = 0; i < poses.Length; i++) { Gizmos.DrawSphere( poses[i], 0.2f ); } //Draw Line Gizmos.color = Color.yellow; Vector2 lastPos = Vector2.zero; for (int i = 0; i < poses.Length; i++) { if( i > 0 ) { Gizmos.DrawLine( lastPos, poses[i] ); } lastPos = poses[i]; } } Gizmos.color = originColor; } }

 

OnInspectorGUI

In the development process, it is often necessary to perform some operations on a specific Component on the editor, for example, a button on the Inspector interface can trigger a piece of code.

eba8aa91-feb9-4427-83fd-1426a84128c1_Inspector.pnguploading.4e448015.gifFailed to export, re-upload canceled

There is a button on the Inspector interface to trigger a piece of code

This type belongs to the editor, so it is usually to create a new script inherited from the Editor in the Editor folder:

 

Create a new script inherited from Editor in the Editor folder

Afterwards, the editor inherits from UnityEditor.Editor. Note here that you must add [CustomEditor(typeof(Monobehavior class bound to the editor script)] to the class and then rewrite its OnInspectorGUI method:

 

using UnityEditor; [CustomEditor(typeof(InspectorTest))] public class InspectorTestEditor : Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); if(GUILayout.Button("Click Me")) { //Logic InspectorTest ctr = target as InspectorTest; } } }

Generally speaking, there are two ways to manipulate variables in the Editor class. One is to change the variables of Monobehaviour through direct access or function calls, and the other is to change the corresponding variables through the serializedObject in the Editor class.

For example, I want to change a public name of Monobehaviour to Codinggamer

Use method one, you can write like this in Editor:

 

if(GUILayout.Button("Click Me")) { //Logic InspectorTest ctr = target as InspectorTest; ctr.Name = "Codinggamer"; }

Click in the editor and you will find that the Hierarchy interface does not show the little stars that will appear after general changes:

 

Clicking the button does not show the little stars that will appear after the general changes

 

The general change is that small stars will appear

If you reopen the scene at this time, you will find that the changed value will return to the original value, that is, your change has not taken effect.

At this time, just call the EditorUtility.SetDirty(Object) method again.

If you want to use method two, you need to write in the Editor code:

 

if(GUILayout.Button("Click Me")) { //Logic serializedObject.FindProperty("Name").stringValue = "Codinggamer"; serializedObject.ApplyModifiedProperties(); }

There is no need to call the EditorUtility.SetDirty(Object) method here, and the changed little stars will already appear in the scene. After saving and reopening the scene, the corresponding value will be found to take effect.

 

Which of these two methods is better?

Generally speaking, the second method is better, but I actually use the first method when there is more logic involved. The advantage of using the second method is that it has a built-in undo function, which means that you can directly undo the changes after calling them, while the first method cannot.

 

OnSceneGUI

This method is also a method in the Editor class, which is used to display a UI element on the Scene view. Its creation is also to create a new script inherited from Editor in the Editor folder:

 

Also create a new script inherited from Editor in the Editor folder

In OnSceneGUI, you can make functions similar to OnDrawGizmo, such as drawing the waypoints of the Vector2 array:

OnSceneGUI can also make waypoint functions

The code is as follows:

 

using UnityEngine; using UnityEditor; [CustomEditor(typeof(SceneGUITest))] public class SceneGUITestEditor : Editor { private void OnSceneGUI() { Draw(); } void Draw() { //Draw a sphere SceneGUITest ctr = target as SceneGUITest; Color originColor = Handles.color; Color circleColor = Color.red; Color lineColor = Color.yellow; Vector2 lastPos = Vector2.zero; for (int i = 0; i < ctr.poses.Length; i++) { var pos = ctr.poses[i]; Vector2 targetPos = ctr.transform.position; //Draw Circle Handles.color = circleColor; Handles.SphereHandleCap( GUIUtility.GetControlID(FocusType.Passive ) , targetPos + pos, Quaternion.identity, 0.2f , EventType.Repaint ); //Draw line if(i > 0) { Handles.color = lineColor; Handles.DrawLine( lastPos, pos ); } lastPos = pos; } Handles.color = originColor; } }

 

The difference between OnDrawGizmos and OnSceneGUI

Because OnSceneGUI is a method on the Editor, and the Editor generally corresponds to Monobehaviour, which means that it can only be generated when the corresponding object is clicked. OnDrawGizmos can be globally visible.

If you need event processing, for example, you need to directly click to add or modify these waypoints on the Scene interface, you need to process the events on the OnSceneGUI to perform some operations.

 

OnSceneGUI can handle events

The complete code is as follows. Note here that the original poses has been changed to the List<Vector2> type for convenience:

 

using UnityEngine; using UnityEditor; [CustomEditor(typeof(SceneGUITest))] public class SceneGUITestEditor : Editor { protected SceneGUITest ctr; private void OnEnable() { ctr = target as SceneGUITest; } private void OnSceneGUI() { Event _event = Event.current; if( _event.type == EventType.Repaint ) { Draw(); } else if ( _event.type == EventType.Layout ) { HandleUtility.AddDefaultControl( GUIUtility.GetControlID( FocusType.Passive ) ); } else { HandleInput( _event ); HandleUtility.Repaint(); } } void HandleInput( Event guiEvent ) { Ray mouseRay = HandleUtility.GUIPointToWorldRay( guiEvent.mousePosition ); Vector2 mousePosition = mouseRay.origin; if( guiEvent.type == EventType.MouseDown && guiEvent.button == 0 ) { ctr.poses.Add( mousePosition ); } } void Draw() { //Draw a sphere Color originColor = Handles.color; Color circleColor = Color.red; Color lineColor = Color.yellow; Vector2 lastPos = Vector2.zero; for (int i = 0; i < ctr.poses.Count; i++) { var pos = ctr.poses[i]; Vector2 targetPos = ctr.transform.position; //Draw Circle Handles.color = circleColor; Vector2 finalPos = targetPos + new Vector2( pos.x, pos.y); Handles.SphereHandleCap( GUIUtility.GetControlID(FocusType.Passive ) , finalPos , Quaternion.identity, 0.2f , EventType.Repaint ); //Draw line if(i > 0) { Handles.color = lineColor; Handles.DrawLine( lastPos, pos ); } lastPos = pos; } Handles.color = originColor; } }

 

MenuItem and EditorWindow

MenuItem can be said to be the most used. Its function is the menu item on the editor. It is generally used for some shortcut operations, such as swapping the positions of two objects:

 

MenuItem can do some quick operations, such as swapping the positions of two objects

As it involves the editor code, it can still be placed under the Editor folder. The specific code is as follows:

 

using UnityEditor; using UnityEngine; public class MenuCommand { [MenuItem("MenuCommand/SwapGameObject")] protected static void SwapGameObject() { //只有两个物体才能交换 if( Selection.gameObjects.Length == 2 ) { Vector3 tmpPos = Selection.gameObjects[0].transform.position; Selection.gameObjects[0].transform.position = Selection.gameObjects[1].transform.position; Selection.gameObjects[1].transform.position = tmpPos; //处理两个以上的场景物体可以使用MarkSceneDirty UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty( UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene() ); } } }

 

EditorWindow

EditorWindow has many applications in the Unity engine, such as Animation, TileMap and Animetor windows should use EditorWindow. The creation method is still to create a script inherited from EditorWindow in the Editor folder. EditorWindow has a GetWindow method. After the call, if there is no current window, it will return a new one. If there is, it will return to the current window. Then call Show to display this window. You can use MenuItem to display this EditorWindow, and rewrite the OnGUI method to write the Editor UI:

 

using UnityEngine; using UnityEditor; namespace EditorTutorial { public class EditorWindowTest : EditorWindow { [MenuItem("CustomEditorTutorial/WindowTest")] private static void ShowWindow() { var window = GetWindow(); window.titleContent = new GUIContent("WindowTest"); window.Show(); } private void OnGUI() { if(GUILayout.Button("Click Me")) { //Logic } } } }

Then click the Menu of the editor and this EditorWindow will come out:

 

Click the Menu of the editor and there will be this EditorWindow

The UI of EditorWindow is written similarly to OnInspectorGUI, basically GUILayout and EditorGUILayout.

 

The difference between EditorWindow and OnInspectorGUI

The main difference is that EditorWindow can be docked on the sidebar and will not regenerate just because you click on an object. The Editor class of OnInspectorGUI calls the OnEnable method every time you switch and click.

 

How EditorWindow draws Scene interface UI

If you need to draw some UI to Scene in EditorWindow, it is invalid to use OnSceneGUI of Editor at this time. At this time, you need to add SceneView event callback at Focus or OnEnable, and remove the callback at OnDestroy:

 

private void OnFocus() { //在2019版本是这个回调 SceneView.duringSceneGui -= OnSceneGUI; SceneView.duringSceneGui += OnSceneGUI; //以前版本回调 // SceneView.onSceneGUIDelegate -= OnSceneGUI // SceneView.onSceneGUIDelegate += OnSceneGUI } private void OnDestroy() { SceneView.duringSceneGui -= OnSceneGUI; } private void OnSceneGUI( SceneView view ) { }

 

ScriptWizard

The BuildSetting window (the pop-up window of Ctr+Shift+B) in the Unity engine uses SciptWizard. It is generally used as a relatively simple generator and initialization type function in the development process. For example, the art gives me a sequence frame. You need to directly generate a GameObject with SpriteRenderer, and it also has an Animator with its own sequence frame.

47cd452e-b832-4053-885f-0b5f478ded74_ScriptWizard.pnguploading.4e448015.gifFailed to export, re-upload canceled

ScriptObject default display style

The creation process is to create a script inherited from ScriptWizard in the Editor folder, call the ScriptWizard.DisplayWizard method to generate and display this window, click Create in the lower right corner to call the OnWizardCreate method:

 

public class TestScriptWizard: ScriptableWizard { [MenuItem("CustomEditorTutorial/TestScriptWizard")] private static void MenuEntryCall() { DisplayWizard("Title"); } private void OnWizardCreate() { } }

 

The difference between ScriptWizard and EditorWindow

If you declare a Public variable in ScriptWizard, you will find that it can be displayed directly in the window, but it cannot be displayed in the EditorWindow.

 

ScriptObject

For some data and configuration in the game, you can consider using ScriptObject to save. Although XML is also available, ScriptObject is relatively simple and can save UnityObjects such as Sprite and Material. You will even find that the classes mentioned above are inherited from SctriptObject. Because it is no longer an editor only, it does not have to be placed in the Editor folder.

Similar to SctiptWizard, it also declares that Public can be seen directly on the window, and the custom drawing GUI is also in the OnGUI method:

 

[CreateAssetMenu(fileName = "TestScriptObject", menuName = "CustomEditorTutorial/TestScriptObject", order = 0)] public class TestScriptObject : ScriptableObject { public string Name; }

The purpose of using the Attribute of CreateAssetMenu is to make it can be generated by right-clicking in the Project window:

 

You can right-click in the Project window to generate

 

The difference between ScriptObject and System.Serializable

Beginners may be troubled with these two (I was troubled at the beginning). At first, I dragged SctiptObject onto Monobehaviour and found that it would not show the properties of ScriptObject.

Dragging SctiptObject onto Monobehaviour will not show the properties of ScriptObject

Then I added [System.Serializable] to SctiptObject, which was useless:

 

[CreateAssetMenu(fileName = "TestScriptObject", menuName = "CustomEditorTutorial/TestScriptObject", order = 0)] [System.Serializable] public class TestScriptObject : ScriptableObject { public string Name; }

Therefore, it is not advisable to use [System.Serializable] on ScriptObject. [System.Serializable] is suitable for ordinary Class, such as:

 

[System.Serializable] public class Data { string Name; }

 

To call the editor on the ScriptObject, you need to call EditorUtility.SetDirty, not EditorSceneManager.MarkSceneDirty

Because MarkSceneDirty, as the name implies, marks the scene as modified, but the edited ScriptObject does not belong to the data in the scene, so if you modify it, you can only call EditorUtility.SetDirty, otherwise the data change will not take effect.

 

Attributes

Attributes is a function of C#, which allows the declaration information to be associated with the code, and it is closely related to the reflection of C#. In Unity, such as [System.Serializable], [Header], [Range] are its applications. Generally speaking, other functions can also be realized by Editor, but the corresponding attributes can be drawn for better reuse.

Expansion of Attribute is relatively more complicated. It involves two classes: PropertyAttribute and PropertyDrawer. The former is to define its behavior, and the latter is mainly its display effect in the editor. Generally speaking, Attribute is placed in Runtime, and Drawer is placed in the Editor folder. The example here is to add [Preview] Attribute, so that we can drag Sprite or GameObject to display the preview image:

Add [Preview] Attribute, so that drag and drop Sprite or GameObject can display the preview image

The code when used is as follows:

 

public class AttributeSceneController : MonoBehaviour { [Preview] public Sprite sprite; }

We now add the PreviewAttribute script inherited from PropertyAttribute to the folder of the Runtime layer:

 

public class Preview : PropertyAttribute { public Preview() { } }

Then add the PreviewDrawer script inherited from PropertyDrawer in the Editor folder:

 

using UnityEngine; using UnityEditor; namespace EditorTutorial { [CustomPropertyDrawer(typeof(Preview))] public class PreviewDrawer: PropertyDrawer { //调整整体高度 public override float GetPropertyHeight( SerializedProperty property, GUIContent label ) { return base.GetPropertyHeight( property, label ) + 64f; } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property); EditorGUI.PropertyField(position, property, label); // Preview Texture2D previewTexture = GetAssetPreview(property); if( previewTexture != null ) { Rect previewRect = new Rect() { x = position.x + GetIndentLength( position ), y = position.y + EditorGUIUtility.singleLineHeight, width = position.width, height = 64 }; GUI.Label( previewRect, previewTexture ); } EditorGUI.EndProperty(); } public static float GetIndentLength(Rect sourceRect) { Rect indentRect = EditorGUI.IndentedRect(sourceRect); float indentLength = indentRect.x - sourceRect.x; return indentLength; } Texture2D GetAssetPreview( SerializedProperty property ) { if (property.propertyType == SerializedPropertyType.ObjectReference) { if (property.objectReferenceValue != null) { Texture2D previewTexture = AssetPreview.GetAssetPreview(property.objectReferenceValue); return previewTexture; } return null; } return null; } } }

Here is some drawing of attributes. In the actual development process, we often need some interactive UI, such as adding a [Button] to the method and then exposing a button in the editor. For specific examples, please refer to NaughtyAttribute

 

AssetPostprocessor

In the development process, I often encounter resource import problems. For example, when I make pixel game pictures, the FilterMode is Point, the pictures do not need to be compressed, and the PixelsPerUnit is 16. It would be troublesome to copy to a picture every time and then modify it. One solution here is that MenuItem can be used to process it, but it requires a few more clicks, and AssetPostprocessor can be used to process it automatically.

Create a new script inherited from AssetPostprocessor in the Editor folder:

 

public class TexturePipeLine : AssetPostprocessor { private void OnPreprocessTexture() { TextureImporter importer = assetImporter as TextureImporter; if( importer.filterMode == FilterMode.Point ) return; importer.spriteImportMode = SpriteImportMode.Single; importer.spritePixelsPerUnit = 16; importer.filterMode = FilterMode.Point; importer.maxTextureSize = 2048; importer.textureCompression = TextureImporterCompression.Uncompressed; TextureImporterSettings settings = new TextureImporterSettings(); importer.ReadTextureSettings( settings ); settings.ApplyTextureType( TextureImporterType.Sprite ); importer.SetTextureSettings( settings ) ; } }

Then import a pixel image and find that it's all set up:

 

Automatic settings for imported pictures

Some conceivable usage scenarios can be based on XML and SpriteSheet to automatically generate animations or automatically cut images, and parse PSD or ASE to automatically import PNG.

 

Some other things worth noting

 

Undo

It was said before that the variables of the original Monobehaviour script can not be undone if the original Monobehaviour script is directly modified in the Editor, but the modification can be undone by using serializedObject. Here you can write an Undo yourself to record so that it can be undone, the code is as follows:

 

if(GUILayout.Button("Click Me")) { InspectorTest ctr = target as InspectorTest; //记录使其可以撤销 Undo.RecordObject( ctr ,"Change Name" ); ctr.Name = "Codinggamer"; EditorUtility.SetDirty( ctr ); }

 

SelectionBase

When you use the [SelectionBase] Attribute in your class, if you click on an object under its child node, it will still only focus on the parent node.

 

After using SelectionBase, clicking on the child node still selects the parent node

 

Do not write editor code in the Editor folder

Sometimes our Monobehaviour itself is very short, and the code to extend the InspectorGUI is also very short. There is no need to create a new script on the Editor. You can directly use the #UNITY_EDITOR macro to create an extended editor. For example, the previous extension InspectorGUI can be written like this :

 

using System.Collections; using System.Collections.Generic; using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif namespace EditorTutorial { public class InspectorTest : MonoBehaviour { public string Name = "hello"; } #if UNITY_EDITOR [CustomEditor(typeof(InspectorTest))] public class InspectorTestEditor : Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); if(GUILayout.Button("Click Me")) { InspectorTest ctr = target as InspectorTest; //记录使其可以撤销 Undo.RecordObject( ctr ,"Change Name" ); ctr.Name = "Codinggamer"; EditorUtility.SetDirty( ctr ); } } } #endif }

 

EditorWindow and Editor save data

Here you need to use EditorPrefs to save and read data, add [System.SerializeField] Attribute to the data that needs to be saved, and then you can save or serialize json during OnEnable and OnDisable:

 

[SerializeField] public string Name = "Hi"; private void OnEnable() { var data = EditorPrefs.GetString( "WINDOW_KEY", JsonUtility.ToJson( this, false ) ); JsonUtility.FromJsonOverwrite( data, this ); } private void OnDisable() { var data = JsonUtility.ToJson( this, false ); EditorPrefs.SetString("WINDOW_KEY", data); }

I feel that this method can also be serialized scripts saved locally at runtime.

 

Retrieve the Editor class corresponding to MonoBehaviour in the code

As mentioned above, SerializedObject is generally used in editor code, like the built-in SerializedObject in the Editor class. In fact, all scripts inherited from SctiptObject or Monobehaviour can generate SerializedObject. The generation method is very simple, just pass in the components you need to serialize when you need new:

 

SerializedObject serialized = new SerializedObject(this);

 

postscript

This article does not involve the use of more APIs, but wants to show what you can do with the extension editor and where you need to start when you want to do some extensions. For example, if you want to make a tool for artists to quickly generate characters, you can use ScriptWizard. If you need to make it easier for artists and designers to adjust character attributes, you can consider using Editor. If you need to make a level editor for the level designer, you can consider using EditorWindow.

I have written a lot. The above is a summary of my experience in using the Unity extension editor and some problems encountered.

Sample Github repository: UnityEditorTutorial

Guess you like

Origin blog.csdn.net/u011105442/article/details/105064596