1
\$\begingroup\$

I made some custom control named Range Control in Unity. It has two buttons a "plus" and a "minus" and an input field that shows the current value. The control has (for now) two implementations:

enter image description here

  1. None-List - The value is clamped between a min and a max.
  2. Using a List - The control accepts a range (list) of values and changing the index (much like a dropdown).

Both implementation has almost identical UI (on the 2nd one the input field is read only cause you get a range from the outside).

I want to make one control of the two. One prefab with one script and let the user decide if it will work like #1 or #2 (or #3 if the future holds).

This is what I have by now. How can I make it better?

public class RangeControlBase : MonoBehaviour
{

    public InputField NumberInputfield;
    public string m_sPrefix;
    public string m_sSuffix;
    public RangeControlType RangeType;

    public Button PlusButton;
    public Button MinusButton;



    void Awake()
    {

        PlusButton.onClick.AddListener(() => ChangeValue(1));
        MinusButton.onClick.AddListener(() => ChangeValue(-1));
    }

    public string Prefix
    {
        get
        {
            return m_sPrefix;
        }
        set
        {
            m_sPrefix = value;
            UpdateValue();
        }
    }

    public string Suffix
    {
        get
        {
            return m_sSuffix;
        }
        set
        {
            m_sSuffix = value;
            UpdateValue();
        }
    }

    public virtual void ChangeValue(int direction)
    {
        throw new NotImplementedException();
    }

    public virtual void UpdateValue()
    {
        throw new NotImplementedException();
    }

}

Range Control:

public class RangeControl : RangeControlBase
{

    public float Stepping = 5;

    float m_fMinRange = 10;
    float m_fMaxRange = 1000;
    float m_fValue;

    public Action<float> OnValueChanged = delegate { };

    void Start()
    {
        NumberInputfield.onEndEdit.AddListener(t => SetStringValue(t));
    }

    public void SetStringValue(string text)
    {
        float value;
        text = Regex.Replace(text, string.Format(@"\({0}|{1}\)", Prefix, Suffix), string.Empty);
        if (float.TryParse(text, out value))
        {
            Value = value;
        }
    }

    public float Value
    {
        get { return m_fValue; }
        set
        {
            m_fValue = value;
            UpdateValue();
        }
    }

    public float MinRange
    {
        get { return m_fMinRange; }
        set
        {
            m_fMinRange = value;
            UpdateValue();
        }
    }

    public float MaxRange
    {
        get { return m_fMaxRange; }
        set
        {
            m_fMaxRange = value;
            UpdateValue();
        }
    }

    public override void ChangeValue(int direction)
    {
        Value += direction * Stepping;
    }

    public override void UpdateValue()
    {
        m_fValue = Mathf.Clamp(Value, MinRange, MaxRange);

        NumberInputfield.text = string.Format("{0}{1}{2}", Prefix, m_fValue, Suffix);

        MinusButton.interactable = m_fValue != m_fMinRange;
        PlusButton.interactable = m_fValue != m_fMaxRange;

        OnValueChanged(m_fValue);
    }
}

List Range Control:

public class ListRangeControl : RangeControlBase
{
    public Action<int> OnSelectedIndexChanged = delegate { };
    public Action<float> OnValueChanged = delegate { };

    List<float> m_itemsList;
    int m_iIndex = -1;

    public List<float> ItemsList
    {
        get
        {
            return m_itemsList;
        }
        set
        {
            m_itemsList = value;
            m_iIndex = 0;
            UpdateValue();
        }
    }

    public int Index
    {
        set
        {
            m_iIndex = value;
            UpdateValue();
        }
    }

    public override void ChangeValue(int direction)
    {
        m_iIndex += direction;
        OnSelectedIndexChanged(m_iIndex);
        UpdateValue();
    }

    public override void UpdateValue()
    {
        if (m_iIndex < 0 || m_iIndex > m_itemsList.Count)
        {
            return;
        }

        float value = m_itemsList[m_iIndex];

        NumberInputfield.text = string.Format("{0}{1}{2}", Prefix, value, Suffix);
        MinusButton.interactable = m_iIndex > 0;
        PlusButton.interactable = m_iIndex < m_itemsList.Count - 1;

        OnValueChanged(value);
    }
}

Test Script:

public class TestRangeControls : MonoBehaviour
{

    public RangeControl RangeControl;
    public ListRangeControl ListRange;

    void Start()
    {

        //Set Range Control
        RangeControl.OnValueChanged += v => { Debug.Log("Selected value is: " + v); };
        RangeControl.MinRange = 200;
        RangeControl.MaxRange = 2000;
        RangeControl.Stepping = 50;
        RangeControl.Value = Mathf.RoundToInt(Random.Range(RangeControl.MinRange, RangeControl.MaxRange));


        //Set List Range Control

        ListRange.OnSelectedIndexChanged += i => { Debug.Log("Selected index is: " + i); };
        List<float> numList = new List<float>();
        for (int i = 1; i < 11; i++)
        {
            numList.Add(i * 100);
        }

        ListRange.ItemsList = numList;
        ListRange.Index = Random.Range(1, 11);

    }
}
\$\endgroup\$
1
\$\begingroup\$

Use Strategy pattern:

Example:

public interface RangeControlLogic {
    void ChangeValue(int direction);
    void UpdateValue();
    void OnValueChanged();
}

public class NoneListRangControl : RangeControlLogic {

    public float Stepping = 5;

    float m_fMinRange = 10;
    float m_fMaxRange = 1000;
    float m_fValue;

    public float Value
    {
        get { return m_fValue; }
        set
        {
            m_fValue = value;
            UpdateValue();
        }
    }

    public float MinRange
    {
        get { return m_fMinRange; }
        set
        {
            m_fMinRange = value;
            UpdateValue();
        }
    }

    public float MaxRange
    {
        get { return m_fMaxRange; }
        set
        {
            m_fMaxRange = value;
            UpdateValue();
        }
    }

    public void ChangeValue (int direction)
    {
        // your code here...
    }

    public void UpdateValue ()
    {
        // your code here...
    }

    void OnValueChanged ()
    {
        // your code here..
    }
}

public class ListRangControl : RangeControlLogic {
    // simillar to NoneListRangControl but with different implementation
} 

Then you use composition to plug those objects in the RangControl:

public class RangeControl : MonoBehaviour {

    public RangeControlLogic logic;// this will be either NoneListRangControl or ListRangControl

    public void ChangeValue(int direction)
    {
        logic.ChangeValue(direction);
    }

    public void UpdateValue()
    {
        logic.UpdateValue();
    }
}
\$\endgroup\$
  • \$\begingroup\$ hey @Benham, thanks a lot for your answer, I just need some clarification. When you say "set them up in Awak() or Start()" it's not that viable as clients would want to set them up in script (changing the value and min max is very common). So either I didn't understand you fully or it's not a good solution for me. For the second part where you suggest interface. Okay let's say I have the interface on both RangeControl and ListRangeControl where do I go from here ? a little code sample ? \$\endgroup\$ – yossi elimelech Mar 21 '17 at 21:26
  • \$\begingroup\$ I added some code examples. I'm basically using Strategy pattern here \$\endgroup\$ – Behnam Rasooli Mar 22 '17 at 2:54
  • \$\begingroup\$ This is almost what I went for before I changed it back. The only problem is that now RangeControl class needs to reveal interface for both nonlist and list range control for the client. (the min/max, value, stepping and/or list). I guess it's not one control after all. \$\endgroup\$ – yossi elimelech Mar 22 '17 at 15:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.