-
Notifications
You must be signed in to change notification settings - Fork 17
Arx 07 Loading Level From External File
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
Feedback is crucial to improve the tutorial!
Let me know if you have any questions, critique, suggestions or just any other ideas.
Chapter 1: Prototype
- The Ball, The Brick, The Platform
- Game Objects as Lua Tables
- Bricks and Walls
- Detecting Collisions
- Resolving Collisions
- Levels
Appendix A: Storing Levels as Strings
Appendix B: Optimized Collision Detection (draft)
Chapter 2: General Code Structure
- Splitting Code into Several Files
- Loading Levels from Files
- Straightforward Gamestates
- Advanced Gamestates
- Basic Tiles
- Different Brick Types
- Basic Sound
- 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
- Improved Ball Rebounds
- Ball Launch From Platform (Two Objects Moving Together)
- Mouse Controls
- Spawning Bonuses
- Bonus Effects
- Glue Bonus
- Add New Ball Bonus
- Life and Next Level Bonuses
- Random Bonuses
- Menu Buttons
- Wall Tiles
- Side Panel
- Score
- Fonts
- More Sounds
- Final Screen
- Packaging
Appendix D: GUI Layouts
Appendix E: Love-release and Love.js
Beyond Programming: