In this tutorial I want to show how easy it is to create a small 2D sideview platformer with the use of Löve, Tiled Map Editor, HardonCollider.
You can download the Löve project for this tutorial here: DownloadThe Tools
A lot of work is done by the librarys and tools we use here. I’ll explain what each component is supposed to do briefly but if you want to go into more details you should check out the projects individually.
- Tiled, for level creation
- AdvancedTiledLoader, to load and display the maps in Löve
- HardonCollider for collision detection
First of all, a platformer consists of a main character whose movement through a world or level is oftentimes controlled through keyboard input. The character can move in horizontal direction as long as he stands on solid ground and you can apply a force that let’s him temporarily float in the air(= jumping).
The Level: In tradition of well known platformer – like super mario – we will use tiles to create our maps(if you are unfamiliar with tilemaps you could for example check THIS out). A good and free tool to edit and create tilemaps is Tiled. In order to load the maps in Löve that were build in Tiled we have to use a library. You can find a few but my tests have shown that the AdvancedTiledMapLoader from Kadoba is the most sophisticated out there.
For the sake of this tutorial I will provide a tilesheet with a tilewidth and tileheight of 16. Feel free to use, copy, modify it as you please.
The second picture is a small 32×32 map. Loading a map and displaying it in Löve is really easy. Be sure to require the AdvancedTiledMapLoader. Set the loader path and load the map in Love.load() with loader.load(), bind the map to a variable and draw via map:draw().
--
-- Tutorial-Platform
--
local loader = require "AdvTiledLoader/Loader"
-- set the path to the Tiled map files
loader.path = "maps/"
function love.load()
-- load the level and bind to variable map
map = loader.load("level.tmx")
end
function love.update(dt)
-- nothing to do (yet!)
end
function love.draw()
-- draw the level
map:draw()
end
If we execute the above code we see that the level is nicely drawn. I will write a love.graphics.scale(2,2) to zoom into the scene a bit.
The collision: We are lazy and do not want to do the collision detection by ourself so we use HardonCollider for this. Note that we still have to do the collision response. We need the collider elsewhere so we also need a file local variable collider. But first require the library to a variable(because the HardonCollider module it returns a value) and set up love.load as the following:
--
-- Platformer Tutorial
--
local loader = require "AdvTiledLoader/Loader"
-- set the path to the Tiled map files
loader.path = "maps/"
local HC = require "HardonCollider"
local hero
local collider
local allSolidTiles
function love.load()
-- load the level and bind to variable map
map = loader.load("level.tmx")
-- load HardonCollider, set callback to on_collide and size of 100
collider = HC(100, on_collide)
-- find all the tiles that we can collide with
allSolidTiles = findSolidTiles(map)
-- set up the hero object, set him to position 32, 32
setupHero(32,32)
end
The tiles from our level don’t just magically appear as HardonCollider shapes so we have to add them to the collision engine. We do this in the findSolidTiles() function. We just loop through all tiles in the level and check if they are solid. This solid property must be set via the Tiled Map Editor. Just Rightclick in the tilesets view on the desired tile click Tile Properties and add the name Solid. You don’t even need a value. Back in our loop we check all tiles and if they are solid we create a rectangular shape. The function looks like this:
function findSolidTiles(map)
local collidable_tiles = {}
local layer = map.tl["ground"]
for tileX=1,map.width do
for tileY=1,map.height do
local tile
if layer.tileData[tileY] then
tile = map.tiles[layer.tileData[tileY][tileX]]
end
if tile and tile.properties.solid then
local ctile = collider:addRectangle((tileX-1)*16,(tileY-1)*16,16,16)
ctile.type = "tile"
collider:addToGroup("tiles", ctile)
collider:setPassive(ctile)
table.insert(collidable_tiles, ctile)
end
end
end
return collidable_tiles
end
Tileshapes get placed in a group and are set to passive so they do not collide with each other and eat up all your cpu. Next thing we set up in the love.load() was the hero. This is relativly easy because we just create a HC shape and attach alle the other custom attributes and methods to it. I also declared the the hero local to the main.lua file right above love.load. The initializing of the hero looks like this:
function setupHero(x,y)
hero = collider:addRectangle(x,y,16,16)
hero.speed = 50
end
The speed of the hero is needed in the input handling method. This also is standard stuff and should look like this:
function handleInput(dt)
if love.keyboard.isDown("left") then
hero:move(-hero.speed*dt, 0)
end
if love.keyboard.isDown("right") then
hero:move(hero.speed*dt, 0)
end
if love.keyboard.isDown("up") then
end
end
As you might have guessed from the delta time argument this function is called from love.update. Because HardonCollider already provides move and moveTo functions for the shapes we don’t even have to this by manually. We also want the hero to fall down so we write up a small update function for the hero:
function updateHero(dt)
-- apply a downward force to the hero (=gravity)
hero:move(0,dt*50)
end
Now we update the code for love.update() and love.load() to the current status:
function love.update(dt)
-- do all the input and movement
handleInput(dt)
updateHero(dt)
-- update the collision detection
collider:update(dt)
end
function love.draw()
-- scale everything 2x
love.graphics.scale(2,2)
-- draw the level
map:draw()
-- draw the hero as a rectangle
hero:draw("fill")
end
At last we need to handle the actual collision response. For now we just use the minimum translation vector to push away the hero from the colliding tile.
function on_collide(dt, shape_a, shape_b, mtv_x, mtv_y)
collideHeroWithTile(dt, shape_a, shape_b, mtv_x, mtv_y)
end
function collideHeroWithTile(dt, shape_a, shape_b, mtv_x, mtv_y)
-- sort out which one our hero shape is
local hero_shape, tileshape
if shape_a == hero and shape_b.type == "tile" then
hero_shape = shape_a
elseif shape_b == hero and shape_a.type == "tile" then
hero_shape = shape_b
else
-- none of the two shapes is a tile, return to upper function
return
end
-- why not in one function call? because we will need to differentiate between the axis later
hero_shape:move(mtv_x, 0)
hero_shape:move(0, mtv_y)
end
Ok, now this was quite a a lot of code. Time for a recap:
We load and draw the level via the AdvTiledLoader, setup HardonCollider, find the solid tiles and add them as shapes to hardoncollider. We introduced a playerobject with movement and simple gravity and handled the collision with solid tiles.
There is still a lot to do. Next steps would be include the crucial part of a jump and run game: the jumping and proper movement.


Hey i got all your code but in the findSolidTiles() function you make a call to a nil value, map.tl
local layer = map.tl["ground"]
It gives me this error: http://i.imgur.com/2521i.png
Any clue whats wrong with it? I looked through some of the code included in AdvTileLoader and i could only find references to the thing but i could never find its function. I did delete a few files i thought were unnecessary so if i was being stupid and deleted something i wasnt suppose to than please tell me?
thanks for pointing that out in love.load it should be:
allSolidTiles = findSolidTiles(map)
fixed that in post
I ran into the same problem and the fix you provided didn’t help. What did though was going unto the Tiled map file and changing the name of the Tile layer to “ground”. Now my play just falls through the ground so I think I forgot to type something or I just missed something out.
@JaydenB
In Tiled the layer has to be called “ground”! Or change the following to your tiled layer name:
I need part two, rawr. :3
Haha I need part 2 too
but I solved it. I found out the layer name thing myself, but then I re-read through the tut, and figured out I needed to change the Tile Properties…
Thanks for this, it’s really helping me learn Love2D and Lua. The only issue i’m trying to figure out based on this tutorial is finding the x and y position of the hero at all times i.e. Im try to attach the basic camera.lua class to these values. I’ve attempted to set up hero.x and hero.y but I just can’t get it to work. Any help for a beginner like me would be much appreciated
I recommend using hump.camera. The documentation is here: http://vrld.github.com/hump/#Camera
and you can get it on github here: https://github.com/vrld/hump
Awesome tutorial man. I’m having trouble though, I typed the code up but I keep getting an error http://bit.ly/KT3Ali
I can’t figure it out.
@Delibrete:
Are you sure you have named the layer “ground” in tiled?
@headchant
Damn it, I knew it was something as simple as that. Thank you very much. I look forward to part 2!
This really helped me get started, thanks!
I do have one issue though. It’s quite hard to explain, but it’s easily recreated by running the Love project that you provided and simply holding down the left arrow key before the hero hits the ground. The hero will get stuck on the wall because the wall is made up of multiple tiles. I’m sure you can much better understand what’s going on than I can describe it.
Any insight towards a solution?
This is an exciting tutorial series . Looking forward for the other parts!
I agree with Stephen up there, what’s the easiest way to find the y and x of the hero?
Do you plan on continuing this tutorial?
Because my country can not access the download page, please send a copy of this tutorial source code to my e-mail, thank you
My e-mail selfcom@163.com
Hey, i am currently authoring a book on Love2d video game programming. i found your tutorials easy and straight to the point, got some ideas from your tutorials and i would like to give you credits for it in my book. Hope you don’t mind?
sure go ahead.
just give credit