File Operations

logic.expandPath

Saving files and loading files in relative path and custom path without specifying user's name manually. We'll be saving some game data and loading that data. The data we'll be saving is the player's location.

Logic Bricks Setup

Fire up the engine, add two keyboard sensor, bind them as F1 and F2 repectively, add two python controllers set their script type to module and name them. First one's module will be written as save and second as load.

Logic Bricks setup for loading and saving

Create a text data-block and name it as whatever you want, I've named it as mycode.py, import our game engine module, get our current controller, get the owner data and create a function that does nothing.

try:
    import bge
except:
    import Range as bge

cont = bge.logic.getCurrentController()
own = cont.owner

def save(cont):
    pass
Saving

Now we need to get our player object's name and position. We can keep this outside the function, so there won't be any need to define them two times.

cube = own.scene.objects["Cube"]
cube_pos = cube.worldPosition

Now we need to save our player's position as a file. It can be any file, we will be saving as .mysave file, and define the relative path. So we do the following

# Relative path
rel_path = bge.logic.expandPath("//")

with open(rel_path+".mysave", "w") as f:
    f.write(str(cube_pos)) # !!! NOT YET DEFINED !!!
    f.close()

Here we get the relative path and do the in-built python file writing operation using with. Altough f.close() is automatically called, its a good practice to do so.
And note, we have not defined the cube's position. We need to save it as a dict.

x = cube.localPosition[0]
y = cube.localPosition[1]
z = cube.localPosition[2]

cube_pos={
"X":x,
"Y":y,
"Z":z
}

The above code might look tricky, the localPosition returns us as Vector, which might be difficult to load the position. We get the X, Y, Z data seperatly by accessing element by key and store them as a dictionary. So now the full save function and other defenitions above looks like this.

try:
    import bge
except:
    import Range as bge

cont = bge.logic.getCurrentController()
own = cont.owner
rel_path = bge.logic.expandPath("//")
cube = own.scene.objects["Cube"]

x = cube.localPosition[0]
y = cube.localPosition[1]
z = cube.localPosition[2]

cube_pos={
"X":x,
"Y":y,
"Z":z
}

def save(cont):
    with open(rel_path+".mysave", "w") as f:
        f.write(str(cube_pos))
        f.close()
        print("File Saved.")
Loading

Now to load the the saved data, we need to use the with like before, but instead of writing we're gonna read file. Passing the argument "r" to the second parameter of open(rel_path, mode='r') is optional, but for your comfort you can set it.

# This code wont work
with open(rel_path+".mysave") as f:
    read = (f.read())
    print(type(read))
    cube.localPosition[0] = read["X"]
    cube.localPosition[1] = read["Y"]
    cube.localPosition[2] = read["Z"]

If you try running the game, you'll be encountering an error like below.

Error: Python.002 (Cube), Python script error Traceback (most recent call last):
File "C:\Users\[user name]\Downloads\[file name].blend\mycode.py", load
line 31, in
ValueError: dictionary update sequence element #0 has length 1; 2 is required

For some reason you can't convert string from a file to a dict, and to solve this problem we use ast.literal_eval() function. So in the code we do something like this.

with open(rel_path+".mysave") as f:
        read = ast.literal_eval(f.read())
        print(type(read))
        cube.localPosition[0] = read["X"]
        cube.localPosition[1] = read["Y"]
        cube.localPosition[2] = read["Z"]

And this must be working for you. So far we created a relative dir with logic.expandPath, created a .mysave file saved it and loaded it. Here is the full working code.

try:
    import bge
except:
    import Range as bge
    
import ast # Can't convert str to dict directly, so we need ast

cont = bge.logic.getCurrentController()
own = cont.owner
rel_path = bge.logic.expandPath("//") # Get relative path
cube = own.scene.objects["Cube"]

# Split the coordinates as X,Y,Z
x = cube.localPosition[0]
y = cube.localPosition[1]
z = cube.localPosition[2]

#Cube Position as dict
cube_pos={
"X":x,
"Y":y,
"Z":z
}

def save(cont):
    with open(rel_path+".mysave", "w") as f:
        f.write(str(cube_pos))
        f.close()
        print("File Saved.") # Just making sure its saved
        
def load(cont):
    with open(rel_path+".mysave") as f:
        read = ast.literal_eval(f.read())
        print(type(read)) # Just making sure its a dict

        # Assign the position
        cube.localPosition[0] = read["X"]
        cube.localPosition[1] = read["Y"]
        cube.localPosition[2] = read["Z"]
        print("File Loaded.") # Making sure its loaded

Download the project file.

os.path.expanduser("~")

coming soon...