Skip to content

Arx 04 Container Classes

noooway edited this page Jan 25, 2017 · 2 revisions

Now, when the preparations are finished, the next step is to draw several bricks on the screen. We'll need an array of some kind to store them. In fact, it turns out to be more convenient to create a specialized class for this task — BricksContainer.

BricksContainer is going to serve two basic purposes. First, it's going to manage all low level details of looping over different bricks. Second, it is responsible for, so to say, collective properties of these bricks. For example, when there are no more bricks left, we would need to change to the next level. Individual bricks do not need to know anything about other bricks. Therefore, responsibility to track remaining bricks is laid upon the BricksContainer class.

The key component of this class is an array, containing all the bricks. Here is an according constructor definition:

function BricksContainer:new( o )
   o = o or {}
   setmetatable(o, self)
   self.__index = self
   o.name = o.name or "bricks_container"
   o.bricks = o.bricks or {}
   .....
end

In arkanoid, bricks are laid on a level in a 2d pattern. We can make o.bricks array plain 1d rather than 2d. However, the debugging and testing considerations suggest, that it is better to mimic the 2d structure and to make this array 2d for easier access to individual bricks. 2d array can be represented as a table of tables, with each inner table holding a single row of bricks.

Such structure allows to define update and draw functions:

function BricksContainer:update( dt )
   for _, brick_row in pairs( self.bricks ) do --(*1)
      for _, brick in pairs( brick_row ) do
         brick:update( dt )
      end
   end   
end

function BricksContainer:draw()
   for _, brick_row in pairs( self.bricks ) do
      for _, brick in pairs( brick_row ) do
         brick:draw()
      end
   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.

We also have to populate the array with bricks. This is also done in constructor. For each brick we have to provide at least it's top left corner position. To calculate this quantity for each brick, BricksContainer needs to know a number of rows and number of columns, position of the top left corner of the first brick, width and height of individual bricks, and, finally, horizontal and vertical distances between them. With such information, it is possible to populate o.bricks:

function BricksContainer:new( o )
   .....
   o.bricks = o.bricks or {}
   o.rows = 10 --(*1a)
   o.columns = 10   
   o.top_left_position = vector( 100, 50 )
   o.horizontal_distance = o.horizontal_distance or 10
   o.vertical_distance = o.vertical_distance or 10
   o.brick_width = o.brick_width or 50
   o.brick_height = o.brick_height or 30 --(*1b)
   for row = 1, o.rows do
      local new_row = {}
      for col = 1, o.columns do	 
          local new_brick_position = o.top_left_position +                    --(*2)
             vector( ( col - 1 ) * ( o.brick_width + o.horizontal_distance ),
                     ( row - 1 ) * ( o.brick_height + o.vertical_distance ) )  
          local new_brick = Brick:new{    --(*3)
             width = o.brick_width,
             height = o.brick_height,
             position = new_brick_position
          }
          new_row[ col ] = new_brick --(*4)
      end
      o.bricks[ row ] = new_row --(*5)
   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 table representing a row
(*5): the table holding a row of bricks is inserted into the bricks table.

After the BricksContainer class is ready, it is necessary to require it in the main.lua We are not going to use individual Brick class from the main.lua any longer (unless for testing and debugging), so it is possible to delete corresponding require lines.

local Platform = require "Platform"
local Ball = require "Ball"
local BricksContainer = require "BricksContainer"
.....

In love.load(), love.update() and love.draw() we also have to change individual brick to bricks_container:

function love.load()
   ball = Ball:new()
   platform = Platform:new()
   bricks_container = BricksContainer:new()
   .....
end
 
function love.update( dt )
   ball:update( dt )
   platform:update( dt )
   bricks_container:update( dt )
   .....
end
 
function love.draw()
   ball:draw()
   platform:draw()
   bricks_container:draw()
   .....
end

While we are discussing container classes, it is also a good time to add walls to the borders of the screen. A wall is a rectangle, just as a brick is, so the Wall class is similar to the Brick class. The only necessary changes are renaming and modification of the default values. By default, the wall will be 20 pixel wide, located vertically on the left side of the screen:

function Wall:new( o )
   o = o or {}
   setmetatable(o, self)
   self.__index = self
   o.name = o.name or "wall"
   o.position = o.position or vector( 0, 0 )
   o.width = o.width or 20
   o.height = o.height or love.window.getHeight()
   return o
end

Since we need 4 walls -- one on each side of the screen, -- it's useful to define WallsContainer class. It is mostly similar to BricksContainer, except for the constructor. In BricksContainer it was necessary to define a 2d array of the bricks; in this case there are only four walls and there is no need to make o.walls a 2d array.

function WallsContainer:new( o )
   .....
   o.walls = o.walls or {}
   o.wall_thickness = o.wall_thickness or 20
   local left_wall = Wall:new{
      position = vector( 0, 0 ),
      width = o.wall_thickness,
      height = love.window.getHeight()      
   }
   local right_wall = Wall:new{
      position = vector( love.window.getWidth() - o.wall_thickness, 0 ),
      width = o.wall_thickness,
      height = love.window.getHeight()            
   }
   local top_wall = Wall:new{
      position = vector( 0, 0 ),
      width = love.window.getWidth(),
      height = o.wall_thickness
   }
   local bottom_wall = Wall:new{
      position = vector( 0, love.window.getHeight() - o.wall_thickness ),
      width = love.window.getWidth(),
      height = o.wall_thickness
   }   
   o.walls.left = left_wall
   o.walls.right = right_wall
   o.walls.top = top_wall
   o.walls.bottom = bottom_wall
   .....
end

The walls overlap a bit, but currently that is not a problem.

Iteration over the walls in update and draw is also a bit simpler for 1d array:

function WallsContainer:update( dt )
   for _, wall in pairs( self.walls ) do
       wall:update( dt ) 
   end   
end

function WallsContainer:draw()
   for _, wall in pairs( self.walls ) do
       wall:draw()
   end   
end

Of course, it is also necessary to require WallsContainer from the main.lua, make an object of this type in love.load(), and then repeatedly call it's update and draw callbacks. This is all similar to the bricks_container case.

    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