Skip to content

Arx 07 Loading Level From External File

noooway edited this page Jan 25, 2017 · 2 revisions

The next thing I want to show is how to load levels from external files and how to switch between the levels.

A common situation is to have a separate file for each level or each distinct location in your game. I'm also going to use such approach. The files with the level descriptions will be kept at the levels folder.

Levels in arkanoid are composed of different arrangements of bricks. Apart from position, bricks also can differ in type (say, have different color ).

Immediately, the question arises, which format to choose to store this information in a level-file. For a large game, most probably you are going to use a stand-alone tool for level design, for example, Tiled map editor. An output of such program is usually an *.xml or some other text file. To load it, it would be necessary either to write a parser or to use an external library. In our case, there is no need for such complications and I'm going to use Lua syntax to describe level files.

While writing a constructor for the BricksContainer class, we have identified the parameters necessary to specify an arrangement of the bricks. These parameters are the top left postion of the top left brick, the width and height of each brick and the horizontal and vertical distance between the bricks. Using these parameters, we produce the 2d array of bricks stored in the .bricks field of the BricksContainer object. We didn't have a type of the brick since we have assumed they were similar. But we obviously need to add the type now.

We could have set all the necessary parameters individually for each brick -- but that would be overkill. Instead, I'm going to place them as default values in the constructor. The only thing that is going to be stored in the level file is a 2d table, telling us, whether or not to create a brick on a certain position, and which type it should have.

It is possible to write each level-file as a separate Lua module. However, it is considered a good practice to avoid any code in level files, preferably making them data-only. Therefore, the only thing that remains from the Lua module structure is the return statement. Here is an example of 01.lua file:

return {
   name = "first",
   bricks = {
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {3, 3, 3, 3, 3, 3, 3, 3, 3, 3},
      {2, 2, 2, 2, 2, 2, 2, 2, 2, 2},
      {1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
   }
}

Brick are created only at nonzero positions, with values indicating the brick type.

We need to add bricktype as the field to the Brick class.

function Brick:new( o )
   .....
   o.height = o.height or 30
   o.bricktype = o.bricktype or 1
   .....
   return o
end

The level file has to be loaded somewhere. A natural place is love.load callback. After loading, we need to pass it to the bricks_container constructor

function love.load()
   level = require "levels/01"
   collider = HC.new()
   .....
   bricks_container = BricksContainer:new( { level = level,
   		      			     collider = collider } )
   .....
end

and record it in the appropriate field

function BricksContainer:new( o )
   .....
   o.bricks = o.bricks or {}
   o.level = o.level or nil
   o.collider = o.collider or {}
   .....
end

If the level-file was not specified, we use the default level-construction procedure. In the opposite case, we construct from the level file.

function BricksContainer:new( o )
   .....
   o.brick_height = o.brick_height or 30
   if not o.level then
	  o:default_level_construction_procedure()
   else
	  o:construct_from_level()
   end
   return o
end

The default procedure doesn't change much from the previous parts. Construction from the file should take into account the specified type of the brick. If it is nonzero, it is assigned to the newly created brick; if it is zero, brick is not created at all. Otherwise, BricksContainer:construct_from_level() is similar to BricksContainer:default_level_construction_procedure():

function BricksContainer:construct_from_level()
   for row = 1, self.rows do
      local new_row = {}
      for col = 1, self.columns do
         local bricktype = self.level.bricks[row][col]           --(*1)
         if bricktype ~= 0 then                                  --(*2)
            local new_brick_position = self.top_left_position +
               vector(
                  ( col - 1 ) *
                     ( self.brick_width + self.horizontal_distance ),
                  ( row - 1 ) *
                     ( self.brick_height + self.vertical_distance ) )
            local new_brick = Brick:new{
               width = self.brick_width,
               height = self.brick_height,
               position = new_brick_position,
               bricktype = bricktype,
               collider = self.collider
            }
            new_row[ col ] = new_brick
         end
      end
      self.bricks[ row ] = new_row
   end   
end

(*1): Reading bricktype from the saved level description.
(*2): Only bricks with nonzero types are created.

To add a bit of diversity, we can make Brick:draw() function display various brick types a bit differently. Let's say, that bricks of type 1 will be in red, 2 - green, and 3 - blue.

function Brick:draw()
   love.graphics.rectangle( 'line',
				self.position.x,
				self.position.y,
				self.width,
				self.height )
   local r, g, b, a = love.graphics.getColor( )
   if self.bricktype == 1 then
	  love.graphics.setColor( 255, 0, 0, 100 )
   elseif self.bricktype == 2 then
	  love.graphics.setColor( 0, 255, 0, 100 )
   elseif self.bricktype == 3 then
	  love.graphics.setColor( 0, 0, 255, 100 )
   else 
	  love.graphics.setColor( 255, 255, 255, 100 ) 
   end	 
   self.collider_shape:draw( 'fill' )
   love.graphics.setColor( r, g, b, a )
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