Saving an Array of Text

Hi All,

So my store for my game is all done except that the price text on an item doesn’t save its state (enabled/disabled) after the game has been closed and re-opened. For example, when you buy an item, the price text of the item is disabled (this part is working), and then after you close and re-open the game the price text should still be disabled (this is where it doesn’t work, the text is re-enabled). I’m not to sure if I’m saving the text’s state properly in order for this to work.

Script for the store and disabling the text:

{

    public AudioSource[] sounds;
    public AudioSource source1;
    public AudioSource source2;
    public AudioSource source3;

    private int price;                //cost of the models
    public List<GameObject> models;
    public int selectionIndex = 0;        //Default index of the models

    private int[] prices = { 0, 100, 200};    //prices. Set for each model

    public Text[] pricesText;     //text


    private void Start ()
    {
        sounds = GetComponents<AudioSource> ();
        source1 = sounds [0];    //player has selected a previoulsy unlocked item
        source2 = sounds [1];    //player can't afford an item
        source3 = sounds [2];    //player unlocked an item

        selectionIndex = PlayerPrefs.GetInt ("CharacterSelected");

        models = new List<GameObject> ();
        foreach(Transform t in transform)
        {
            models.Add (t.gameObject);
            t.gameObject.SetActive (false);
        }
        models [selectionIndex].SetActive (true);
    }


    public void OnClick (int index)
    {

        Text priceText = pricesText[index];     //text

        //Select unlocked characters
        if ((DataManagement.datamanagement.modelAvailibilty & 1 << index) == 1 << index)
        {
            models [selectionIndex].SetActive (false);
            selectionIndex = index;
            models [selectionIndex].SetActive (true);

            source2.Play ();        //previously unlocked item has been selected

            priceText.enabled = false; //disables text

            if (index == selectionIndex)
            {
                PlayerPrefs.SetInt ("CharacterSelected", index);
            }
        }
        else
        {
            //Unlock an item
            int price = prices[index];
            Debug.Log (price);


            if (DataManagement.datamanagement.totalCoins >= price)
            {
                DataManagement.datamanagement.totalCoins -= price;
                DataManagement.datamanagement.modelAvailibilty += 1 << index;
                models [selectionIndex].SetActive (false);
                selectionIndex = index;
                models [selectionIndex].SetActive (true);

                PlayerPrefs.SetInt ("CharacterSelected", index);

                source3.Play ();    //player has unlocked an item

                priceText.enabled = false;     //disables text
            }
            else
            {
                //Player doesnt have enough coins to unlock the item
                source1.Play ();
            }
        }
        DataManagement.datamanagement.SaveData ();
    }
}

DataMangement:

{

    public static DataManagement datamanagement;
    [Space(10)]
    public int currentCoins;
    public int totalCoins;        //Sets how much the player will have the first time they play
    [Space(10)]
    public int currentDistance;
    public int totalDistance;
    [Space(10)]
    public int highScore;
    [Space(10)]
    public int modelAvailibilty = 1;

    private Text[] pricesText;     //text


    void Awake ()
    {
        if (PlayerPrefs.HasKey ("CharacterSelected"))
        {
            modelAvailibilty = PlayerPrefs.GetInt ("ModelAvailibilty");
        }
      
        if (datamanagement == null)
        {
            DontDestroyOnLoad (gameObject);
            datamanagement = this;
        }

        else if (datamanagement != this)
        {
            Destroy (gameObject);
        }
    }


    public void SaveData ()
    {
        BinaryFormatter binForm = new BinaryFormatter();
        FileStream file = File.Create (Application.persistentDataPath + "/gameInfo.dat");
        gameData data = new gameData();
        data.highScore = highScore;
        data.totalCoins = totalCoins;
        data.totalDistance = totalDistance;
        data.modelAvailibilty = modelAvailibilty;
        data.pricesText = pricesText;     //text
        binForm.Serialize (file, data);
        file.Close ();
    }


    public void LoadData ()
    {
        if (File.Exists (Application.persistentDataPath + "/gameInfo.dat"))
        {
            BinaryFormatter binForm = new BinaryFormatter ();
            FileStream file = File.Open (Application.persistentDataPath + "/gameInfo.dat", FileMode.Open);
            gameData data = (gameData)binForm.Deserialize (file);
            file.Close ();
            highScore = data.highScore;
            totalCoins = data.totalCoins;
            totalDistance = data.totalDistance;
            modelAvailibilty = data.modelAvailibilty;
            pricesText = data.pricesText;     //text
        }
    }
}


[Serializable]
class gameData
{
    public int highScore;
    public int totalCoins;
    public int totalDistance;
    public int modelAvailibilty;
    public Text[] pricesText;     //text
}

This is my first game and I am very new to programming so any advice/help is greatly appreciated!! :slight_smile:

Once you load the data, where are you checking your data to see if stuff should be on or off?

Also, I may be overlooking it, but I don’t see you loading data either?

When you say “checking your data to see if stuff should be on or off,” would that go in Start and how would I do that? I don’t believe that I have anything in my script checking the state of the text currently, I just have line 50 and 76 disabling the text.

I think loading the data is my problem now that you point that out. If I’m thinking about this correctly, I should load the data in Start in the Store script, right? Now, how would I go about doing that?

I’m just guessing at what you are doing. But I don’t see a call to LoadData and I’m not sure where you’re saving what text objects are turned off and on. If all text are on normally in a scene, when you close the app and reopen, those text objects will be on again. So you would need something to determine what needs to be off again.

You should be able to do this in awake or start, but it also depends on how your game is setup. You may even need to do this in a loading scene if it proves heavy(shouldn’t with this, but I don’t know what else you have going on)

The state of the text should be loaded in the DataManagement script, which is loaded in the main menu scene and presists through all scenes (lines 53 to 66 in the DataMangement script). The DataMangement should also be saving the text’s state in line 84. Is there another place that I have to load/save the data? Also, the text re-appears when you leave the store scene and then return to it (idk if this info helps).

Thank you for your help! :slight_smile:

Edit: I’ve done some debugging and the text data is successfully saving. So, I believe my issue is with loading the state of the text once the scene is re-opened. The DataMangement script is loading my other variables like current coins and total coins, but it doesn’t seem like it is loading my pricesText (the enabled/disabled state). Any idea of where I might be missing something where I need to load this?

Ok, great. I didn’t see where you were calling loadData, so wasn’t sure on that.

Normally you have to save out those states in some way. For example, in a game I worked on, a player earns achievements. Normally the achievement is displayed with info on what it does and an empty trophy slot. But when they log back in the game, we check if that achievement is completed and then we turn on the trophy image and update whatever text needs to be changed to show they have the achievement.

You just need a way to know that if they return to the game, certain things might need to be on or off or text changed to something else. If you are saving that out in your data, then when you load that data in, you should be able to check values.

I do see where you have pricetext.enabled = false, but are you saving out that this shouldn’t be enabled or checking something and then turning these off again? (like a purchase history or something that says they own it already)

Hopefully I explained it well, if not, I’ll try to be a little more clear on what I mean.

1 Like

I should be saving that the text shouldn’t be enabled (I think).

In the store script it should be saving the state of the text:

DataManagement.datamanagement.SaveData ();

Then in the DataManagement script:

public void SaveData ()
    {
        BinaryFormatter binForm = new BinaryFormatter();
        FileStream file = File.Create (Application.persistentDataPath + "/gameInfo.dat");
        gameData data = new gameData();
        data.highScore = highScore;
        data.totalCoins = totalCoins;
        data.totalDistance = totalDistance;
        data.modelAvailibilty = modelAvailibilty;
        data.pricesText = pricesText;                  //saves the text here
        binForm.Serialize (file, data);
}
        file.Close ();

Now, what would I use to check the states of the text?

I believe that this needs to be done here, Store Script, because this is where it loads what model the player previously had selected:

private void Start ()
    {
        //Data for text loaded here???

        sounds = GetComponents<AudioSource> ();
        source1 = sounds [0];    //player has selected a previoulsy unlocked item
        source2 = sounds [1];    //player can't afford an item
        source3 = sounds [2];    //player unlocked an item

        selectionIndex = PlayerPrefs.GetInt ("CharacterSelected");   //model is loaded

        models = new List<GameObject> ();
        foreach(Transform t in transform)
        {
            models.Add (t.gameObject);
            t.gameObject.SetActive (false);
        }
        models [selectionIndex].SetActive (true);

    }

What would I use to load my text data here? Ive tried DataManagement.datamangement.LoadData (); but it messes up other parts of the data like the coins, distance, etc. Basically it loads a whole new set of data.

When you load data, I see you’re putting it into a gameData variable which is local to the method. You are then taking those values and putting them into different variables.

What you may want to do is put this in a static global gameData variable. This way you can access it from anywhere and change values as needed. Then, when you save data, you just save the global gameData variable.

So within the DataManagement class, you could add this.

public static gameData myGameData;

//Within the loadData
myGameData = (gameData)binForm.Deserialize (file);

//Within the saveData
binForm.[URL='http://unity3d.com/support/documentation/ScriptReference/30_search.html?q=Serialize']Serialize[/URL] (file, myGameData);
//Then to access what you want, just access your static data variable
DataManagement.myGameData.pricesText = //Some value;
PriceText.text = DataManagement.myGameData.pricesText; //Some text gui, get the value out of here.

Then you may also save after any values change so it’s always up to date. This is just an idea, and is similar to a system I used in a game.

Awesome! So my DataMangement is using both of your suggestions and is serializing the pricesText data at the bottom of the script.

public class DataManagement : MonoBehaviour
{
    public static DataManagement datamanagement;
//blah,blah
}

[Serializable]
class gameData
{
    public int highScore;
    public int totalCoins;
    public int totalDistance;
    public int modelAvailibilty;

    public Text[] pricesText;
}

I’ve been playing around with how the text is disabled:

public void OnClick (int index)
    {
        //Select unlocked characters
        if ((DataManagement.datamanagement.modelAvailibilty & 1 << index) == 1 << index)
        {
//blah, blah
        }
        else
        {
            //Unlock an item
            int price = prices[index];
            Debug.Log (price);

            Text pricesText = priceText[index];

            if (DataManagement.datamanagement.totalCoins >= price)
            {
//blah, blah

                DataManagement.datamangement.pricesText = false;   //this is where I'm playing around with how its disabled
            }
            else
            {
                //Player doesnt have enough coins to unlock the item
                source1.Play ();        //plays sound when player cant afford an item
            }
        }
        DataManagement.datamanagement.SaveData ();
        Debug.Log ("Text saved in StoreButton");
    }

In line 20, it is throwing this error, “Cannot implicitly convert type ‘bool’ to ‘UnityEnigine.UI.Text[ ]’”
Im not to sure how to correctly access my pricesText in my DataMangement and then deactivate the text that the player has clicked on.

Either you have to do to turn off the text component

DataManagement.datamangement.pricesText[index].enabled = false;

or do this to turn off the gameobject

DataManagement.datamangement.pricesText[index].gameObject.setActive(false);

These will turn off the text completely, if that is what you are wanting to do. You could also change the alpha on the text to give it a faded view. Also note your pricesText is an array, so you have to use an index to access the individual text elements.

But…I’m not sure after looking if this is what you are trying to do. Are you trying to disable a priceText in your scene? I see you have the priceText in your DataManagement but then right above that you are doing a

Text pricesText = priceText[index];. So I’m a little confused on that.

1 Like

For the first and second suggestions,

DataMangement.datamangement.pricesText[].enabled = false;

and

DataManagement.datamangement.pricesText[index].gameObject.setActive(false);

, should I be populating the array in my DataManagement script for it to be disabling the text? Currently, I have the array in my store script populated with the text that I need to disable.

So this is how the game is setup (the scenes just have some base placeholder objects right now, not nearly finished :face_with_spiral_eyes:): The player loads into the MainMenu scene and then can go to the store which is a different scene that DataManagement will persist to. (screenshots below)
Each one of the buttons switches the player’s character and deducts X amount of coins. The price text on each button is what should be disabled when the player buys that character.

2937621--217310--Store Scene.jpg
2937621--217311--Main Menu.jpg

Would it be better to just destroy the text when the player buys the model? Would that be a way around this whole saving issue? I really never need the text to show again after the player has bought the model…

I may be confused on something. So let me make sure. When you set up a scene, you may have the text in your scene in a certain state. Let’s say it’s enabled with prices. Then, and some point a person buys something and you turn off that text. Assuming you don’t change anything else and you don’t switch scenes, that text will be off and should stay off when you open the shop again if you aren’t turning stuff on again.

If you change scenes completely or reload the game, the text will be enabled again with the prices, which means if you want to turn off the prices, you’ll have to check the status of such items and turn the text off again if it’s been bought. So even if you destroy it, when you leave the scene and come back, assuming nothing in that scene is dontdestroyonload, you’ll have to turn off the store prices once more through your save system.

1 Like

Yes, so that is what should be happening. Ive think I know where my problem lies after hours of looking things over :hushed:.

First off, In my store script there is the array with the text in it. This store script is located within the Store scene. The DataManagement script also had an array of:

public Text[] pricesText;

I think that this is where my problem lies. The array that is on the store scene is not the same as the one in DataManagement. I need to have one array and it needs to be in either the Store script or the DataMangement script. Ive played around with this idea but have hit a wall. I’ve chosen the route of putting the array in the DataManagement script and then accessing it in the store script with:

DataManagement.datamanagement.pricesText [index].enabled = false;

to disable text in the array.

Now, I think this might work if I could populate the array with my text. The DataMangement script is in the Main Menu scene (player loads into this scene), while my text is in my Store scene. I haven’t been able to figure out how to populate this array in the DataMangement script with text that is in my store scene.

If I can get the text into that array, how would I do so? Or, is this idea of having the array in the DataManagement not going to work?

Thanks again for all of your help:)! This is my last step to completing the scripting of my game!!!

For further clarity, Im attaching a videos of how the game functions:

This one shows where I want to be putting the text elements in the DataManagement script (at 0:07):

https://www.youtube.com/watch?v=KyJ29oaifrA

This shows how the text should disappear: (has different scripting in order to make it half-way work) (shows disabling at about 0:50)

https://www.youtube.com/watch?v=Tg12fFpEbXk

The problem with creating an array of text objects is that they aren’t tied to the stuff in your scene if you don’t reference them. So, creating the text objects in your main menu scene and then going to your store doesn’t work as your array doesn’t point to anything in that scene. How to fix this? You’ll need some way of doing things without using text objects.

So, if your store has several items and each one is different, your shop gameobjects may be named a certain thing.
Lets just say for the sake of it…
ShopItem1
ShopItem2
ShopItem3
ShopItem4

Now, in your data class you could have an array of strings that just tracks what you bought. So if I buy ShopItem3, I add that to the array (note, probably better to use a List as you can just add to a list and not worry about the size of an array). Then, the next time I open my shop, I check if my list of “purchased” items contains my gameobjects name. If so, turn off the text.

Another option is an array of bools. If you have 10 items in your shop, you start with an array of 10 bools that all have a value of false. Now, you can purchase lets say item 3. You then can set your index 2(since 0 based) to true and save that out. Then when you come back to the shop, you just have to loop through and check each value in the bool array to see if it’s match shop item should be turned off or on.

The main thing I think is to move away from trying to save out an array of Text objects.

1 Like

Yay!! The bool array works!
Datamangment script:

 public bool[] pricesText;

Store Buttons script:

DataManagement.datamanagement.pricesText[index] = false;

Each one successfully turns to false when you click on a button in the store and saves that it has been “turned off.” (Screenshots) So, how do I “attach” my text to this? Right now the array has little boxes that are either enabled or disabled.

2939619--217535--Screen Shot 2017-01-29 at 11.00.14 AM.png
2939619--217540--Screen Shot 2017-01-29 at 11.08.42 AM.png

Does your store change ever? Is it always in the same order?

So, if all your shop items are in the same order, then you can easily do a getChild

public Transform shopItems; //Parent of all the shop items

for(int x = 0;x < DataManagement.datamanagement.pricesText.Length;x++)
{
   if(DataManagement.datamanagement.pricesText[x]) //if value is true
   {
      shopItems.getChild(x).findChild("priceText").gameObject.SetActive(false); //Find the child in the matching index and find the child of it named "priceText" and set it inactive.
   }
}

Something along these lines. (note typed in forum, so excuse cap mistakes.)

Note, if your shop changes, you may have to go with my earlier suggestion, or consider a dictionary where the object name can be used as the key.

1 Like

Ok, so I have that in my store script now:

//this is where the player buys something
if (DataManagement.datamanagement.totalCoins >= price) 
            {
                DataManagement.datamanagement.totalCoins -= price;
                DataManagement.datamanagement.modelAvailibilty += 1 << index;
                models [selectionIndex].SetActive (false);
                selectionIndex = index;
                models [selectionIndex].SetActive (true);

                PlayerPrefs.SetInt ("CharacterSelected", index);
                source3.Play ();

                //Placed here:
                for (int x = 0; x < DataManagement.datamanagement.pricesText.Length; x++)        
                {
                    if (DataManagement.datamanagement.pricesText [x])
                    {
                        shopItems.GetChild(x).FindChild ("btn Model").FindChild ("Price").gameObject.SetActive (false);
                    }
                }

            }

Is this in the right spot?

If I’m correct, the (“btn Model”) has to be the exact name of the child.

Also, the text is a child of the children of the “Models” object (screenshot 1), do I access it by what I did above …FindChild(“btn Model”).FindChild(“Price”)…?

The little check boxes on the bool array are no longer turning off and the text doesn’t get disabled.
It is throwing this error:
NullReferenceException: Object reference not set to an instance of an object
StoreButtons.OnClick (int32 index) (at…

Im guessing I have something out of order or in the wrong place here :face_with_spiral_eyes:

2939871--217561--%22Shop Items%22 populated with %22Models%22.png
2939871--217562--Screenshot 1.png

shopItems.GetChild(x).FindChild ("btn Model").FindChild ("Price").gameObject.SetActive (false);

should be

shopItems.GetChild(x).FindChild ("Price").gameObject.SetActive (false);

The if statement checks if the bool is true(thus item purchased) turn off the price gameobject. You could put it the other way also if you prefer where its false then turn off the gameobject. You’ll just add a ! in front of DataManagement like if(!DataManagment.datamanagement…

1 Like


Thanks man!! If you haven’t gotten the message yet, its working!! :smile:
Its doing everything I needed it to flawlessly!

Here’s what I ended up with in the end with the store script:

public Transform shopItems;


    private void Start ()
    {
        for (int x = 0; x < DataManagement.datamanagement.pricesText.Length; x++)
        {        
            if (DataManagement.datamanagement.pricesText [x])
            {
                Debug.Log ("Checked for items that aren't bought");
            }
            else
            {
                shopItems.GetChild(x).FindChild ("Price").gameObject.SetActive (false);
                Debug.Log ("Checked for items that are bought and set bools to false");
            }
        }
    }
//other stuff blah, blah

//when the player buys something
for (int x = 0; x < DataManagement.datamanagement.pricesText.Length; x++)        
                {
                    if (DataManagement.datamanagement.pricesText [x])
                    {
                        shopItems.GetChild(index).FindChild ("Price").gameObject.SetActive (false);
                        Debug.Log ("Price Disabled");
                        DataManagement.datamanagement.pricesText[index] = false;
                        Debug.Log ("Index Bool set to false");
                    }
                }

Once again, thank you for all of the help!