How To Make A Platformer
Tile Engine Basics
By cmbeke
Hopefully this will be the first in a series of tutorials about making a platformer. If you have any questions, comments, or see I am doing something wrong or inefficiently please by all mean post your opinion. This tile engine was made by Osgeld and I take no credit for it in its state now. I know that some of you just want the code and don't really care about the explanation (I know I'm that way myself) so here you go:
color = { black = Color.new(0,0,0), grey = Color.new(76,76,76), lightGrey = Color.new(102,102,102), darkGrey = Color.new(51,51,51), white = Color.new(255,255,255), red = Color.new(255,0,0), orange = Color.new(255,140,0), yellow = Color.new(255,185,15), green = Color.new(0,255,0), blue = Color.new(0,0,255), purple = Color.new(104,34,139), pink = Color.new(255,28,174), brown = Color.new(92,51,23) }
setup = {
tileSize = 24
}
tiles = {
-- Brick
Image.createEmpty(setup.tileSize, setup.tileSize)
}
player = {
x = 4,
y = 2,
image = Image.createEmpty(setup.tileSize, setup.tileSize)
}
tiles[1]:clear(color.grey)
player.image:clear(color.red)
world = {
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1},
{1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1},
{1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1},
{1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1},
{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1},
{1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1},
{1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1},
{1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
}
function drawTiles()
for Y = 1, #world do
for X = 1, #world[Y] do
local x = (X - 1) * setup.tileSize
local y = (Y - 1) * setup.tileSize
for tileNum = 1,#tiles do
if world[Y][X] == tileNum then
screen:blit(x, y, tiles[tileNum])
end
end
end
end
end
function movePlayer()
if pad:up() and world[player.y - 1][player.x] == 0 then
player.y =player.y - 1
end
if pad:down() and world[player.y + 1][player.x] == 0 then
player.y =player.y + 1
end
if pad:left() and world[player.y][player.x - 1] == 0 then
player.x =player.x - 1
end
if pad:right() and world[player.y][player.x + 1] == 0 then
player.x =player.x + 1
end
screen:blit((player.x - 1)*setup.tileSize, (player.y - 1)*setup.tileSize, player.image)
end
while true do
pad = Controls.read()
System.draw()
screen:clear()
drawTiles()
movePlayer()
System.endDraw()
screen.flip()
screen.waitVblankStart(10)
end
If you want to know how this works just keep on reading, I will provide a full explanation below. The first thing you need is an up-to-date version of LuaPlayer Euphoria (LPE). Assuming you have that the next thing you will want is a nice editor, I recomend SciTE,
windows version and
linux version,it looks nice and highlights key words. Ok now that you have LPE and SciTE we can get on the coding part. Just to warn you I assume that you know the basics of Lua and tables (Dont worry, I'll remind you about them). I know some are you are confused, you thought this would teach you how to make a platform engine. Don't worrry it will, you just need to know the basics first.
color = { black = Color.new(0,0,0), grey = Color.new(76,76,76), lightGrey = Color.new(102,102,102), darkGrey = Color.new(51,51,51), white = Color.new(255,255,255), red = Color.new(255,0,0), orange = Color.new(255,140,0), yellow = Color.new(255,185,15), green = Color.new(0,255,0), blue = Color.new(0,0,255), purple = Color.new(104,34,139), pink = Color.new(255,28,174), brown = Color.new(92,51,23) }
I thought I would be nice and include some colors for you to choose from, I made sure all of them look nice on the psp. You can choose which ever color you want when we start to make the tiles, what i put is just an idea.
setup = {
tileSize = 24
}
This table isnt that important righ now, but it will be in the future. The only value you have to worry about is tileSize, I like 24, but you can change it to any number you like and the engine will work the same (note the tile map is use is made to be viewed with size 24 and below)
tiles = {
-- Brick
Image.createEmpty(setup.tileSize, setup.tileSize)
}
This is just a simple table to hold our tile pictures, currently the only one there is your simple brick. This table is also there to ensure a simple blitting process.
player = {
x = 4,
y = 2,
image = Image.createEmpty(setup.tileSize, setup.tileSize)
}
This table hold our important info about our player, the x is what column you are in and y is what row. Heres an image to show you what I mean:
As you can see the red block (your player) is four columns over and 2 rows down. The image is exactly what it says it is, the image of your player. Look at the width and height values, they are setup.tileSize from the setup table so what ever you set tileSize as it will be (I set setup.tileSize equal to 24 so the player image should be 24 by 24)
tiles[1]:clear(color.grey)
player.image:clear(color.red)
These two lines are very important, they color the brick and your player. "tiles[1]" refers to the first item in the table, so it would be your brick image that is being colored grey, (Remeber that all our colors are stored in the "color" table) feel free to change it to any color you like. The next line is a little different because I set our player image to a variable, insead of player[3] we have player.image, personally I like the look of this better.
world = {
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1},
{1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1},
{1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1},
{1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1},
{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
{1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1},
{1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1},
{1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1},
{1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
}
This is the map that we will be blitting to the screen, 1's stand for a brick and 0's for empty space. Do you notice that our player is not marked in this? That is because to easily move your player you stored their positions in the "player" table (player.x and player.y).
function drawTiles()
for Y = 1, #world do
for X = 1, #world[Y] do
local x = (X - 1) * setup.tileSize
local y = (Y - 1) * setup.tileSize
for tileNum = 1,#world[1] do
if world[Y][X] == tileNum then
screen:blit(x, y, tiles[tileNum])
end
end
end
end
end
This looks like alot, but dont worry I'll cut it down into managealbe chunks. This is the all important function that blits our map to the screen (The numbers in the table called "world").
function drawTiles()
I know your thinking I know what this is, I know that everything below this is part of the "drawTiles" function. I know that, I just didnt want to skip any code.

for Y = 1,#world do
for X = 1,#world[Y] do
Ok you probably have no idea what this means, I know I didnt. Basicly your setting up a variable. "for Y=1,#word do" This is basicly saying Y is equal to all numbers inbetween 1 and #world (Y is also equal to 1 and #world. I know your saying what the heck is #world? Its acually pretty easy, #world is saying count the
number of rows in the table "world". The next part is doing almost the same thing, "for X = 1,#world[Y] do". Again X is equal to 1 and #world[Y] and all numbers inbetween, now insead of #world we have #world[Y]. This peice of code is also easily explained, #world[Y] is basicly saying now that you have all the rows count all the columns.
local x = (X - 1) * setup.tileSize
local y = (Y - 1) * setup.tileSize
"local x = (X - 1) * setup.tileSize" this creates a local variable called x, x is equal to (X-1)*setup.tileSize. The reason you have to subract one from X is because the first brick in the table is at 1,1; when you blit things you want to start at 0,0 so you dont have a black boarder. The *setup.tileSize is for blitting again, the second brick you want to blit is at 1,2 in the table, so when we do the math you will blit that brick at 0,24 (24 or what ever your tileSize is).
for tileNum = 1,#tiles do
if world[Y][X] == tileNum then
screen:blit(x, y, tiles[tileNum])
end
end
end
end
end
This is the part of the code that actually blits the image. The "for tileNum = 1,#tiles do" part of the code creates variable tileNum which is equal to 1 and #tiles and all numbers in between (just like #world). The next part of the code "if world[Y][X] == tileNum then" is saying if the tile in x,y of the table world is equal to tileNum do this. The next part "screen:blit(x,y, tiles[tileNum])" basicly says blit on x,y (local vars from before) tiles[tileNum] (tiles[tileNum] could only equal 1 because there is only one peice of data in the "tiles" table, we could have just as easily done "screen:blit(x,y, tiles[1]) but tiles[tileNum] allows us to add more bricks later on.)
if pad:up() and world[player.y - 1][player.x] == 0 then
player.y = player.y - 1
end
if pad:down() and world[player.y + 1][player.x] == 0 then
player.y = player.y + 1
end
if pad:left() and world[player.y][player.x - 1] == 0 then
player.x = player.x - 1
end
if pad:right() and world[player.y][player.x + 1] == 0 then
player.x = player.x + 1
end
screen:blit((player.x - 1)*setup.tileSize, (player.y - 1)*setup.tileSize, player.image)
end
end
Almost done here, lets get to it "if pad:up() and world[player.y - 1][player.x] == 0 then" this checks if the block above your player is a 0 (it subtracts 1 from the players y posistion and checks to see if that block is a zero). The same thing happens for the down function but instead of checking out the block above it checks blow you. "if pad:right() and world[player.y][player.x + 1] == 0 then" this code checks one block to your right to see if it is a zero (it add 1 from the players x posistion and checks to see if that block is a zero), the same is true of moving left. To blit the player it uses the same prossess as the blitting of your tile map.
while true do
pad = Controls.read()
System.draw()
screen:clear()
drawTiles()
movePlayer()
System.endDraw()
screen.flip()
screen.waitVblankStart(10)
end
Finally we are to the while true loop, what runs your program. I am going to assume that you all know what this is. The last line of code just tells the psp to wait 10 frames before doing anything, you can remove it and see a speed up.
Now you have a simple tile engine, but i bet you dont like the way the player moves, nether do I. Around sometime next week the next tutorial wiil be out, we will cover smooth movement, gravity and jumping. If you have any questions or comments feel free to post away, I will try and help you as much as I can. Untill next week, enjoy.
cmbeke
[/font]