Week 6: Checkpoint System


This week I implemented a checkpoint setting to Triggers which, upon a player activating the trigger, would update a variable tracking a resettable position. 

Previously, the reset function was controlled by a Clock method in the PlayerController:


It tracks the Player's last position on solid ground every second by checking if there is a collider under the player.  This works well enough but can be unpredictable where the player will respawn upon death depending on how fast the player moves and how much time they spend in midair versus moving on the ground.  


This issue also caused a lot of frustration during playtesting especially in more difficult platforming sections; the became much more punishing than before. 

My solution to this is to create pre-placed checkpoints that trigger once when the player moves through them. Even if the player falls into a previous challenge, or if they move quickly and / or in midair, the checkpoint will remain as the active reset point until the player reaches a new one.




To start, I added new variables into the Trigger script to track the new checkpoint settings:

The above code adds a list to track all of the triggers that have been set as checkpoints. 

 


Below there is a flag to control whether the trigger will serve as a checkpoint, a list of triggers within a specific area that should be reset by the checkpoint upon a player reset (falling below the blast zone which will be referred to as "a death") , and a list of objects within an area that should be reset by the checkpoint. 


Since I plan on having levels broken into rooms and segments, this method of having a checkpoint reset specifically determined objects and triggers would be more efficient than reloading the entire scene (resetting everything).  By the way, resetting a trigger in this case means resetting the use count to 0 and re-enabling the trigger's collider if it has been deactivated.




Next, I register the checkpoint in the trigger's Awake method by checking the checkpoint variable and checking if the triggerCheckpoints list does not already contain the current trigger instance:


The second check sets a hard limit on a checkpoint's max uses and limits the use to once, in case these variables had been modified in the editor prior to running.  This is so that the checkpoint can only trigger once, and the player won't re-activate a previously reached checkpoint.





After this, I added this conditional inside a larger block of code that executes when the player enters the trigger's collision:


It calls the SetCheckpoint method from the GameManager, while passing in the index value of the current trigger instance within the triggerCheckpoint list.

The GameManager (aptly named) is typically used to manage large-scale events that happen in-game, and it is normally given access to several other scripts.  This particular GameManager is initialized as a SecureSingleton, which is a custom variant on the normal Singleton, which allows only one instance of itself to be created and accessed at a time. Because of this, the GameManager can also be accessed freely by other scripts, which is how this method is called.



Here's the relevant code for the checkpoint feature inside of the GameManager script:

This index will iterate through the triggerCheckpoints list (housed inside of the Trigger script) to determine which checkpoint is active, and through that which objects and triggers should reset upon player death.  This variable is set to -1 by default because lists use zero indexing (meaning the index of the first object will be 0, the second will be 1, and so on).  

I wanted the reset function to still work (at least for the player's position) without needing a trigger at spawn.  The playerController's lastPosition variable (which is the variable that tracks the position the player will reset to upon death) is set on Awake (the first frame of runtime) so none of the checkpoints will be active before it is triggered by the player, and so the reset would still work.




The SetCheckpoint method in the GameManager calls the PlayerController script's setCheckpoint method and sets the checkpointIndex to the passed in value, which will be the value of the trigger instance that calls it.


The reason I call several methods of the same name in various scripts is to avoid accessing variables from other scripts too often, as it is easier to read and generally better to let each script handle its own variables, and use methods to access / change them.

Here's the SetCheckpoint method in the PlayerController script:



Separating the methods like I did made them each simple and easy to read.



So I've covered how I set and update the active checkpoint, but how does the player reset work?   Here's the method from the GameManager:


This method, like the clock method in the PlayerController shown at the start of this post, runs every second.  Whenever the player falls below a global position of -50 (this value is hard-coded for now but could be changed) the ResetPosition method is called from the PlayerController, and if the checkpointIndex is greater than or equal to 0 (meaning it is a valid index in the list) the Game Manager calls the checkpoint trigger of that index to reset the dynamic objects set to it (which is a list set in the editor).


First, here's the ResetPosition method in PlayerController:


It resets the player's velocity and sets the player's position to .25 units above the lastPosition (reset position), essentially respawning the player.


Next, here's the ResetDynamicObjects method that is called, located within the Trigger script:


This method iterates through both the list of objects that should be reset and the triggers to reset.  For the triggers, it simply resets the use counter to 0 and re-enables disabled triggers. 



For the objects, the ResetTransform method from the TransformObject script is called. Above this method are variables called awakePosition and awakeRot that store the original position and rotation respectively of the object from the first frame. 


I can't use the variables origPostion and origRot that already exist (meaning "original position" and "original rotation")  because my Move and Rotate methods work as an offset, where these original position variables are updated every time that method is called.  Therefore, I made separate variables to store the transformations from when the object is initially active.


Here's the ResetTransform method now:


It sets the object to be active (because if an object falls into the death zone [being -50] it is deactivated, being effectively removed from the scene.)  Next, the transformations are reset to the values stored in the object's Awake method.  Note: I don't need an awakeScale variable because the scale method is not an offset.

Finally, the velocities of the object are reset as well.



With this, the positions and settings of objects and triggers are stored and reset upon player death.  In the future I plan to add color settings and such into this feature, but since I've been primarily working with the transformations of objects so far, I made this as an initial version.


I set up a test in my prototype scene. Before I show a demo, here's what the scene looks like from the editor:


Red triggers are checkpoints, while green triggers affect objects.  Note the settings of the triggers, and how they can be used once more after they are reset.

Here's the demo of this new checkpoint feature. Now, the player's respawn point changes and the objects and triggers within the area are also reset (except for the first rotating platform, which I forgot to add to the reset list oops!) :


I've been learning a lot lately, and I'm excited to keep going. Until next week!

Leave a comment

Log in with itch.io to leave a comment.