Skip to content

Spawning Bonuses

noooway edited this page Mar 29, 2017 · 36 revisions

So far the game has been somewhat dull. To add some variety and diversity, bonuses are introduced. They provide additional challenge and create minor modifications to the gameplay. Parts 3-04 -- 3-09 are dedicated to the bonuses. This part opens the sequence by addressing a question of how bonuses appear in the game and their basic behavior.

A bonus is an object that is spawned on destruction of a brick. After it is created, it falls down with constant velocity. If we catch it with the platform, i.e. on bonus-platform collision, the effect of the bonus is applied.

There could be several bonuses on a screen simultaneously. For that reason, the basic definitions for the bonuses are similar to the bricks.

Functions for a single bonus.

local bonuses = {}

bonuses.radius = 14
bonuses.speed = vector( 0, 100 )

function bonuses.new_bonus( position, bonustype )
   return( { position = position,
             bonustype = bonustype,
             quad = bonuses.bonustype_to_quad( bonustype ) } )
end

function bonuses.update_bonus( single_bonus )
   single_bonus.position = single_bonus.position + bonuses.speed * dt
end

function bonuses.draw_bonus( single_bonus )
   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

.....
return bonuses

Functions that apply to bonus container

bonuses.current_level_bonuses = {}

function bonuses.update( dt )
   for _, bonus in pairs( bonuses.current_level_bonuses ) do
      bonuses.update_bonus( bonus )
   end
end

function bonuses.draw()
   for _, bonus in pairs( bonuses.current_level_bonuses ) do
      bonuses.draw_bonus( bonus )
   end
end

function bonuses.add_bonus( bonus )
   table.insert( bonuses.current_level_bonuses, bonus )
end

function bonuses.clear_current_level_bonuses()
   for i in pairs( bonuses.current_level_bonuses ) do
      bonuses.current_level_bonuses[i] = nil
   end
end

And a tileset definition

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

When this is done, it is necessary to incorporate the bonuses in the "game" state: insert the calls to the bonuses.update and bonuses.draw methods into corresponding gamestate callbacks and so on. To avoid missing something, it helps to trace the occurrences of the bricks table in the "game" functions. Since the bricks and the bonuses are alike, bonuses occur mostly in the same places as the bricks.

.....
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

.....
  1. Spawning bonuses. Now it is necessary to address a question of how to actually add new bonuses into the game. So, it means that each brick should be associated with a certain bonustype. The bricks are defined in level files in a form of a table. It is natural to add into the level file a table with bonustypes. It should have the same size as the bricks table and should hold bonustypes for 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 the bonustype is going to be stored inside the brick, it is necessary to add an appropriate argument to bricks constructor bricks.new_brick( ....., bonustype ). The bricks for the level are constructed in a bricks.construct_level function. Now apart from parsing brick types, it is also necessary to extract bonustypes and pass it to bricks constructor.

function bricks.construct_level( level )
   .....
   for row_index, row in pairs( level.bricks ) do
      for col_index, bricktype in pairs( 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

Now, when the bonustypes are associated with bricks, it is necessary to spawn the corresponding bonuses on brick destruction. First, it is necessary to pass bonuses table into a function that resolves brick-ball collision. Since the brick destruction is done in the bricks.brick_hit_by_ball function, and bonuses are created only on brick destruction, it is necessary to pass bonuses table into that function as an argument.

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

4.2) New bonus is added on brick destruction.

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

function bonuses.generate_bonus( position, bonustype )
   if bonuses.valid_bonustype( bonustype ) then
      bonuses.add_bonus( bonuses.new_bonus( position, bonustype ) )
   end
end

function bonuses.valid_bonustype( bonustype )
   if bonustype and bonustype > 10 and bonustype < 19 then
      return true
   else
      return false
   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