Save Material in game by user - c#

i Have color picker in my game to customize character clothes by user in game.
how can i save this changes ?
i've try this way but doesn't work and i don't know why :
public Material[] ShirtColorMat;
Renderer renderer;
int PantSaver;
void Start()
{
renderer = this.GetComponent<Renderer>();
PantSaver = PlayerPrefs.GetInt("Saver");
}
public void _000000() // i use this on button
{
renderer.material = ShirtColorMat[0];
Save();
}
void Save()
{
PlayerPrefs.SetInt("Saver", PantSaver);
}
thanks

You are not updating the PantSaver value.
It should probably rather be
public void _000000()
{
PantSaver = 0;
renderer.material = ShirtColorMat[PantSaver];
Save();
}
void Save()
{
PlayerPrefs.SetInt("Saver", PantSaver);
}
In general a property might be of good use here:
private int _pantSaver;
private int PantSaver
{
get => _pantSaver;
set
{
_pantSaver = value;
PlayerPrefs.SetInt("Saver", value);
PlayerPrefs.Save();
renderer.material = ShirtColorMat[value];
}
}
This way everytime you assign a new value to PantSaver it is automatically saved and the renderer updated.
So then you would only need to do
public void _000000()
{
PantSaver = 0;
}
Finally please rename _000000 to something more meaningful like e.g. ResetPant or something similar.

You might in Save() want to say PlayerPrefs.SetInt("Saver", ShirtColorMat[0]); instead. What you have in "PantSaver" is not a reference but a value. This means that with the above code you are currently first getting the value "Saver" and then setting the same value again.
The PlayerPrefs.SetInt's first string parameter is the key you save it to and later can get it from.

Related

Setting Singleton Property definition with dynamic String

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

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!

Passing Value on Property Change

I have 2 forms: Form A and Form B. I also have a property field class.
Form A contains the label I want changed when a property is changed. Form B contains code that will change the property field.
Property Class Code:
public class Controller
{
private static string _customerID;
public static string customerID
{
get { return _customerID; }
set
{
_customerID = value;
if (_customerID != "")
{
FormA.ChangeMe();
}
}
}
}
Form B Code:
private void something_Click(object sender, SomethingEventArgs e) {
Controller.customerID = "Cool";
}
Form A Code:
public static void ChangeMe()
{
var frmA = new FormA();
MessageBox.Show("Test: " + Controller.customerID); //This works! Shows Cool
frmA.lb2Change.Text = Controller.customerID; //This kind of works..
MessageBox.Show("Test2: " + frmA.lb2Change.Text); //This shows the correct value. Shows Cool
}
The property field value is passed (which I know from the MessageBox) however it does not update the value on the form label itself. Why is this? What am I doing wrong? I also believe there is a better alternative for achieving what ChangeMe() method is intended to achieve -- if so are there any suggestions?
You can do the following
To define a delegate
To Implement Property Change Notification
Delegate
public delegate void OnCustomerIDChanging(object sender,CancelEventArgs e);
public delegate void OnCustomerIDChanged(object sender,object value);
public class Controller
{
private static string _customerID;
public event OnCustomerIDChanging CustoerIDChanging;
public event OnCustomerIDChanged CustoerIDChanged;
public static string customerID
{
get { return _customerID; }
set
{
// make sure that the value has a `value` and different from `_customerID`
if(!string.IsNullOrEmpty(value) && _customerID!=value)
{
if(CustomerIDChanging!=null)
{
var state = new CancelEventArgs();
// raise the event before changing and your code might reject the changes maybe due to violation of validation rule or something else
CustomerIDChanging(this,state);
// check if the code was not cancelled by the event from the from A
if(!state.Cancel)
{
// change the value and raise the event Changed
_customerID = value;
if(CustomerIDChanged!=null)
CustomerIDChanged(this,value);
}
}
}
}
}
}
in your Form and when you are initiating the Controller Object
var controller = new Controller();
controller.CustomerIDChanging +=(sd,args) =>{
// here you can test if you want really to change the value or not
// in case you want to reject the changes you can apply
args.Cancel = true;
};
controller.CustomerIDChanged +=(sd,args) =>{
// here you implement the code **Changed already**
}
The above code will give you a great control over your code, also will make your controller code reusable and clean. Same
result you can get by implementing INotifyPropertyChanged interface
INotifyPropertyChanged
you might have a look on this article to get more information
In your static method ChangeMe you are creating a new Form every time, you want to Change the value. Instead of that you want to change the value of an existing form. Therefor your Controller needs an instance of this FormA. Try it like this:
public class Controller
{
//You can pass the form throught the constructor,
//create it in constructor, ...
private FormA frmA;
private string _customerID;
public string customerID
{
get { return _customerID; }
set
{
_customerID = value;
if (_customerID != "")
{
frmA.ChangeMe();
}
}
}
}
Now you donĀ“t need to be static in your FormA:
public void ChangeMe()
{
MessageBox.Show("Test: " + Controller.customerID);
this.lb2Change.Text = Controller.customerID;
}

Categories