Setting Singleton Property definition with dynamic String - c#

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.

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!

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();
}
}

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)
}
}
}

Related

Access preferences from different ViewModels

I have SettingsViewModel with:
public class SettingsViewModel : BaseViewModel, ISettingsViewModel
{
public SettingsViewModel()
{
}
private string _gaugeColor;
public string GaugeColor
{
get => Preferences.Get("GaugeColor", "#17805d");
set
{
Preferences.Set("GaugeColor", value);
this.OnSettingsChanged();
this.OnPropertyChanged();
}
}
public event EventHandler<SettingsChangedEventArgs> SettingsChanged;
private void OnSettingsChanged() => this.SettingsChanged?.Invoke(this, new SettingsChangedEventArgs(this.Settings));
public Settings Settings { get; private set; }
}
}
I set color by HEX string.
Then in PanelViewModel I have:
private Color _gaugeColor;
public Color GaugeColor
{
get => Color.FromHex(Preferences.Get("GaugeColor", "#17805d"));
set
{
_gaugeColor = value;
OnPropertyChanged();
}
}
Now if I change HEX string from Settings view in UI, color does not change in PanelViewModel until I restart an application.
Question is: How to make color change in PanelViewModel right after it has been changed in SettingsViewModel?
I have tried to add this into PanelViewModel, but apparently this creates a new instance of SettingsViewModel and Color does not follow into PanelViewMode. Maybe there is some direct solution and I am using Xamarin.Essentials wrong?
public PanelViewModel()
{
this.SettingsViewModel = new SettingsViewModel();
this.SettingsViewModel.SettingsChanged += OnSettingsChanged;
}
private Color _gaugeColor;
public Color GaugeColor
{
get => Color.FromHex(Preferences.Get("GaugeColor", "#17805d"));
set
{
_gaugeColor = value;
OnPropertyChanged();
}
}
private void OnSettingsChanged(object sender, SettingsChangedEventArgs e)
{
this.GaugeColor = Color.FromHex(e.Settings.GaugeColor);
}
private SettingsViewModel SettingsViewModel { get; }
https://learn.microsoft.com/en-us/dotnet/api/xamarin.essentials.preferences?view=xamarin-essentials
Question is: How to make color change in PanelViewModel right after it
has been changed in SettingsViewModel?
Yes, a simple method is to use MessagingCenter.
The MessagingCenter class implements the publish-subscribe pattern, allowing message-based communication between components that are inconvenient to link by object and type references. This mechanism allows publishers and subscribers to communicate without having a reference to each other, helping to reduce dependencies between them.
You can refer to the following code:
In SettingsViewModel.cs, we can publish a message in the constuctor of it,just as follows:
public class SettingsViewModel
{
public string Title { get; set; }
public SettingsViewModel() {
Title = "SettingsView";
MessagingCenter.Send<Object, Color>(this, "Hi", Color.Yellow);
}
}
And in PanelViewModel.cs ,we can subscribe to this message:
public class PanelViewModel
{
public string Title { get; set; }
public PanelViewModel() {
Title = "PanelView";
MessagingCenter.Subscribe<Object, Color>(this, "Hi", async (sender, arg) =>
{
System.Diagnostics.Debug.WriteLine("-----> receive color= " + arg);
// here ,we can use the received color to update the UI
});
}
}
Note:
Once we receive the color, we can use the received color to update the UI, and the color field in the PanelViewModel.cs should implement interface INotifyPropertyChanged.

Trying to store values in a script that does not inherit from MonoBehaviour

I'm trying to make it so that once you click "Accept" on a UI button that it will store all the values from the quest into the following script:
[System.Serializable]
public class DataHolder
{
//Active Quest
public bool isActive;
public string title;
public string description;
public int goldReward;
public QuestGoal goal;
}
The script that stores it is here:
public class QuestGiver : MonoBehaviour
{
public Quest quest;
public DataHolder data;
public GameObject questWindow;
public Text titleText;
public Text descriptionText;
public Text goldText;
public void OpenQuestWindow()
{
Debug.Log("Quest Window Opened");
questWindow.SetActive(true);
titleText.text = quest.title;
descriptionText.text = quest.description;
goldText.text = quest.goldReward.ToString();
}
public void AcceptQuest()
{
questWindow.SetActive(false);
data.isActive = true;
data.title = quest.title;
data.description = quest.description;
data.goldReward = quest.goldReward;
}
}
and the script that is in the scene so that when a button is pressed (Tab) it will pop up a UI that will show what the current quest is, which should be stored in the first "DataHolder" script:
public class QuestChecker : MonoBehaviour
{
DataHolder data;
public GameObject questWindow;
public Text titleText;
public Text descriptionText;
public Text goldText;
void Update()
{
if (Input.GetKeyDown(KeyCode.Tab))
{
CheckQuest();
}
}
public void CheckQuest()
{
questWindow.SetActive(true);
titleText.text = data.title;
descriptionText.text = data.description;
goldText.text = data.goldReward.ToString();
}
public void CloseQuestWindow()
{
questWindow.SetActive(false);
}
}
The reason I want it stored in "DataHolder" is because I want it to be stored between scenes. I believe my main problem is not knowing how to properly reference the "DataHolder" script as Unity gives me the warning:
Assets\Scripts\QuestChecker.cs(8,13): warning CS0649: Field 'QuestChecker.data' is never assigned to, and will always have its default value null
Any help would be appreciated. I'm pretty new to programming, please let me know if this won't work for what I want it to do and need to do it another way or if you need more info. Thanks
If all you want to do is store some data between scenes, you could simply do this:
public static class DataHolder
{
//Active Quest
public static bool isActive;
public static string title;
public static string description;
public static int goldReward;
public static QuestGoal goal;
}
Then just reference it as DataHolder.isActive etc. Note that you're not actually "saving" the data anywhere (serialising). The SerializableAttribute just indicates that the item CAN be serialised. But, this WILL store data between scenes.
Is it the best way to do so? Well, if it's just some simple data you need to store between scenes, then there's nothing wrong with doing it this way. And it'll probably fit in nicely with what you've already programmed.
You're never actually creating an instance of DataHolder.
Somewhere along the line you need a new DataHolder() that is assigned to data before trying to set any of its properties.
You need to create a relationship between QuestGiver and QuestChecker
eg.
public class QuestChecker : MonoBehaviour
{
public QuestGiver giver;
}
Now drag your QuestGiver game object onto the QuestChecker.giver field in the inspector. Then you are able to access your saved data.
public void CheckQuest()
{
titleText.text = saver.data.title;
}

Creating a List of UnityActions And Assigning Them to Buttons

I am working on a tycoon game and I want to have several options on each building the player might click on. A shop should have an Eat, Invest, and Buy-off option while an office should have something like Work, Talk to Manager, etc. To do this I created one panel for the options and spawn in the number of options per building from a list of strings which instantiates a button for each string and assigns the button text to that particular string as such:
public override void Interact()
{
Debug.Log(type);
SetUpFunctions(activites);
}
public void SetUpFunctions(List<string> activs)
{
foreach (string item in activs)
{
optionsUI.AddOption(item);
}
}
The list of activities is assigned in the inspector. This method works perfectly and assigns each activity string to the button text. However, all the buttons created all have the same function assigned to them, so I decided to do the same thing with a list of methods and assign them to each button as I did with the strings:
[HideInInspector]public List<UnityAction> functions; //list of functions tied to each activity
public override void Interact()
{
Debug.Log(type);
functions.Add(Eat);
functions.Add(Work);
functions.Add(StopWork);
SetUpFunctions(activites, functions);
}
public void SetUpFunctions(List<string> activs, List<UnityAction> funcs)
{
//foreach (string item in activs)
//{
// optionsUI.AddOption(item);
//}
var col = activs.Zip(funcs, (x, y) => new { X = x, Y = y });//combine the two lists as one
foreach (var entry in col)
{
optionsUI.AddOption(col.X, col.Y);//Assign the activity name and function
}
}
public void Eat()
{
Debug.Log("Player is eating");
}
public void Work()
{
Debug.Log("Player is Working");
}
public void StopWork()
{
Debug.Log("Player Stopped Working");
}
//From the OptionsUI script
public void AddOption(string optionName, UnityAction optionFunc)
{
OptionUIItem emptyOption = Instantiate(itemProfile, scrollViewContent);
emptyOption.transform.SetParent(scrollViewContent);
emptyOption.SetupOption(optionName, optionFunc);
}
//The script assigned to each button created
public class OptionUIItem : MonoBehaviour
{
public Text text;
public Button btn;
public void SetupOption(string name, UnityAction func)
{
text.text = name;
btn.onClick.AddListener(func);
}
public void OnOptionSelect()
{
Debug.Log("Option selected");
}
}
However, when I try doing it this way I get a NullReferenceException on the first function.Add(Eat);
My question is
Is it possible or even right to do it this way?
If so how do I resolve this in order to assign each function on the list to each button created and
any other methods that might be better to get this done like maybe using a dictionary?
Thank you.
If you hide a field in the Inspector it is not automatically initialized (only serialized fields are), thus the NullReferenceException when trying to Add something to it.
You should be sure to do this already together with the declaration
public List<UnityAction> functions = new List<UnityAction>();

I want to link two variables by ref so as when I update one variable the other updates also

I want to assign one variable to another by reference so as when one variable updates so does the other.
I have a series of variables that I need to update based on something that a piece of hardware I am connected to is doing. I have set up an object array and am assigning each of my variables to an index in the object array. It is the object array that gets updated but when this happens I want the property setter for my original variable to fire so as I can do something. The reason for the object array is so as I can iterate through it in a loop and because my original variables can be of different types. I have shown an overly simplified version of what I am trying to do below, I hope it makes sense.
Edit: Just to note that the items in the testVars array are being updated elsewhere, I have just shown a simplified version of what I am trying to do below.
public partial class Form1 : Form
{
private bool _test0 = false;
private int _test1 = 0;
public object[] testVars = new object[2];
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
testVars[0] = test0;
testVars[1] = test1;
}
public bool test0
{
get
{
return _test0;
}
set
{
_test0 = value;
UpdateTest0();
}
}
private void UpdateTest0()
{
bool NewTest0 = _test0;
}
public int test1
{
get
{
return _test1;
}
set
{
_test1 = value;
UpdateTest1();
}
}
private void UpdateTest1()
{
int NewTest1 = _test1;
}
}
One solution I thought might work for me was to use pointers but because the variable declaration and pointer creation must be in different scope in my application this will not work. Also everything I have read on forums has suggested that pointers should not be used.
# Michael Ceber, many thanks for the prompt response. I think I understand what you mean and have updated my example code accordingly but it still does not behave as I want. My updated code is as follows
namespace VariableLinks
{
public partial class Form1 : Form
{
private bool _test0 = false;
private int _test1 = 0;
public ObjectX[] testVars = new ObjectX[2];
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
testVars[0] = new ObjectX();
testVars[0].val = test0;
testVars[1] = new ObjectX();
testVars[1].val = test1;
}
public bool test0
{
get
{
return _test0;
}
set
{
_test0 = value;
UpdateTest0();
}
}
private void UpdateTest0()
{
bool NewTest0 = _test0;
}
public int test1
{
get
{
return _test1;
}
set
{
_test1 = value;
UpdateTest1();
}
}
private void UpdateTest1()
{
int NewTest1 = _test1;
}
private void button1_Click(object sender, EventArgs e)
{
testVars[0].val = true;
}
}
public class ObjectX : object
{
public object val = new object();
}
}
Now when my button click event fires and updates testVars[0].val I expect the setter for test0 to fire but it doesn't. I assume I have misunderstood what you meant?
If you store objects or reference types to objects in your array, i.e not value types like int, bool etc, which contain the values, then this will help.
e.g if your object 'objectx' has a property called value then objectx.value can store such a value.
Then you can set any variables you like to this objectx and whenever you update the value of objectx.value all variables will pickup the change, as they are all pointing to the same object...
So in your example
testVars[0] = test0;
testVars[1] = test1;
this would become
testVars[0].value = test0;
testVars[1].value = test1;
maybe to achieve this
public object[] testVars = new object[2];
should become
public ObjectX[] testVars = new ObjectX[2];
and add the single public property 'value' to this ObjectX class...
Hope that makes sense!

GridView FocusedRowChanged - Child Class object

I need some help here.
I've created a child class called MyEditorRow from DevExpress EditorRow, and added 3 properties
public class myEditorRow : EditorRow
{
public myEditorRow()
{
}
private string inRowDescription = null;
public string RowDescription
{
get { return inRowDescription; }
set { inRowDescription = value; }
}
private bool inRequired = false;
public bool Required
{
get { return inRequired; }
set { inRequired = value; }
}
private bool inInherits = false;
public bool Inherits
{
get { return inInherits; }
set { inInherits = value; }
}
Second part of the code somewhere in the program adds instance of MyEditorRow to DevExpress VGrid Control.
vgcGrid.Rows.Add(Row);
My question is this: How can I link MyEditorRow class with DevExpress VGrid Control FocusedRowChanged event, so I can get my custom properties when row focus changes.
Thanks
The e.Row parameter is of the BaseRow type. So, to obtain an instance of the MyEditorRow object in the FocusnedRowChanged event handler, use the following code:
private void vGridControl1_FocusedRowChanged(object sender, DevExpress.XtraVerticalGrid.Events.FocusedRowChangedEventArgs e) {
if(e.Row is myEditorRow) {
myEditorRow row = ((myEditorRow)e.Row);
// your code here
}
}

Categories