Concise Save/Load. (With demo blend.)

Below is a demo of my take on saving and loading in the BGE.

[ATTACH]492528[/ATTACH]

Controls
[Arrow keys] move purple cube (the player).
[A] Saves, [S] Loads
[Q] & [W] drop “instanced objects”; objects that have been spawned from a hidden layer.
[Z] & [X] modify props, some that are set to save, some that are not (look at debug at top left)


Important note The “Kill” module MUST be called before the “load” module, or all hell will break loose.

Notes:
This is a fairly simple save/load script. It’s intended to be a small, concise, starting point for one to extend as needed.

Only objects with the “save” property are saved, everything else is ignored.

This saves out to a file, (<name_of_scene>.save) So, even if you close and restart BGE, hitting [S] will return you to right where the save file says you were.

This doesn’t save physics constraints. If you set up, say, a hinged door, your hinge will likely break and the door will fall if you try and save then load it.

Some items in this demo are INTENTIONALLY not saved/loaded, to demonstrate that feature. The Suzanne is one of these, it’s props are also not restored.

The camera owns the save/loading SCA logic. The cube owns keys for simple movement. The empty spawner owns the keys for spawning “instanced objects.”

This was made by me, fairly recently (2015-2016). It’s compatible with at least Blenders 2.5 - 2.78

Here is the raw code for those that don’t want to mess with a blend. No license. Go nuts.


import bge, pickle, time#, pprint

def save(cont):
    # hack to prevent repugnant double trigering of script.
    if not cont.sensors['AKey'].positive:
        return
    time_start = time.time()
    own = cont.owner
    scene = own.scene
    objects = scene.objects
    
    save = {} # save dictionary, which will contain all data for *all* save objects.
    count = 0 # Counter, to append to each dict entry, giving each object a unique name.
    
    for object in objects:
        if object.invalid == False:
            props = scene.objects[str(object)].getPropertyNames()
            if "save" in props:
                
                # data structure of each object in the save dictionary,
                obj = {}
                obj['LinVel'] = []
                obj['AngVel'] = []
                obj['Positi'] = []
                obj['Orient'] = [[], [], []]
                obj['props'] = {}
                
                # Instances of objects must have unuiqe keys in the save dict for them to be
                # referenced individually, so we append an instance number to their name/key.
                if 'InstanceOf' in props:
                    count = count + 1
                    obj['name'] = str(object) + "_" + str(count)
                else:
                    obj['name'] = str(object)

                ##########################################################################
                # The pickler cannot understand abstract (well, compound) data-types like
                # vectors or matrices. To work around this, the float elements of these
                # data-types must be extracted and flattened to lists "manually". The BGE
                # python API, fortunately, can understand these flattened lists as their 
                # representative data types, so the reverse operation need not be done.
                LinVel = object.getLinearVelocity(False)
                for x in range(len(LinVel)):
                    obj['LinVel'].append(LinVel[x])

                AngVel = object.getAngularVelocity(False)
                for x in range(len(AngVel)):
                    obj['AngVel'].append(AngVel[x])
                
                Positi = object.worldPosition
                for x in range(len(Positi)):
                    obj['Positi'].append(Positi[x])
                
                Orient = object.worldOrientation
                for x in range(len(Orient)):
                    for y in range(len(Orient[x])):
                        obj['Orient'][x].append(Orient[x][y])
                ##########################################################################

                # Get all properties, make them a list. All properties data-types are pickle-able
                for prop in props:
                    obj['props'][prop] = object[prop]
                    
                save["%s" % (obj['name'])] = obj
    
    # ~5 second delay (AMD FX 4100 "quad" core @ 4.0Ghz.)
    #for X in range(0, 7590):
    #    for Y in range(0, 9000):
    #        Z = X + Y
    
    output = open(str(scene) + ".save", 'wb+')
    pickle.dump(save, output)
    output.close()
    #print("Saveing took: %.4f sec" % (time.time() - time_start))
    return

def load(cont):
    #print("Stage Two
")
    time_start = time.time()
    own = cont.owner
    scene = own.scene
    objects = scene.objects
    ReturnState = cont.actuators['return']

    pkl_file = open(str(scene) + ".save", 'rb')
    save = pickle.load(pkl_file)
    pkl_file.close()
       
    for object in save:
        if 'InstanceOf' in save[object]['props']:
            scene.objects['Spawner'].worldPosition = save[object]['Positi']
            scene.objects['Spawner'].worldOrientation = save[object]['Orient']
            
            justadded = scene.addObject(save[object]['props']['InstanceOf'], scene.objects['Spawner'])

            justadded.setAngularVelocity(save[object]['AngVel'], 0)
            justadded.setLinearVelocity(save[object]['LinVel'], 0)
            
            for prop in save[object]['props']:
                justadded[prop] = save[object]['props'][prop]
        else:
            scene.objects[object].setAngularVelocity(save[object]['AngVel'], 0)
            scene.objects[object].setLinearVelocity(save[object]['LinVel'], 0)
            scene.objects[object].worldPosition = save[object]['Positi']
            scene.objects[object].worldOrientation = save[object]['Orient']
            
            for prop in save[object]['props']:
                scene.objects[object][prop] = save[object]['props'][prop]
    
    cont.activate(ReturnState)
    print("Loading took: %.4f sec" % (time.time() - time_start))
    return

def Kill(cont):
    if cont.sensors['SKey'].positive:
        time_start = time.time()
        own = cont.owner
        StateChange = cont.actuators['State']
        #print("Stage One
")
        # Any object that is "instanced" from a hidden layer object cannot be handled safely.
        # therefore, we must delete any so we can safely reconstruct from data we *CAN* trust.
        # Instanced objects need to have a Message.sens-&gt;endObject.act combo, with the below text as the subject filter 
        Mess = bge.logic.sendMessage
        Mess('KillSelf', '', '')
        cont.activate(StateChange)
        #print("Killing instances took: %.4f sec" % (time.time() - time_start))
    return

Hi.
Why do I save a save file to the user’s root directory, and not to the one where the game is? I have Linux.

Why do I save a save file to the user’s root directory, and not to the one where the game is? I have Linux.

That’s a very good question.

I would guess it’s a difference in the security model of Linux vs. Windows. Could also simply be the difference in how Python works on Linux and Windows. I think EVERY multi-platform language has tiny quirks like this that change platform to platform.

Looks like you can specify the path manually if you import the OS module and use os.path.join().

The (TOTALLY UNTESTED!!!) change would look something like this…


import bge, pickle, time, os

. . . Then . . .

      save_path = '/usr/games/MyGame/' #&lt;----- Or wherever else you want.
      FullName = os.path.join(save_path, str(scene) + ".save")
      output = open(FullName, 'wb+')
      pickle.dump(save, output)
      output.close()
      #print("Saveing took: %.4f sec" % (time.time() - time_start))
       return

Edit: One more thing, virtually every game in existence saves your save file in your personal directory. This is so you can uninstall the game, or upgrade, and your saves will not be deleted.


path = bge.logic.expandPath(<i>"//"</i>)

Converts a blender internal path into a proper file system path.
Use / as directory separator in path You can use ‘//’ at the start of the string to define a relative path; Blender replaces that string by the directory of the current .blend or runtime file to make a full path name. The function also converts the directory separator to the local file system format.