I’ve been wanting to make a public example of this ever since I found out how to do it. Here goes!
When you double-click a GameObject with a mesh, the Scene View will focus on that object and zoom to the mesh’s extents in the scene. That’s great for objects with a mesh, but how would you do it for other kinds of objects?
It’s actually pretty simple. As we know, for any MonoBehaviour based class, you can define a custom editor. Say you have a script that defines a set of points in local space (meaning Vector3 points that are relative to the object’s transform.position). When you double-click this object under normal circumstances, the Scene View will focus on it as expected, but you may not see all of the points you’ve defined. This is because the default inspector doesn’t know to take these points into account when trying to figure out how big your object is in world space.
So, you need to make a custom editor for your class. In that editor you will need to create two methods that the Scene View will look for when the object is double-clicked:
private bool HasFrameBounds()
private Bounds OnGetFrameBounds()
Returning true from HasFrameBounds tells the Scene View that there is an OnGetFrameBounds method. Return a Bounds object from OnGetFrameBounds to define the size of your object. In the case of the example, you would want the bounds to encapsulate all of the points as well as the object’s transform.position value.
Let’s look at an example class.
using UnityEngine; using System.Collections; public class FrameBoundsObject : MonoBehaviour { [Range(0.01f, 2.0f)] [SerializeField] private float m_ExtentsMultiplier = 1.0f; [SerializeField] private bool m_HasFrameBounds = true; [SerializeField] private Vector3[] m_Points; public Vector3[] Points { get { return m_Points; } } public float ExtentsMultiplier { get { return m_ExtentsMultiplier; } } public bool HasFrameBounds { get { return m_HasFrameBounds; } } private void OnDrawGizmos() { Vector3 position = transform.position; Gizmos.color = Color.blue; Gizmos.DrawSphere(position, 1.0f); if (m_Points == null) return; Gizmos.color = Color.green; for (int i = 0; i < m_Points.Length; i++) { Gizmos.DrawSphere(position + m_Points[i], 1.0f); } } }
It’s just a data class, so there’s no logic here. Using Gizmos, the object’s position is drawn as a blue sphere, and each point in the array of points is drawn as a green sphere. Let’s drop this into a scene and add a few points to it.
So, now we have six points all at 100m from the center where the object’s transform sits. At this point, we haven’t made a custom editor for the class so double-clicking the object will exhibit the default behavior. Now, double-click the object in the Hierarchy. The Scene View zooms in so that the blue sphere is in the center. But, all of the green spheres representing each point aren’t in view!
Okay. Let’s add the custom editor so that the object bounds can be defined.
using UnityEditor; using UnityEngine; using System.Collections; [CustomEditor(typeof(FrameBoundsObject))] public class FrameBoundsEditor : Editor { private FrameBoundsObject m_Target; private Transform m_Transform; private void OnEnable() { m_Target = target as FrameBoundsObject; m_Transform = m_Target.transform; } private bool HasFrameBounds() { return m_Target.HasFrameBounds; } private Bounds OnGetFrameBounds() { Bounds frameBounds = new Bounds(); frameBounds.Encapsulate(m_Transform.position); if (m_Target.Points != null) { for (int i = 0; i < m_Target.Points.Length; i++) { frameBounds.Encapsulate(m_Target.Points[i]); } } frameBounds.extents *= m_Target.ExtentsMultiplier; return frameBounds; } }
Take a look at the OnGetFrameBounds method body. Notice that it is creating a Bounds object and “bumping out” the extents using Encapsulate. Also, notice that the target’s transform.position is being encapsulated. This is because I wanted it to work this way for the example. If your object won’t need to take the transform into account, then don’t. The bounds can be whatever you need!
Now double-click the object. All of the green spheres should now be in the Scene View. Looks great, right? Well, not really.
It’s too far away! What’s up? It turns out that Unity, behind the scenes is…uhm…fixing the bounds that you return from OnGetFrameBounds. There are good reasons for doing this. The Editor is trying to make sure that your whole object is visible from every angle. But, the adjustments can yield unexpected results. Crack open ILSpy and look at the SceneView.FrameSelected method. Toward the bottom you’ll see that the bounds’ extents are being scaled by 1.5f and then again by 2.2f. Because of this, you may want to adjust the extents value to counteract the scaling a little.
Adjust the object’s Extents Multiplier value to 0.45 and double-click again. Everything should be much closer now.
Feel free to play with that value while changing the angle of the Scene View camera to see the effect of adjusting the extents by a static value. Also, toggle the Has Frame Bounds and double-click to see that when the HasFrameBounds method returns false, the frame bounds aren’t calculated when framing the object.