You created a resource to have different controls for two players and customize the characters’ looks.
It’s now time to put the characters and the menu together in the obstacle course.
In this lesson, we will:
We will add a menu and code a game pause in the next lesson.
To get started, open the
ObstacleCourse_Part3/ObstacleCourse.tscn
scene.
We updated its node structure to offer a split-screen view that appears after the intro animation.
Let’s quickly go over the changes in the scene structure. Then, we’ll focus our attention on character customization and the save system.
In this version of the obstacle course, we mainly added a parent
and to the level (the Course node). This creates a new view of the game world.Create two viewports like this, put them in an
, and you have two views side-by-side.We also removed the old Godot scene instance and added two PlayerCharacter instances as children of the Course node.
We gave each character a
child node to move cameras in the split-screen views.If you try to run the scene now, you’ll get an error because the
characters lack a PlayerSettings
resource.
We will solve this by adding our save game. Let’s get started.
We have two players in the game so we need to save both players’ settings.
We will create a new resource to represent our save file and nest both players’ settings in there. Godot will take care of saving the nested resources for us.
Right-click on the ObstacleCourse_Part3/
folder and
create a new script. Name the file SaveGame.gd
.
We start by extending Resource
, defining a class name,
and exporting two properties to save each player’s settings
resource.
# Resource used to save and load the players' inputs and character skins.
class_name SaveGame
extends Resource
# We use the type Resource because we cannot export our own resource types in
# Godot 3.
export var player_1: Resource
export var player_2: Resource
For saving and loading to work, all we have to do is to put our
PlayerSettings
resources in there and use
ResourceSaver.save()
.
It gets even better: we can share PlayerSettings
resource between the level, the menu, and the save resource.
When we change the resource in one place, it will also affect the
SaveGame
resource. That’s because all references to a given
resource point to the same place in the computer’s memory.
To run the game without errors, we need to provide the characters
with PlayerSettings
resources.
But as we want support for saving and loading, we can’t just load our
player_1_settings.tres
and
player_2_settings.tres
files.
Those two files are part of our Godot project. Once we export the game to play on other computers, we will not be able to modify them directly.
Instead, we have to:
SaveGame
resource from code.PlayerSettings
resources.SaveGame
resource in the user://
directory.Still in the SaveGame.gd
script, we code two new
functions to write the resource to the disk and load it.
We write the functions mainly to keep the save file path hidden in
the SaveGame
script.
# The file path of our save file. For player data, you must use the user://
# prefix, this will save the files to a special directory depending on the
# player's device.
const SAVE_GAME_PATH := "user://savegame.tres"
func write_savegame() -> void:
save(SAVE_GAME_PATH, self)
ResourceSaver.
static func load_savegame() -> Resource:
if ResourceLoader.exists(SAVE_GAME_PATH):
return load(SAVE_GAME_PATH)
return null
It’s very important to save those files using the
user://
prefix instead of res://
.
Godot takes care of putting files in the right places depending on the platform you export your games to.
Instead of having to worry about platform-specific file systems and device permissions, you only need to know about two prefixes:
res://
to load your project’s resources.user://
to save and load user files, like the player’s
options, a save game, or a mod.We are now ready to create the save game. We will do that in the obstacle course script.
Why? Because it’s the root node of our game scene. It will be able to pass the settings resources to the characters both in the level and in the customization menu.
The easiest way to make our save game work is to try to load it at the start of the game. If there is no save file yet, we create a new one.
Then, because the SaveGame
contains
PlayerSettings
resources, we can pass them to the player
characters.
Open the ObstacleCourse.gd
script where we start with a
variable and a function to create a SaveGame
resource.
# The underscore before the variable name is a convention meaning that it should
# not be used outside of this script.
var _savegame: SaveGame = null
# Creates a save file with the default configuration
func _create_savegame() -> void:
_savegame = SaveGame.new()
_savegame.player_1 = preload("player_1_settings.tres").duplicate()
_savegame.player_2 = preload("player_2_settings.tres").duplicate()
# We can instantly save the file to ensure we're always working with a
# player-specific file from here on out.
_savegame.write_savegame()
We need to duplicate the resources in the
_create_savegame()
function because otherwise, we will
directly modify player_1_settings.tres
and
player_2_settings.tres
.
We only use these resources to provide default player settings, we don’t want to overwrite them when changing player settings during development.
In the _ready()
function, we try to load an existing
save game. If after that we still don’t have a SaveGame
resource, we create a new one.
func _ready() -> void:
# ...
_savegame = SaveGame.load_savegame()
if not _savegame:
_create_savegame()
The _create_savegame()
function instantly writes the
save file to the disk. To see it working, run the obstacle course scene
by pressing F6, then press F8 to close it, and go
to Project -> Open Data Folder.
There, you should see a file named savegame.tres
. The
.tres
extension stands for text resource. You can open the
file in a text editor to see how Godot saves your data.
We now need to pass the settings resources from the save game to each of the characters to clear the error and restore two-player controls.
In the script, we’ve prepared an array that gets our two PlayerCharacter instances.
onready var obstacle_course := $ViewportsRow/ViewportContainer/Viewport/Course
onready var players := [obstacle_course.get_node("PlayerCharacter"), obstacle_course.get_node("PlayerCharacter2")]
We can access the first player by writing players[0]
and
the second player by writing players[1]
. We use that to
give them the settings resources in the _ready()
function.
func _ready() -> void:
# ...
0].settings = _savegame.player_1
players[1].settings = _savegame.player_2 players[
If you run the game now, the characters will use the texture and your settings resources again. You will also be able to control each individually.
All that’s left is to add our menu an update the saved game. We will do that in the next lesson.
Open the practice Saving the inventory.
In this practice, you will write code to save and load an inventory
from the Inventory
resource.