0
\$\begingroup\$

Can I have a custom serialized class whereby I set a value in the editor and that value persists into runtime, as happens with serialized monobehaviour fields?

Currently it sets itself to 0 or a default hard coded from within the class.

I noticed that using a struct actually works but using a struct breaks the events used in my actual use case (included below).

Example code:

public class Class1 : MonoBehaviour
{
    // this variable can be set in the editor and persist into run time
    [SerializeField] 
    private float testVar1;

    // this variable can not
    [SerializeField]
    private Class2 class2;

    private void Start()
    {
        class2 = new Class2();
    }
}

[Serializable]
public class Class2
{
    [SerializeField]
    private float testVar2;
}

My use case:

[Serializable]
public class EditorFloat
{
    [SerializeField]
    public float value; 
    public EventProperty<float> eventProperty;

    public EditorFloat(EventProperty<float> eventProperty)
    {

        this.eventProperty = eventProperty;
        Object.FindObjectOfType<CentralParams>().OnUiChange += Update;
        this.eventProperty.OnChanged += UpdateEditor;
    }
    public void Update()
    {
        eventProperty.Value = value;
    }
    public void UpdateEditor(float valueFromEventProperty)
    {
        value = valueFromEventProperty;
    }
}

Thanks!

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

This happens automatically. Unless you go out of your way to break it.

Your code goes out of its way to break it.

private void Start()
{
    class2 = new Class2();
}

This says, "after you've deserialized the value of class2 that I set in the inspector, please throw that in the garbage and replace it with a new Class2() that has default values instead"

If you don't want to overwrite your data with blanks, don't write code that overwrites your data with blanks.

\$\endgroup\$
3
  • \$\begingroup\$ Sorry still getting my head around all of this. Bearing in mind that my actual class needs to be passed objects through the constructor, how can I avoid that line? If I try to initialize it all in one go outside of the start method I get an error. \$\endgroup\$ Apr 23, 2021 at 19:03
  • \$\begingroup\$ If you have a problem with an error, you might want to include that in your question. We cannot help you with code you have not shown us, throwing errors you have not told us. \$\endgroup\$
    – DMGregory
    Apr 23, 2021 at 19:04
  • 1
    \$\begingroup\$ That said, I don't see any reason why that needs to be a constructor. Why not an Initialize() method you can call after the object has been constructed / deserialized? I'm also not sure why you have two layers of class wrappers around your float instead of just one. \$\endgroup\$
    – DMGregory
    Apr 23, 2021 at 19:05
0
\$\begingroup\$

Assuming this question comes from Passing properties as arguments such that the resulting class can modify the original

//wraps a struct and invokes an event when the struct is modified
[System.Serializable]
public class EventProperty<T> where T : struct {
    public delegate void ValueChange(T value);
    public event ValueChange OnChanged = delegate { };

    //might not show in inspector, but would from a concrete child class, see below
    [SerializeField] private T value;

    public EventProperty() {
        value = default(T);
    }

    public EventProperty(T value) {
        this.value = value;
    }

    public T Value { 
        get => value;
        set {
            this.value = value;
            OnChanged?.Invoke(value);
        }
    }
}

[System.Serializable]
public class FloatEventProperty : EventProperty<float> { }

public class MyBehaviour : MonoBehaviour {
    //won't show in inspector because it's generic
    [SerializeField] private EventProperty<float> floatValue1;
    //will show in Inspector
    [SerializeField] private FloatEventProperty floatValue2;
}

If I recall correctly, a generic class/field won't show in the inspector. However, if you have a subclass of the generic class which uses a concrete type (such as FloatEventProperty, which uses float as the concrete type), the field will show in the inspector as long as the concrete type is serializable.

\$\endgroup\$
5
  • \$\begingroup\$ In Unity 2020, simple generics like this will show up in the inspector, so we don't have to make the concrete aliases anymore. \$\endgroup\$
    – DMGregory
    Apr 23, 2021 at 20:21
  • \$\begingroup\$ @Kevin you are a very helpful individual, thanks for your time and tips! \$\endgroup\$ Apr 23, 2021 at 21:21
  • \$\begingroup\$ Thanks, glad to hear it! Please don't forget to upvote or mark as the correct answer if applicable. @DMGregory Oh, nice! \$\endgroup\$
    – Kevin
    Apr 23, 2021 at 22:37
  • \$\begingroup\$ @Kevin I would if I could, I'm so new here it won't let me. With this method, is it possible to have OnChanged event be invoked when the private variable is directly modified by the editor, which I presume is bypassing the setter? \$\endgroup\$ Apr 25, 2021 at 21:28
  • \$\begingroup\$ @arcadeperfect New users can still upvote and accept answers. The inspector would not invoke the event. \$\endgroup\$
    – Kevin
    Apr 27, 2021 at 17:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .