deprecated Bricks and Walls - noooway/love2d_arkanoid_tutorial GitHub Wiki

In this part I'm going to add an array of the bricks on the screen and the walls on the borders.

First thing to notice, is that love.update and love.draw functions from the previous part can be split into several functions, corresponding to update and draw of independent game objects:

function love.update( dt )
   ball.update( dt )
   platform.update( dt )
   .....
end
 
function love.draw()
   ball.draw()
   platform.draw()
   .....
end

The ball.update and ball.draw are defined as

function ball.update( dt )
   ball.position_x = ball.position_x + ball.speed_x * dt
   ball.position_y = ball.position_y + ball.speed_y * dt   
end

function ball.draw()
   local segments_in_circle = 16
   love.graphics.circle( 'line',
                         ball.position_x,
                         ball.position_y,
                         ball.radius,
                         segments_in_circle )   
end

The functions for the platform are similar.

With bricks the situation is more complicated. First of all, there are going to be several of them, each with it's own characteristics. I replace the brick table from the previous part with a different one - bricks, where I'm going to store all the relevant information.

local bricks = {}
.....

The bricks themselves are going to be stored in the bricks.current_level_bricks table, which will be populated on level construction.

bricks.current_level_bricks = {}

Each single brick is represented as a table with position_x, position_y, width and height fields. I define a special function bricks.new_brick to construct such objects:

function bricks.new_brick( position_x, position_y, width, height )
   return( { position_x = position_x,
             position_y = position_y,
             width = width or bricks.brick_width,           --(*1)
             height = height or bricks.brick_height } )
end

(*1): If width is supplied as an argument to the bricks.new_brick call, than that value is used; otherwise the default one bricks.brick_width is used.

Each single brick can be drawn with the following function:

function bricks.draw_brick( single_brick )
   love.graphics.rectangle( 'line',
                            single_brick.position_x,
                            single_brick.position_y,
                            single_brick.width,
                            single_brick.height )   
end

As before, there is nothing to update in brick:

function bricks.update_brick( single_brick )   
end

In Arkanoid the bricks are arranged in a 2d-pattern. The simplest way to create such a pattern is to define several rows and columns with bricks.

bricks.rows = 8
bricks.columns = 11

Actual bricks construction happens during level creation. For each brick we have to provide at least it's top left corner position. To calculate it, we need to know a number of rows and number of columns, position of the top left corner of the top left brick, width and height of individual bricks, and, finally, horizontal and vertical distances between them. With such information, it is possible to populate bricks.current_level_bricks:

local bricks = {}
bricks.rows = 8                    --(*1a)
bricks.columns = 11
bricks.top_left_position_x = 70
bricks.top_left_position_y = 50
bricks.brick_width = 50
bricks.brick_height = 30
bricks.horizontal_distance = 10
bricks.vertical_distance = 15      --(*1b)
bricks.current_level_bricks = {}
.....
function bricks.construct_level()
   for row = 1, bricks.rows do
      for col = 1, bricks.columns do
         local new_brick_position_x = bricks.top_left_position_x +   --(*2)
            ( col - 1 ) *
            ( bricks.brick_width + bricks.horizontal_distance )
         local new_brick_position_y = bricks.top_left_position_y +   --(*2)
            ( row - 1 ) *
            ( bricks.brick_height + bricks.vertical_distance )     
         local new_brick = bricks.new_brick( new_brick_position_x,   --(*3)
                                             new_brick_position_y )
         table.insert( bricks.current_level_bricks, new_brick )      --(*4)
      end      
   end   
end

(*1a)-(*1b): definition of the properties necessary to compute top left corner position for each brick.
(*2): top left position is computed.
(*3): a new brick is created.
(*4): the new brick is inserted into the bricks table.

It is possible to update and draw each brick by iterating over bricks.current_level_bricks and calling the corresponding function for each single brick:

function bricks.draw()
   for _, brick in pairs( bricks.current_level_bricks ) do   --(*1)
      bricks.draw_brick( brick )
   end
end

function bricks.update( dt )
   for _, brick in pairs( bricks.current_level_bricks ) do
      bricks.update_brick( brick )
   end
end

(*1): an underscore _ is a valid Lua name, that is commonly used for dumb variables, that are not necessary in the further code.

In this part, I also want to add the walls on the borders of the screen. A wall is a rectangle, just as a brick is, so I use a walls table which is quite similar to bricks in it's structure.

There is a minor difference in walls.new_wall constructor: I do not provide a default width and height for the wall:

function walls.new_wall( position_x, position_y, width, height )
   return( { position_x = position_x,
             position_y = position_y,
             width = width,
             height = height } )
end

In walls.construct_walls, the left, right, top and bottom walls are constructed and than placed into the walls.current_level_walls table.

function walls.construct_walls()
   local left_wall = walls.new_wall(
      0,
      0,
      walls.wall_thickness,
      love.graphics.getHeight()
   )
   local right_wall = walls.new_wall(
      love.graphics.getWidth() - walls.wall_thickness,
      0,
      walls.wall_thickness,
      love.graphics.getHeight()
   )
   local top_wall = walls.new_wall(
      0,
      0,
      love.graphics.getWidth(),
      walls.wall_thickness
   )
   local bottom_wall = walls.new_wall(
      0,
      love.graphics.getHeight() - walls.wall_thickness,
      love.graphics.getWidth(),
      walls.wall_thickness
   ) 
   walls.current_level_walls["left"] = left_wall
   walls.current_level_walls["right"] = right_wall
   walls.current_level_walls["top"] = top_wall
   walls.current_level_walls["bottom"] = bottom_wall
end

Constructors for the walls and the bricks have to be placed in love.load, so the walls and the bricks are created on the start of the game:

function love.load()
   bricks.construct_level()
   walls.construct_walls()
end

Finally, the love.update and love.draw functions now look like:

function love.update( dt )
   ball.update( dt )
   platform.update( dt )
   bricks.update( dt )
   walls.update( dt )
end
 
function love.draw()
   ball.draw()
   platform.draw()
   bricks.draw()
   walls.draw()
end