Kokizzu
Kokizzu

Reputation: 26848

Programatically bind script property on Unity (edit mode not at runtime)

For example, I have MonoBehaviour derived class called Foo:

class Foo : MonoBehaviour {
   public Button lastButton; 
   public Image []images;
}

[CustomEditor(typeof(Foo))]
class FooEditor : Editor {

    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        Foo foo = (Foo)target;
        if(GUILayout.Button("Bind last Button and all Images")) {
           /* how to bind programatically last foo.GetComponentsInChildren<Button>(); 
              to foo.gameObject.scene's 
                 foo.gameObject.GetComponent<Foo>().lastButton 
              in edit mode's property window 
                 (without have to drag on the editor)?
           */
           /* and how to bind programatically all foo.GetComponentsInChildren<Image>(); 
              to foo.gameObject.scene's 
                 foo.gameObject.GetComponent<Foo>().images 
              in edit mode's property window 
                 (without have to drag all one by one)?
           */
        }
    }
}

I want to automate the binding without have to drag one by one or do it in runtime, how can I do it programatically?

the only workaround I can think of (if there's no such API) is by saving the scene, parsing the scene yaml, then update the binding on the yaml file, and force UnityEditor to reload the yaml, but not sure if it's gonna work since loading yaml and rewriting it doesn't give equal value

Upvotes: 1

Views: 1616

Answers (1)

derHugo
derHugo

Reputation: 90683

Though I still don't understand the purpose of "binding" to be honest, it seems to me you actually are talking about simply referencing all the components via the editor script instead of having to drag and drop them. (Maybe the binding is included here and I just don't know it under that name?)

First of all make sure that either the entire code for FooEditor is placed in a folder called Editor or wrap it in

#if UNITY_EDITOR
    // any code using UnityEditor namespace
#endif

in order to strip them off in a build later. Otherwise you'll get compiler errors since UnityEditor will not exist in a build.

Then the important thing is to allways manipulate the SerializedPropertys of the SerializedObjects in editor scripts. This handles all the marking as dirty, saving changes and Undo/Redo entries for you. If you should happen to manipulate rather the fieds directly you would have to take care of these things yourself ...

a keyrole herefore play SerializedObject.Update and SerializedObject.ApplyModifiedProperties for loading and writing back values between the real target component and its serialized object - I like to call it "shadow clone".

#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
using UnityEngine.UI;

class Foo : MonoBehaviour
{
    public Button lastButton;
    public Image[] images;
}

#if UNITY_EDITOR
[CustomEditor(typeof(Foo))]
class FooEditor : Editor
{

    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        // fetch current values from the real target into the serialized "clone"
        serializedObject.Update();

        Foo foo = (Foo)target;
        if (GUILayout.Button("Bind last Button and all Images"))
        {
            // get last button field as serialized property
            var lastButtonField = serializedObject.FindProperty("lastButton");

            // get last Button reference
            // pass true to also get eventually inactive children
            var buttons = foo.GetComponentsInChildren<Button>(true);
            var lastButton = buttons[buttons.Length - 1];

            // asign lastButton to the lastButtonField
            lastButtonField.objectReferenceValue = lastButton;


            // slightly more complex but similar
            // get the field as serialized property
            var imagesField = serializedObject.FindProperty("images");

            // get the images references
            // again pass true to also get eventually inactive ones
            var images = foo.GetComponentsInChildren<Image>(true);

            // now first set the according list size
            imagesField.arraySize = images.Length;

            // assign all references
            for (var i = 0; i < imagesField.arraySize; i++)
            {
                // serialized property of the element in the field list
                var entry = imagesField.GetArrayElementAtIndex(i);

                // simply assign the reference like before with the button
                entry.objectReferenceValue = images[i];
            }
        }

        // write back changes to the real target
        // automatically handles marking as dirty and undo/redo
        serializedObject.ApplyModifiedProperties();
    }
}
#endif

enter image description here


I hope I'm not completely off and this is what you ment by "binding".


Note if you want to further change values of a reference you optained which is not the target itself you can easily use new SerializedObject(objectReference) in order to get a serialzed object e.g.

var serializedLastButton = new SerializedObject (lastButton);

Or also

var serializedLastButton = new SerializedObject(lastButtonField.objectReferenceValue);

now when changing any field in it again using FindProperty(propertyName) make sure to again also use

serializedLastButton.Update();

// make changes

serializedLastButton.ApplyModifiedProperties();

Upvotes: 3

Related Questions