- Home /
Custom Editor : Call a function on multiple selected objects
Learning how to write custom editor scripts.
When I select multiple objects, I can modify a variable and this is assigned to all selected objects, but when I use a custom editor button to call a function, only the first selected object has the function called.
Test scene : Create a new scene, create 2 cubes, create and attach the TestButtons script to both. Now create the TestButtonsInspector and store it in the Editor folder. Symptoms are as above.
TestButtons
#pragma strict
public var myTransform : Transform;
public var targetPosition : Vector3;
function Update()
{
LookAtTarget();
}
function ButtonOne()
{
Debug.Log( "Button One was pressed" );
LookAtTarget();
}
function ButtonTwo()
{
Debug.Log( "Button Two was pressed" );
transform.LookAt( Vector3.forward );
}
function LookAtTarget()
{
transform.LookAt( targetPosition );
}
TestButtonsInspector
#pragma strict
@CustomEditor( TestButtons )
@CanEditMultipleObjects
class TestButtonsInspector extends Editor
{
var myTransform : SerializedProperty;
var targetPosition : SerializedProperty;
var obj : GameObject;
var objScript : TestButtons;
function OnEnable()
{
obj = Selection.activeGameObject;
objScript = obj.GetComponent( TestButtons );
myTransform = serializedObject.FindProperty( "myTransform" );
targetPosition = serializedObject.FindProperty( "targetPosition" );
}
function OnInspectorGUI()
{
// Update the serializedProperty - always do this in the beginning of OnInspectorGUI.
serializedObject.Update();
// myTransform
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField( myTransform );
EditorGUILayout.EndHorizontal();
// targetPosition
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField( targetPosition );
EditorGUILayout.EndHorizontal();
// Editor GUI Buttons
EditorGUILayout.BeginHorizontal();
if ( GUILayout.Button( "Button One", GUILayout.MinWidth( 80 ), GUILayout.MaxWidth( 250 ) ) )
{
//target.ButtonOne();
//serializedObject.ButtonOne();
//var obj : TestButtons = serializedObject.TestButtons;
//TestButtons.ButtonOne();
//var go = serializedObject;
//go.SendMessage( "ButtonOne" );
//this.ButtonOne();
//var obj : GameObject = Selection.activeGameObject;
//var objScript : TestButtons = obj.GetComponent( TestButtons );
objScript.ButtonOne();
}
if ( GUILayout.Button( "Button Two", GUILayout.MinWidth( 80 ), GUILayout.MaxWidth( 250 ) ) )
{
//var obj : GameObject = Selection.activeGameObject;
//var objScript : TestButtons = obj.GetComponent( TestButtons );
objScript.ButtonTwo();
}
EditorGUILayout.EndHorizontal();
// Apply changes to the serializedProperty - always do this in the end of OnInspectorGUI.
serializedObject.ApplyModifiedProperties();
}
}
Hello, is there anybody in there? Just nod if you can hear me. Is there anyone at home?
Answer by Bunny83 · Jul 06, 2013 at 06:17 AM
Your problem is that you mix two fundamental things together. The Selection class of course holds the actual selection, no matter if it's a gameobject, a prefab, an asset, ... So whatever you select in the Unity editor it will be referenced by the Selection class.
Selection.activeGameObject only holds a reference to the "last" object that has been selected. It works exactly like in Windows. When you select multiple files and grab one of them to drag the files around, the last one you touched is the active one.
Selection.gameObjects holds the list of gameobjects that are selected.
However since you use a custom editor you don't need the selection class at all.
The Editor class has 3 properties that should be enough to work with:
target is actually kind of obsolete. It also holds the reference to the component on the active gameobject. target can be useful for single-component editors. For multiple-object editors you have to use the "targets" and the serializedObject which wraps the whole selection at once.
Thank you for your answer Bunny83. I've been struggling on understanding Serialized Objects. I shall take some time to study the information you have linked to, hopefully I will have that 'Eureka' moment today. Thanks for explaining and defining what I need to look at.
The serialized object is just to handle data that can be serialized by Unity. In other words everything that you can adjust setup in the inspector and is saved along with the instance.
UT put a lot of efford into this handy class to make multi-object editing more easy. Ins$$anonymous$$d of handling all the single objects on their own the serialized object treats them as one.
However if you want to call a function on each selected instance you should simply iterate through the targets array
Thank you so much for your help. I still have a great amount to learn, but my immediate solution was surprisingly simple using Selection.gameObjects
#pragma strict
@CustomEditor( TestButtons )
@CanEdit$$anonymous$$ultipleObjects
class TestButtonsInspector extends Editor
{
var myTransform : SerializedProperty;
var targetPosition : SerializedProperty;
var obj : GameObject;
var objScript : TestButtons;
function OnEnable()
{
obj = Selection.activeGameObject;
objScript = obj.GetComponent( TestButtons );
myTransform = serializedObject.FindProperty( "myTransform" );
targetPosition = serializedObject.FindProperty( "targetPosition" );
}
function OnInspectorGUI()
{
// Update the serializedProperty - always do this in the beginning of OnInspectorGUI.
serializedObject.Update();
// myTransform
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField( myTransform );
EditorGUILayout.EndHorizontal();
// targetPosition
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField( targetPosition );
EditorGUILayout.EndHorizontal();
// Editor GUI Buttons
EditorGUILayout.BeginHorizontal();
var objScript : TestButtons;
if ( GUILayout.Button( "Button One", GUILayout.$$anonymous$$inWidth( 80 ), GUILayout.$$anonymous$$axWidth( 250 ) ) )
{
for ( var obj in Selection.gameObjects )
{
Debug.Log( "" + obj.name );
objScript = obj.GetComponent( TestButtons );
objScript.ButtonOne();
}
}
if ( GUILayout.Button( "Button Two", GUILayout.$$anonymous$$inWidth( 80 ), GUILayout.$$anonymous$$axWidth( 250 ) ) )
{
for ( var obj in Selection.gameObjects )
{
Debug.Log( "" + obj.name );
objScript = obj.GetComponent( TestButtons );
objScript.ButtonTwo();
}
}
EditorGUILayout.EndHorizontal();
// Apply changes to the serializedProperty - always do this in the end of OnInspectorGUI.
serializedObject.Apply$$anonymous$$odifiedProperties();
}
}
Of course it will work, but it's not ment to be used like this ;)
An Editor instance is created to edit the a certain set of objects. Those objects are referenced by the targets array. So the even more easier solution is:
for ( var obj : TestButtons in targets )
{
Debug.Log( "" + obj.name );
obj.ButtonOne();
}
The Selection class belongs to the scene-view and what is selected there. Each inspector has it's own reference(s) to the object(s) it is inspecting. Just try your solution, select some objects, lock the inspector and deselect the objects. The inspector will still be active as well as it's targets array but the SceneView Selection will be empty.
Wow, I would have never realized. Your comment gave a different perspective and really helped me get closer to understanding what is actually going on. Note closer, not full understanding!
This is my first step in editor GUI. Several of my own built tools have different functions I wish to run in edit mode. $$anonymous$$y previous method was to select a value in an enum and then use context menu ( see my answer here for an example : http://answers.unity3d.com/questions/420154/modify-existing-mass-place-trees-function.html ). I thought it was time to add some buttons, but have been stuck and lost on this for a while. Thanks again for your help, it is has been invaluable and very much appreciated (sounds cliche but its true).