-
Notifications
You must be signed in to change notification settings - Fork 17
Arx 10 More Gamestates: Gamepaused and Gamefinished
Apart from the "menu" and the "game" gamestates it is also common to have "gamepaused" and "gamefinished" states. The first one is activated, when, say, an "Esc" key is pressed, and the second one displays a congratulations screen at the end of the game. This chapter shows how to implement them. Besides, I also want introduce certain modifications to the already defined "game" and "menu" states.
The "gamefinished" state is mostly similar to the current "menu" except for
some changes in the love.graphics.print
text:
.....
local gamefinished = gamefinished or {}
.....
state_name = "gamefinished"
function gamefinished:enter()
end
function gamefinished:update( dt )
end
function gamefinished:draw()
love.graphics.print(
"Congratulations, you have finished the game.\n" ..
"Press Enter to restart or Esc to quit", --(*1)
10, 10)
end
.....
(*1): I'll comment on how to implement reaction on "Esc" or "Enter" keys later.
Transition to the "gamefinished" stated happens from the "game" state, when there are no more levels.
In previous parts, love.event.quit()
was called in such cases. Now we need to change it to the transition to "gamefinished":
function game:enter()
.....
if level_counter > #level_sequence.sequence then
Gamestate.switch( gamefinished )
end
.....
end
function switch_to_next_level()
.....
if level_counter > #level_sequence.sequence then
Gamestate.switch( gamefinished )
else
.....
end
For the transition to the "gamepaused" state, we need to define keyreleased
callback in the "game" state.
After resuming from the pause, we want to restore the state of the game objects -- bricks in the bricks_container
, the position and the speed of the ball
, and the platform
.
If we simply use Gamestate.switch( gamepaused )
call, the game objects will be deleted on leaving the "game" state and then reconstructed on entering, effectively restarting the level.
The solution is to pass all the objects, which we want to save, to the "gamepaused" state, and then pass them back to "game" state.
The Gamestate.switch
is defined in such a way, that it accepts an arbitrary number of arguments.
We can pack all the necessary objects into a table, and pass it as an argument.
function game:keyreleased( key, code ) --(*1)
if key == 'escape' then --(*2)
Gamestate.switch( gamepaused, --(*3)
{ level_sequence = level_sequence, --(*4)
level_counter = level_counter,
level = level,
collider = collider,
ball = ball,
platform = platform,
walls_container = walls_container,
bricks_container = bricks_container } )
end
end
(*1): In the "game" gamestate, we define keyreleased
callback.
(*2): When "Esc" is pressed( released, actually ), we pause the game.
(*3): The first argument to the Gamestate.switch is the gamepaused
state.
(*4): The second argument is a table packed with gameobjects, which state we don't want to loose
on transition to another gamestate.
In the pause regime, the game objects are in the frozen state, that is, they are not updated.
However, it is possible to display them in the gamepaused:draw
callback.
Apart from that, the gamepaused
module is similar to the menu
.
.....
function gamepaused:enter( previous_state, ... )
game_objects = ... --(*1)
end
function gamepaused:update( dt ) --(*2)
end
function gamepaused:draw()
for _,obj in pairs( game_objects ) do
if type(obj) == "table" and obj.draw then
obj:draw() --(*3)
end
end
self:cast_shadow() --(*4)
love.graphics.print(
"Game is paused. Press Enter to continue or Esc to quit",
10, 10)
end
function gamepaused:keyreleased( key, code )
if key == 'return' then
Gamestate.switch( game, game_objects ) --(*5)
elseif key == 'escape' then
love.event.quit()
end
end
function gamepaused:leave()
game_objects = nil
end
function gamepaused:cast_shadow() --(*4)
local r, g, b, a = love.graphics.getColor( )
love.graphics.setColor( 10, 10, 10, 100 )
love.graphics.rectangle("fill",
0,
0,
love.window.getWidth(),
love.window.getHeight() )
love.graphics.setColor( r, g, b, a )
end
return gamepaused
(*1) The received game objects are stored in the game_objects
table.
In Lua, ...
is called a vararg expression. In the argument list of a function,
it indicates, that the function accepts arbitrary number of arguments. In the body of
the function, ...
acts like a multivalued function, returning all the collected arguments.
(*2) We do not need to update those objects.
(*3) However, we need to draw them. We draw each object of a table type that has a draw
method.
(*4) There is also some darkening effect.
(*5) Finally, we need to return the received objects back to the 'game' state.
On leaving the "gamepaused" state and entering the "game" state, it is necessary to restore the game objects.
This requires changes in the game:enter
function:
function game:enter( previous_state, ... )
args = ...
level_sequence = args.level_sequence or require "levels/sequence"
level_counter = args.level_counter or 1
if level_counter > #level_sequence.sequence then
Gamestate.switch( gamefinished )
end
level_filename = "levels/" .. level_sequence.sequence[ level_counter ]
level = args.level or require( level_filename )
collider = args.collider or HC.new()
ball = args.ball or Ball:new( { collider = collider } )
platform = args.platform or Platform:new( { collider = collider } )
bricks_container = args.bricks_container or
BricksContainer:new( { level = level,
collider = collider } )
walls_container = args.walls_container or
WallsContainer:new( { collider = collider } )
end
Gamestates are convenient for managing level switches.
In the switch_to_next_level
function we need to insert the Gamestate.switch( game, ..... )
call with the appropriate arguments.
function switch_to_next_level()
if bricks_container.no_more_bricks then
level_counter = level_counter + 1
ball:destroy()
platform:destroy()
bricks_container:destroy()
walls_container:destroy()
if level_counter > #level_sequence.sequence then
Gamestate.switch( gamefinished )
else
Gamestate.switch( game, { level_sequence = level_sequence,
level_counter = level_counter } )
end
end
end
To start the game from the first level, it is necessary to update
the Gamestate.switch( game )
in the "menu" and "gamefinished",
manually setting level_counter
argument to 1.
function menu:keyreleased( key, code )
if key == 'return' then
Gamestate.switch( game, { level_counter = 1 } )
elseif key == 'escape' then
love.event.quit()
end
end
function gamefinished:keyreleased( key, code )
if key == 'return' then
Gamestate.switch( game, { level_counter = 1 } )
elseif key == 'escape' then
love.event.quit()
end
end
Finally, we need to require the new gamestates in the main.lua
.
A non-obvious thing is that we have to declare and preferably load tables for all gamestates in advance, since they can circularly depend on one another.
local Gamestate = require "gamestate"
menu = {}
game = {}
gamepaused = {}
gamefinished = {}
local menu = require "menu"
local game = require "game"
local gamepaused = require "gamepaused"
local gamefinished = require "gamefinished"
function love.load()
Gamestate.registerEvents()
Gamestate.switch( menu )
end
.....
This is the reason for local gamepaused = gamepaused or require "gamepaused"
line in the game.lua
.
We either use preloaded 'gamepaused' module or if it has not been loaded for some reason we load it manually.
local Gamestate = require "gamestate"
local gamepaused = gamepaused or require "gamepaused"
local gamefinished = gamefinished or require "gamefinished"
local Platform = require "Platform"
.....
Feedback is crucial to improve the tutorial!
Let me know if you have any questions, critique, suggestions or just any other ideas.
Chapter 1: Prototype
- The Ball, The Brick, The Platform
- Game Objects as Lua Tables
- Bricks and Walls
- Detecting Collisions
- Resolving Collisions
- Levels
Appendix A: Storing Levels as Strings
Appendix B: Optimized Collision Detection (draft)
Chapter 2: General Code Structure
- Splitting Code into Several Files
- Loading Levels from Files
- Straightforward Gamestates
- Advanced Gamestates
- Basic Tiles
- Different Brick Types
- Basic Sound
- Game Over
Appendix C: Stricter Modules (draft)
Appendix D-1: Intro to Classes (draft)
Appendix D-2: Chapter 2 Using Classes.
Chapter 3 (deprecated): Details
- Improved Ball Rebounds
- Ball Launch From Platform (Two Objects Moving Together)
- Mouse Controls
- Spawning Bonuses
- Bonus Effects
- Glue Bonus
- Add New Ball Bonus
- Life and Next Level Bonuses
- Random Bonuses
- Menu Buttons
- Wall Tiles
- Side Panel
- Score
- Fonts
- More Sounds
- Final Screen
- Packaging
Appendix D: GUI Layouts
Appendix E: Love-release and Love.js
Beyond Programming: