-
Notifications
You must be signed in to change notification settings - Fork 17
Spawning Bonuses
So far the game has been somewhat dull. To add some diversity to the gameplay, bonuses are introduced. They alter the game mechanics in minor ways and provide additional challenge for the player. Parts 3-04 -- 3-09 are dedicated to the bonuses. This part opens the sequence by addressing a question of how the bonuses appear in the game and their basic behavior.
A bonus is an object that emerges on destruction of a brick. After it appears, it falls down with constant velocity. If the player catches it with the platform, i.e. on bonus-platform collision, the effect of the bonus is applied.
There could be several bonuses on the screen simultaneously. For that reason, basic definitions for the bonuses are similar to the bricks.
Functions, responsible for behavior of a single bonus are shown below.
local bonuses = {}
.....
bonuses.radius = 14 --(*2)
bonuses.speed = vector( 0, 100 ) --(*3)
.....
function bonuses.new_bonus( position, bonustype ) --(*1)
return( { position = position,
bonustype = bonustype,
quad = bonuses.bonustype_to_quad( bonustype ) } )
end
function bonuses.draw_bonus( single_bonus ) --(*2)
if single_bonus.quad then
love.graphics.draw(
bonuses.image,
single_bonus.quad,
single_bonus.position.x - bonuses.tile_width / 2,
single_bonus.position.y - bonuses.tile_height / 2 )
end
local segments_in_circle = 16
love.graphics.circle( 'line',
single_bonus.position.x,
single_bonus.position.y,
bonuses.radius,
segments_in_circle )
end
function bonuses.update_bonus( single_bonus ) --(*3)
single_bonus.position = single_bonus.position + bonuses.speed * dt
end
.....
return bonuses
(*1): To create a single bonus, it necessary to provide to the constructor a position and a bonus type.
Similarly to the bricks, a quad is inferred from the provided bonustype.
(*2): A bonus is represented as a sphere with the radius defined by bonuses.radius
variable.
In the drawing function, apart from the quad, the circle is drawn
with the love.graphics.circle
for testing purposes.
(*3): A bonus falls down with a constant speed, defined by bonuses.speed
variable.
There can be several bonus objects in the game simultaneously, and the functions that manage them are following:
bonuses.current_level_bonuses = {} --(*1)
function bonuses.update( dt ) --(*2)
for _, bonus in pairs( bonuses.current_level_bonuses ) do
bonuses.update_bonus( bonus )
end
end
function bonuses.draw() --(*2)
for _, bonus in pairs( bonuses.current_level_bonuses ) do
bonuses.draw_bonus( bonus )
end
end
function bonuses.add_bonus( bonus ) --(*3)
table.insert( bonuses.current_level_bonuses, bonus )
end
function bonuses.generate_bonus( position, bonustype ) --(*3)
if bonuses.valid_bonustype( bonustype ) then
bonuses.add_bonus( bonuses.new_bonus( position, bonustype ) )
end
end
function bonuses.valid_bonustype( bonustype ) --(*3)
if bonustype and bonustype > 10 and bonustype < 19 then
return true
else
return false
end
end
function bonuses.clear_current_level_bonuses() --(*4)
for i in pairs( bonuses.current_level_bonuses ) do
bonuses.current_level_bonuses[i] = nil
end
end
(*1): The presently active bonuses are stored in the bonuses.current_level_bonuses
table.
(*2): draw
and update
methods iterate over the currently active bonuses and call
the corresponding functions for the each individual bonus.
(*3): To create a bonus at the given position with a given bonustype, first it is necessary to check
the validity of the bonustype. After that, the constructor for a single bonus can be called,
and the resulting object can be inserted into the bonuses.current_level_bonuses
table.
(*4): On transitions between the levels, it is necessary to clear the table with active bonuses.
A tileset definition for the bonuses is analogous to the bricks: quads in the tileset are indexed with a two-digit number where the first digit specifies the row, and the second - the column. Valid bonustypes are from 11 to 18.
bonuses.image = love.graphics.newImage( "img/800x600/bonuses.png" )
bonuses.tile_width = 64
bonuses.tile_height = 32
bonuses.tileset_width = 512
bonuses.tileset_height = 32
function bonuses.bonustype_to_quad( bonustype )
if bonustype == nil or bonustype <= 10 or bonustype >= 20 then
return nil
end
local row = math.floor( bonustype / 10 )
local col = bonustype % 10
local x_pos = bonuses.tile_width * ( col - 1 )
local y_pos = bonuses.tile_height * ( row - 1 )
return love.graphics.newQuad(
x_pos, y_pos,
bonuses.tile_width, bonuses.tile_height,
bonuses.tileset_width, bonuses.tileset_height )
end
After the basic definitions are ready, it is necessary to incorporate the bonuses into the game: insert the calls to the bonuses.update
and bonuses.draw
methods into the corresponding gamestates callbacks and so on. To avoid missing something, it helps to trace the occurrences of the bricks
table in the gamestate modules. Since the bricks and the bonuses are alike, bonuses occur mostly in the same places as the bricks. It turns out, that only the "game" state needs modifications.
.....
local bricks = require "bricks"
local bonuses = require "bonuses"
local walls = require "walls"
.....
function game.update( dt )
.....
bricks.update( dt )
bonuses.update( dt )
walls.update( dt )
.....
end
.....
Now it is necessary to address a question of how to actually add new bonuses into the game. Depending on a desired game mechanics, this could be done in a variety of different ways. I want the bonuses to appear only after a destruction of a brick. A simple way to do this is to associate each brick with a certain bonustype and call the bonus constructor when the brick is destroyed. The bricks are defined in the level files in a form of a table. It is natural to add a table with the bonustypes into each level file. It should have the same size as the bricks table and should hold bonustypes associated with each brick.
return {
bricks = {
{51, 51, 00, 00, 00, 00, 51, 51},
{51, 00, 00, 00, 00, 00, 00, 51},
{00, 00, 00, 00, 00, 00, 00, 00},
{00, 00, 00, 00, 00, 00, 00, 00},
{00, 00, 00, 00, 00, 00, 00, 00},
{21, 21, 22, 23, 24, 25, 26, 26},
{31, 31, 32, 33, 34, 35, 36, 36},
{41, 41, 42, 43, 44, 45, 46, 46},
{11, 11, 12, 13, 14, 15, 16, 16},
{00, 00, 00, 00, 00, 00, 00, 00},
{00, 00, 00, 00, 00, 00, 00, 00}
},
bonuses = {
{00, 00, 00, 00, 00, 00, 00, 00},
{00, 00, 00, 00, 00, 00, 00, 00},
{00, 00, 00, 00, 00, 00, 00, 00},
{00, 00, 00, 00, 00, 00, 00, 00},
{00, 00, 00, 00, 00, 00, 00, 00},
{17, 11, 12, 13, 14, 15, 16, 18},
{17, 11, 12, 13, 14, 15, 16, 18},
{17, 11, 12, 13, 14, 15, 16, 18},
{17, 11, 12, 13, 14, 15, 16, 18},
{00, 00, 00, 00, 00, 00, 00, 00},
{00, 00, 00, 00, 00, 00, 00, 00}
}
}
Since each brick is going to hold an associated bonustype, it is necessary to add
an appropriate argument to the bricks constructor bricks.new_brick( ....., bonustype )
.
The bricks for the current level are constructed in the bricks.construct_level
function.
Apart from parsing the brick types from the level file, now it is also necessary to extract the
bonustypes and pass them to the bricks constructor.
function bricks.construct_level( level )
.....
for row_index, row in ipairs( level.bricks ) do
for col_index, bricktype in ipairs( row ) do
if bricktype ~= 0 then
.....
local new_brick_position = vector( new_brick_position_x,
new_brick_position_y )
local bonustype = level.bonuses[ row_index ][ col_index ]
local new_brick = bricks.new_brick( new_brick_position,
bricks.brick_width,
bricks.brick_height,
bricktype,
bonustype )
table.insert( bricks.current_level_bricks, new_brick )
end
end
end
end
function bricks.new_brick( position, width, height, bricktype, bonustype )
return( { position = position,
width = width or bricks.brick_width,
height = height or bricks.brick_height,
bricktype = bricktype,
quad = bricks.bricktype_to_quad( bricktype ),
bonustype = bonustype } )
end
After the bonustypes are associated with the bricks, it is necessary to spawn the corresponding bonuses
on bricks destruction. Since the brick destruction is done in the bricks.brick_hit_by_ball
function, it needs an access to the bonuses
table. I provide it as an argument, which results in necessity to add it to several other collisions-related functions.
function game.update( dt )
.....
collisions.resolve_collisions( ball, platform, walls, bricks, bonuses )
.....
end
function collisions.resolve_collisions( ball, platform,
walls, bricks, bonuses )
.....
collisions.ball_bricks_collision( ball, bricks, bonuses )
.....
end
function collisions.ball_bricks_collision( ball, bricks, bonuses )
.....
if overlap then
ball.brick_rebound( shift_ball )
bricks.brick_hit_by_ball( i, brick, shift_ball, bonuses )
end
.....
end
If the brick is to be destroyed, bonuses.generate_bonus
is called.
It receives the bonus position and the bonustype, extracted from the brick properties.
This function checks the validity of the bonustype and if it is valid, adds the
corresponding bonus into the bonuses.current_level_bonuses
table. Non-valid bonustypes
are simply ignored.
function bricks.brick_hit_by_ball( i, brick, shift_ball, bonuses )
if bricks.is_simple( brick ) then
bonuses.generate_bonus(
vector( brick.position.x + brick.width / 2,
brick.position.y + brick.height / 2 ),
brick.bonustype )
table.remove( bricks.current_level_bricks, i )
simple_break_sound:play()
elseif
.....
elseif bricks.is_cracked( brick ) then
bonuses.generate_bonus(
vector( brick.position.x + brick.width / 2,
brick.position.y + brick.height / 2 ),
brick.bonustype )
table.remove( bricks.current_level_bricks, i )
armored_break_sound:play()
elseif bricks.is_heavyarmored( brick ) then
.....
end
end
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: