Making Games Replayable
Introduction: Beyond the Single Run
Currently, our game has a major limitation: once you collide with an obstacle, the program simply closes. In game development, we call this a "one-and-done" loop. To make our game feel professional, we need to handle failure gracefully.
In this lesson, we will implement a Finite State Machine. This sounds complex, but it's just a fancy way of saying our game can be in different modes: a start screen, active gameplay, and a "Game Over" screen. Our goal is to create a seamless loop where a player can lose, see their score, and jump right back into the action.
Understanding Phase State vs. Session Data
It's important to distinguish between where the player is in the game menu (State) and what is happening in the game (Session Data).
game_state: This is our compass. It tells the game loop whether to move characters or just draw text on a menu.- Session Data: These are variables like
playerposition,score, andobstacle_speed.
When a player hits "Restart," we don't just change the game_state string to "playing". If we did that without cleaning up our data, the player would immediately collide with the same obstacle again! We must explicitly reset the session data to give the player a fresh start.
Transforming the Collision Response
Previously, we used running = False to kill the program on collision. Now, we want the heartbeat of the game to keep thumping, but we want the action to freeze.
We achieve this by swapping the state:
By changing the state to "game_over", the code inside our elif game_state == "playing": block will stop running on the next frame. The obstacles will stop moving, the player will freeze, and we can now draw a dedicated Game Over UI.
Designing the Game Over Screen
A good Game Over screen does two things: it provides feedback (the final score) and clear instructions (how to try again).
Inside our main loop, we add a new branch:
Using get_rect(center=(...)) is a life-saver here. It automatically calculates the width of your text and centers it perfectly on the screen regardless of how many digits the score has.
The Power of a Reset Helper
When the player presses Enter to restart, we need to perform several tasks: move the player back to the starting line, clear the old obstacles, spawn new ones, and set the score back to zero. Doing this directly inside our event loop would make it messy.
Instead, we create a reset_game() function:
Pro-tip: Notice that we modify player and obstacles directly. Because they are objects (a Rect and a list), Python passes them by reference, meaning changes inside the function affect the original variables!
Wiring it All Together
In our event handling section, we now differentiate what "Enter" means based on our current state:
Conclusion
By adding a Game Over state and a reset function, you've moved from a simple script to a real "game loop." Your game can now be played indefinitely, which is the foundation of every addictive arcade title. Next up, we'll add some atmosphere with background music!
