Skip to content

Spawning Bonuses

noooway edited this page Apr 8, 2017 · 36 revisions

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

    Home
    Acknowledgements
    Todo

Chapter 1: Prototype

  1. The Ball, The Brick, The Platform
  2. Game Objects as Lua Tables
  3. Bricks and Walls
  4. Detecting Collisions
  5. Resolving Collisions
  6. Levels

    Appendix A: Storing Levels as Strings
    Appendix B: Optimized Collision Detection (draft)

Chapter 2: General Code Structure

  1. Splitting Code into Several Files
  2. Loading Levels from Files
  3. Straightforward Gamestates
  4. Advanced Gamestates
  5. Basic Tiles
  6. Different Brick Types
  7. Basic Sound
  8. 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

  1. Improved Ball Rebounds
  2. Ball Launch From Platform (Two Objects Moving Together)
  3. Mouse Controls
  4. Spawning Bonuses
  5. Bonus Effects
  6. Glue Bonus
  7. Add New Ball Bonus
  8. Life and Next Level Bonuses
  9. Random Bonuses
  10. Menu Buttons
  11. Wall Tiles
  12. Side Panel
  13. Score
  14. Fonts
  15. More Sounds
  16. Final Screen
  17. Packaging

    Appendix D: GUI Layouts
    Appendix E: Love-release and Love.js

Beyond Programming:

  1. Game Design
  2. Minimal Marketing (draft)
  3. Finding a Team (draft)

Archive

Clone this wiki locally