6) Files, Paths, and Save Data
Goal: save and reload game state at a portable per-user path, then understand when to use text or typed binary values.
Step 1: Build a Platform-Correct Save Path
Dim savePath As String
savePath = PathCombine$(AppDataDir$(), "tutorial-save.txt")
DebugPrint("Save path: " + savePath)
AppDataDir$ chooses the user-local application data folder on macOS and Windows. PathCombine$ inserts the correct path separator, so avoid joining paths with a hard-coded slash.
Step 2: Write a Fresh Text Save
Dim fileRef As Integer
Dim score As Integer
Dim level As Integer
score = 1250
level = 4
If FileExists(savePath) Then
DeleteFile(savePath)
End If
fileRef = OpenFile(savePath, True)
If fileRef <> 0 Then
WriteLine(fileRef, Str(score))
WriteLine(fileRef, Str(level))
CloseFile(fileRef)
Else
DebugPrint("Could not create the save file")
End If
A writeable OpenFile creates a missing file but does not promise to remove old trailing content. Deleting the old tutorial save first gives this simple format a clean start.
Step 3: Read and Convert the Values
If FileExists(savePath) Then
fileRef = OpenFile(savePath)
If fileRef <> 0 Then
score = ToInt(ReadLine(fileRef))
level = ToInt(ReadLine(fileRef))
CloseFile(fileRef)
End If
End If
DebugPrint("Loaded score: " + Str(score))
DebugPrint("Loaded level: " + Str(level))
Text files are easy to inspect during development. Keep a documented line order and convert each line into the expected type.
Step 4: Read Variable-Length Text Safely
fileRef = OpenFile("assets/dialogue.txt")
If fileRef <> 0 Then
While Not EndOfFile(fileRef)
DebugPrint(ReadLine(fileRef))
Wend
CloseFile(fileRef)
End If
EndOfFile prevents reading beyond the available lines. Close the file on every branch after a successful open.
Step 5: Use Typed Binary Values When Appropriate
Dim binaryPath As String
binaryPath = PathCombine$(AppDataDir$(), "tutorial-save.dat")
If FileExists(binaryPath) Then
DeleteFile(binaryPath)
End If
fileRef = OpenFile(binaryPath, True)
If fileRef <> 0 Then
WriteDword(fileRef, score)
WriteDword(fileRef, level)
DebugPrint("Bytes written: " + Str(FileTell(fileRef)))
FileSeek(fileRef, 0)
score = ReadDword(fileRef)
level = ReadDword(fileRef)
CloseFile(fileRef)
End If
Typed functions preserve numeric representation and avoid string conversion. Use FileTell, FileSeek, and FileSize when a format needs explicit byte positions.
Checkpoint: Save, Reset, and Load
Dim savePath As String
Dim fileRef As Integer
Dim playerName As String
Dim score As Integer
Dim level As Integer
savePath = PathCombine$(AppDataDir$(), "tutorial-save.txt")
playerName = "Luna"
score = 1250
level = 4
' Save a fresh three-line file.
If FileExists(savePath) Then
DeleteFile(savePath)
End If
fileRef = OpenFile(savePath, True)
If fileRef <> 0 Then
WriteLine(fileRef, playerName)
WriteLine(fileRef, Str(score))
WriteLine(fileRef, Str(level))
CloseFile(fileRef)
End If
' Reset memory so the reload is visible.
playerName = ""
score = 0
level = 0
' Load the values in the same order.
If FileExists(savePath) Then
fileRef = OpenFile(savePath)
If fileRef <> 0 Then
playerName = ReadLine(fileRef)
score = ToInt(ReadLine(fileRef))
level = ToInt(ReadLine(fileRef))
CloseFile(fileRef)
End If
End If
DebugPrint("Loaded " + playerName + _
", score " + Str(score) + _
", level " + Str(level))
The checkpoint should reload Luna, 1250, and 4. Open the text file shown by the earlier diagnostic to inspect the format.
Robust Save-Data Rules
- Use a user data directory for saves and settings; use
AppDir$for read-only packaged assets. - Check every file handle before using it.
- Close each successful open exactly once.
- Write a format version first when save layouts may evolve.
- Validate loaded ranges before applying them to the game.
Try It Yourself
Add a Boolean fullscreen setting with ToBoolean, write a version number on the first line, and refuse to load an unexpected version.
Functions Introduced
AppDataDir$, PathCombine$, OpenFile, ReadLine, WriteLine, EndOfFile, and FileSeek.
Navigation: Previous | Course outline | Next: Particle Effects