0
\$\begingroup\$

I'm really not experienced with c# so maybe my whole methodology is wrong but here goes.

I'm trying to build a GUI that allows many many parameters to be modified in real time and have a central repository for all those variables. So I am trying to build a wrapper class around the slider so I don't have to explicitly link it to each property in the parameters class manually, and make it easier to add other generic functionalities like events or whatever.

So right now I have a UI class that holds the sliders, a slider wrapper class that holds some extra functionality, and a central parameters class. The central class shouldn't have any dependencies of the other two classes.

The UI class instantiates the slider wrapper class, and passes a Unity slider object. That part works fine. It also passes the property from the central parameter class to which it should be linked. That's the part I'm stuck on.

UI class:

public class UIParams : MonoBehaviour
{
 
    private GeneralParams _centralParams;   // reference to central parameters class
    private Transform _mainUI;               // reference to main UI panel
    private Transform _panel1;               // reference to sub panel
 
    private MySlider _mySlider1;           // instance of my slider wrapper class
    // more sliders go here
   
    void Start()
   
    {
        //init
        _centralParams = GameObject.FindObjectOfType<GeneralParams>();
        var Panel1 = GameObject.Find("MainUI").transform.Find("Panel1");
 
       
       
        //sliders
        _mySlider1 = new MySlider(
            Panel1.Find("Slider1").GetComponent<Slider>(),  //pass the actual slider to the wrapper
            _centralParams.Parameter_1);                     //pass the central parameters property to the wrapper
       
       
        // more sliders go here
   
    }
 
}

Slider wrapper class:

Code (CSharp):
class MySlider
{
    private Slider _slider;                 // object to hold the slider
    private float _sliderValue;                   // value of the slider
 
    public MySlider(Slider Slider, float CentralParameterProperty)
    {
        _slider = Slider;                                                   // reference to Slider
        _slider.onValueChanged.AddListener(delegate { update();});      // listen for changes to slider, don't want to be checking all the time
        _sliderValue = CentralParameterProperty;                            // set slider to default value in CentralParams. How do I modify that value???
    }
 
    private void update()
    {
        _sliderValue = _slider.value;       // how do I change the original value from Central Params without explicitly linking each property manually?
    }
}

Central parameter class:

Code (CSharp):
public class CentralParams : MonoBehaviour
{
   
    public Action GeneralUpdateEvent;       // tell other classes when an update has occured
   
    [SerializeField]
    private float parameter_1;
    public float Parameter_1
    {
        get => parameter_1;
        set
        {
            parameter_1 = value;
            GeneralUpdate();
        }
    }
   
    // more parameters go here
   
 
    void GeneralUpdate()
    {
        GeneralUpdateEvent?.Invoke();
    }
}

Passing the float CentralParameterProperty into the MySlider instance works to set the default value of the slider. But obviously from that point onwards the slider only modifies the internal variable _sliderValue, not the property of the central class.

Can I set it up so I pass some object from the UI class into the MySlider on instantiation such that the MySlider instance can both get and set the values of the CentralParams property, without explicitly linking to central class within the MySlider class? Or is this just Wong?

Hope that made sense...

\$\endgroup\$
2
  • \$\begingroup\$ Have you considered creating something like a ModifiableProperty<T> wrapper class, so instead of accepting a float, your slider takes a ModifiableProperty<float>? Then it's a reference type, and you can do whatever you want with that reference. \$\endgroup\$
    – DMGregory
    Apr 22 '21 at 21:19
  • \$\begingroup\$ I will look into that thanks! \$\endgroup\$ Apr 22 '21 at 21:29
2
\$\begingroup\$

Here's a simple implementation along the lines of DMGregory's suggestion, which also invokes an event when the value is changed to make synchronizing UI easier.

//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
    [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);
        }
    }
}

Usage:

EventProperty<float> property = new EventProperty<float>(0);
property.OnChanged += Property_OnChanged;
property.Value = .5f;

private void Property_OnChanged(float value) {
    Debug.Log("Value changed to " + value);
}

I included the where T : struct constraint because it doesn't make sense to wrap a reference type in the EventProperty class. The point of EventProperty is to wrap a value type (struct) so that it can be used like a reference type. If T is already a reference type, there could be unexpected side effects:

List<float> list = new List<float>{1,2,3,4,5};
EventProperty<List<float>> wrapper = new EventProperty<List<float>>(list);
//does not fire OnChanged because we didn't pass a new value to wrapper.Value
wrapper.Value.Add(6);

If you want to serialize the value and be able to see it in the inspector, you'll probably need to subclass EventProperty, e.g.

[System.Serializable]
public class FloatEventProperty : EventProperty<float> { }
//...
[SerializeField] private FloatEventProperty floatProperty;

Serialized generic fields will show in the inspector if they are given a concrete type in a child class.

\$\endgroup\$
6
  • \$\begingroup\$ Amazing thanks that is very helpful. \$\endgroup\$ Apr 22 '21 at 22:13
  • \$\begingroup\$ Do you mind explaining why the "where T: struct" constraint is necessary? \$\endgroup\$ Apr 22 '21 at 22:39
  • \$\begingroup\$ @arcadeperfect It isn't strictly necessary, but it doesn't make sense to wrap a reference type in the EventProperty class. I'll add a little more detail to the answer. \$\endgroup\$
    – Kevin
    Apr 23 '21 at 0:24
  • \$\begingroup\$ last question - I have it working with the slider, but is there a way to also expose the EventProperty's value in the Unity editor? I can only expose fields and now I'm running into a similar problem getting the field to sync with the EventProperty \$\endgroup\$ Apr 23 '21 at 16:28
  • \$\begingroup\$ Never mind! I have a solution, somewhat convoluted though it may be \$\endgroup\$ Apr 23 '21 at 16:59

You must log in to answer this question.

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