Serializing ScriptableObject Reference (InstanceID) - c#

Let's say I have a PlayerData ScriptableObject that holds a reference to AvatarData ScriptableObject like so:
public class PlayerData : ScriptableObject
{
public AvatarData avatar;
}
During gameplay player can change the avatar to another one. All AvatarData objects are saved in .asset files. I want to save the reference to currently selected AvatarData instance and save it in json/binary file for loading back.
In case of objects created in memory the InstanceID will change each game session. What about instances of objects serialized in .asset files? Since the instance is saved to a file can we rely on it/be sure InstanceID won't change? JsonUtility.ToJson will generate json with InstanceID for each object but I need to be sure I will get the same object when I call JsonUtility.FromJsonOverwrite. I know it won't work with objects created in memory but I think it should work with instances saved as asset files. I am afraid the InstanceID may change each time I build the app.
Does anyone have experience with this? Is it better to create own UUID/Hashtable to reference the objects in Unity3D?
Most answers I found suggest using UnityEditor namespace but it cannot be used in builds.

I found this post about the issue. Seems we cannot rely on InstanceID in our save systems and this is the way to go:
Create another ScriptableObject assets that holds a list of references to your ScriptableObject assets. In your save data, record the list index. You can implement ISerializationCallbackReceiver and get/set the index in the callbacks.

Related

Save preferences and use them in other platform

I wanted to ask if it's possible to save preferences in unity editor and use them when project is built and access them from other platform.
For example, I have set n=5 then saved it using playerprefs in unity editor and then I want to get the same integer on other platform when the project is build.
Is there any possible way to achieve this type of problem?
Sounds like you'd be better off using JSON and serializing your data. In the desktop, Unity saves PlayerPrefs to the registry. In Android, it is saved to a manifest file. So, in other words.. No, PlayerPrefs is not a cross-platform kinda thing. Furthermore, if this is "save-game" data, you should know that PlayerPrefs is used to store simple data like an Options menu. For saving an entire save game file you are going to want to use something a little more robust.
Sounds like what you want is turning your data (the value of n in your example) into assets in your builds.
PlayerPrefs does not fit your need because it does not create assets at the build time that could be loaded back during run time. Instead, it is used to serialize data generated during run time to users' devices and use across application sessions (by session I mean the time from launch to quit an application).
My suggestions would be finding someway to serialize your data into assets that Unity can read. There are at least two ways of doing such thing:
Using ScriptableObject. ScriptableObject is a class that inherits from UnityEngine.Object that is designed for serializing data as assets. Here is a simple example:
[CreateAssetMenu("my_data.asset", "My Data")]
public class Data : ScriptableObject
{
[SerializeField]
private int _n;
[SerializeField]
private string _someString;
public int N { get { return _n; }}
public string SomeString { get { return _someString; }}
}
The normal usage would be defining your data class , say Data, that inherits from ScriptableObject. Then, you put desired data into it as serialized fields (_n and _someString here). The attribute CreateAssetMenu is used to create a menu item in the Unity editor for you to create the asset.
After doing this, you can use the data in your MonoBehaviour by referencing it through a field marked with [SerializeField] just like any other Unity assets (like materials or sprites):
public class SomeMono : MonoBehaviour
{
[SerializeField]
private Data _data;
void Awake()
{
Debug.Log("Data.SomeString is " + _data.SomeString);
}
}
Serialize the data into your own format (JSON, BSON, XML, whatever you like) and save them and read back as TextAsset. Here is an example:
public class SomeMono2 : MonoBehaviour
{
[SerializeField]
private TextAsset _text;
void Awake()
{
// Assumes that the data is just a plain string sequence.
// However, it could be any text data you like.
Debug.Log(_text.text);
// Or you can serialize your data as bytes
// byte[] byteData = _text.bytes
}
}
Please note that Unity only automatically identifies files with extension txt (and maybe json? not quite sure about this one) as text assets.

Unity/C# Savegame Migration

I've written a SaveLoad class, which contains a Savegame class that has a bunch of ints, doubles, bools but also more complex things like an array of self-written class objects.
That savegame object is being created, serialized and AES encrypted on save and vice versa on load - so far, so good.
The problem I'm facing now is that if there are new variables (in a newer version of the game) that have to be stored and loaded, the game crashes on load, because the new variables can't be loaded correctly (because they are not contained in the old save file). E.g. ints and doubles contain the default 0 while an array is not initialized, thus null.
My current "solution": For each variable that is being loaded, check if it doesn't contain a specific value (which I set in the Savegame class).
For example: In Savegame I set
public int myInt = int.MinValue;
and when loading, I check:
if(savegame.myInt != int.MinValue){
//load successful
}else{
//load failed
};
This works so far for int and double, but once I hit the first bool, I realized, that for every variable I have to find a value that makes "no sense"(not reachable usually), thus was a failed load. => Shitty method for bools.
I could now go ahead and convert all bools to int, but this is getting ugly...
There must be a cleaner and/or smarter solution to this. Maybe some sort of savegame migrator? If there is a well done, free plugin for this, that would also be fine for me, but I'd prefer a code-solution, which may also be more helpful for other people with a similar problem.
Thanks in advance! :)
Your issue is poor implementation.
If you are going to be having changes like this, you should be following Extend, Deprecate, Delete (EDD).
In this case, you should be implementing new properties/fields as nullables until you can go through and data repair your old save files. This way, you can check first if the loaded field is null or has a value. If it has a value, you're good to go, if it's null, you don't have a value, you need to handle that some way.
e.g.
/*We deprecate the old one by marking it obsolete*/
[Obsolete("Use NewSaveGameFile instead")]
public class OldSaveGameFile
{
public int SomeInt { get; set; }
}
/*We extend by creating a new class with old one's fields*/
/*and the new one's fields as nullables*/
public class NewSaveGameFile
{
public int SomeInt { get; set; }
public bool? SomeNullableBool { get; set; }
}
public class FileLoader
{
public SavedGame LoadMyFile()
{
NewSaveGameFile newFile = GetFileFromDatabase(); // Code to load the file
if (newFile.SomeNullableBool.HasValue)
{
// You're good to go
}
else
{
// It's missing this property, so set it to a default value and save it
}
}
}
Then once everything has been data repaired, you can fully migrate to the NewSaveGameFile and remove the nullables (this would be the delete step)
So one solution would be to store the version of the save file system in the save file itself. So a property called version.
Then when initially opening the file, you can call the correct method to load the save game. It could be a different method, an interface which gets versioned, different classes, etc but then you would require one of these for each save file version you have.
After loading it in file's version, you could then code migration objects/methods that would populate the default values as it becomes a newer version in memory. Similar to your checks above, but you'd need to know which properties/values need to be set between each version and apply the default. This would give you the ability to migrate forward to each version of the save file, so a really old save could be updated to the newest version available.
I'm facing the same problem and trying to build a sustainable solution. Ideally someone should be able to open the game in 10 years and still access their save, even if the game has changed substantially.
I'm having a hard time finding a library that does this for me, so I may build my own (please let me know if you know of one!)
The way that changing schemas is generally handled in the world of web-engineering is through migrations-- if an old version of a file is found, we run it through sequential schema migrations until it's up-to-date.
I can think of two ways to do this:
Either you could save all saved files to the cloud, say, in MongoDB, then change their save data for them whenever they make updates or
You need to run old save data through standardized migrations on the client when they attempt to load an old version of the save file
If I wanted to make the client update stale saved states then, every time I need to change the structure of the save file (on a game that's been released):
Create a new SavablePlayerData0_0_0 where 0_0_0 is using semantic versioning
Make sure every SavablePlayerData includes public string version="0_0_0"
We'll maintain static Dictionary<string, SavedPlayerData> versionToType = {"0_0_0": typeof(SavablePlayerData0_0_0)} and a static string currentSavedDataVersion
We'll also maintain a list of migration methods which we NEVER get rid of, something like:
Something like
public SavablePlayerData0_0_1 Migration_0_0_0_to_next(SavablePlayerData0_0_0 oldFile)
{
return new SavablePlayerData0_0_1(attrA: oldFile.attrA, attrB: someDefault);
}
Then you'd figure out which version they were on from the file version, the run their save state through sequential migrations until it matches the latest, valid state.
Something like (total pseudocode)
public NewSavedDataVersion MigrateToCurrent(PrevSavedDataVersion savedData)
{
nextSavedData = MigrationManager.migrationDict[GetVersion(savedData)]
if (GetVersion(nextSavedData) != MigrationManager.currentVersion) {
return MigrateToCurrent(nextSavedData, /* You'd keep a counter to look up the next one */)
}
}
Finally, you'd want to make sure you use a type alias and [Obsolete] to quickly shift over your codebase to the new save version
It might all-in-all be easier to just work with save-file-in-the-cloud so you can control migration. If you do this, then when a user tries to open the game with an older version, you must block them and force them to update the game to match the saved version stored in the cloud.

Persistent storage of object type identifiers

I've read that databases should primarily be used to store data that changes, and that business logic data should reside in the program executable.
If this is the case, how does one represent relations between runtime instances in a way that can be rehydrated in the next session?
Say I have a game in which the player has equipped a "Sword of a Thousand Truths". In C# I've described this as an instance of a type: var s = new SwordOf100Truths() and associated it with the player's inventory: pc.Equip("MainHand", s). Then the player saves the game and exits.
When that player logs back in, I need to know how to recreate their inventory.
Of course I could store records in SQL for every possible item type (GameObject.Equipment.Weapon.Sword.SwordOf100Truths, GameObject.Consumeable.Potion.HealthRank3...), and then have a PlayerInventories table which would get a new record keyed to that particular item id. Then when the player logs back in, I parse the type name and create an instance of it. But this seems awfully inefficient.
Or I could just store the object's type name in the inventory table and recreate it, without having to store all possible items.
Table PlayerInventories
Id, PlayerId, TypeName, InventorySlot
foreach(item in GetPlayerInventoryData(playerId))
{
pc.Equip(item.InventorySlot, Activator.CreateInstance(type.GetType(item.TypeName)));
}
Is this the way to go? How do folks generally deal with this?
You are already thinking of the player as an object and its items as objects -- store them that way as well. No need to go relational. Store the whole player's object graph as a single entity. There are a number of tools and techniques to accomplish this. I've used BSON and JSON to represent the objects, and they lend themselves to easy serialization\deserialization and can be placed in any data store. JSON is nice if you are working with javascript on your front end as you can consume it directly.
This also means you can add objects at any time without having to redo your database, as long as you can serialize the object, you can save it and re-constitute it.

Serialize whole instance with child instances (class serialization) and save to file

I'm building a game with Unity3D. I have a class called GameInstance containing all the current game instance data in it. It contains multiple sub classes for instance the Player property would return a Player object.
Yet all objects are simple key/values objects, it's only data. Now I'd need to serialize all of this and save it to a file so I can reload it to restart the game where it left.
That's what I basically intent to do, maybe somebody would have a better suggestion but yet that's the most flexible option I found.
I used .NET XML object serialization in the past but it's been a while and I'd need to have a more direct advice on this. Should I serialize to XML or JSON for example?
TL;DR: I want to serialize a whole class with all its content with C#/.NET in a Unity3D project. How should I proceed?
Thanks!
I personally prefer json. If you're using json.NET this will be as simple as;
string json = JsonConvert.SerializeObject(MyObject);
or to compact it;
File.WriteAllText("./myfile", JsonConvert.SerializeObject(MyObject));
Then to deserialize you would just do;
MyObject obj = JsonConvert.DeserializeObject<MyObject>(File.ReadAllText("./myfile"));
EDIT: In response to the exception, you want to use this overload which allows you to change the serilization settings;
JsonConvert.SerializeObject(ResultGroups,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});

Loading level data from xml file in XNA

I am creating a tile-based game in XNA and will be loading the level information from xml files. I have no problem with loading xml data but I would like feedback on the approach I'm thinking of using for reading the xml file.
The code has a class hierarchy such that:
A Level class contains:
- a collection of TileLayers
- a collection of Tilesets
A TileLayer class contains
- a collection of Tiles
A Tileset class contains:
- a collection of TilsetTiles
A TilesetTile class contains:
- a TileId
- a collection of TileProperties
- a TileRectangle
Each of the above classes requires some information from the xml level file.
When I load a level I would like to simply call Level.Load();
My intention is that each class will have a Load() method that will:
1. Load any specific info it needs from the xml file.
2. Call Load() on its children.
The problem I see is that the code for processing the xml will be scattered around in different files making changes difficult to implement (for instance if I decide to encrypt the xml file), and no doubt breaks several aspects of the SOLID principles.
I have this idea that I could create an xmlLevelReader class whose sole purpose is to read an xml level file.
This class could then expose methods that can be called from the Load() method in each of the classes described above.
For example the TileLayer class Load() method could call xmlLevelReader.GetTiles() which would return an IEnumerable<Tile>
Do you think this approach will work?
Can you foresee any difficulties?
Is this approach too simplistic/complicated?
Any constructive criticism welcomed!
Thanks
Based on your comment, I see that you are using Tiled Map Editor. This lead me to suggest that you use TiledLib. Here is a brief explanation of how you can get up and running with importing your .tmx files for use in game.
Content Pipeline Overview
File -> Content Import -> Content Process -> Content Write -> .xnb -> ContentRead -> Game Object
TiledLib
TiledLib only handles the ContentImporter part of the above diagram. It will essentially handle reading the .tmx XML and allow you to process the data into whatever objects you need at run time. Fortunately, the TiledLib author has provided a Demos section in the download as well.
Basic Tiled Map Processor Demo
BasicDemo main game project which contains the ContentManager.Load call.
BasicDemoContent project which has the .tmx file from Tiled
BasicDemoContentPipeline project which has the ContentProcessor
TiledLib which has the ContentImporter
You really only need to worry about how the ContentProcessor works because TiledLib handles all the importing for you. Although I do suggest looking through the different classes to understand how it is deserializing the XML (for educational purposes).
The example ContentProcessor in the Basic Demo project takes in a MapContent object from the ContentImporter (TiledLib) and outputs a DemoMapContent object which is serialized to .xnb at build time and deserialized to a Map object at run time. Here are the classes that represent the map after being processed completely:
[ContentSerializerRuntimeType("BasicDemo.Map, BasicDemo")]
public class DemoMapContent
{
public int TileWidth;
public int TileHeight;
public List<DemoMapLayerContent> Layers = new List<DemoMapLayerContent>();
}
[ContentSerializerRuntimeType("BasicDemo.Layer, BasicDemo")]
public class DemoMapLayerContent
{
public int Width;
public int Height;
public DemoMapTileContent[] Tiles;
}
[ContentSerializerRuntimeType("BasicDemo.Tile, BasicDemo")]
public class DemoMapTileContent
{
public ExternalReference<Texture2DContent> Texture;
public Rectangle SourceRectangle;
public SpriteEffects SpriteEffects;
}
A Map contains a tile width, tile height, and a list of MapLayers.
A MapLayer contains a width, a height, and a list of Tiles.
A MapTile contains a texture, a source rectangle (proper rectangle in the tileset), and optional sprite effects (I've never used any).
How It's Made
I suggest reading the comments of the ContentProcessor to understand what is happening, but in brief, this is the basics:
Load texture data for tile set
Get source rectangles for each tile from within the texture
Iterate over all layers of the map
For each layer, iterate over all tiles
For each tile, get the proper texture and source rectangle
Assign all data from the input to the output properly
Caveats
As long as you stick to basic types (vague, I know), you also do not need to worry about ContentWriter and ContentReader parts of the content pipeline. It will automatically serialize and deserialize the .xnb files at build and run time, respectively. See a question I asked about this: here.
Also, you'll notice that if you're using object layers from within Tiled, the demo does not show you how to process those. TiledLib does properly import them, you just need to pull the data out and stick it in your proper classes. I'll try to edit this answer later with an example of how to do that.
If you are just wanting to load in XML without manipulating the data at all, you can just use the built in XNA Content Serializer.
Essentially you define a class which maps to your xml format, and then read the XML into an instance of that class.
For example. Here I define the class I want to load into:
SpriteRenderDefinition.cs
I chose this one because it has nested classes like the case you describe. Note that it goes into the ContentDefinitions project of you XNA solution.
Now here is the xml file that fills in the content of a SpriteRenderDefinition:
Sprite.xml
The format of that XML maps directly to the member names of SpriteRenderDefinition.
And finally, the code to actually load that XML data into an actual Object at runtime is very straight forward:
SpriteRenderDefinition def = GameObjectManager.pInstance.pContentManager.Load<SpriteRenderDefinition>(fileName);
After calling that line, you have a SpriteRenderDefintion object populated with all the content of the XML file! That is all the code I wrote. Everything else is built into XNA. It's really quite slick and useful if you take an hour or so to figure it out!

Categories

Resources