How to create a Platformer

@d.mach That seems to be the prevailing advice. Thank you for contributing to that!

I have been asking around the forum in other places and some people have pointed me to the following two non-Corona tutorials on creating platformers. I haven’t been able to sink my teeth into them yet but I wanted to share them with the rest of y’all so you have them. The Youtube link is to a complete series on creating a platformer using Java. The developer who provided the link says he used it as the basis for his Corona SDK collision engine.

http://www.wildbunny.co.uk/blog/2011/12/20/how-to-make-a-2d-platform-game-part-3-ladders-and-ai/

https://www.youtube.com/playlist?list=PL-2t7SM0vDfcIedoMIghzzgQqZq45jYGv

@rakoonic Correct me if I am wrong, but don’t you need Corona physics bodies in order to use Corona raycasting? Does that mean you do use the bodies but just don’t use them for collision detection?

@jerejigga - I do my own raycasting. Basically if you have a single pixel-perfect collision detection routine, you just run it in a line (ray!) until it either reaches the end of its distance, or at the first (solid) collision it detects. It isn’t massively fast to do this, which is why you limit using it a lot for the player character (which requires 3 or more for decent effects).

Enemies just use a couple - left / right and down. You could probably even limit this to one, by having them alternate their rays between frames, although naturally you lose accuracy. Actually you’d probably always want a down ray, otherwise it’d look funny going over slopes, but you could cycle the horizontal rays between all enemies, as left and right values don’t change really (although you’d need to make sure you check far enough ahead for the missed frames so it can’t go through walls, and also with any biggish vertical height change you’d have to do a fresh horizontal check).

So say you have 20 enemies, and you check horizontal and vertical each frame - that’s 40 rays per frame just for the enemies. Might be a lot, dunno :slight_smile:

But now say you have 20 enemies, they always check down, but each enemy only checks horizontally every 5 frames, cycled accordingly (so only 4 enemies check each game frame) - now you’ve dropped from 40 rays to 24 - almost a 50% drop.

Mostly though, I’d just get a game up and running, and then work out what (if anything) needs to be optimised.

Regarding collision detection - if you are just going to use squares, IE a tile is either fully solid or not, then the collision detection becomes extremely quick and trivial to write - you can make a loooot of assumptions and shortcuts.

I’d probably start with that, as at its most basic it differs somewhat from collision detection using slopes:

  • Tiles are solid / nonsolid completely. Apart from the assumptions, the collision code works with rectangles / squares (IE the player is a rectangle, colliding against square tiles).

  • With slopes, it makes more sense that you move to a simple axis-aligned raycasting model. This method often does have certain anomolies for collision detection, and certain cases you’ll want to avoid, unless you want to end up with super bloaty/slow collision detection.

However, nearly all of these odd situations are easily avoidable by level design, and in fact most of these odd situations won’t actually crop up under normal design situations, as they represent extremely bizarre set ups (one common example is a flat floor, and a ceiling that slopes down gradually to the ground - you’d likely never want *collision* data set up like this, although visuals is entirely seperate of course, because once you get to the part where the ceiling is closer to the ground than the height of the player, you wouldn’t be able to enter it anyway. Essentially you should never be able to crush yourself to death simply by walking along the ground into a narrowing corridor).

Hi there!

I’m going to be brief: everything you need to know when programming your own tile based platformer (without Box 2D) is in the attached pdf!

Happy reading! :slight_smile:

Hi @jerejigga:

In a bit I’ll be releasing Dusk 1.0, and there will be some more complex non-Box2D physics samples (Link in a platformer, anyone?) in it. My physics engine was a commission from a Corona developer that I was paid for, so I don’t really feel comfortable sharing it with everyone at this time. When the Dusk samples come, though, there will be plenty of physics behaviours to hack and examine.

Also, @thomas6’s guide looks good!

  • Caleb

Hi!

Always willing to share my platform physics code. Been wanting to do a tutorial for a loooooooooooooong time about that.

@CalebP

I totally understand!! As a side note, I think it’s awesome that you are doing paid work already! My first paid development work was in the area of $1 per game. I imagine you are getting a lot more than that! :slight_smile: You do great work and totally deserve it.

Regarding Dusk 1.0, I am very much looking forward to it! Like I’ve said already, most of my focus so far has been on MTE. Presently though, I am soaking up general information on Tiled based games including the amazing info in this forum topic. Once Dusk 1.0 comes out, I plan to jump right in! I am very much looking forward to the revised API, improved documentation, and the “physics” samples you just referred to.

@thomas6

Thank you for sharing the PDF!! Obviously it’s based on Flash, but DANG! This PDF contains the best documentation on creating a tiled-based game that I have ever seen! I just read the first few pages so far and scanned the rest but it looks quite thorough. I am primarily a Java developer and have done lots of JavaScript. Flash’s ActionScript looks very similar so I should have no problem adapting this to Corona/Lua. Awesome stuff!

Hi Caleb (and others),

Yes, that PDF is pretty awesome, I know :wink:

@thomas6 You mentioned sharing your platforming physics code. Is that something you could do right now or is that in regard to writing a tutorial and, presumably, doing some clean-up/refactoring first? If it’s something you can share now, I for one would certainly love to have access to it, tutorial or not. I love looking at other people’s code to see how they do things. I am sure that many other people in this community would really enjoy and appreciate seeing such code too.

Hold on. I just posted my code, but decided to delete it because it was way too chaotic (and the code formatting was all over the place). Let me do a quick clean up first. Be right back.

Okay. Here’s a big chunk. Not sure if it’ll be of any help without explanation, but if you’d like to check it out, go ahead and feel free to ask questions:

hero.frameLoop = function(event) local touching = touching -- compute potential horizontal motion for next frame --print(editor.state) if hero.dead == false then if touching == "left" then hero.xVel = hero.xVel - hero.xAccel elseif touching == "right" then hero.xVel = hero.xVel + hero.xAccel elseif touching == false then hero.xVel = hero.xVel \* hero.friction end end if hero.xVel \> hero.xMaxVel then hero.xVel = hero.xMaxVel elseif hero.xVel \< -hero.xMaxVel then hero.xVel = -hero.xMaxVel end hero.xPosNext = hero.xPos + hero.xVel if hero.platform ~= false then -- add platform xOffset to hero.xPosNext hero.xPosNext = hero.xPosNext + platformList[hero.platform].xOffset end -- compute potential vertical motion for next frame if hero.jumpState == "falling" then hero.yVel = hero.yVel + hero.yAccel jumpTouch = false elseif hero.jumpState == "grounded" then if jumpTouch == true then jumpTouch = false hero.jump() end end if hero.yVel \> hero.yMaxVel then hero.yVel = hero.yMaxVel elseif hero.yVel \< -hero.yMaxVel then hero.yVel = -hero.yMaxVel end -- set yPosNext if hero.platform == false then -- move hero with velocity hero.yPosNext = hero.yPos + hero.yVel else -- move along with platform hero.yPosNext = platformList[hero.platform].image.y end -- reevaluate next position for hero based on collisions! local levelMap = levelMap -- first correct the vertical movement, based on current X and next Y position !!! local bottomRow = math.floor((hero.yPosNext)/94) -- first row = row 0 which is good for row multiplier 0\*mapwidth for Arraypos's local topRow = math.floor((hero.yPosNext-252)/94) -- first row = row 0 which is good for row multiplier 0\*mapwidth for Arraypos's local leftColumn = math.floor((hero.xPos-62)/126) -- first column = column 0 so add 1 for Arraypos's local rightColumn = math.floor((hero.xPos+62)/126) -- first column = column 0 so add 1 for Arraypos's if (hero.jumpState == "falling") or (hero.platform ~= false) then if hero.yVel \> 0 then -- moving down -- check if bottom points are in a platform if hero.platform == false then for i = 1, #platformList do local vDistance = hero.yPosNext - platformList[i].image.y local hDistance = math.abs(hero.xPosNext - platformList[i].image.x) if (vDistance \> 0) and (vDistance \< 64) then -- 64 for platform height if platformList[i].type == 3 then if (hDistance \< 236) then hero.jumpState = "grounded" hero.platform = i hero.yPosNext = platformList[i].image.y if platformList[i].dropform == true then platformList[i]:startTimer() end end elseif platformList[i].type == 2 then if (hDistance \< 156) then hero.jumpState = "grounded" hero.platform = i hero.yPosNext = platformList[i].image.y if platformList[i].dropform == true then platformList[i]:startTimer() end end end end -- if end -- for platformList end -- repeat check for bottom points in a dropform!!! local botLeftTile = levelMap.physics[leftColumn+1+bottomRow\*levelMap.width] local botRightTile = levelMap.physics[rightColumn+1+bottomRow\*levelMap.width] if (botLeftTile ~= 1) or (botRightTile ~= 1) then -- one or more bottom corners will be on a solid tile audio.play(landSound) hero.yVel = 0 hero.yPos = (math.floor(hero.yPosNext/94)\*94)-1 hero.jumpState = "grounded" hero.platform = false else hero.yPos = hero.yPosNext end elseif hero.yVel \< 0 then -- moving up local topLeftTile = levelMap.physics[leftColumn+1+topRow\*levelMap.width] local topRightTile = levelMap.physics[rightColumn+1+topRow\*levelMap.width] if (topLeftTile == 2) or (topRightTile == 2) then -- one or more top corners will be on a solid tile hero.yVel = 0 -- WRONG!!! hero.yPos = (math.floor(hero.yPosNext/94)\*94)+64 else hero.yPos = hero.yPosNext end elseif hero.yVel == 0 then -- no vertical movement hero.yPos = hero.yPosNext end -- if (hero.jumpState == "falling" or platform is true) else -- if hero == "grounded" end -- then correct the horizontal movement, based on corrected Y and next X position !!! bottomRow = math.floor((hero.yPos)/94) -- first row = row 0 which is good for row multiplier 0\*mapwidth for Arraypos's local midRow = math.floor((hero.yPos-126)/94) -- first row = row 0 which is good for row multiplier 0\*mapwidth for Arraypos's topRow = math.floor((hero.yPos-252)/94) -- first row = row 0 which is good for row multiplier 0\*mapwidth for Arraypos's leftColumn = math.floor((hero.xPosNext-62)/126) -- first column = column 0 so add 1 for Arraypos's rightColumn = math.floor((hero.xPosNext+62)/126) -- first column = column 0 so add 1 for Arraypos's local xSpeedWithPlatform = hero.xVel if hero.platform ~= false then xSpeedWithPlatform = hero.xVel + platformList[hero.platform].xOffset end if xSpeedWithPlatform \> 0 then -- moving right local topRightTile = levelMap.physics[rightColumn+1+topRow\*levelMap.width] local midRightTile = levelMap.physics[rightColumn+1+midRow\*levelMap.width] local botRightTile = levelMap.physics[rightColumn+1+bottomRow\*levelMap.width] if (topRightTile == 2) or (midRightTile == 2) or (botRightTile == 2) then -- one or more right corners will be on a solid tile -- check wether on platform and only bottom point colliding if (hero.platform ~= false) and (topRightTile ~= 2) and (midRightTile ~= 2) then -- hitting only bottom point to get off platform! hero.xPos = hero.xPosNext else hero.xVel = 0 hero.xPos = (math.floor(hero.xPosNext/126)\*126)+63 end else hero.xPos = hero.xPosNext end elseif xSpeedWithPlatform \< 0 then -- moving left local topLeftTile = levelMap.physics[leftColumn+1+topRow\*levelMap.width] local midLeftTile = levelMap.physics[leftColumn+1+midRow\*levelMap.width] local botLeftTile = levelMap.physics[leftColumn+1+bottomRow\*levelMap.width] if (topLeftTile == 2) or (midLeftTile == 2) or (botLeftTile == 2) then -- one or more top corners will be on a solid tile -- check wether on platform and only bottom point colliding if (hero.platform ~= false) and (topLeftTile ~= 2) and (midLeftTile ~= 2) then -- hitting only bottom point to get off platform! hero.xPos = hero.xPosNext else hero.xVel = 0 hero.xPos = (math.floor(hero.xPosNext/126)\*126)+64 end else hero.xPos = hero.xPosNext end elseif hero.xVel == 0 then -- no horizontal movement hero.xPos = hero.xPosNext end -- gate check here! for i = 1, #gateList do local gate = gateList[i] if gate.state == "closed" then -- start checking stuff -- if gate is OPEN we do nothing at all! :-) -- CHECK wether hero is IN gate rect if math.abs(gate.group.x - hero.xPos) \< 128 then -- in horizontal area so check V overlap if (gate.group.y - hero.yPos) \< 192 and (gate.group.y - hero.yPos) \> - 444 then -- Y also within gate if hero.holdingKey == false then -- hero is blocked because gate is closed and hero is not holding a key -- BLOCK HERO!!! if hero.xVel \< 0 then -- moving left hero.xVel = 0 hero.xPos = gate.group.x + 128 elseif hero.xVel \> 0 then -- moving right hero.xVel = 0 hero.xPos = gate.group.x - 128 end else -- HERO is HOLDING A KEY! -- So check if it is the RIGHT KEY index for this gate if keyList[hero.holdingKey].group.index == gate.group.index then -- hero opens door and moves through gate.open(gate) -- is equal to gate:open() keyList[hero.holdingKey].group.y = -1000 keyList[hero.holdingKey].state = "used" hero.holdingKey = false else -- hero is holding a key with the wrong index -- BLOCK HERO!!! if hero.xVel \< 0 then -- moving left hero.xVel = 0 hero.xPos = gate.group.x + 128 elseif hero.xVel \> 0 then -- moving right hero.xVel = 0 hero.xPos = gate.group.x - 128 end end -- check if holding the right key end -- hero holding key or not check ends end -- if hero is in vertical rect check ends end -- if hero is in horizontal rect check ends end -- if gate.state == CLOSED or OPEN check ends end -- for loop over all gates ENDS -- finally, check to see if hero is still standing on solid tiles or on finish -- if not on solid or cloud, set jumpState to "falling" -- if on finish tile, level is completed! bottomRow = math.floor(((hero.yPos)/94)+1) -- first row = row 0 which is good for row multiplier 0\*mapwidth for Arraypos's leftColumn = math.floor((hero.xPos-62)/126) -- first column = column 0 so add 1 for Arraypos's rightColumn = math.floor((hero.xPos+62)/126) -- first column = column 0 so add 1 for Arraypos's local botLeftTile = levelMap.physics[leftColumn+1+bottomRow\*levelMap.width] local botRightTile = levelMap.physics[rightColumn+1+bottomRow\*levelMap.width] if hero.platform ~= false then -- hero is standing on a platform local hDistance = math.abs(hero.xPosNext - platformList[hero.platform].image.x) if platformList[hero.platform].type == 3 then if hDistance \> 236 then hero.platform = false hero.jumpState = "falling" hero.yVel = -10 end elseif platformList[hero.platform].type == 2 then if hDistance \> 156 then hero.platform = false hero.jumpState = "falling" hero.yVel = -10 end end else -- hero is not on a platform if (botLeftTile == 1) and (botRightTile == 1) and (hero.jumpState == "grounded") then -- both bottom corners will be on an air tile hero.yVel = -10 hero.jumpState = "falling" elseif (botLeftTile == 8) and (botRightTile == 8) and (hero.jumpState == "grounded") then if hero.finished == false then --print("finished!") audio.stop(1) audio.play(victorySound) -- write unlocked and score into levelTable data and SAVE! -- set current level unlocked from PLAYABLE to PLAYED levelTable.data[levelMap.number][3]=2 -- set next level unlocked from LOCKED to PLAYABLE levelTable.data[levelMap.number+1][3]=1 -- set scores (temp to Gold Silver Bronze!) levelTable.data[levelMap.number][7]=3 levelTable.data[levelMap.number][8]=2 levelTable.data[levelMap.number][9]=1 -- then SAVE the levelTable levelTable.save() hero.finished = true UI.closeShutterLevelFinished() end elseif (botLeftTile == 4) or (botRightTile == 4) and (hero.jumpState == "grounded") then -- hero is on death tile with one or two corners if hero.dead == false then hero.die() end else -- hero is still standing on solid, cloud, ice or jump with one or both bottom corners end end

The true essence of the code is that you do the following for each frame (so in an enterFrame loop) for your player character, and possibly other characters:

xSpeed = xSpeed + xAcceleration -- when going left xSpeed = xSpeed - xAcceleration -- when going right xSpeed = xSpeed \* xFriction -- when slowing doing, with xFriction at .9 or .8 xPositionNext = xPositionCurrent + xSpeed

The same goes for the vertical speed, but it’s a bit different because you check for jumping state etc…

After you’ve calculated the next X and Y position, you do a collision check for these “next” positions by checking if any of the four corners of your player are in a solid tile in the “next” position. If none is in a solid tile, your can set your player to this “next” position, and it becomes the current position. If there is a collision you need to correct your next position (and player speed), and then set this corrected next position to the current player position.

That’s really the whole basic concept!

I spent 15 minutes going over that doc, and once you ignore all the Flash bits, you are left with a very simplistic and occasionally factually incorrect set of sections once things get beyond the basic ‘here’s rectangles, let’s collide with other rectangles’.

Here’s the more important obvious errors it contains:

  • Player rectangle can’t be bigger than tiles.

  • Platforms only move in a single axis.

  • Slopes can only be fully diagonal, other (freeform) shapes aren’t possible.

There’s also other things which don’t apply to Corona (I mean apart from the general coding differences), merging clips in the section on ladders, for example, as well as many areas where Corona would be vastly better (you don’t need to move every tile when scrolling for example, you just stick everything into a group and move that, using animations to change the frames).

Given the emphasis on Flash-based structure, I’d sadly say that for us *at best* the document is useful merely as a theoretical framework, but much of that strength is lost because of the non-native English writer and errors :frowning:

The most interesting parts are those less often covered in tile-based games - isometric and pathfinding, but these are of limited value to most people.

Woah I have a lot of catching up to do, the Corona email only sent me to the first new post and I, like an idiot, failed to see there were more as they’d gone to page 2. Forgive me if my post above is a little out of context as a result!

OK it seems to me we are talking about two different approaches to tile-based games.

Thomas6 and jerejigga are covering a basic, beginner-style one (no offense to either of you intended, I’m not referring to your knowledge levels, merely the complexity of the game itself), while Caleb and myself are involved in a more complicated type of engine.

Again though, let’s not forget we can split a tile-based game into 2 areas - the drawing (which MTE and Dusk both handle), and the collision (I’ve not seen anything to suggest MTE can handle complicated collisions, but Dusk is definitely moving in the right direction, you just need patience with Caleb!).

So for now, for a simple platformer or zelda-type game there’s no reason you couldn’t use Dusk to draw, and that document to create a simple collision engine (unlike Caleb, I don’t like calling these a physics engine, dunno why not!). And it is likely more than good enough for a simple game.

I do feel though that gamers today expect a little more functionality and polish than what that document can provide. However, we all start somewhere, and I wouldn’t advocate waiting until you have the perfect code to release your first game, so I’d say that the simpler code would actually be ideal for a first couple of games, and I’ll try to bear that in mind as this thread develops.

However, if Caleb generously releases his code later on with his full physics*, it would almost immediately make many things redundant :slight_smile:

* Or indeed, if I do, but I’m not really sure whether I’ll do that, although I might look into releasing protected libraries as plugins or something.

Bottom line is, it’s all good!

Hi Rakoonic,

No offense taken. My doc is more about a Mario-level of complexity, that’s true. However, it’s pretty easy to expand things, to be honest. As it stands, the physics implementation is limited, but that’s also the beauty of working with it.

p.s. Making players bigger than tiles is easy: you just need to check collision on more than the four corner points.

No offense taken here either. And yes, I agree, we are talking about at least two different things. To your last points Rakoonic, that is basically my plan. I need to start somewhere and it makes logical sense to start simple. I will build on that and eventually create something sophisticated and truly amazing. One of things I originally asked for in this forum topic was for a how-to guide. What I am getting instead its lots of theories and concepts. In the long run, that is exactly what I need and far more valuable than a how-to guide! That said, even though the PDF that thomas6 shared might have issues, it’s still very thought-provoking. Earlier, I shared a link to a YouTube video series that someone else pointed me to. I have been working through that series and it definitely has flaws. It’s still very valuable to me though because it’s forcing me to think through the approach. Ideally, I would have a YouTube video series or PDF or web page series that provides a walkthrough of creating a Platformer using Corona SDK, from scratch, using all the best practices! Until that comes into being though, this what I have. :slight_smile:

Just a note: for MetroidVania-type platformers my simple physics should be plenty powerful to use as a base.

Amazing thread guys. I especially loved your demo Rakoonic, very impressive indeed. Also agree about not liking the event model.

If I can be so cheeky, we were thinking of doing some optimisations to our tile maps for when the player zooms out. At that point you can have around 30x30 tiles of 256x256 on screen at the same time. Corona handles it admirably but I don’t believe any MIP mapping is being done by Corona. Does anyone have any suggestions of how best to do it? My best guess at the moment is just to have 3 maps identically overlaid, all the same layout, using 3 sets of tiles (256, 128, or 64 squared) and for us to switch between them depending on the zoom. Hopefully we’ll get to it in the next month but any other suggestions are very welcome. Thanks!