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->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