I have a section of my game where I have to click a button to load the next scene. I'm trying to make it so that when any single button is clicked it can no longer be clicked again, since you will be revisiting the same scene later in the game. (It is a text adventure section, so I'm making the route you took unavailable on the next run) I'm rather new at unity and C# so I'm struggling with what should be something simple.
I have fiddled around with DontDestroyOnLoad to store variables of the buttons that have been clicked. I have also tried using a dictionary to store a bool on clicked, and an int for the button ID. I have been looking at tons of documentation and other questions from other users, but I can't for the life of me make this work.
This is a Variable Manager script on an empty game object at the start of the game, which should store if a button has been clicked and its id -- I followed a tutorial to make it, so it won't get duplicated or destroyed by accident at any point, and so I can make other objects DontDestroyOnLoad as well, but that is not important.
public class VarManager : MonoBehaviour
{
[HideInInspector] public string objectID;
public static Dictionary<int, bool> clickedButtons = new Dictionary<int, bool>();
private void Awake()
{
objectID = name transform.position.ToString();
}
void Start()
{
for (int i = 0; i < Object.FindObjectsOfType<VarManager>().Length; i )
{
if (Object.FindObjectsOfType<VarManager>()[i] != this)
{
if (Object.FindObjectsOfType<VarManager>()[i].objectID == objectID)
{
Destroy(gameObject);
}
}
}
DontDestroyOnLoad(gameObject);
}
void Update()
{
}
}
This is the button destroy script that I have on each button. It catches the ID of the button, as well as if it's been clicked or not, and should pass it to the Variable Manager, so it is stored across scenes, so that when you come back to the scene, it already knows to be destroyed.
public class ButtonDestroy : MonoBehaviour
{
[SerializeField] int buttonID;
public bool clicked;
private void Start()
{
if (clicked == true)
{
Destroy(gameObject);
Debug.Log("Button Destroyed");
VarManager.clickedButtons.Add(buttonID, clicked);
foreach (KeyValuePair<int, bool> kvp in VarManager.clickedButtons)
Debug.Log("Key = {0} Value = {1}" kvp.Key kvp.Value);
}
}
public void TaskOnClick()
{
clicked = true;
Debug.Log("You have clicked the button");
}
private void Update()
{
//foreach (VarManager.clickedButtons<int, bool> );
}
}
The code on this second one is not finished, as I got frustrated and decided to put it down for now.
Is my logic flawed? Is there a much simpler solution? Should I start it from scratch again?
CodePudding user response:
This is one way you might implement accessing your VarManager from the buttons. This assumes your ButtonDestroy component is on the actual button GameObject:
public class ButtonDestroy : MonoBehaviour
{
[SerializeField] int _buttonID;
// We don't need to "serialise" the clicked state. We can use a property.
public bool clicked { get; set; }
private void Start ( )
{
// If there is no entry, then this button hasn't been clicked, or unclicked .. nada
if ( VarManager.clickedButtons.TryGetValue ( _buttonID, out var c ) )
return;
clicked = c;
if ( c )
{
//Destroy ( gameObject ); Do you really want to destroy it, or just deactivate it...?
gameObject.SetActive ( false );
Debug.Log ( "Button Deactivated" );
}
}
public void TaskOnClick ( )
{
clicked = true;
VarManager.clickedButtons.Add ( _buttonID, true );
Debug.Log ( "You have clicked the button" );
}
private void Update ( )
{
// ...
}
}
If your intention is to only ever record once a button has been clicked, you could use a HashSet instead. That doesn't store any additional data, it would simply store the buttonId, which you then infer that the button has been clicked. If you would like buttons that can be clicked, then unclicked, you could still use a HashSet and simply remove the entry. As you can see from my comment in the code, it seems like you'd ever only be storing 'true' in the dictionary with the associated buttonId. This is redundant.
CodePudding user response:
Since it sounds like this is about scenes instead of your manager class and deal with scene loading etc I would rather go through e.g. PlayerPrefs
or a save file and store the IDs of buttons that are no longer available.
This way it would still work after closing and reopening your app and would simplify things a lot (imho)
So e.g. your button code could simply look like
[RequireComponent(typeof(Button))]
public class OnlyOnceAvailableButton : MonoBehaviour
{
// I will just assume due to your usage you are already ensuring a unique ID for those anyway
[SerializeField] private int uniqueID;
[SerializeField] private Button button;
private void Start()
{
if(PlayerPrefs.GetInt(uniqueID.ToString()) == 1)
{
Destroy(gameObject);
return;
}
if(!button) button = GetComponent<Button>();
button.onClick.AddListener(MakeButtonUnavailable);
}
private void MakeButtonUnavailable()
{
PlayerPrefs.SetInt(uniqueID.ToString(), 1);
Destroy(gameObject);
}
}
So instead of a manager the PlayerPrefs
could already store/provide which buttons are unavailable.