- Home /
OnInspectorGUI - Using the default Object Selection popup.
Hi everyone, this seems pretty basic stuff, but I can't seem to get it to work for the life of me so I hoped maybe someone could show me the silly little thing I'm doing wrong to fix it.
I want to build a doubly-linked-list of GameObject nodes that can be placed in the world. Each node has a next node, and a previous node. When I set an objects "Next Node" or "Prev Node" I want both objects to automatically point to each other (the user shouldn't have to go and change both nodes to correctly point to eachother). I can do this by hiding my NextNode and PrevNode members behind accessors, like so:
using UnityEngine; using System.Collections;
public class CameraNode : MonoBehaviour { // public members //////////////////////////////////////////////
// public methods ///////////////////////////////////////////////
// public accessors /////////////////////////////////////////////
public CameraNode NextNode
{
get { return m_nextNode; }
set
{
m_nextNode = value;
if( ( value != null ) &&
( value.PrevNode != this ) )
{
value.PrevNode = this;
}
}
}
public CameraNode PrevNode
{
get { return m_prevNode; }
set
{
m_prevNode = value;
if( ( value != null ) &&
( value.NextNode != this ) )
{
value.NextNode = this;
}
}
}
// private methods ///////////////////////////////////////////////
// private members //////////////////////////////////////////////
private CameraNode m_nextNode;
private CameraNode m_prevNode;
}
Now, I do want these nodes editable in the inspector. No worries, I can write an editor script. So I do:
using UnityEngine; using UnityEditor; using System.Collections;
[CustomEditor(typeof(CameraNode))] public class CameraNodeEditor : Editor { public override void OnInspectorGUI()
{ if( target is CameraNode ) //thank you yoyo { CameraNode editing = (CameraNode)target; GameObject nextNodeObj = (GameObject)EditorGUILayout.ObjectField(
"Next Node"
, editing.NextNode
, typeof( CameraNode ) );
if( nextNodeObj != null )
{
editing.NextNode = nextNodeObj.GetComponent<CameraNode>();
}
DrawDefaultInspector();
}
} }
But here's the problem, I'm expecting this (look at the highlighted Test)
- I can select an object that only has a particular component.
- I can select form objects in the world, or prefabs (assets)
But what I get is this:
- I can only see assets?
- It looks ugly in the inspector, and non standard.
Now I've seen other questions on the forums that ask this question too, and people recommended a TextField? But that's not really what I want here. Surely this is a reusable component right? How did the Unity developers do it?
Thanks for your time,
- Howard
Upvoted for the nicely formatted question, clearly asked, with screenshots to boot! I think this is a bug in Unity 3, but I hesitate to post that as an answer. BTW, the docs state that the ObjectField method "$$anonymous$$ake[s] an object drop slot field" -- does drag/drop from the Hierarchy work as expected?
Aside: rather than "if( target.GetType() == typeof( CameraNode ) )" it's clearer to write "if (target is CameraNode)".
When I attempt to drag an object from the hierarchy I get the following error:
"InvalidCastException: Cannot cast from source type to destination type. CameraNodeEditor.OnInspectorGUI () (at Assets/Editor/CameraNodeEditor.cs:14) UnityEditor.InspectorWindow.OnGUI () (at E:/BuildAgent/work/71ca6fec1b41cc30/Editor/$$anonymous$$ono/Inspector/InspectorWindow.cs:364) System.Reflection.$$anonymous$$ono$$anonymous$$ethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture)"
Answer by runevision · Jan 19, 2011 at 10:29 AM
It seems that the ObjectField
never shows scene objects. I guess this is because the object selector doesn't know if the object is going to be stored in a persistent object (prefab) or a scene object, and a prefab is not supposed to be able to ever reference a scene object. I guess we need to figure out a way to handle that.
In the mean time, you can work around it by using a PropertyField
.
First of all, note that you have to make your variables serialized in order for them to be stored. All public
variables of supported data types are serialized by default, but private
variables can also be serialized by using the [SerializeField]
attribute. This will make them show up in the Inspector, but you can hide them again, using the [HideInInspector]
attribute:
[HideInInspector]
[SerializeField]
private CameraNode m_nextNode;
[HideInInspector]
[SerializeField]
private CameraNode m_prevNode;
Now, use a PropertyField to show the object field:
using UnityEngine; using UnityEditor; using System.Collections;
[CustomEditor(typeof(CameraNode))] public class CameraNodeEditor : Editor { SerializedObject m_Object; SerializedProperty m_NextNodeProperty;
void OnEnable ()
{
m_Object = new SerializedObject (target);
m_NextNodeProperty = m_Object.FindProperty ("m_nextNode");
}
public override void OnInspectorGUI()
{
EditorGUIUtility.LookLikeInspector();
if( target.GetType() == typeof( CameraNode ) )
{
CameraNode editing = (CameraNode)target;
CameraNode nodeBefore = editing.NextNode;
EditorGUILayout.PropertyField (m_NextNodeProperty);
if (m_NextNodeProperty.objectReferenceValue as CameraNode != nodeBefore)
editing.NextNode = m_NextNodeProperty.objectReferenceValue as CameraNode;
DrawDefaultInspector();
}
}
}
I have confirmed that to work fine. The scene objects are displayed.
Note that using EditorGUIUtility.LookLikeInspector();
will make the controls look like default inspector controls. We are moving away from that ourselves though; you'll notice that a lot of components in Unity 3 have different look; for example the Camera component, Light component, and some more.
The root of the problem is that I would like to enforce that node.NextNode.PrevNode == node
. I think the correct way to do this is by using accessors ins$$anonymous$$d of public members, unfortunately, accessors cannot be serialized in the inspector(?). That was where my editor script comes in, it's basically a way to let me edit members and run additional code during the set so that I can always be sure that NextNodes and PrevNodes are always in sync, even across different GameObjects. If I could just make the raw members public I wouldn't have to bother with the editor script =]
The custom inspector I posted DOES use the properties and thus all assignment through the Inspector will ensure the list is linked correctly. I just remembered that you don't need to make the variables public either, you can use the [SerializeField] attribute ins$$anonymous$$d. I have edited my answer to point this out.