Home

Categories

About

GD50 Lecture 05 - Super Mario Bros

Posted on May 13, 2018 by Abhirath Mahipal
Tags  game-developmentnotes

This is a part of a series of notes. You can find notes to other lectures here Please feel free to bring to my attention mistakes, other interesting resources and feedback via the comments section. I’m all ears and will do my best to incorporate them into the blog post.


Resources


Intro

  • Not using the original sprite sheet due to copyright issues.
  • Released in 1985, it kind of revived the gaming industry after the crash in the 70’s. Probably the most famous game of all time.
  • 2D Platformer. Quite complex for it’s time, move around, effected by gravity, spawn enemies etc.

What Goes Into Mario?

  • Tilemaps - Each level is designed using individual 16 x 16 tiles.
  • 2D Animations - Kind of like a flip book.
  • Collision Detection - We’ll be deviating from AABB that we normally use. We don’t need to iterate through every tile in the world checking for collisions.
  • Camera - We track the player and move the world along with him. The notion of a camera is introduced.

Some columns in the level there are no tiles (chasms the player can fall into), some columns are a bit higher (player has to jump), some have power ups and so on. All these variations in pattern put together create a level.

Tilemaps

2D array of numbers. Each number normally depicts the kind of tile that should appear on the corresponding spot of the level. They can have a host of other properties like allow to pass things, trigger an animation and so on. You could do this by associating properties (using Tables in Lua) against each tile. The sky is the limit.

tiles0 “Static Tiles”

Colour in the background and just render a few tiles. That’s all.

Question by Colton:- What would be the first step to implement this?

Student Answer:- Loop over tiles and render them.

Colton goes on to explain that we’ll create a table to store tiles. We just won’t store numbers. We will store each tile as a mini table that will have an id as well as some properties (sort of like metadata).

  • Create a sprite sheet using generateQuads.
  • A small sprite is used. We index into a the table of quads just generated to get the relevant sprite.
  • Line 39 - 42 => Generate a random colour. It’ll denote the sky.
  • Variables mapWidth and mapHeight are used to to fill a table with appropriate number of entries for this demo.
  • TILE_SIZE is a constant set to 16. It’ll be 16 throughout the lecture.
  • Lines 47 - 50 => Populate the table with data denoting the structure of the map. If height is less than 5 (top portion that is) make it a sky tile else a ground tile. It’s just a table with integers denoting sky or ground. We do not store the actual quad.

Line 74 - 85 => Iterate over and use the above generated table to render. Sky tiles have no quad. If it’s a ground tile render the appropriate quad.

Final outcome:- Screen with tiles and a random sky colour.

tiles1 “Scrolling Tiles”

Types of scrolling in games.

  • In some games you cannot scrolling. For example in Temple Run you cannot go back and forth. The game keeps moving forward all the time.
  • Then there are games like Mario where you make the entire world move respective to the player.

love.graphics.translate() Moves the coordinate system. Everything drawn to the screen after that gets rendered respective to the new coordinate system. So we shift everything and move the world. This gives the effect of the world moving as the player moves. We pass in X & Y based on the player position.

Demo => Key presses now move the tiles. If you move too much you will reach a point where there are no tiles. Normally such information is hidden from the user (recall that games are illusions).

  • Line 95 => Translate by negative camera speed. If the player moves to the left, we want everything to move to the right to give the illusion of the player moving. Y coordinate is set to 0 because we don’t want the map to move vertically. Math.floor is used as decimal values might cause blurring (called Blur Artifacting). Any time you condense (texture rendered on a small virtual window) or magnifying values while using a virtual screen there are chances of weird blurs appearing.

  • Line 80 - 87 => Take care of updating camera scroll based on right and left key inputs.

We translate before we draw. Everything drawn after the translate gets effected by it.

Love 2D is a low level game engine which gives more control and is good for learning. It has no camera objects and the likes. Higher level game engines like Phaser etc have camera objects which encapsulate this translate behvaiour.

character0 “The Stationary Hero”

We again generate quads from the sprite sheet and just draw the first quad. It’s just static and does nothing. The other quads you notice in the image files are going to be used for animating the character.

character1 “The Moving Hero”

Now instead of camera scroll, have a variable called characterSpeed. In this demo the character moves but the world (i.e tiles) are stationary.

3 things yet to be done.

  • World moves as character moves.
  • Animate character as he moves.
  • Clamp (or limit beyond a certain point) if the character reaches the edge of the world. In such a scenario, the character moves but the world doesn’t.

character2 “The Tracked Hero”

The camera is fixed on the player. World moves relative to the player.

This demo is just to show how the world changes with respect to the player so does not account the case where the player reaches the edge. See the actual code in mario/src for that.

character2/main.lua Lines 93 - 103 =>

function love.update(dt)
    -- update camera scroll based on user input
    if love.keyboard.isDown('left') then
        characterX = characterX - CHARACTER_MOVE_SPEED * dt
    elseif love.keyboard.isDown('right') then
        characterX = characterX + CHARACTER_MOVE_SPEED * dt
    end

    -- set the camera's left edge to half the screen to the left of the player's center
    cameraScroll = characterX - (VIRTUAL_WIDTH / 2) + (CHARACTER_WIDTH / 2)
end

In the line cameraScroll = characterX - (VIRTUAL_WIDTH / 2) + (CHARACTER_WIDTH / 2) character width is added so that the world’s coordinates line up perfectly in the center. Also notice that we check for keypresses and keep updating characterX.

In this example we just translate the world by the X axis and leave Y unchanged. Say mario could jump extremely high or we were to implement some sort of vertical scrolling game, we would have to translate on the Y axis as well (using the same approach as given above).

character3 “The Animated Hero”

If the player moves loop through some image to create the illusion of animation. Animations are nothing but a series of images shown in succession.

Created a class called Animation.

  • Pass in the frames you want animated.
  • Time interval between each frame (quad) switch.
  • If all frames have been looped through, repeat from start again.

The class also includes logic for animating single and multiple frames. Idle animation for instance has a single frame. So by writing it this way the same class can be used for all animations.

What about moving left animation?

We just flip the drawing when calling love.graphics.draw(). If you recall it takes in optional arguments to flip it (rather scale, rotate i.e translate it). So we have to keep track whether he’s walking left or right and flip it on the X axis if he’s walking towards the left.

Animation.lua => Notice that while initialising the class the current frame it set to 1 (line 17). Also on line 22 we check if there are more than one frames. If there’s only one frame it’s kept constant throughout otherwise on the completion of the specified timer we keep changing the frame.

Notice that we have a function function Animation:getCurrentFrame() that returns the frame. The main render function basically calls this function every frame to get which quad to render. So every few hundred milliseconds it changes internally and therefore returns the next quad and the main render function shows that quad.

The animation class does not actually render the quad on screen. It just keeps track of the ongoing frame in the animation. Any render function can use this object to query the current frame and render it.

main.lua Lines 51 - 58 => The idle animation object is defined here. An interval is passed into it for the sake of consistency even though it doesn’t make a difference. He’s also defined movingAnimation after that. Notice that the sprites 10 and 11 are the sprites which show the character moving (the walking away frames).

Lines 60 and 67 => currentAnimation keeps track of the animation currently activated. direction keeps track which direction the player is walking in so that we can flip conditionally flip the sprite while rendering.

Line 150 => love.graphics.draw() gets the current frame from the animation object by calling getCurrentFrame(). The animation object keeps updating the frame as time progresses and the render function gets the current frame to show from the animation object.

Lines 150 - 160 => All these lines are just one function call. When a sprite is flipped, it is done so with respect to it’s origin. Default origin is the top left corner. So by flipping the character it will drift a bit from the center. So while calling love.graphics.draw() the last argument we pass, we set the origin to be the center of the sprite. Normally the sprite is recentered when we flip sprites and want consistency.

If you’re wondering how does the current animation switch?

Lines 115 - 125 => Continuously checks for keypresses. If right or left key is pressed, change currentAnimation to movingAnimation else let it remain idleAnimation.

character4 “The Jumping Hero”

Question:- How to jump?

  • Check for space, we need gravity, delta y. (Recall Flappy Bird and how we tracked gravity and delta y).
  • Take care of jump animation.

On closer observation it becomes apparent that we need two states. One while going up and the other to indicate that we are coming down.

Why do we need two states?

While jumping we destroy blocks, when falling down we kill enemies. The inverse doesn’t hold true. We need a way to differentiate.

The two states share the same animation but have different behaviour.

Main.lua 120 => if key == 'space' and characterDY == 0 then we ensure that DY is zero so that the player only jumps when he’s on ground. If he’s mid air, DY isn’t going to be zero and allowing him to jump in such a state will allow a player to jump infinitely in mid air.

Main.lua 132 - 135 => We haven’t reached collision detection yet so we quickly hack it up. If the character reaches below the ground set his dy to zero so that he stops moving.

Procedural Level Generation

Colton found the easiest way to think and reason about generating tiles for this game was to go column by column. First you start with no tiles (just the sky denoted by a background colour). Then you go column by column and ask do I want ground here, if yes fill it will tiles from the bottom edge till ground level. Do you want a raised pillar here, if yes add a few more tiles post ground level. If you don’t want ground in that column (i.e a chasm or a ditch) don’t fill any tiles at all in that column. All this can be done with boolean flags and math.random().

Try thinking of ways to generate it row by row (horizontal strips across the screen) and it will dawn upon you that it’s much harder. Column by column helps to think of it in individual units (this column has a chasm, this has normal ground and so on) but row by row you are forced to deal with tiles that aren’t a part of the same structure.

how-metaphors-models-help

Taken for the book Code Complete 2nd Edition Chapter 2.1

The essence being how we see or model things (abstracting into classes or functions, think of a map as a 2D grid and those sort of stuff) makes a lot of difference while working on them. It opens up new possibilities, greatly simplifies and can even break stuff.

You could easily extend this idea. Say you want a pillar of varying width you could introduce a width variable and fill up the next few columns with a pillar and decrement the width variable by one on every iteration.

Placing objects (grass, enemies, powerups etc) can be dealt with after you are done with the iteration that places all tiles. You can conditionally place objects on certain types of tiles (should not place grass on ditches and those sort of things). You can also keep track of where you’ve placed obstructions and place the next obstruction at a reasonable distance from the last.

He then goes ahead to show how a varying sprite sheet with a simple algorithm like the one described above can be used to generate rich levels. Assortment of sprites for the ground tiles, topper (thin layer near the top most tile added for visual appeal) tiles, backgrounds, obstacles make it look vivid.

The sprite sheet is well organised into similar chunks. This organisation provides easy programmatic access (splitting the sprite sheet into quads and accessing different variations of the same element. Most importantly the same can be done for all of them).

level0 “Flat Levels”

topper-ground-identification

Topper with ground. How it’s done?

He’s just taking the topper and the tile set (ground tiles) from different parts of the sprite sheet. We don’t fix a place for toppers so as to keep it general enough to work with pillars and chasms. There’s a flag which indicates whether a tile has a topper or not. If it’s set to true if it’s the topmost tile of that column.

Splitting tile set and toppers?

Have a look at tiles.png for a better idea of what we’re about to do.

level0/utils.lua => We need to write a four way nested loop to get the tiles and toppers. The first two levels of nesting is to actually get one of the 30 (3 X 10) different segments of tile sets. After that’s done, we’ll have to iterate again and get tiles across the X and Y dimensions again. So we’ll need a two level nested loop for that. Have a look at GenerateTileSet(). It takes in the actual quads, the X and Y of the large segment (3 columns by 10 rows) and the size of each segment as well. Basically taking a segment of the sprite sheet and extracting the desired group of related tiles from it.

This is only possible as the sprite sheet is structured and laid out in an organised fashion. Programmatic access would not have been possible if they were randomly spread out.

level0/main.lua =>

  • Constants for width, height etc.
  • Gets quads from the respective sprite sheet and then split them into relevant sets using GenerateTileSets().
  • Lines 223 - 249 => Use a table to represent the entire world. Fill in blank tiles to represent the sky, if height is equal to 7 give it a topper (thin layer that the player actually walks on).
  • Line 139 & 140 => The tile and topper set is a result to something random when r is pressed.

level1 “Pillared Levels”

How to put pillars?

Just put up tiles above ground level in that column.

You could have a flag spawn pillar which controls whether to have a pillar or not. You could also pass in the width or something and draw the same height multiple times and set the flag back to false.

main.lua Line 227 - 236 => Fill the entire map with the sky tile. So the level is basically empty at this point.

Once done we don’t have to worry about inserting or managing the table. We can just replace the stuff we want.

main.lua Line 239 - 259 => Go column by column to generate the level map. Randomly set a flag to generate a pillar or not. If the flag is true fill in positions 4, 5 & 6 with tiles (these are 3 positions above ground.) Also place a topper on the top most tile.

main.lua line 253 to 259 => Fills tiles to create the ground in that column. If a pillar exists don’t put a topper else place a topper as usual.

You can also create mario like steps instead of straight pillars in a similar manner. Keep reference to the previous height and thn iterate (each iteration reduce height). You could also keep track of width and keep increasing height till half way up and reduce height from there on.

Because of the code’s random nature it’s possible that two pillars spawn next to each other. Pseudo random generators can also surprise the person who wrote the code.

level2 “Chasmed Levels”

How to generate chasms?

You just skip a column. So at the start itself you randomly decide if you want a chasm in the block or not (in the code there’s an arbitrary set to math.random(7) for 1 in a 7 chance, which in general is a bad practice).

I think (I’m not sure myself) the reason being you have no control over where the chasm ends up (like the very first step where the player stands) and it probably is a very raw approach, we could do better by having a few hand spun rules like at so many intervals, only after a certain point in the level.

Lua has no continue statements, so we use a goto statement and avoid rendering ground in the column. Using ::continue:: is a community accepted standard work around for the same.

Audience Question => Level generation do we do it all at once?
In mario we do it all at once. It’s pretty small and can easily fit in memory. Also it’s much easier to teach like that.

Incremental approach can be taken for infinite runners. You split the game into parts. As the user approaches the right end, generate the new level, obstacle etc and append it to the level. You can discard the level or obstacle that the user has already crossed (in infinite runners you normally can only move in one direction).

Audience Question => Do we render the entire level at once?

Yes . Since it’s a small game rendering the entire map 60 times a second isn’t a problem. Large game render only a certain section. You would write your code to ensure it renders only areas near the camera or the user.

Related resource from a previous lecture Games are Illusions.

Tile Collision

We have a fixed 2D tile system. We can get to the tile from the x & y coordinates. We can use that to your advantage. We just have to check that particular tile. If you recall with AABB collision detection you have to iterate over all the tiles to check if it’s colliding with the player. In this case we know where the player is and we just can check that tile.

You can find the relevant function in mario/TileMap.lua. Also I’d recommend seeing slide#25 for more details.

Tile collisions while jumping up => Just check both the top edges of our character (which tile the head hits). Checking only one side will miss a collision if half his body is under a tile and the other half is not blocked by anything (and we happen to check the side which is not under anything). We just need the tile our player will hit. We get the index of the tile and check if it’s solid (property set via a flag in the table).

jumping-through-exact-gaps

Notice how the top portions of the bounding box of the player touches the tiles (it therefore will trigger a collision even though the player’s head is not touching the box). Reduce one pixel from each side which checking for collisions so that it can go through gaps that are exactly of the same size without triggering a collision.

To get the tile index => 6th from top, 5th from top and so on , we just have to divide the player’s Y coordinate by a tile height and add one to it. The same things can be done for the X coordinate as well. We can then index into the tilemap table.

Bottom Collisions => We do something very similar for the bottom. This time we check for collisions with the character’s bottom edges. The coordinates of the bottom edges are (x, y + height) and (x + width, y + height). The same is done for checking right and left collisions.

Remember that top, right, bottom and left collisions are checked in different states. We need to check top collision while jumping for instance. We needn’t check for top collisions while walking towards the right for instance.

mario/src/TileMap.lua =>

  • Lines 27 - 33 => We can easily get the current position of the player and from that we can easily get the tile the player is on.
  • Lines 28 - 29 => If the player happens to be beyond the map boundaries then return nil rather than a tile.
  • Line 32 => Lua tables are one indexed but geometric coordinates are zero indexed. So we add 1 to the indices we get.

Advantages over AABB

  • Just find out which tile the player is on.
  • We don’t have to iterate over all the tiles and perform AABB collision on them (recall Breakout).
  • Kind of like a linked list vs array. Array you know exactly where to look and can get what you want immediately. Linked list you have to iterate one by one until you find it.

This is good to repeat again. You check for collisions at different points on the character based on the different actions he performs. Say he is jumping you check for collisions on the top left and right edges. Since the gaps are wide enough to let our player pass between them we only check the top edges while jumping. Assume that our player is 8 tiles tall, in that case we’d have to check for collisions across his entire body even while walking.

Game objects need a different treatment and are a little more complex. Have a table of some objects like snails and we have to iterate over them one by one to check for collisions. It’s because they have their own X & Y coordinates and unlike the tiles they aren’t fixed at a place.

Entities

  • Almost anything that you want can be an entity.
  • In this particular game Colton has modelled everything that moves or is sentient as an entity (snails for instance).
  • They can have their own state.
  • He explained a little about Entity Component Systems. In such systems everything is an entity, each entity has components (we’ll discuss it below). Unity for instance is an entity component system.
  • I like to think of them like CSS classes. Every new class added to the HTML element gives it additional properties.
  • Everything has properties and these properties ultimately define the behaviour of the object.
  • Instead of a deep tree with inheritance (Say you have a base class Monster, Goblin a subclass inherits from it, the subclass GoblinWarlord inherits from Goblin and so on), you have a container which you compose (or fill) with components or properties. All the pieces together create what you want rather than having to inherit from parent classes.
  • Entities are often objects that do things or perform some action. In this game the tile map are just statically rendered and are not entities. Stationary objects are not considered to be entities as well.

Interacting with Entities

mario/scr =>

  • GameLevel.lua Line 14 - 16 => References to entities (enemies), objects (the non living stuff like obstacles, coins etc) and tilemap.
  • GameLevel.lua Line 36 - 46 => Update (position, health, consumed or not etc) the tilemap, the objects and entities. The main update function calls this. Notice how the individual update function of each object and entity is called.
  • GameLevel.lua Line 51 - 58 => This piece of code in responsible for rendering them. It calls the individual render function of each object and entity.

Player Falling State

mario/src/states/entity/PlayerFallingState.lua Lines 62 - 78 => Iterate over every game object. If the game object is solid change dy (rate of change of height) to zero and move the player just above the object. If the object is consumable (gem for instance) remove the consumable object without doing anything to the player.

Depending on the object do different stuff. If it’s an enemy it dies or kills you, if power up show the relevant effect and so on.

Check resources for the link on Entity Component Systems.

Game Objects

game-objects

  • Things that don’t go well with the tile dimensions, maybe they are of different sizes, appear at weird places etc. Examples are powerups, ladders, coins, ropes etc.
  • Test them using AABB collision.
  • We could have modelled them as entities but in this project it’s not the case.
  • In this game, entities are living sentient beings, game objects are non living. Colton adds that for small projects as this everything could be entities.
  • Definitely have a look at Slide #30.
  • In bigger games, different varieties of the same object exist (different types of bricks, gems etc). In such a case we could have a class for object and have sub classes which inherit from the base. This way we’d easily model a lot more objects.

How would you implement ladders?

Check for a collision with a ladder. If it’s true and the up arrow key is pressed then enter climb state. If reached the end enter walking state again.

You can generate a lot of objects based on functionality. For instance:-

  • Ropes and ladders all trigger a climbing state.
  • Bushes, mushrooms are cosmetic. Nothing happens when you jump on them.
  • Spikes and a few other objects are lethal. You can have a flag isLethal against the object and on collision it should kill the player.

LevelMaker.lua =>

  • It generates game objects randomly.
  • Every object has flags like collidable, consumable, solid etc. when a player collides with an object the respective callback function is activated. When a gem is consumed for instance, score increases, a sound is played and the gem disappears.
  • Each game object also has an associated texture, height, width, x and y coordinates.

mario/src/LevelMaker.lua =>

  • Lines 56 - 74 => There’s a one in 8 chance to spawn a pillar. There’s a one-eight chance of having a bush on a pillar. So in all there’s a one in 64 chance of having a pillar with a bush.
  • Line 85 - 91 => Look at how the properties are set. Especially note line 91 which sets collidable = false. Bushes therefore don’t trigger anything when collided against.
  • Line 97 - 112 => Randomly deciding to generate a block or not. On line 112 solid = true. The user cannot pass or move through them.

The above set properties are tested for and certain actions can then be triggered depending on these properties. Open mario/src/Player.lua and have a look at the methods Player:checkRightCollisions(dt), Player:checkObjectCollisions() to see how they are used.


Audience Question => Wouldn’t it be better to have a helper class that takes care of creating objects?

Such a pattern is called a Factory Pattern (or at least is very similar in spirit).

At this scale it isn’t necessary. So he just created one class called GameObject that abstracts away creation of all objects. He also felt the need to reduce the number of files to simplify the code and increase comprehension. If Colton were to design a large game he would probably create a class for each kind of object like a bush class, a gem class and so on. In a large game there’d be many kinds of gems, bushes and so on. The class would take optional parameters to create different variations of bushes and so on.

The current GameObject class is abstracted enough to allow for custom event handlers that can be tested for conditions. We can thus define custom behaviour for them. For instance a gem is consumable but a bush isn’t. So on collision we check if the flag consumable is true and then we call a custom function.

He again briefly hints at Entity Component Systems. It’s a pretty neat way of modelling stuff. You just add components like consumable to an object such as a gem.

Check resources and check out the link on Factory Patterns.