Home > Software engineering >  Setting Singleton Property definition with dynamic String
Setting Singleton Property definition with dynamic String

Time:07-30

I have a singleton class called KeyManager that is used to define and change player keybinds. It needs references to Text objects that will display what the currently set Keybind is. The singleton and text objects are in different scenes so I want to set the object definitions using a separate script that is attached to the (parent of) the Text objects themselves.

Here is the Singleton in its basic implementation.


public class KeyManager : MonoBehaviour
{
    public static KeyManager Instance { get; private set; }

    public static Dictionary<string, KeyCode> Keybinds = new Dictionary<string, KeyCode>();

    private GameObject currentKey;

//the Text objects that need references
    public static Text PitchUp { get { return pitchUp; } set { pitchUp = value; } }
    private static Text pitchUp;
    public static Text PitchDown { get { return pitchDown; } set { pitchDown = value; } }
    private static Text pitchDown;
    public static Text RotateLeft { get { return rotateLeft; } set { rotateLeft = value; } }
    private static Text rotateLeft;
    public static Text RotateRight { get { return rotateRight; } set { rotateRight = value; } }
    private static Text rotateRight;
    
//...irrelevant singleton logic continues
}

Here is the script attached to the parent GameObject of each Text object to be referenced (parent is a button). The GameObject name of the button matches the Singleton property names.


public class KeybindCRData : MonoBehaviour
{
    string buttonName;
    Text childText;

    void Start()
    {
        buttonName = this.name; //collect name of GameObject as set in Editor as a string
        childText = this.transform.GetChild(0).GetComponent<Text>(); // collect Text object that will be assigned

        //set Singleton property to this objects child Text
        KeyManager.-INSERT PROPERTY NAME HERE- = childText;
          //KeyManager.(Text)buttonName = childText; 
          // ^ this doesn't work of course, but this is the idea/goal
    }
}

What is needed so I can dynamically change which property of the Singleton I am referencing based on the name of the Text object? I want to be able to just slap the KeybindCRData script on each button and be done.

The alternative that I see is specifically defining the property and related button for each keybind, but with 30 keybinds it would not be ideal to end up with 30 different scripts each tailored to only 1 button.

Thanks for any help.

CodePudding user response:

I would try to redesign the code, so that your manager does not need to know about all the different text components. To keep your manager code simple and focused, it's better to invert the flow and have all your text components subscribe to an event in the manager to get notified whenever a key binding changes.

public static class KeyBindings
{
    public static event Action<string, KeyCode> Changed;
    
    private static readonly Dictionary<string, KeyCode> bindings = new Dictionary<string, KeyCode>();
    
    public static KeyCode Get(string name) => bindings[name];
    
    public static void Set(string name, KeyCode key)
    {
        bindings[name] = key;
        Changed?.Invoke(name, key);
    }
}

public class KeyBindingDisplayer : MonoBehaviour
{
    [SerializeField]
    private string keyBindingName;

    private void Reset() => keyBindingName = name;

    private void OnEnable()
    {
        KeyBindings.Changed  = OnKeyBindingsChanged;
        UpdateText(KeyBindings.Get(keyBindingName));
    }
    
    private void OnDisable()
    {
        KeyBindings.Changed -= OnKeyBindingsChanged;
    }
    
    private void OnKeyBindingsChanged(string name, KeyCode keyCode)
    {
        UpdateText(keyCode);
    }
    
    private void UpdateText(KeyCode keyCode)
    {
        GetComponent<Text>().text = keyCode.ToString();
    }
}

CodePudding user response:

Instead of creating new property for each Text object, use a Dictionary<string, Text>. The Key can be the buttonName and the value can be childText.

public class KeyManager : MonoBehaviour
{
    public static KeyManager Instance { get; private set; }

    public static Dictionary<string, KeyCode> Keybinds = new Dictionary<string, KeyCode>();

    private GameObject currentKey;
    
    //Key: buttonName (pitchUp, pitchDown, rotateLeft.. etc )
    public static Dictionary<string, Text> Texts;

}

In KeybindCRData class, set the value to the dictionary.

public class KeybindCRData: MonoBehaviour
{
    string buttonName;
    Text childText;

    void Start()
    {
        buttonName = this.name; 
        childText = this.transform.GetChild(0).GetComponent<Text>();
         
        //Add appropriate logic as per requirement
        if(!KeyManager.Texts.ContainsKey(buttonName))   {
          KeyManager.Texts.Add(buttonName, childText)
        }       
    }
}

CodePudding user response:

Thanks for the previous replies, they show other ways to do this more efficiently.

I figured out how to answer the crux of my original question though, so here is the code if it is useful to anyone else.

In the Manager Singleton, I add this public method.

    public void SetProperty(string propertyName, TextMeshProUGUI value)
    {
        this.GetType().GetProperty(propertyName).SetValue(this, value);
    }

In the KeybindCRData script that is attached to each individual button(used to rebind), I added this method call to the Awake() function.

    void Awake()
    {
        //collect name of GameObject as set in Editor as a string
        buttonName = this.name;

        // collect Text object that will be assigned
        childText = this.transform.GetChild(0).GetComponent<TextMeshProUGUI>();

        //set Singleton property to this objects child Text
        KeyManager.Instance.SetProperty(buttonName, childText);

    }

This allows me to "spell out" the Property value using a String, grabbed from the matching Button-name. Thank you everyone, cheers!

  • Related