What kind of save file architecture I need?

Hello guys,
I’ve decided to use 2.78+ or UPBGE for my next project.
The game will involve building structures, multiple different units and various parameters for the players progress like units level,resurces,buildings…etc.
So I was wondering how to arrange my save file. I need to save current building type,position,hit points and % of its constructions (if it is not ready) also roads will be build so I will need position and orientation of each road tile, units - types, positions, health level and current job assignment, current game level…all in all everything that a classic strategy game have to save.
The game will not be a strategy kind of game, it will be rogue-like manager. However I need to come up with some kind of a save file architecture and I’m not sure how to do this.
Any suggestions?

Visuals will be very much like this, but gameplay will be nothing like this:

It seams you already worked out what you want to store to restore the state after a load.

It is not that difficult. Place the data in a dictionary and that’s it. The dictionary is your storage.

Be aware you need the data in a format that you can:

A) store/save
B) restore the status you want

A) depends on how you store. E.g. mathutils.Vector cant be saved by the saveActuator. You need to store such information as list:


positon = list(house.worldPosition)

Do not try to save game objects, scenes or controllers. This is not possible.

B) after restart all objects will be loaded from blend. This means the scene is at it’s initial status. You need to apply the status from the storage. E.g. Identify objects that match the objects whose data is stored in the storage. Set positions, add objects that where added in the original scene.

Example:

  • original scene contains a game object “Cube” at (10,3,0).
  • reloading the scene has a Cube too, but at the initial position at (2,0,0).

this means you need to store at least two things:

  • a criteria to identify the object (e.g. name = “Cube”)
  • the changed position

On restore you search for object named “Cube” and apply the stored position (10,3,0). This way after restore the Cube is at (10,3,0) which is the same position as at the moment of storing.

master =[]
for object in own.scene.objects:
    if 'saveMe' in object:
         position = object.worldPosition
         position = [position.x,position.y,position.z]
         rot = object.worldOrientation.to_euler()
         rot = [ rot.x,rot.y,rot.z]
         props = object.getPropertyNames()
         propList=[]
         for prop in props:
             propList.append([prop,object[prop]])
         master.append([ position,rot,propList,object['spawnName'] ])


    

each entry in master is a gameobject and all it’s properties
you can pickle or json etc from here

then from knowing the index of the data you just recall the level later,

this is nice because its no overhead except when saving and loading

I have even sent data between scenes using this method to pass a list.

be aware, objects that start in the scene intially[that you want to save position]

objects should be spawned in using empties (so they use ensure you have a object to spawn a new copy from later)

so on load scene purge anything with ‘saveMe’ and then add them back using the stored data

I save some options an complete shop state like (if buy?, is it selected?, money? and other things) in cfg files with the “configparser” class.

of course not the easiest way you can do it other wise as “ini”

simple example:



from bge import logic
import configparser

def save(cont):
    own = cont.owner
    config = configparser.ConfigParser()
    scene = logic.getCurrentScene()
    obj_raw = []
    for o in scene.objects:
        if "get_obj" in o:
            obj_raw.append(o)
            obj = str(obj_raw)
            print(obj)
    
    try:
        config.read_file(open('test_text.cfg'))
    except IOError:
        config.add_section('test')
    config.set('test', 'objecte', obj)

    with open('test_text.cfg', 'w') as file:
        config.write(file)

  

Thank you!
If we assume I know the basics how to write/read and apply data from .cfg/.txt file to objects and states in scene - how to make the architecture of all this, I mean on more abstract level - If I have 500 units with various save properties and 50 buildings and 300 more other game objects and states as road tiles, trees with different grow level, stone mines with different resource depletion, inventory items…etc. How to combine them in lists and dictionaries and how to call/retrieve an object id/position/state from that data.
Is it better to use xml data format for initializing different object types and inventory items and retrieve that data from the xml on load or use just a .txt file.
The problem is in the structure of all that data and how to organize it, so that it will be easy to access it for read and write whenever I need. It is more like a node scheme of the process, that I need.
Is there someone who already face this kind of problem and came up with a solution?

I use a data tree. Fancy word for a “Node” thing that contains a set of key-value string pairs and a set of children that are also “Node” things. The recursive structure of the tree practically solves the read-write problem by itself: you define the code that writes a node and you apply it to each node in the tree. If you choose an output format like xml or json, that also have a recursive structure, the read/write algorithm is a matter of few lines.

I’d say abstract as much as possible.

I’d have a save file that looked like this (using JSON notation for easiness to type/debug)


{
'game_settings':{'map':'/path/to/map', 'difficulty':0, 'time_in_game'....}

'player_list':[{'name':player_1, 'score':1234, 'resources':{'metal':5, 'fuel':2}}, {'name', player_2, ....]
'building_list':[
    {'type':'shipyard', 'health':654, 'position':[pos, rot], 'data':{'construction_unit':'Boat', 'construction_percent':0 ....},
    {'type':'town center', 'health':654, 'position':[......
],
'unit_list':[
    {'type':'horse', 'health':43, 'position':[pos, rot], 'data':{'carrying_message':False, .....}
]
}

Why did I pick this structure?
Mostly because It’s easily expandable. If you want to add the ability for multiple players, it’s trivial to add. If you need a new unit type, just put a different string in ‘type.’
One key point is that every item in the various lists are the same except for the contents of ‘data’ which depends on the type. This is similar to how blender treats objects - a position in space that ‘contains’ either a mesh, curve etc.

I’m a big fan of JSON because it’s easy to debug/read, and trivial to dump information into. If you don’t want people tweaking the save-files, you could do something else or encrypt the JSON.

Now, you should build your game’s internal architecture with intent to save it/load it. For example, somewhere in your game you will probably have a list of units. So make it so you can do the same operation on every item in that list. Since every entity in your game is (probably) a python class. You can add a to_save_dict() and load_from_dict() function which manipulates the necessary data for that unit. This is what I did in a system that had to deal with user-settable objective data. This was a function like:


class TownCenterClass(building):
    def to_json(self):
        '''Returns a JSON string of the object'''
        out = super().to_json() #Does the object name, position and other things common to all buildings

        out['data'] = {'construction_percent':self.construction_percent....}
        return out

Then exporting a save file is:


save_file_data = dict()
save_file_data['building_list'] = list()
for building in internal_building_list:
     save_file_data['building_list'].append(building.to_json())

json.dump(save_file_data, save_file_path)

And loading it is:


CLASS_DICT = {
    'Refinary':RefinaryClass(subclass_of_building),
    'Town Center':TownCenterClass(subclass_of_building)
}

for building_data in save_file_building_list:
    type_str = building_data['type']
    type_class = CLASS_DICT[type_str]
    new_building = type_class()
    new_building.load_from_json(building_data)
    internal_building_list.append(new_building)

Bear in mind I haven’t worked on a save game system in some years, nor of the scale you seem to need.

Now what happens if we want to, say, have units able to go into buildings? Thankfully, it is supremely unlikely that we can have units in multiple places, so we don’t have to store our person in the unit list. We can store it in a unit list inside the building, eg:
building_data = {‘unit_list’:[{‘name’:‘raider’, ‘type’:‘Horse’…}]}
It’s very likely that you’ll already have a system to destroy/recreate entities when they enter buildings. You can take advantage of this and when a person is in a building, work with there data in the same way you do as if they were ‘saved.’ (ie you ‘save’ the person into the building when he enters, and ‘load’ him when he exits).


If we assume I know the basics how to write/read and apply data from .cfg/.txt file to objects and states in scene - how to make the architecture of all this, I mean on more abstract level

Good luck finding this out on these forums. Perhaps a more general game development forum would be more appropriate? If you find a good one let me know. (It seems the indie dev universe is lacking in people who actually design the systems they build)

step 1 generate ‘intial’ level state or random level states

step 2 -store the list of location vectors in a place you can access them in the game data (after generating world first time store world as master list of objects)

step 3- on game start, build kdtree from vectors in the lists your store and then get points in loading radius and use indexes to load level

using a small and large kdtree radius check one can make a loading bubble

mathutils.kdtree is the fastest structure I know bge has out of the box

The thing to realize it is all depends how you’ve structured the rest of your game. Tie into the systems you already have, or (ideally) design both at the same time.

i noticed that all of you do not use globaldict anymore, why? it is sipmly a choice or globaldict isn’t so good?

It just doesn’t offer anything more than your own ‘global dictionary’ does, except that having your own gives a bit more control.

Thanks! You guys gave me some good ideas.

We can store it in a unit list inside the building, eg:
building_data = {‘unit_list’:[{‘name’:‘raider’, ‘type’:‘Horse’…}]}

Actually most of the units will be in the buildings or assigned to a building for most of the time. Good idea!
It is even possible to actually use only that for the units - to assume they are properties of the buildings and not separated entities.

The thing to realize it is all depends how you’ve structured the rest of your game

True, that is why designing game structure at the beginning is essential.
Now as I design my game, I am coming to a conclusion that it would become quite huge and complex thing, which is not what I’m aiming for. So I may re-think the basic game design and eventually redesign and scale down to more simple and easy to develop idea.

i would use a sqlite database to store the data.

In this example (https://github.com/agoose77/basic_serialisation) I show how to separate the “what” of serialisation and the “how”. It’s not a totally pure distinction - the stream object enforces some type restrictions and implements a modifier system (see below for more).

This means that if one wanted to add a backend to write to a mysql DB, or even to bit-pack data, one could. If I were doing bandwidth sensitive networking, I wouldn’t do things this way, or rather, I would write a stream which doesn’t accept names for the fields, as positional control would be more important, but the concept would remain the same.

The Stream objects add/remove metadata about the data being written to the stream. It is trivial to implement new serialisers, and to add a different serialiser backend (if you can think of one).

Modifiers add some autonomy to the process, decomposing types into more primitive types (and vice versa). Using modifiers will output slightly more verbose data than explicitly calling the serialiser methods for these types, but makes the system far more convenient by adding context to the output file…

A basic example shows the simple API
An advanced example shows using modifiers to allow serialisable objects to be written directly to the stream (stream.write(X) instead of X.write(stream)), and supports a vector object
A struct example demonstrates modifiers used to serialise data “structs”.

After this, the real challenge is how you encode your gamestate. Using the modifier context object, you could handle object references by modifying them for integer IDS, which can be looked up from some object manager referenced in the context object.