I still don't know how to save progress with PlayerPrefs

I finished this course, but I still can’t make a game that always loads the last level the player was on. I made a static int to keep track of the level, and whenever you go to the menu it sets it to the level the players on. It almost works but of course if I quit the game, it will start at level 1 again. I tried writing to player prefs, bet I can’t make the level manager load load the level in playerPrefs without getting an error! It’s a great disappointment that they never teach you something as important as saving progress in this course!

Hello @F-J-Valentin,

Can you post your code for your level manager please and also provide a screenshot of the error you are receiving.


See also;

Here the relevant part of the code:

and heres the error in detail:

Hi,

That isn’t really enough to go by.

Can you post the full code for your level manager and your player preferences manager.

Thanks.

As I see it GetPlayerLevel is written like it is a method, if that’s the case you forget the parentheses. If not, monobehavior complains that you’re not cast a string as expected by the loadscene method.

1 Like

That is true, however, there are other issues here I believe also. For example, if that is a method and it doesn’t return anything, the SceneManager.LoadScene() method will throw an error again because there will be an attempt to load a scene called “” (empty string).

The reason for the request to post the full code was to help mitigate these issues by suggesting introducing validation, specifically around whether or not a player preference has already been saved or not.

I think seeing the full code of both of the requests scripts in this case would be beneficial in order to reduce further problems.

1 Like

@Dieedi
Thanks! that stopped the error!
@Rob
The errors are gone but I’m a little skeptical about how well it will work. I may still end up upload my code.

The things you may want to consider are the what-ifs…

At the moment the SceneManager.LoadScene() method is expecting you to return either a string or an int. As your method signature is string, it is expecting that. But “” (empty.string) is a valid string, right up until you try using it as the name of the scene to load :slight_smile:

The Unity documentation exolains the various different locations for different devices for where the player prefs are stored. If you made a PC build for example, I could easily change your string of “Level_01” to “Any_Other_Value”. At best, I get to.play other levels of my choosing, perhaps ones I’ve not actually unlocked, at worst, I crash the game.

You code for accessing the player prefs should at least check to see if a value exists before trying to use it. If you have a stored value then perhaps compare it against a list of acceptable values. If it passes these checks, then use it as the name of the next level to load, but perhaps pass it into your method as a string rather than as the actual method call.

I believe there are size limitations regarding player prefs, however, you could consider encrypting the sensitive values that you store.

Hope this is of use, if you want any further help please do post the code.

Yah, the problem now is that I kind figure out how to set the default value for player prefs in my playerprefs manager. Should I post that code?
Also, is there a way I can make sure I don’t hit the size limitations?

That shouldn’t be a problem.

Sure, post that but also the level manager and if there is anything else related that too, it’s easoer to have too much than have to keep asking for the next bit.

Regarding the limit, I wouldn’t worry too much at this stage. From memory, and you may need to check, I think it is limited to 1mb, that may be just for mobile devices, again, you would need to chexk the documentation.

Adding the validation will not be too difficult.

Hers the level manager

:

Hers the player prefs manager:

And this last script is responsible for updating the players progress. I simply add it to an empty in every seen I want to save (bad Idea?)

I notice that the when I make a build of the game, the playerprefs seem to remember the last level I was on in the unity editor. Is going to the scene I want so star on in the unity editor before I build a safe way of setting the starting level that will always work on all devices?
Edit
Never mind. I did a webGL build and it tried to go to level 0, which it was already on.

Hi,

Ok, thanks for posting - again, it’s easier normally if it is just copy/pasted in - see the link at the bottom regarding formatting - it enables the people that help you to copy bits of your code easily to make suggestions. :slight_smile:

So, you have a key called the_level and you are storing the build index of the level the player has got to.

There’s still some part to this which are unclear, for example, I’m guessing the Start/Main Menu scene has a lower index value than the first playable level? Also, what do you do regarding the Win / Lose scenes, are they lower in number than the playable scenes, or are they at the end as it were, thus their build index will be higher? If there is a LevelManager on the Win / Lose scenes, which from memory I believe there was, what is stopping them from setting that the player got to that build index for example?

What calls the LoadCertainLevel() method?


MyPlayerPrefsManager.cs

Within the GetPlayerLevel() method, you return the build index for the save player preference. Here is problem number one. As there is no validation at all here, if the setting doesn’t exist, or if you have saved it as one name, update your code, and then try to retrieve it as another and it doesn’t exist etc, you will get there default value for the data type (see the link below), which, as it is an int means you will be return a 0 (zero).

Problem two is that there is no validation to check if this happens either. So, it will effectively always return true and give you a zero if the setting doesn’t exist. Which scene is build index zero? I’m guessing the Start/Main Menu scene?

PlayerPrefs has the method HasKey() (see the link below) which will return true if the key exists, or, false if it doesn’t. By implementing this in your code you could control exactly which value is returned based on the existence of the key or not. For example;

public static int GetPlayerLevel()
{
    int levelToLoad = 0;    // should set this to something which would be a meaningful default 
                                // if a key hasn't been saved, not necessarily zero, will depend on your build indexes

    if(PlayerPrefs.HasKey(LEVEL_PLAYER_IS_ON)
    {
        // ok, so now we know we have a stored key, chances are we will have a value also
        levelToLoad  = PlayerPrefs.GetInt(LEVEL_PLAYER_IS_ON);
    }

    return levelToLoad ;
}

In the above example, we default the levelToLoad variable to zero, however, you may need to change this if you, for example, wanted the player to always be taken to the first playable level rather than the Start/Main Menu scene. This may be build index 2 or 3 for example.

We then check to see if the key exists, if it does, then the player (a player) has been here before, in which case we should more safely be able to assume that there will be a valid data item stored with that key. We then retrieve it using your code as before.

If it doesn’t have that key stored, the default value will be returned.

One thing you could consider here is that if the key doesn’t exist it would suggest that the game hasn’t ever been played before, or, not by this user (depending on platform). You could potentially call your SetPlayerLevel() method here, pass in the default value as described above, and then return as before. This would mean that on the very first check to see if a key exists or not, if it doesn’t, one is created.


LevelManager.cs

The method LoadCertainLevel() is used specifically for the loading of saved progress. Should the LevelManager really care whether there is saved progress or not? I would suggest not, being that you have a class called MyPlayerPrefsManager which is responsible for that.

What you could do here instead is simply change that method to reflect the other SceneManager.LoadScene() method signature, e.g. the one that takes an int as a parameter for the build index instead of a name. e.g;

public void LoadLevel(int buildIndex)
{
    Debug.Log("New Level loaded (build index): " + buildIndex;
    SceneManager.LoadScene(buildIndex);
}

What you would then have is a LevelManager which is simply reflecting the underlying Unity methods for loading scenes, whether by name or build index.

Somewhere in your other script(s) you will have a line of code like this;

levelManager.LoadCertainLevel()

What you can then do is update that to be;

levelManager.LoadLevel(MyPlayerPrefsManager.GetPlayerLevel());

Thus, you have a tidy LevelManager, and you access the improved MyPlayerPrefsManager method GetPlayerLevel().


One thing I still do not like about this though is that you have to keep running back and forth to get this value, that seems unnecessary. If you think about it, you know when it needs to change, because you must be updating what is stored with the key when the player completes a level and moves on to the next. What you could do is consider using a static variable on a relevant class to hold this value locally. The one proviso is that you will need to ensure that it is updated when you update the saved key. The tidiest way of doing that would be within the same method.

By doing this, you can make one call to retrieve the stored value, if it exists, when the game starts. You then only need to make calls to the PlayerPrefs to update the data when a change occurs, e.g. progress through the game. Once the game is running, any subsequent requests for this information can be made within the game itself locally, as long as you keep both updated etc, this should be potentially faster and more efficient. Bonus.


Finally, I’m not a big fan of putting method calls as parameters for method, e.g. what we did above;

levelManager.LoadLevel(MyPlayerPrefsManager.GetPlayerLevel());

It should always be returning either a stored scene index from player progression, or an acceptable default starting scene index based on the example code given above. But because we are ramming that in, we leave no room for further validation, or, any extensibility.

This statement would presumably only be called once anyway, when the player choose to start from their previous failed level. Again, we can use our static variable for this, we read this in once, populate the static variable and then use that within this method call instead. Should you want to do any further validation, or put any specific rules in place, you can do that after you have populated the static variable, but before you call the method above.


I have provided a link below for the main page of the PlayerPrefs documentation, this provides some information with regards to where these are stored on differing devices.

Regarding the size limitation, it looks like the 1mb limit may have been specifically for the Web Player build, no obsolete, however other articles suggest that Android for example just kept going up and up until it crashed entirely. Keeping this data small would be advisable. Storing an int for the scene index is no real issue. When/if you encrypt this value hwoever you will see it grows significantly.

I hope the above and the links below are of use, any queries please let me know.

Updated Fri Jul 14 2017 21:19

Just spotted the last screenshot and question ref “bad idea”. Not necessarily, but it does mean a lot of duplication. If for example you already have code which calls the LoadNextLevel() method within the LevelManager, that implies you know when a level is complete, as such, you could just put that code into a method and call it from the same place.


See also;

Forum User Guides : How to apply code formatting within your post
Unity Scripting API : PlayerPrefs
Unity Scripting API : PlayerPrefs.HasKey
Unity Scripting API : PlayerPrefs.GetInt

I did what you did to GetPlayerLevel() and set levelToLoad as 1 since thats where my game starts. It works beautifully!!! Thanks a lot!!! Hopefully I won’t have any more problems. Can I credit you (under special thanks maybe?) if I finish the game?

1 Like

Great to hear and you are more than welcome.

You can if you want to but honestly, there is no need - just happy to help. :slight_smile:

Thanks a lot! :grinning:

1 Like

Privacy & Terms