Hey there,
i want to put a dictionary with the types <int, int> into a string, so i can store it in PlayerPrefs. How can I do this?
Hey there,
i want to put a dictionary with the types <int, int> into a string, so i can store it in PlayerPrefs. How can I do this?
add .ToString() at the end
Debug.Log(myInt.ToString());
This just converts my dictionary into this: “System.Collections.Generic.Dictionary`2[System.Int32,System.Int32]”
Not much i can do with this afterwards.
You’ll have to either create your own methods that generates a string from the dictionary, or get hold of some library that does it for you.
SimpleJSON should be able to handle it.
What you want to turn the listed contents of the dictionary into a string?
Well first you have to define what the format of the string will be… are you going to comma-delmit it? Maybe pipe-delimit it? Maybe convert it to json or xml… or you might just turn it into raw binary data and b64 encode it.
This is called serialization, and there’s many many many different ways to do it.
So it’s not just “turn it into a string”… there’s steps in between. And you need to pick the steps you want to take.
…
Also… PlayerPrefs as a save location… it upsets me how unity put this one out there and it’s so damn popular.
STOP FILLING MY REGISTRY WITH JUNK!
PlayerPrefs writes data into the windows registry on windows. The registry is intended for carrying simple data name-value pairs. Jabbing huge chunks of serialized data in to a name/value pair isn’t really what the registry is for.
It’s what %appdata% is for. Or in the case of unity, Application.persistantDataPath:
Yuuuup.
If you’re saving stuff, best to serialize it into a file. Here’s an excellent tutorial on that. You should be able to put your Dictionary into a class like the one he has in the tutorial and it’ll Just Work ™.
I wanted to implement some Save/Load features into my game. Until now, i was using a serializable class that contains all the info I need for my game. After this, I’ve been using LitJson to parse all the stuff that should be saved into a string and stored it into PlayerPrefs (sorry lordofduct).
Upon loading my game, i just read out the string in PlayerPrefs and reconstruct my Base-Class from it.
When this is done, i write the contents of this class into PlayerPrefs. (Like: which scene to load next, player location and some other game-related stuff). Now i want to store the Players Inventory as well but it turned out, that LitJson doesn’t like Dictionaries so i need something else.
I just took a quick look at SimpleJSON but I don’t really seem to understand whats going on. Is this page the only kind of documentation they have?!
With LitJson I simply wrote " string json_string = JsonMapper.ToJson(saveLoad); " to store my object into a Json-String. What do i need to do with SimpleJSON?
LitJson works fine with dictionaries in my experience (if it didn’t, my current project would be up a creek without a paddle). Can you post some code where LitJson and dictionaries don’t work?
The error-message I get is this one:
InvalidCastException: Cannot cast from source type to destination type.
LitJson.JsonMapper.WriteValue (System.Object obj, LitJson.JsonWriter writer, Boolean writer_is_private, Int32 depth)
LitJson.JsonMapper.WriteValue (System.Object obj, LitJson.JsonWriter writer, Boolean writer_is_private, Int32 depth)
LitJson.JsonMapper.ToJson (System.Object obj)
SaveGameManager.SaveContent (Int32 saveSlot) (at Assets/SaveGameManager.cs:60)
SaveGameManager.SaveGame (Int32 slot) (at Assets/SaveGameManager.cs:43)
UnityEngine.Events.InvokableCall`1[System.Int32].Invoke (System.Object[] args) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:189)
UnityEngine.Events.CachedInvokableCall`1[System.Int32].Invoke (System.Object[] args) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:309)
UnityEngine.Events.InvokableCallList.Invoke (System.Object[] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:637)
UnityEngine.Events.UnityEventBase.Invoke (System.Object[] parameters) (at C:/buildslave/unity/build/Runtime/Export/UnityEvent.cs:773)
UnityEngine.Events.UnityEvent.Invoke () (at C:/buildslave/unity/build/Runtime/Export/UnityEvent_0.cs:52)
UnityEngine.UI.Button.Press () (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:35)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/UI/Core/Button.cs:44)
UnityEngine.EventSystems.ExecuteEvents.Execute (IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:50)
UnityEngine.EventSystems.ExecuteEvents.Execute[IPointerClickHandler] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.EventFunction`1 functor) (at C:/buildslave/unity/build/Extensions/guisystem/UnityEngine.UI/EventSystem/ExecuteEvents.cs:261)
UnityEngine.EventSystems.EventSystem:Update()
This just appeared after adding the <int, int> Dictionary to my serializable class, so i guess the dictionary is causing some problems, right? The dictionary is correctly filled with key-value pairs.
EDIT:
And here’s the variables inside my serializable class.
[System.Serializable]
public class SaveLoad {
// PlayerPosition
public double playerPositonX, playerPositonY, playerPositonZ;
public double playerRotationX, playerRotationY, playerRotationZ;
// Player Equipment
public int currentWeaponID, currentArmorID, currentGlovesID, currentPantsID, currentBootsID, currentAccessoireID;
// Mouse X, Y
public double currentX, currentY;
// Opened Chests
public int[] chests;
// last scene
public string currentScene;
// event Levels
public int eventLevelWoods, eventLevelMountains, eventLevelTown, eventLevelDragonHills;
// Player stats
public int currentHP, maxHP, currentMana, maxMana, attack, defense, speed, luck, exp, expNeeded, level, gold;
// Inventory
public Dictionary<int, int> currentItems;
...
}
Can you post the code from SaveGameManager.cs where you are serializing / deserializing this?
Also, make sure you start “fresh” with a new json file, just in case.
The methods inside SaveGameManager that should Save the current game are these:
public void SaveGame(int slot)
{
saveLoad.GetCurrentGameContent();
SaveContent(slot);
player.saveFinished = true;
}
public void SaveContent(int saveSlot)
{
string json_string = JsonMapper.ToJson(saveLoad);
PlayerPrefs.SetString("saveSlot" + saveSlot, json_string);
}
public void LoadContent(int saveSlot)
{
string json_string = @PlayerPrefs.GetString("saveSlot" + saveSlot);
saveLoad = JsonMapper.ToObject<SaveLoad>(json_string);
}
saveLoad.GetCurrentGameContent() is this one:
public void GetCurrentGameContent()
{
SceneLoader.Instance.SavePlayerPosition();
playerPositonX = PlayerPrefs.GetFloat("LastPositionX");
playerPositonY = PlayerPrefs.GetFloat("LastPositionY");
playerPositonZ = PlayerPrefs.GetFloat("LastPositionZ");
playerRotationX = PlayerPrefs.GetFloat("LastRotationX");
playerRotationY = PlayerPrefs.GetFloat("LastRotationY");
playerRotationZ = PlayerPrefs.GetFloat("LastRotationZ");
currentX = PlayerPrefs.GetFloat("currentX");
currentY = PlayerPrefs.GetFloat("currentY");
currentScene = SceneLoader.Instance.GetCurrentScene();
currentWeaponID = PlayerPrefs.GetInt("currentWeapon", -1);
currentArmorID = PlayerPrefs.GetInt("currentArmor", -1);
currentGlovesID = PlayerPrefs.GetInt("currentGloves", -1);
currentPantsID = PlayerPrefs.GetInt("currentPants", -1);
currentBootsID = PlayerPrefs.GetInt("currentBoots", -1);
currentAccessoireID = PlayerPrefs.GetInt("currentAccessoire", -1);
eventLevelWoods = PlayerPrefs.GetInt("eventLevel_Woods00", 0);
eventLevelMountains = PlayerPrefs.GetInt("eventLevel_Mountains00", 0);
eventLevelTown = PlayerPrefs.GetInt("eventLevel_Town00", 0);
eventLevelDragonHills = PlayerPrefs.GetInt("eventLevel_DragonHills00", 0);
currentHP = PlayerPrefs.GetInt("VennCurrentHP", 120);
maxHP = PlayerPrefs.GetInt("VennMaxHP", 120);
currentMana = PlayerPrefs.GetInt("VennCurrentMana", 24);
maxMana = PlayerPrefs.GetInt("VennMaxMana", 24);
attack = PlayerPrefs.GetInt("VennAttack", 35);
defense = PlayerPrefs.GetInt("VennDefense", 8);
speed = PlayerPrefs.GetInt("VennSpeed", 12);
luck = PlayerPrefs.GetInt("VennLuck", 6);
exp = PlayerPrefs.GetInt("VennEXP", 0);
expNeeded = PlayerPrefs.GetInt("VennExpNeeded", 30);
level = PlayerPrefs.GetInt("VennLevel", 1);
gold = PlayerPrefs.GetInt("gold", 0);
chests = new int[20];
for(int i = 0; i < chests.Length; i++)
{
chests[i] = PlayerPrefs.GetInt("Chest_" + i, 0);
}
Item[] items = Inventory.Instance.GetItems();
List<Item> cleanedUpItems = new List<Item>();
for(int i = 0; i < items.Length; i++)
{
if (!cleanedUpItems.Contains(items[i]))
{
if(items[i].ID == -1)
{
continue;
}
Debug.Log("Adding ID " + items[i].ID + " and amount of " + items[i].amount);
cleanedUpItems.Add(items[i]);
}
}
currentItems = new Dictionary<int, int>();
foreach(Item item in cleanedUpItems)
{
currentItems.Add(item.ID, item.amount);
}
}
My first instinct is actually that your error might be because you’re trying to load data that was saved with an older version of that class; for example, if currentItems was ever anything besides Dictionary<int, int>, that’s the error I’d expect to see. So try to fully “flush” out PlayerPrefs, or use a different key for PlayerPrefs, and see if that fixes that particular error.
That said, you have a lot of code there to accomplish this, and TBH the issue could be rooted in many different places. So the rest of this post will help you simplify your whole design here, get rid of unnecessary code, and eradicate PlayerPrefs in the process. Read on!
Some general notes
1) Unity uses floats, not doubles. They usually convert seamlessly, but you'll occasionally get errors. There's generally no reason to use a double unless you're writing a space simulator or something.
2) Use Vector3 directly, instead of having three different variable
3) You're storing the mouse position? ....why....?
4) You probably made this decision for memory efficiency, but I guarantee you're going to hate yourself later for using int's to reference ID's for inventory. I recommend strings; they take up more memory, but not on a scale that will ever matter. And when it comes time to save and load them, you can just spawn them out of the Resources folder by name, etc.
The biggest issue: You're getting data FROM PlayerPrefs, to put into a custom class to serialize, and then put back into a different part of PlayerPrefs. That's all kinds of bad. You're creating a lot of unnecessary work for yourself.
And I'm pretty sure it's all because at some point, someone told you that PlayerPrefs was a good place to store data between scenes. It's really not. Honestly, PlayerPrefs is well-named; it's literally only good for storing preferences.
You're using PlayerPrefs to store save-game data between scenes, and to store the save game itself. It's good at NEITHER of these things. So, let's eradicate PlayerPrefs from this code entirely. It's not as scary as it sounds.
Let’s start with how to store data between scenes. You have two good options here:
public class AnyClassYouFeelLike {
public static SaveLoad persistentData;
}
// anywhere else in your project
AnyClassYouFeelLike.persistentData = new SaveLoad();
AnyClassYouFeelLike.persistentData.currentWeaponID = whatever;
//and so on
The best way to access the GameObject in #2 would be to make it a singleton, so you’re likely going to be using a static thing somewhere in either case. The main advantage of using #2 is that you would be able to see the SaveLoad data in the object’s inspector (which makes debugging easier).
Secondly, you need to save stuff to a file, instead of to a string in PlayerPrefs. There's a wonderful function called File.WriteAllText, and you can use Application.persistentDataPath to get a reliable spot to save files (no matter what platform you're running on).
```
string fullFilePath = Application.persistentDataPath + "saveData.json";
File.WriteAllText(fullFilePath, yourJsonString);
//reading
string yourJsonString = File.ReadAllText(fullFilePath);
```
Well, this fixed my error so far ![]()
I started out with using floats, but something went wrong with them somewhere and i was forced to use Doubles instead. I don’t really remember what it was, but I try to get back to using floats.
I’m using Mouse-Coordinates to Move my camera around.
Yes this seems accurate to me. I had an internship in a Game-Company once and they taught me to use PlayerPrefs like this. I thought this was okay, but apparently it isn’t.
Using a static class that doesn’t get destroyed seems very useful. I was using some classes this way already, but i wasn’t smart enough to handle these kind of information like this as well. I give it a shot ![]()
I am very grateful for your kind explanation.
Thanks for taking the time to explain everything. It was really eye-opening ![]()
Maybe I’ll add to my roster of articles, one explaining the issues with PlayerPrefs and how to avoid using it inappropriately… It does seem that a lot of people use it as their first and only means of saving data.