Feature: Checkpoints - #145
Merged
Merged
Conversation
Since we don't have a general LevelManager script, I am adding it for now in DefeatConditionManager
Needed to the map<UID, GameObject*> in the manager
# Conflicts: # ScriptsProject/Damageable.h # ScriptsProject/EnemyDamageable.cpp # ScriptsProject/EnemyDamageable.h # ScriptsProject/ScriptsProject.vcxproj # ScriptsProject/ScriptsProject.vcxproj.filters
# Conflicts: # ScriptsProject/ScriptsProject.vcxproj.filters
javiercasman
marked this pull request as ready for review
July 31, 2026 18:12
# Conflicts: # Engine/Assets/NavMeshes/Level1.navmesh # Engine/Assets/NavMeshes/Level1.navmesh.metadata # Engine/Assets/Scenes/Level1.scene # Engine/Assets/Scenes/Level1.scene.metadata # ScriptsProject/EnemyDamageable.cpp
JorxPi
approved these changes
Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
We all know what a checkpoint is. However, there are several ways to define them: how to activate them, what the level will be like upon respawning (whether enemies will be alive or dead, whether pickups will be collected or not, etc.).
This implies the need to somehow save the game state when activating a checkpoint for a possible future respawn. Below, I will explain in detail how a checkpoint works in this game, and what the flow is from when characters die until they respawn at a checkpoint.
1. How a Checkpoint Works
First, players must activate a checkpoint (currently, it's a
TriggerArea, activated automatically upon entering. Depending on design decisions, this could change in the future, for example, by adding an object to activate).When a checkpoint is reached, a "state" is saved, defined by the following elements:
This means that if players are defeated and choose to respawn, they will respawn at the last checkpoint reached, taking into account all the elements saved in the "state": defeated enemies will remain defeated, puzzles will remain solved, etc.
2. Implementation Details
From here on, I will explain how this system is implemented: its structure, how states are stored and persisted, etc.
2.1. PersistingCheckpointState
PersistingCheckpointStateis, as its name indicates, a class that persists throughout the game. This means that it is NOT a script (replicated behavior ofPersistingPowerupState). Therefore, it is not contained in a GameObject, but rather is a class (specifically, a Singleton) that persists during gameplay. Consequently, it is not affected, on its own, by changes between levels or scenes. This is key and essential if we want to maintain saved information between respawns.PersistingCheckpointStateacts as a class that stores and updates the elements that will be restored upon respawning, and other classes will access this information to know what to do when players respawn. For example,CharacterDamageablewill access this Singleton when, at respawn time, it needs to know how much health Lyriel (or Death) should have when reappearing at the checkpoint.To do this, the information is stored in different data types.
std::vector<UID>for dead enemies, broken breakables, and triggered events.floatsfor current HP and reaper gauge amount.array<bool>for solved puzzles and another for unlocked power-ups.Respawning is, ultimately, restarting the scene from scratch. Therefore, certain scripts will look in
PersistingCheckpointStatefor the information they need to know what to do in theirStart()function. You can guess which scripts will do this:BreakableDamageable,EnemyDamageable,PuzzleManager, etc.For
std::vector<UID>, a simple search will suffice to find the UID of the GameObject it's contained in, and if it's included, its GameObject will disappear from the scene (I won't go into specific details about which scripts follow this pattern).You can see that there are two different vectors for each type: the normal vector and the "persistent" vector.
The first acts as a kind of "cache": it stores the defeated enemies (or whatever corresponds to the vector) until the next checkpoint is reached.
The second serves to "confirm" those dead enemies (or whatever corresponds) once the checkpoint is reached, and this information is saved persistently until the level ends.
Upon reaching a checkpoint, the first checkpoint will save its elements to the second, and the first will reset, continuing to accumulate new UIDs. The purpose of this is to avoid storing duplicate UIDs.
2.1.1. m_triggeredEvents
This is where events that have already been triggered and should not be triggered again will be stored.
For an event to be stored here upon being triggered, two conditions must be met:
TriggerOnlyOnce == trueisPersistent == trueisPersistentis a newboolto differentiate betweenEventsthat haveTriggerOnlyOnce = trueand should be stored in this array, and those that don't. For example,MusicStateEventisTriggerOnlyOnce, but we don't want to store it in this array, as otherwise the music won't be able to be reactivated upon respawning.Other events like
CameraTransitionEvent,CombatAreaEvent, orPopUpEventwill haveisPersistent = true.2.2. CheckpointEvent
CheckpointEventis a script that inherits fromGameplayEventAction, and it's the script responsible for activating the checkpoint so that players respawn at it.When the event is triggered with
ExecuteEvent(), it will save Lyriel's and Death's current lives, the amount of Reaper Gauge, and any unlocked power-ups. It will also callPersistingCheckpointState::SetCheckpoint(checkpointId),which will set the ID of the newly activated checkpoint, as well as update the persistent vectors and reset the others.IMPORTANT
CheckpointEventis intended to be a base class to simplify things. I'll go into more detail about this when I explain the checkpoint prefab.2.3. PersistingManager
PersistingManageris a Manager script whose function is to manage persistent information when loading a scene.Each time a scene loads,
PersistingManagerwill reset thePersistingCheckpointStatetemporal vectors to refresh the stored UIDs. It will also update any activated power-ups (PersistingPowerupState::setUnlockedPowerupState()).To ensure the proper functionality of these persistent classes, a new script function has been added to the engine:
OnGameStop(), an event that will be called when the game stops (when the stop button is pressed). This function ensures thatPersistingCheckpointStateandPersistingPowerupStateare reset when the game ends/the stop button is pressed.2.4. Checkpoint Prefab
To simplify the implementation of checkpoints in the scene, I've created a prefab for quickly adding a checkpoint. The only changes needed are to modify the added Event script (by default, it's
Checkpoint1_Level1_Event, just create a new one with the correct naming) and change theCheckpointIddefined inStart(). Since it's anenum, it will need to be added to theCheckpointIdenum ofPersistingCheckpointState, if it doesn't already exist.In addition, there's also a
GameplayEventTriggerthat will define the checkpoint's activation area (a particle or something similar will need to be added in the future to indicate the area to the player; currently, it's invisible).Finally, there are two GameObjects:
LyrielRespawnandDeathRespawn. These GameObjects are used to define the respawn positions of the characters at that checkpoint; simply change the Transform to the desired position of each one.Pending changes & observations
Here I'll mention some pending changes and observations that should be considered for the correct use of checkpoints.
PuzzleManagerLVL1, since the classes don't exist and therefore I haven't been able to apply any changes.TODO
Resolve conflicts: add again new elements on Level 1 scene