my question is about make a true and best class to store data. (for example like clash of clans or royale or RTS games).
We have many ways. Suppose we have several cards(units) that have special properties. they have changeable(like current_level,coin_upgradeCost,num_upgradeCard,…) and constant attributes like (id,name,open_levelNum,type,…).
we can create one monobehaviour class and fill constant information into the inspector for each units.
public class UnitInfo{
int id;
string name;
int open_levelNum;
int type;
}
public class Database:Monobehaviour{
public UnitInfo[ ] unitsInfo; // initialize from inspector
}
So we can use these unitsInfo(Constant information for units) using index for example unitsInfo[0].name.
now We need to have a serialized class to store changable fields.
[System.Serializable]
public class StoreUnit{
public int id;
public int current_level;
public int coin_upgradeCost;
public int num_upgradeCard;
}
[System.Serializable]
public class StoreUnits{ // save this class into a file(playerprefs)
List StoreUnits_list;
}
Is it true and standard method? that we split constant and storing (changeable) information ? or another method to store and get information in card strategy game like Royale?
Please post the script properly by using the “Insert code” button so it looks proper.
I’d recommend using a serializable dictionary. This is what I use to save data, works for anything :
using UnityEngine;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using System;
using System.Collections.Generic;
//this is SaveInfoNewer.cs
//this script saves and loads all the info we want
public class SaveInfoNewer : MonoBehaviour
{
public SlotLoader SlotLoader;
//data is what is finally saved
public Dictionary<string, int> data;
void Awake()
{
//LoadUpgradeData();
LoadData();
//WARNING! data.Clear() deletes EVERYTHING
//data.Clear();
//SaveData();
}
public void LoadData()
{
//this loads the data
data = SaveInfoNewer.DeserializeData<Dictionary<string, int>>("PleaseWork.save");
}
public void SaveData()
{
//this saves the data
SaveInfoNewer.SerializeData(data, "PleaseWork.save");
}
public static void SerializeData<T>(T data, string path)
{
//this is just magic to save data.
//if you're interested read up on serialization
FileStream fs = new FileStream(path, FileMode.OpenOrCreate);
BinaryFormatter formatter = new BinaryFormatter();
try
{
formatter.Serialize(fs, data);
Debug.Log("Data written to " + path + " @ " + DateTime.Now.ToShortTimeString());
}
catch (SerializationException e)
{
Debug.LogError(e.Message);
}
finally
{
fs.Close();
}
}
public static T DeserializeData<T>(string path)
{
//this is the magic that deserializes the data so we can load it
T data = default(T);
if (File.Exists(path))
{
FileStream fs = new FileStream(path, FileMode.Open);
try
{
BinaryFormatter formatter = new BinaryFormatter();
data = (T)formatter.Deserialize(fs);
Debug.Log("Data read from " + path);
}
catch (SerializationException e)
{
Debug.LogError(e.Message);
}
finally
{
fs.Close();
}
}
return data;
}
}
Have a look at ScriptableObjects (learn section, live training, there’s been a few recently, bunch of other stuff in there. Also very good talk at unite 2016, google “unite 2016 monobehaviour tyranny” (no, really :p))
At there most basic use they hold data as assets in your project which are accessible from any scene, but there is a lot more fun to be had with them for a lot of other things.
Thx but you don’t understand my question. My question is not how to save data into files! I save my data using playerprefs with encryption.
I said how to organize constant and changeable fields in unit class. You save all of them using serialized class?
or separate them at first and save only changeble variables.
I said for example unit class has two type fields (type A: constant not change: open_level, name,id, initial_attackpoint,initial_hitpoint etc) and type B: (fields must be stored(id,curr_upgradeLevel,curr_num_card))
I save only type B fields into files and store type A fields in the code or inspector not file that can access them
Just a bit of feedback, wrap the FileStream in a using instead of calling close(). It’s advised to make ensure that the object is disposed, and while close() does call dispose, wrapping the stream in a using is recommended.
Please stop doing that. Playerprefs is meant for just that, player preferences, not entie RTS databases (just to note, PlayerPrefs saves to the registry in Windows, not the place you want to save non pref data too).
That said,
How have you set up your units? Do you have a good example of how you have set up the system at the moment, what properties do units have, and what properties as shared among multiple units? As I can’t image you having data that is the same for all different type of units. The only common thing I can imagine for multiple units is the type, and for that I would personally create an Enum with unit type names to set that up (then you have the unit type name and type Id in one object eg:
public enum
{
Soldier = 0,
Civilian = 1,
Scout = 2
}
Also please use Code Tags properly when posting code, as currently it’s very hard to read.
Suppose it is like clash royale game. you have units cards and can upgrade them.
public enum Unit_type {
...
}
[System.Serializable]
public class Unit{
int id;
string name;
Unit_type unit_type;
int initial_attackPoint; // initial attack point of the unit (40 constant)
int initial_hitPoint;// initial hitpoint of the unit (100 constant)
int current_numUpgradeCards; // must be stored(5,20,50,100,...)
int cost_upgrade; // coins value needed to be upgraded(20,100,200,...)
int current_upgradeLevel;//(1,2,3,4,...) must be stored
int current_hitPoint;
int current_attackPoint;
}
You can see we have different fields. Some of them are constants and don’t need to store them like type,name,initial ap,initial_hp.
but some of them must be stored like current_upgradeLevel,current_numUpgradeCards.
We see some fields are not constant but can be computed with other fields like cost_upgrade can be computed using current_upgradeLevel or current_hitPoint and current_attackPoint that can be computed using current_upgradeLevel and initial_attackPoint and initial_hitPoint.
So my question is “do you store the serialized class with all fields?” or create another class like StoreUnit and only store the class into file?
[System.Serializable]
public class StoreUnit{
[SerializeField]
int id;
[SerializeField]
int current_upgradeLevel;
[SerializeField]
int current_numUpgradeCards;
}
and define InventoryUnit class that keep constant information for a unit. So we can create an array of it and initialize them into the inspector.
public class InventoryUnit{
int id;
string name;
UnitType unitType;
int initial_hitPoint;
int initial_attackPoint;
}
public class UnitManager:Monobehaviour{
public InventoryUnit[] inventoryUnit; // initilize from the inspector
}
So we can use InventoryUnit array and access to them(Constant fields)
As @Timelog said, please do NOT use PlayerPrefs as they can easily be lost during Windows upgrades, re-installs, registry modifications etc… also PlayerPrefs are saved as a text file so it is easy for someone to read them, making a potential hacker 1 step closer to cracking the encryption. The script I provided is much safer to use as it creates its own file that won’t be accidentally changed by Windows.
Also, thanks for your advise: It’s advised to make ensure that the object is disposed, and while close() does call dispose, wrapping the stream in a using is recommended
Problem is, I really don’t know what you mean… that is probably the most complicated script I’ve written so I don’t quite get what is “wrapping the stream in a using”. Sorry for the noob knowledge level
On standalone build targets PlayerPrefs are stored in the OS registry - which is why you really don’t want to use them for actual game save data.
In terms of OP’s question - we have a class called GameConstants that holds const values. If you have const values associated with unit data then it’s fine to keep them in the same class and just let the serializer ignore them.
thx but I said playerprefs is not my question and I explain that data are encrypted with key َAES etc. in Android we can lost playerprefs?!
Thank you KelsoMRK.
So you say it is better to have all fields into unit class(constant and store data) and ignore constant data by using “nonserialized”
which case do you use GameConstants in? constants that don’t belong to any class? for example private key, physics constants, etc?
On Android the chance of losing playerprefs isn’t as big as on Standalone builds, however:
Unity - Scripting API: PlayerPrefs
Wouldn’t do that. I personally would keep a kind of Dictionary and just get the base values from there when you instantiate/load a unit. Something like below (Warning: pseudo code):
public enum UnitType
{
Soldier,
King
}
[Serializable]
public struct BaseValues
{
public int Id;
public string Name;
public float BaseDamage;
}
[Serializable]
public struct BaseUnits
{
public UnitType Type;
public BaseValues BaseValues;
}
public class BaseUnitsDataStore : MonoBehaviour
{
public List<BaseUnits> baseUnits;
public BaseValues GetBaseValues(UnitType type)
{
return baseUnits.FirstOrDefault(b => b.Type == type);
}
}
Now all you need to do is make sure you have a field/property for the base values struct in the Unit class, and call the datastore GetBaseValues(unityType) to get them (would write an example for that as well, but I am at work atm). This way you can still easily edit the values, but you only have to set them in one place.
I’d generally disagree. Constant values are ignored by serialization anyway and in RTS games memory consumption can be a concern when unit numbers get high so saving where ever possible is advised. If something is truly constant then make it constant (which is a good rule to go by anyway). And if it’s constant then it’s only defined in one spot anyway.
thank you all, I determined to store constant values for each unit into the inspector. like name,type,… for each unit
and then initialize unit class, serialize only variable fields and store them into a file and ignore remain fields of the class:)
Now hold on a second. You said constants but then said you were setting them in the Inspector. Those two things aren’t compatible. When I talk about constant values I’m talking about compile-time constants that can’t be changed. It sounds like you’re talking about values that you don’t intend to change. Those are different.
I’m talking about this:
// anywhere my code references SomeInt the compiler will
// essentially do a find-replace on SomeInt with the literal value 5
const int SomeInt = 5;
wow I said several times. each unit has name,type, etc that don’t change. no I don’t mean const variable!
in the code I implemented unit class that has non changeable(type,name,initial attack point etc. They are initialized only one time at first for each unit) and changeable variables(current_numUpgradeCards,current_upgradeLevel that can be changed and upgraded). I never wrote const int SomeInt = 5 in my class.
my question was I need to save all fields or no. I don’t have any const compile time fields!
Then stop calling them constant values
Yes it has ambiguity between the meaning of the word in English language and programming language
There a reason why I called them Base values ^.^
Also, for memory usage, one thing you could do, is drop the name and id and purely rely on the enum for that because:
public enum UnityType
{
Soldier = 1,
King
}
public class Unit
{
public UnitType type = UnitType.Soldier;
public string Name { get { return type.ToString() } } // Returns Soldier
public int Id { get { return (int)type } } // Returns 1
}
As always when trying to strike a balance between memory usage and performance you’ll have to make a choice. Above sample will give you slightly more CPU overhead with the casting, however it will give you a slightly lower memory footprint due to the two fields that are dropped, and it will save slightly (like a really tiny) amount of saved data on disk. There even is a third option, and that is to cast only ones on Start() or Awake() or OnEnable(), and store that in a field that exist purely for the livetime of the object.
Then again, early optimization is the root of all evil, so don’t go overboard with it ^.^ I would personally go for above example as I find that if I can reduce fields in my code where it makes sense, in general it’s easier to manage.