Saving an unlocked character

Hey guys!
I’m relativley new to Unity and I have a problem with the saving of an unlocked character! So basically I have a shop scene where you can unlock new characters with your coins/money, but when you unlock/buy something (which works fine) and then you stop the inspector and start again you would have to buy this character again, because I don’t know how to save it!

Here are my scripts and I think that I should add a line in the “BuysScript2” in line 35, because that’s where I unlock the character!

Thanks for your help!

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public static class globalcurrency  {

    public static int character;
    public static bool character2unlocked;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class BuyScript : MonoBehaviour {

    public int myChar;
    private float price;

    public Text coinss;
    public Text pricetext;



    void Start() {
        price = GetComponent<BuyScript2> ().price;

    }

    void Update(){

        if (myChar == 1)
        {
            if (globalcurrency.character == 1)
            {
                pricetext.text = "selected";
            }
            else
            {
                pricetext.text = "select";
            }
        }

        if (myChar == 2)
        {
            if (globalcurrency.character2unlocked)
            {
                if (globalcurrency.character == 2)
                {
                    pricetext.text = "selected";
                }
                else
                {
                    pricetext.text = "select";
                }
            }
            else
            {
                pricetext.text = price + "x" ;
            }
        }
        coinss.text =  ((int)PlayerPrefs.GetInt ("Coins")).ToString () + "x";
    }





}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BuyScript2 : MonoBehaviour {

    private int myChar;
    public int price;
    private int money;


    // Use this for initialization
    void Awake () {
        myChar = GetComponent<BuyScript> ().myChar;
        money = PlayerPrefs.GetInt ("Coins");

    }
   
    // Update is called once per frame
    void Update () {

        if (myChar == 1) {
            globalcurrency.character = 1;

        }

        if (myChar == 2) {
            if (globalcurrency.character2unlocked) {
                globalcurrency.character = 2;
            } else {
                if (money >= price) {
                    PlayerPrefs.SetInt("Coins", money - price);
                    PlayerPrefs.Save();
                    globalcurrency.character2unlocked = true;

                }
            }
        }

        GetComponent<BuyScript2> ().enabled = false;
    }

}

There are many potential ways to save data. You’ve already got one in your script (PlayerPrefs).
Although the simplest method, it’s not very secure because players can easily modify these values to cheat by giving themselves coins.

Data serialisation is another way of doing it by saving specific information to a local file. This is a bit more secure, gives you more control, and allows you to define your own structures rather than relying on playerprefs.
You can learn more about this from this Unity tutorial (or search youtube).

The most secure way (but also the most complex) is to save the values to a database.
If you want a super secure way of storing your users progress, this is the way to go, but it will take a lot of research.
There’s plenty of tutorials on youtube for how to achieve this if you feel like going the extra mile:

Then again, if you’re just doing it for fun and don’t care about cheating, just use PlayerPrefs to store your unlocks too.

You could use an Integer to store whether the item is unlocked or not e.g. 0 = locked and 1 = unlocked.

//Unlock a character:
PlayerPrefs.SetInt(“Char2Unlocked”, 1);

//Check if a character is unlocked
globalcurrency.character2unlocked = PlayerPrefs.GetInt(“Char2Unlocked”) = 1;

1 Like

I’d try the Serialization, it is very easy and secure enough for beginners:

1: Creating the Save/Load Methods

Create a whole new script, make it look like this:

using UnityEngine;
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

public class PersistentData : MonoBehaviour {

    void Start() {
        //The first (and only) thing this script does as a component, is Load the saved data
        Load();
    }
  
    //Creates a new save file, or overwrites the previous save file
    public static void Save() {

        //Starts the File Stream
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        //Creates a new file (overwriting the previous one)
        FileStream fileStream = File.Create(Application.persistentDataPath + "/playerData.dat");
        


        ///Stores all values, so they can be saved
        PlayerData pD = new PlayerData();

        //The 'character' value in the new file is set to whatever the value in 'globalcurrency'
        pD.character = globalcurrency.character;

        //The 'character2unlocked' value in the new file is set to whatever the value in 'globalcurrency'
        pD.character2unlocked = globalcurrency.character2unlocked;


        ///Wraps up the new file, and saves it
        binaryFormatter.Serialize(fileStream, pD);

        //Finishes the File Stream
        fileStream.Close();
    }

    public static void Load() {
        //First of all, checks if the file exists. If it doesn't exist, nothing happens because there is nothing to Load.
        if (File.Exists(Application.persistentDataPath + "/playerData.dat")) {

            //The file does exist, then, the File Stream is started
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            
            //The saved file is opened
            FileStream fileStream = File.Open(Application.persistentDataPath + "/playerData.dat", FileMode.Open);

            //The opened file is interpreted as a 'PlayerData' class
            PlayerData pD = (PlayerData)binaryFormatter.Deserialize(fileStream);

            //The File Stream is closed, because we already opened the file and got all the values from it.
            //All the values of it are now in the variable 'pD' by the way.
            fileStream.Close();

            ///Loads all Values 
            //The value of 'character' in globalcurrency is set to whatever was Loaded
            globalcurrency.character = pD.character;
            //The value of 'character2unlocked' in globalcurrency is set to whatever was Loaded
            globalcurrency.character2unlocked = pD.character2unlocked;
        }
    }
}

[Serializable] class PlayerData {
    public int character;
    public bool character2unlocked;
}

2: Implementing the Save/Load Feature

Once you can save and load, you have to make it happen. So search every time the value ‘globalcurrency.character’ is modified (globalcurrency.character = 3 for example) and right after it, add the line “PersistentData.Save();.” The same for every time the value ‘globalcurrency.character2unlocked’ is modified.

Then, if your new code looks like this:

        if (myChar == 1) {
            globalcurrency.character = 1;
            PersistentData.Save(); //Saves the changes
        }
        if (myChar == 2) {
            if (globalcurrency.character2unlocked) {
                globalcurrency.character = 2;
                PersistentData.Save(); //Saves the changes
            } else {
                if (money >= price) {
                    PlayerPrefs.SetInt("Coins", money - price);
                    PlayerPrefs.Save();
                    globalcurrency.character2unlocked = true;
                    PersistentData.Save(); //Saves the changes
                }
            }
        }

That was the Saving part, now we have to Load it whenever the game starts. Thus, in the starting scene of your game, create an empty gameobject and attach a PersistentData component to it. Whenever the game starts, it will Load automatically. You might want to modify the execution order of PersistentData so it always runs first of everything.

Optional: Modifying the Execution Order

You select any script in the inspector, and in the top-right corner you will find the button:


You cick that little “+” sign, and search for “PersistentData”.
You just assign PersistentData a negative number and voila.

Great! Now you can save and load. I’d recommend making the coins harder to cheat tho.

There is an easy way of making there be multiple save files by editing a few things in PersistentData, but its late rn so I’m not going to explain how today, and wont at all unless someone asks.

4 Likes

Thanks! I will try it out as soon as possible!

And I know it’s easy to cheat with Playerprefs, but do you think that so many people are going to do that or do even know about it?

Yes. Unity has had it since the very beginning and it has been used in a huge number of games because it’s very easy for a beginner to use. Additionally the data is stored in a very easy to access location in a format that is trivial to edit.

https://gamedev.stackexchange.com/questions/124221/is-it-bad-practice-to-store-inventory-and-scores-in-playerprefs

It’s working now and it does save which one is selected, but how can I now unlock the character in my game scene? I tried that I just made two script with two methods (one gameobject.setactive(true) and one with gameobject.setactive(false)) and then I added in my buyscript2 that when the player selects the first character it calls at the first character the method setactive(true) and at the the second character it simply does setactive(false), but it won’t work that way :frowning:

so this is the script where I do setactive and I added it to my first character in the game scene

public class tooglecar1 : MonoBehaviour {

    public static tooglecar1 Instance { set; get; }

    public void SetActive ()
    {
        gameObject.SetActive (true);
    }

    public void Disabled ()
    {
        gameObject.SetActive (false);
    }
}

the other script looks the same just with a different name!

and then I thought I could simply add line 24,25 and 32, 33

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BuyScript2 : MonoBehaviour {

    private int myChar;
    public int price;
    private int money;


    // Use this for initialization
    void Awake () {
        myChar = GetComponent<BuyScript> ().myChar;
        money = PlayerPrefs.GetInt ("63952");

    }
  
    // Update is called once per frame
    void Update () {

        if (myChar == 1) {
            globalcurrency.character = 1;
            tooglecar1.Instance.SetActive ();
            tooglecar2.Instance.Disabled ();
            PersistentData.Save ();
        }

        if (myChar == 2) {
            if (globalcurrency.character2unlocked) {

That’s because in your code you updated playerprefs but you never updated the money variable.

Your scripts are confusing and contain unnecessary redundant copies of your money/highscore. If you’re going to store the money in a global variable anyway, why do you have a separate local variable to store it in each of your other two classes?

I’m thinking that you want to update the highscore in your save manager because that is what drives the HighScore text display?

1 Like

I’m not sure yet. Why is it called the save manager? Right now it doesn’t save anything.

Anyway, the quick fix would be: In your BuyScript2, under the line of code that says:
PlayerPrefs.SetInt("Highscores", money - price);
add
SaveManager.Instance.HighScore = money-price;

Or, actually, maybe you’d rather do
GlobalValues.money = money-price;

1 Like

If you want to use this persistent data thing, than I would probably remove any use of playerprefs in your BuyScript2. You probably want to get rid of the money variable too and use GlobalValues.money instead.

1 Like

Oh, I just noticed that your persistent data is not saving the money or the highscore. just everything else. I thought you were saving everything in the GlobalValues. Did you mean to add these to the persistent data class?

1 Like

So add those to your persistant data class. Then that should replace wherever you were saving and loading these to playerprefs before.

1 Like

It’s all the same as the other items you already have in there. You should be able to look at what’s already there and figure out how to add the new ones.
In your player data, add members to store those values:

public int money;
public int highscore;

In your save function where you already have:

pD.character5unlocked = GlobalValues.character5Unlocked;

just add

pD.money = GlobalValues.money;
pD.highscore = GlobalValues.highscore;

Do the same thing in your Load() function.

Basically, your just adding money and highscore to the list that’s already there.

1 Like

But didn’t you already have code that did that? All you have to do is change it to use persistentData instead of PlayerPrefs.

1 Like

It looks like it mostly works. You just need to subtract the money, right?

            if (money >= price)
                {
                  

                    GlobalValues.character2Unlocked = true;
                    PersistentData.Save();
                }

So right before you call Save(), just add:

GlobalValues.money = GlobalValues.money-price;
1 Like

I don’t know what happens before the code that you listed. Are you sure that myChar==2 is ever true? Use Debug.Log to see the value of variables in the console as the program is running.

As for highscore, are you actually looking at globalValues.highscore or are you looking at SaveManager.Instance.HighScore? Did you ever actually set the globalValues.highscore to a value in the first place?

1 Like

Currently your save manager is only ever going to show 0 for highscore or currentscore. Maybe you want to set these text fields to show globalValues.highscore and globalValues.money instead?

When do you want highscore to change?

1 Like

I guess you need to set the highscore at the end of each game then?

1 Like

By the way, in BuyScript2, this line in Awake probably doesn’t do what you think it does:
myChar = GetComponent<BuyScript>().myChar;

This copies the value from BuyScript.myChar into the local myChar once on Awake. If you change BuyScript.myChar at any other time, the value of BuyScript2.myChar will not change.

Why are BuyScript and BuyScript2 separate anyway?

1 Like