Daniel Lip
Daniel Lip

Reputation: 11319

How can I create a custom inspector guilayout.toggle?

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        Control control = (Control)target;

        if (GUILayout.Toggle(control.isControl, "Control"))
        {
            control.ToControl();
        }
    }
}

And

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Control : MonoBehaviour
{
    public Rigidbody rigidbody;
    public bool isControl = false;

    // Start is called before the first frame update
    void Start()
    {

    }

    public void ToControl()
    {
        if(isControl == false)
        {

        }
        else
        {
            Destroy(rigidbody);
        }
    }
}

What I want to do is a guilayout.toggle or a button and to be able to destroy and to add a Rigidbody to the gameobject the Control script will be on.

How do I create back add the Rigidbody to the gameobject ?

And how do I use the isControl flag ? The idea is to use the guilayout.toggle in the editor script.

I want to destroy or add a new rigidbody while the game is running ! But using a guilayout.toggle or button in the inspector.

Upvotes: 0

Views: 2653

Answers (1)

derHugo
derHugo

Reputation: 90620

Actually you wouldn't need an inspector script for that at all. Simply add a repeadetly check for the bool like e.g. in LateUpdate and make the component [ExecuteInEditoMode] like

using UnityEngine;

[ExecuteInEditoMode]
public class Control : MonoBehaviour
{
    public Rigidbody rigidbody;
    public bool isControl;

    // repeatedly check the bool
    private void LateUpdate()
    {
        ToControl();
    }

    public void ToControl()
    {
        if (!isControl && rigidbody)
        {
            // in editmode use DestroyImmediate
            if (Application.isEditor && !Application.isPlaying)
            {
                DestroyImmediate(rigidbody);
            }
            else
            {
                Destroy(rigidbody);
            }

            rigidbody = null;
        }
        else if(isControl && !rigidbody)
        {
            rigidbody = gameObject.AddComponent<Rigidbody>();

            // adjust settings of rigidbody
        }
    }
}

This way LateUpdate is called both, in playmode and in editmode, and will simply react to the isControl value.


Ofcourse there is an overhead for calling this LateUpdate all the time so if you want to avoid it you can call it only from the editor. However, since you are using base.OnInspectorGUI(); you don't really need an additional Toggle since you already have the one of the default inspector.

So could simply do

using UnityEngine;

public class Control : MonoBehaviour
{
    public Rigidbody rigidbody;
    public bool isControl;

    public void ToControl()
    {
        if (!isControl && rigidbody)
        {
            if (Application.isEditor && !Application.isPlaying)
            {
                DestroyImmediate(rigidbody);
            }
            else
            {
                Destroy(rigidbody);
            }

            rigidbody = null;
        }
        else if(isControl && !rigidbody)
        {
            rigidbody = gameObject.AddComponent<Rigidbody>();
        }
    }
}

and in the editor script simply do

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
    private Control control;

    // calle when the object gains focus
    private void OnEnable()
    {
        control = (Control)target;
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        if (!control.isControl && control.rigidbody)
        {
            control.ToControl();
            Repaint();
        }
        else if (control.isControl && !control.rigidbody)
        {
            control.ToControl();
            Repaint();
        }
    }
}

BUT you will already notice that this might affect how Undo/Redo works - in this case it would e.g. reset the isControl value but not remove along the RigidBody component leading to errors (see more below)


Or since you asked it you can add the ToggleField (currently you will have it twice since one also ships with base.OnInspectorGUI();)

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
    private Control control;

    // calle when the object gains focus
    private void OnEnable()
    {
        control = (Control)target;
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        control.isControl = EditorGUILayout.Toggle("additional isControl", control.isControl);

        if (!control.isControl && control.rigidbody)
        {
            control.ToControl();
            Repaint();
        }
        else if (control.isControl && !control.rigidbody)
        {
            control.ToControl();
            Repaint();
        }
    }
}

BUT you will notice that this solution changing the value using the additional isControl lacks the possibility of using Undo/Redo completely and it will NOT mark your scene as "dirty" so Unity might not save those changes!


So if you really want to have your custom toggle field in an inspector script I would actually recommend strongly to use proper SerializedPropertys instead of directly making changes to the target (sometimes it can't be avoided like with the adding of the component though):

[CustomEditor(typeof(Control))]
public class ControlEditor : Editor
{
    private SerializedProperty _isControl;
    private SerializedProperty rigidbody;
    private Control control;

    // calle when the object gains focus
    private void OnEnable()
    {
        control = (Control)target;

        // link serialized property
        _isControl = serializedObject.FindProperty("isControl");
        rigidbody = serializedObject.FindProperty("rigidbody");
    }

    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();

        // load current values into the serialized copy
        serializedObject.Update();

        if (!_isControl.boolValue && rigidbody.objectReferenceValue)
        {
            DestroyImmediate(rigidbody.objectReferenceValue);
            rigidbody.objectReferenceValue = null;
        }
        else if (_isControl.boolValue && !rigidbody.objectReferenceValue)
        {
            var rb = control.gameObject.AddComponent<Rigidbody>();
            rigidbody.objectReferenceValue = rb;
        }

        // write back changed serialized values to the actual values
        serializedObject.ApplyModifiedProperties();
    }
}

This looks more complicated and actually you have duplicate code but it gives you full Undo/Redo support and marks your objects and scenes dirty so Unity saves the changes.

Upvotes: 1

Related Questions