Extending Unity with Editor Scripting
上QQ阅读APP看书,第一时间看更新

Creating gizmos through code

We explored how to add simple gizmos through the Unity editor. This section will cover how to properly implement the OnDrawGizmos and OnDrawGizmosSelected methods to achieve a similar but more flexible solution.

Note

All the scripts in this section are examples, and are not meant to be used in the Level Creator tool.

The OnDrawGizmos and OnDrawGizmosSelected methods

To get started, in a new project, create a script called GizmoExample.cs. We will use this as a guinea pig for our first gizmos experiments (don't worry, the script is not going to suffer too much!)

Write the following code in GizmoExample.cs:

using UnityEngine;
public class GizmoExample : MonoBehaviour {
        private void OnDrawGizmos() { 
        }
}

When you implement the OnDrawGizmos method, you can add gizmos that always drawn in the Scene View and also allows the possibility to be selected with a click.

In this case, the method is empty. However, if you come back to Unity and wait for the compiler to end, you'll find that the class GizmoExample is listed in the Gizmos dropdown on the Scene View:

The OnDrawGizmos and OnDrawGizmosSelected methods

The Gizmos dropdown allows you to change the way gizmos are displayed in the Scene View. At the top of the dropdown, you can change the scale size that gizmos are drawn at; next to that, you will see a list of the available gizmos. To turn gizmos on or off, simply click on the checkbox to hide or show the gizmo itself.

If you select the column icon associated with GizmoExample, you will get the same kind of results we saw when we clicked on the cube icon in the inspector. Here, again, you can choose between using a label, a built-in icon, or a custom icon. For now, just choose a label.

Tip

Changing the icon in the Gizmos dropdown will also change the icon used to represent the script in the Project browser and the inspector.

As soon you add the GizmoExample.cs script to a new game object in the scene, a gizmo label with the name of the game object will be displayed in the Scene View.

Tip

The OnDrawGizmos method is not called if the component collapses in the inspector window.

We explored a simple way to make our script visible to Unity, but there are more powerful things to explore. Remove the icon from the Gizmos dropdown and update the GizmoExample.cs script as follows:

using UnityEngine;

public class GizmoExample : MonoBehaviour {
  
  private void OnDrawGizmos () { 
    Gizmos.color = Color.white;
    Gizmos.DrawCube (
      transform.position, Vector3.one);
  }

  private void OnDrawGizmosSelected () { 
    Gizmos.color = Color.red;
    Gizmos.DrawWireCube (
      transform.position, Vector3.one);
  }
}

We added a new method, OnDrawGizmosSelected. Implement this to draw gizmos only if the object is selected. Here, gizmos aren't pickable.

Inside the OnDrawGizmos and OnDrawGizmosSelected methods, we made use of the Gizmos class to draw gizmos. Now, you should see a solid cube in the position of the game object with the GizmoExample.cs script attached, and then a solid cube with a color outline when this game object is selected:

The OnDrawGizmos and OnDrawGizmosSelected methods

At this point, you may have noticed that the Gizmos class is part of the UnityEngine namespace instead of the UnityEditor namespace. This means you can use it in your MonoBehaviour class directly if you have the method OnDrawGizmos or OnDrawGizmosSelected implemented.

Tip

Take into consideration that these methods are called in an Editor context and will not be available in your final video game.

Adding gizmos using the DrawGizmo attribute

The DrawGizmo attribute allows you to display gizmos using a separated class from the original MonoBehaviour class and without using the OnDrawGizmos or OnDrawGizmosSelected method explicitly.

Here, we will need two scripts. The first one, called TargetExample.cs, is the MonoBehaviour class we want to add the gizmo; and the second one, called DrawGizmoExample.cs is a script that implements the gizmo.

Let's start creating the script TargetExample.cs in a new project:

using UnityEngine;

public class TargetExample : MonoBehaviour {
  
}

As we can see, no gizmo logic was added here. Because the DrawGizmo attribute is part of the UnityEditor namespace, we will create the DrawGizmoExample.cs script inside an Editor folder. For this, add the following code:

using UnityEngine;
using UnityEditor;

public class DrawGizmoExample {

    // This emulates OnDrawGizmos
    [DrawGizmo(GizmoType.NotInSelectionHierarchy | 
               GizmoType.InSelectionHierarchy | 
               GizmoType.Selected | 
               GizmoType.Active | 
               GizmoType.Pickable)]
    private static void MyCustomOnDrawGizmos(
        TargetExample targetExample, GizmoType gizmoType) {
        Gizmos.color = Color.white;
        Gizmos.DrawCube(
            targetExample.transform.position, Vector3.one);
    }
}

Save everything you've done so far, and in a new scene, add a game object with TargetExample.cs attached. You will see this:

Adding gizmos using the DrawGizmo attribute

The TargetExample class is rendering a gizmos cube similar to the past example, but now the DrawGizmoExample.cs , and external editor script, is responsible of that.

Any method meant to be used for render gizmos, in this case, MyCustomOnDrawGizmos, must be static and take two parameters: the object for which the gizmo is being drawn, and a GizmoType parameter, which indicates the context in which the gizmo is being drawn. Inside this method, you can use the Gizmos class again to add all the gizmos you want.

Then, to make this work, we used the DrawGizmo attribute on the method MyCustomOnDrawGizmos and passed as parameters the GizmoType we want to use. These are flags that specify scenarios in which the gizmos will be rendered and their behavior.

The GizmoType method offers five properties you can use:

  • InSelectionHierarchy: This draws the gizmo if it is selected or it is a child of the selected
  • NotInSelectionHierarchy: This draws the gizmo if it is not selected and also no parent is selected
  • Selected: This draws the gizmo if it is selected
  • Active: This draws the gizmo if it is active (shown in the inspector)
  • Pickable: The gizmo to be drawn can be picked from the editor

In the case of MyCustomOnDrawGizmos, to emulate the behavior of the OnDrawGizmos method, we used all the gizmo types available. You can use different combinations to achieve different results, for example, let's try to emulate the behavior of OnDrawGizmosSelected creating a method called MyCustomOnDrawGizmosSelected inside the DrawGizmoExample class:

[DrawGizmo(GizmoType.InSelectionHierarchy | 
               GizmoType.Active)]
    private static void MyCustomOnDrawGizmosSelected(
      TargetExample targetExample, GizmoType gizmoType) {
      Gizmos.color = Color.red;
      Gizmos.DrawWireCube(
        targetExample.transform.position, Vector3.one);
    }

Save the changes and wait for Unity to compile the scripts, and then you will see the functionality of the original OnDrawGizmosSelected method achieved by the MyCustomOnDrawGizmosSelected method:

Adding gizmos using the DrawGizmo attribute

Here, we presented an alternative to rendering gizmos in Unity, but in most cases, using the OnDrawGizmos or OnDrawGizmosSelected method is enough to create visual aids in the Scene View.

In which scenarios do you want to use this approach? Well, one instance is if you really want to separate what is related to your video game and what is related to the editor stuff. However, most of the times, this approach would be helpful when you don't have access to the implementation of the specific MonoBehaviour class, so using the method OnDrawGizmos or OnDrawGizmosSelected is not possible.

The Gizmos class

The Gizmos class has all the methods to draw gizmos in the Scene View and we will explore these methods in this section. If you want to reproduce the examples that we will work on here, just add the code snippets inside a MonoBehaviour class.

Note

This section is an extended version of the official documentation about the Gizmos class. If you want to check the original version visit: http://docs.unity3d.com/ScriptReference/Gizmos.html.

DrawCube

This draws a solid box with center and size.

Example:

// Method signature public static void DrawCube(Vector3 center, Vector3 size);
public Vector3 center = Vector3.zero;
public Vector3 size = Vector3.one;

private void OnDrawGizmos() {
  Gizmos.DrawCube(center, size);
} Result:  
DrawCube

DrawWireCube

This draws a wireframe box with center and size.

Example:

// Method signature public static void DrawWireCube(Vector3 center, Vector3 size);
public Vector3 center = Vector3.zero;
public Vector3 size = Vector3.one;

private void OnDrawGizmos() {
  Gizmos.DrawWireCube(center, size);
} Result:  
DrawWireCube

DrawSphere

This draw a solid sphere with center and radius.

Example:

// Method signature public static void DrawSphere(Vector3 center, float radius);
public Vector3 center = Vector3.zero;
public float radius = 1f;

private void OnDrawGizmos() {
  Gizmos.DrawSphere(center, radius);
} Result:  
DrawSphere

DrawWireSphere

This draws a wireframe sphere with center and radius.

Example:

// Method signature public static void DrawWireSphere(Vector3 center, float radius);
public Vector3 center = Vector3.zero;
public float radius = 1f;

private void OnDrawGizmos() {
  Gizmos.DrawWireSphere(center, radius);
} Result:  
DrawWireSphere

DrawRay

This draws a ray , a line starting at some position and going in some direction. This is a good way to visualize ray casting algorithms when you are unsure what the length or direction of a ray is.

Example:

// Method signatures public static void DrawRay(Ray r);
// public static void DrawRay(Vector3 from, Vector3 direction);
public Vector3 from = Vector3.zero;
public Vector3 direction = Vector3.up;

private void OnDrawGizmos() {
  Gizmos.DrawRay(from, direction);
} Result:  
DrawRay

DrawLine

This draws a line.

Example:

// Method signature public static void DrawLine(Vector3 from, Vector3 to);
public Vector3 from = new Vector3(1, 0, 0);
public Vector3 to = new Vector3(0, 0, 1);
  
private void OnDrawGizmos() {
  Gizmos.DrawLine(from, to);
} Result:
DrawLine

DrawIcon

This draws an icon at the world space Vector3, in Center at the specified position. The icon should be a regular image file, such as a PNG or JPG image, which is to be placed in the Assets/Gizmos folder. Whether or not the icon will be scaled and displayed or hidden is determined in the Gizmos dropdown. Using this method instead of the approach we saw at the beginning of this chapter gives you more control over the icons. For example, you can simulate toggle icons to visually represent boolean values in your code.

To make this code work, we previously created a copy of the asset Game/Art/UI/ UI_LivesAvatar.png inside a folder called Gizmos. Note that the icon always faces the Scene View camera.

Example:

// Method signature public static void DrawIcon(Vector3 center, string name, bool allowScaling = true);
private void OnDrawGizmos() {
  Gizmos.DrawIcon(
     transform.position, "icon.png");
} Result:
DrawIcon

DrawGUITexture

This draws the Texture inside the ScreenRect method on the Scene View using the XY plane (where the Z coordinate is zero). The values of the texture rectangle are given in scene units.

The optional border values specify an inset from each edge within the rectangle in scene units; the texture is drawn inside the inset rectangle and the edge pixels are repeated outward.

In this example, we pass the reference of the texture as a parameter. You will see that the texture is inverted; this is because the origin of the coordinate system is in the top-left corner.

Example:

// Method signature public static void DrawGUITexture(Rect screenRect, Texture texture, Material mat = null);
// public static void DrawGUITexture(Rect screenRect, Texture texture, int leftBorder, int rightBorder, int topBorder, int bottomBorder, Material mat = null);
public Rect screenRect = new Rect(0, 0, 100, 100);
public Texture theTexture;
  
private void OnDrawGizmos() {
  if(theTexture != null) {
    Gizmos.DrawGUITexture(screenRect, theTexture);
   }
} Result:
DrawGUITexture

DrawFrustrum

The DrawFrustrum draws a camera frustum using the currently set Gizmos.matrix for its location and rotation (don't worry about the meaning of the Gizmos.matrix variable, we will talk about that soon).

Example:

// Method signature public static void DrawFrustum(Vector3 center, float fov, float maxRange, float minRange, float aspect);
public Vector3 center = Vector3.zero;
public float fov = 60;
public float maxRange = 1;
public float minRange = 3;
public float aspect = 1.3f;
  
private void OnDrawGizmos() {
  Gizmos.DrawFrustum(
    center, fov, maxRange, minRange, aspect);
}
DrawFrustrum

Now, you have all the necessary knowledge to work with gizmos, so apply what you learned to move forward with the development of the Level Creator tool.