Arx 07 Loading Level From External File - noooway/love2d_arkanoid_tutorial GitHub Wiki
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