This lecture from CS50 2D teaches how to build a Super Mario-style platformer game using tile maps, procedural level generation, 2D animation, and collision detection in Lua. The course covers key concepts including tile-based world representation, camera systems using love.graphics.translate, sprite sheet animation with frame cycling, and entity-based game architecture with state machines for player and enemy behavior. Students learn to implement procedural level generation with pillars and chasms, collision detection using point-to-tile conversion, and game object systems for interactive elements like gems and blocks.
Deep Dive
Prerequisite Knowledge
- No data available.
Where to go next
- No data available.
Deep Dive
CS50 2D - Lecture 4 - Super Mario Bros.Added:
Hello world, this is CS50 2D and this is Lecture 4.
Today we'll be looking at Super Mario Brothers, which is a very famous, possibly the most famous, uh, it's arguable, uh, sort of franchise of games that we'll be looking at through this term, and I'm even joined on stage by a very fitting Mario plushie, which is very cool.
And it fits sort of harmoniously with the slide here as well as you can see also by the slide, we won't be necessarily using the exact Mario assets today.
We'll be looking at a really cool sort of Creative Commons version of the same platformer tile set, but we'll be exploring all the ideas that ultimately make up Mario.
So this is the OG Mario and also you know CS 50 traditionally has had a history of using Mario in its own materials, and I think it's a great illustration for a number of reasons.
But it's, you know, in a lot of ways a great transition to this idea of a virtual world in our game as opposed to the sort of, you know, so far we've looked at things kind of abstractly.
We have Pong, which is, you know, these rectangles moving through space, Flappy birds, we looked at illusions, but there is no really a world representation per se.
And even last week when we looked at Mach 3 and then Breakout, these are relatively abstract, not, not so much worlds, but today we'll be actually looking at how to model something similar to this with a few differences.
But you know, we see we have enemies here moving around.
We have a ground point at some part.
We have Mario jumping which we have looked at jumping through Flappy Birds, so that part will be somewhat of a.
Reminder from Flappy Bird, but you know we have blocks, pipes, and we have a background, all these things.
We feel like we're in a space which is super cool.
So this is the original Mario for NES.
Then Mario 3 came out for the NES, which also was very sensational when it came out, an amazing game.
I grew up playing more so Super Mario World for the Super Nintendo, which also has a tremendously positive reputation.
And then nowadays, you know, Mario's doing all kinds of other stuff in 3D and here he is with the dinosaur for Odyssey.
Also a tremendously fun game, very different, sort of the franchise has changed quite a bit, but the core is still there and we'll be taking an initial look at how to sort of start representing these virtual worlds in our games and start to really model things that are, I think, a lot of fun.
Some of the things we'll be looking at today, so we looked at tile maps last week with Match 3.
We're gonna be looking at the same idea this week except instead of having a puzzle board now, we can use the same idea of these sort of.
IEDs mapping to these quads that we draw onto the screen as being tiles that then matter in terms of like are they collidable, what do they look like, so on and so forth.
2D animation is important.
Mario, when he's jumping, he has a different pose than when he's walking and sort of that also when he's walking, he's going frame by frame from one sort of stepping motion to another.
He's not just a sprite moving around.
We haven't really had this idea of sprites that actually change how they how their appearance is frame by frame as they move, so that'll be new.
This week has one of the more sort of I think representative procedural level generation focuses where we actually will make all of our levels in code, we won't actually hand create our levels, so you certainly could do this.
And then we'll talk about how platformer physics differs from AABB and allows us to detect whether our sort of Mario Avatar is hitting blocks when he's jumping or moving or so on and so forth.
A very, very simple version of AI for the sort of moving Goomba-esque creature we'll have today, which will be a snail, and then lastly how to have things like power ups which will tie into the problem set.
So our goal is going to be this representation on screen.
I've taken a sort of range of screenshots to show where we'll be going.
You see, we have the classic initial starting screen, but also all the levels here are just randomly generated.
They've got different backgrounds, different tile sets, gaps and chasms and sort of these different props in the background and also blocks with power ups and things like that, gems that we can activate through jumping, and I think it'd be good to actually demonstrate what it looks like.
So if we could get a volunteer who'd be willing to come on stage somewhere.
Yes, yeah, why don't you come on up.
Hey this is Dane, right?
David oh David, OK, OK, nice to meet you, nice to meet you.
OK, so I'm gonna go ahead and hit this button here to get this started.
And now feel free OK?
to press enter press enter, right.
And you are now controlling Mario.
It'll be the arrow keys to move.
You can press space bar to jump.
You hit the snail and caused it to uh cause it so some of the blocks won't have, will have gems in them, some won't, but as you can see by jumping onto a snail, you will hit it.
the terrain is collidable, so you're walking on top of the terrain.
Some some blocks, so this block, for example, doesn't have a gem in it, but if you were, it's random in terms of how this is implemented, which one will have a gem in it.
But if you keep moving forward to the level, we might get lucky and get a block that does have one inside of it.
They all have different tiles, a different frame chosen for them, so that one doesn't either.
We're getting unlucky.
Oh Oh, that one did.
So now you can, you can if you if you were to jump on top of it, there you go.
I or close enough to it.
This is my best game yet.
It, it's pretty impressive.
This is, I spent a lot of hours playing this as a kid.
It shows and so we sort of have all the pieces here, even the, you know, you're walking and hitting these sort of pillars in the level collision on the snails you've got this jump your frames are changing.
If you were to theoretically maybe even uh pretend of course to to uh fall down.
you want me to and we do trigger a sort of like a like a sort of death state at the end of the level but thank you so much.
Yeah, that was uh yeah.
So as we can see there is quite a lot of pieces there, a lot of new things and, and sort of we've taken a step forward in a way that we haven't really done before and that now we are thinking in terms of existing in a world.
Flappy Bird was our initial step.
And it was doing similar a sort of philosophically I guess spiritually it was making us feel as if we were somewhere with this illusion that we have the scrolling background we've got these pipes that are moving.
Flappy Bird is in the center of the screen and not really moving technically, but like you could jump and you could use gravity to try to avoid the pipes, and there was this feeling that we were somewhere in space.
Mario, we actually feel like we're moving.
We have a camera sort of tracking Mario.
We've got an offset into how we're rendering things.
We have this tile map and here this is this slide sort of illustrates what tile maps are, but essentially it's not all that different from what we looked at last week with the grid of tiles with Match 3, except last week we were using them to render tiles that could.
Swap and then calculate matches this time we'll be actually using them to draw these blocks on the screen that we can walk on.
We can walk up against a but in the case of for example the blocks that we could hit those were technically game objects, but they're similarly functioning in terms that they're just being rendered on the screen as a subset of that texture and they move around in space relative to the camera.
And so just with this drawing of these little pieces of this texture and then putting them in a data structure that models some sort of level where maybe if you envision 0 being space, 1 being the ground, in our example it's slightly different, it's 5 for the space and 3 for the ground.
But you can essentially map that to a level that then we just calculate, OK, at x y coordinate there's a tile there we should collide with it or not, render it as a tile or not, and then we just sort of feel like we're in a space now all of a sudden magically.
So in order to illustrate this, why don't we go ahead and step into some code with tile 0 where we'll look at some static tiles.
I go ahead and transition over to the source code here.
I'm going to close the Mario example.
Let's open up tile 0.
And I'm just gonna run it so that folks can see what it looks like.
But it's very simple.
It's just a, and this is just some very basic art that I've put together here just before we get to the actual nice tile set that we're using in this lecture, which is a very impressive tile set and we'll show it.
But we just have a background color being drawn here, and this is just a random color that I'm clearing the screen with.
We use love.graphics.lear in the past.
That's exactly what we're using here for the sky, and we're just not drawing tiles there.
And then we are drawing tiles where the bricks are.
And so if we envision just sort of this 2D sort of grid like we did last time, we can visualize how we do this in the same way that we did the match 3 grid.
We'll look at the code here.
The tile size we're working with 16 pixel tiles.
We've got two IDs that we care about sky and ground.
We had a tiles array.
This is not all that different from the tiles or the sort of the match 3 tiles table that we used last time.
And then we're just going to slice up our tile sheet, which in this case is just a 2 tile tile sheet which is going to give us back just two frames that again we can index into and draw with our one texture.
It's a very meager modest texture here as you can see this is a, uh, in this case this this one on the right here is just VS code's way of saying this is transparent.
There's technically nothing here on the right side of this texture, but here on the left is a brick texture that I sort of created very primitively using a bright or something.
And then here we have this empty so that we can still index into it, treat it as if it's drawing a tile, but in truth it's just drawing opaque pixels, nothing's getting drawn to the screen.
So we have a background RGB that gets randomly selected here we're just populating again a 2D array, a 2D table of tiles which are just going to be in this case IEDs.
In this case they're constants mapping to sky or ground.
If we go down to the draw function here.
We have a clear color that's happening and then literally just a two dimensional loop that's gonna iterate over this 2D table structure that we used similar to last week and it's just gonna draw it's gonna grab the tile and it's just gonna draw again indexing into our quads table to get the exact offset that rectangle.
From 1 to 2 it'll get either the 1 or the 2 and then it's just going to draw it at the XY minus 1 to get coordinate space multiplied by tile size as the offset very sim very simple, very similar to what we did last time with the match 3 grid.
Nothing is really new here.
So if we go to tiles one, I think the thing that really stands out is that we don't have, you know, uh this genre at Mario is kind of like a scrolling platformer or 2D platformer.
They've got like scrolling shooters, other types of genres similar in similar vein to that.
In the sense that you have a background, sure, but it can't just be static.
We want to be able to move around and to be able to feel like we're in a world and to do that we have to have some way of shifting this map around on the screen.
And so we have a handy way that we can start to do that with a function called love.graphics.translate.
And so what this will do is rather than everything being drawn just with an assumption that it's at 00, love.graphics.
We will take an X and a Y and then everything that you draw thereafter will essentially be offset by that x y coordinate such that you can essentially mimic a camera if you think that we have X50 here, everything will be shifted 50 pixels forward and so you can sort of think of it as an inverse sort of direction of the camera moving.
So if we were to shift everything 50 pixels to the right, we're going to feel as if the camera has moved 50 pixels to the left.
And so on and so forth for every direction on every axis.
And through this method we now have a way to sort of envision a camera, sort of a couple of variables, an X and a Y, and particularly we'll focus on X, but you can have an X and a Y value that model more or less how we can think of the world shifting in the way that we would think of a camera moving on our scene, which then gives us the ability to track a character as we could sort of anticipate and then move through our 2D world.
So with that, let's go ahead and look at the code for this.
It's going to be a simple addition.
To close tile 0.
We're going to open up tiles 1.
And then I'm going to go ahead and scroll right down here.
We see a camera scroll speed, so we're going to scroll.
This is a test example where we're going to scroll and actually why don't we run it just so we can see what it looks like.
So it's the same example as last time.
We have a color in the background that's random, so that's why it's pink instead of green now.
But if I hit the arrow keys, you'll see that I am indeed scrolling the screen left to right.
And all this is really doing is just drawing everything with an offset.
So rather than drawing everything, if we assume here more or less everything is at 00, then it's a very simple sort of way to see that OK, 4 tiles down at you know X times 0 with the first tile.
Drawn X times 1, the next tile size 1, tile size 0, tile size 3x 2, whatever, they all get drawn sort of relative to the top left corner of the screen, which is 00 in love 2D game space.
But by using love.graphics.translate.
If we scroll down here, notice that the camera scroll stays at 0, we're going to manipulate this variable using delta time just here like anything else that we've done, just some variable that's going to change based on input here.
It's going to be a delta time multiplication on that constant.
And then right before we do our actual drawing we just say, OK, I'm going to translate by the negative in this case we're rounding down with math.floor just to keep in the event that you don't you have fractional sort of translations, you can have artifacting in weird sort of lines, especially using push and other just in other domains you want to keep things rounded down for rendering smoothly.
But if we go ahead and just translate by our negative camera scroll, we end up actually seeing a shift as if we are moving left and right throughout our scene.
If I hit left, if I hit the left arrow key, we do indeed move left, and if I hit right, we do indeed move right.
We are negative, we are negating rather the variable because we want to.
Sure that when we press left we think as if we're moving the camera left, but recall we have to move X positive in order to actually get that effect by moving the camera to move the camera left because everything gets drawn with love.graphics.translate with that offset.
So we have to negate if we're thinking in terms of left and right in our input, we have to negate that in order for that to actually reflect in camera space.
But that's essentially all we have to do.
We just negate the camera scroll variable we're keeping track of, and now we all don't have to really manage anything in terms of our tiles.
They will automatically look as if they're drawing in the right spot because we are adjusting the entire coordinate system effectively to account for this translation.
So that's love.graphics.translate that takes care of the scrolling, so we have one very important, one of the most important pieces tackled, but now we have to also factor in, OK, we have a world, sure we can do that, but we should also be able to have a character on the screen, obviously that we can start to move around and then we can sort of envision this scrolling these variables following the character, you know, the character should be in the center of the screen.
We can do all of that, but to even get started we need to have some character on the screen to begin with, so.
What we'll do is I'm gonna go ahead and open up character 0 and let's just demo that see what that looks like.
So as you can see, very simple, very similar to what we saw last time, except now we're using a little Mario-esque avatar, a little alien looking guy standing on top of the ground, so positioned just so that his feet are touching the top of the ground.
If I move, I can still move the camera left and right, so this isn't quite accomplishing our full intentional goal of having the camera follow the player, but you know we can see that we're one step closer to our end goal, so things are moving in a good direction.
Let's go ahead and look at the code for this.
So there's a height and width of the character that we should factor in for positioning.
So in this particular Sprite sheet, which if we open that up really quickly.
This is what we'll be using throughout throughout the remainder of these examples.
This is our avatar, his Sprite sheet, and it's a sort of set of similarly sized sprites all in one texture.
This is just a single row and they're all 16 by 20.
They've got different frames in here.
You can see there's somewhere where he's just standing still.
He's got looking to the right or whatever this might be.
This looks kind of like tiptoeing on the right, very far right here we have.
Some walking frames we've got jumping frames which are these we've got like climbing frames we've got like a a ducking one here and then something that looks kind of like he's unhappy right here.
We'll use a few of these in particular the walking one and the jumping one here in a little bit of time, but for right now we only care about this very first frame and it's not unlike what we've looked at previously for for match.
And even for this example with tiles we're just slicing up this texture into these frames and then we can just index into this array that we know we'll get back to generate quads for whatever this tile sheet is and then if we want him to look as if he's moving, we can just set the ID of that frame to whatever this end is here, 11 or 12 or so or one will just be, you know, standing static.
So if we go over to main.lua, we're still using the constants there for the tiles.
So we're going to go ahead and chop up the character sheet.
We get its own set of quads here.
We're going to set the character X and Y, and we're going to offset basically put him right above.
We're essentially setting it at 7 tiles down right above tile 7, we want to set it minus the character height so that he's standing right above where the tiles, the solid ground tiles begin in our array, our 2D tile, our 2D tile map rather, which is what's done here.
And then if we come down here.
We'll see That we have a draw call done just at sheet at quads 1 at character X Y, which if we look up here again, character X and Y are just being set so there it's pretty much right in the middle of the screen positioned right above where our tiles are being drawn.
So that's fine.
Things are working pretty well for that.
Simple example, but clearly we want the character to be able to move, and that's kind of one of the important aspects of Mario to win a level in Mario, you have to move from the left side of the screen to the right side of the screen.
And then in the full game there's a flag that you can jump on and pull down, and that ends up transitioning to the next level.
In our example, that's probably part of the problem set is actually implementing that stage of it.
But in order to even get towards that we have to be able to move so let's start to think about how we can do that and let's look at character one so I'm gonna go ahead to character one let's run this, see what it looks like.
So we have kind of the same thing again.
These backgrounds are all random, so we can see we indeed do have a character that is moving now it's not moving the camera.
So there's a couple of pieces here very clearly.
We have one to move the character, so the character in the world is moving, but we also have to move the camera along with the character, and we have to make sure the camera is sort of statically fastened to the character's position.
So we'll look at how to do that in a second.
I'm just going to go ahead and open up Cha one's example so that we can see what that looks like.
I'm going to close all these other examples.
It'll be down here in our update function where we can see that we're just doing the same thing that we've always done historically.
We're just changing the X value by delta time through some constant that we've defined as character move speed.
All of these constants typically are tweakable with the exception of maybe constants that index.
To your tile sheets and then we do all the same code as before.
Pretty standard.
We're not calling we are calling Translate ultimately, but we've essentially replaced the actual logic that adjusted the camera scroll with the character's position.
So now we have to essentially marry these two ideas together.
So that brings us to character 2 which would be the tracked hero which allow us to actually start doing just that.
So let's go ahead and close this and then I'm gonna go ahead and open up character 2.
And if I move this character, we'll see indeed somehow the character is remaining completely within the middle of the screen, but as we can see by the scrolling of the tiles and the fact that we're reaching the map's sort of end points, we are indeed moving our character in world space.
So how are we accomplishing this?
I'm going to go ahead and scroll down.
We are doing this in the update function.
So here we are doing the same thing where we are moving the character's position because the character does need to move in the world.
That's not something that needs to change.
The difference is that now we need to essentially set the scroll to be equal to what's effectively the the offset of the character to its its left side effectively.
So we need to essentially take whatever the character's X position is, subtract half the virtual width, which will put it approximately in the center of the screen.
And then add the characters with divided by 2 because remember everything is drawn or everything in our in our games is relative to its top left corner so we need to shift halfway through the character's width to be properly centered and then now the camera's scroll will always just be whatever that half screen width is away from the character, meaning that no matter where our character moves.
The camera's position effectively is going to track the character's position and so here we can see we didn't really have to change anything here.
The camera scrolls now just managed in addition to the character's own position being set.
The character's position is now dictating with the, with the offset of half the screen width where the camera gets drawn.
So that's great.
We have tracking now.
Cool.
There's a few pieces left though.
Things are a little bit static.
Things feel a little bit weird.
The fact that we're just kind of moving the static sprite around feels unnatural.
So the first step is going to be let's animate the character.
We've seen the Sprite sheet.
We've seen that we have this ability to do this.
Let's actually do it.
Let's actually look at what it means to animate something, animate a character or any Sprite over time.
Let me go back to here.
I think the thing I think I have a slide about this just to illustrate this, but we sort of talked about this briefly, but essentially there's a set of tiles that we have.
This is the only one we're using, but if we imagine that over time this number, this quad index into this table of quads that we're generating could change and then maybe loop around at some point.
With some interval of time we can imagine just like using delta time to either toggle a couple of sprites back and forth or a couple of frames in the in this texture back and forth or we could, you know, do any number of things with any number of frames just over some time.
And we can see at the very end we've got a couple of frames in particular.
Those last two are going to be sort of like the toggles we could envision for walking if we're looking to the left, looking to the right.
Maybe we flip the texture because this is all, you know, folks might notice this is all just looking in one direction to the right, but if we're moving to the left, well, we want to also reflect that literally and just visually so we need to flip the.
To look towards the left, so we'll do that as well.
And then, you know, essentially you can just think of it like a flip book.
Like if you have a bunch of images of if we envision the same images just on top of each other and a flip book repeated and just you flip the pages quickly it'll look like he's walking at varying speeds.
So we can take a look at that.
So let's go ahead and look at character 4 or 3.
I'm gonna go ahead, close this and let's just show what this looks like.
So everything's the same although uh slightly harsh with that green color so that's actually redo it so it looks slightly better.
That looks a little bit easier contrast wise so now if I hit left and right, you'll notice he's static right now, but as soon as I hit left, he looks to the left, he's walking, his legs are moving.
I let go of the arrow key.
he goes back to this idle state.
He's no longer moving.
He no longer has an animation.
If I move to the right.
Same thing.
It's except now they're looking to the right instead of looking to the left and then back to center.
So we've done all the things that I've just alluded to.
We're animating and we're also adjusting the position.
Let's look and see how we're doing this.
You'll notice that I have an animation.lua in here now, and this is the backbone of how our animation is gonna work and also how animation is gonna work in the distro.
It's the same, the same class here.
But essentially it's all the things I just alluded to.
We're going to have a set of frames.
You think of an animation as a set of frames that should play over time and then typically you'll just loop back through the beginning of them.
So if you imagine the walking animation, it's literally just two frames.
It's one, it's the 11 and 12 ID wise or approximately there.
If you imagine those sort of flipping back and forth or it's 1112 and then looping back to 11, it's essentially a sort of flipping.
Two things if your animation was 5 frames, 1112, 1314, 15, you'd play them in order and then loop back to 1111, 1213, 1415, 1112, 1314, 15, probably assuming that your animation loops sort of elegantly smoothly, which is an ideal characteristic of an animation that is intended to loop, it should look smooth in the way that our Flappy Bird parallax background was smooth and the same backgrounds in this distro are smooth.
The animation should have an interval, and then often it's the case that an interval is the same, but you could in theory have intervals that are different and so you just need, you would need to keep track of each interval per frame what that is, and then you could set an animation to change sort of differently over time.
All of our animations for this district will be the same interval and it's usually some very small duration in seconds or half or fractions of seconds.
In this case, the walking animation is probably like 0.25 or 0.5 seconds.
And then a timer to manage that and then whatever the current frame is we're going to get this back whenever we call our animation, we're going to need to render the animation at whatever its frame is.
So the key detail which I alluded to which is delta time here again.
Delta time is often the solution to these sorts of problems.
So if there are more than one frame in the animation, then we're going to just update our timer and then assuming that we've crossed over that interval, so whatever that might be, we'll pass that in in terms of a definition here, which is what this deaf parameter is to the constructor.
We will then set the timer back to the modulo of that interval which we wrap it back to zero and start it with whatever the fractional overlap was, and then we'll just set it to be no greater than 1 or rather the the uh higher of 1 so it can't be below 1 or in this in this case we're going to modulo if we take our current for whatever our current frame is plus 1 and then modulo the number of frames plus 1 onto that.
The reason that we're adding plus 1 here is so that when we get to the current frame being the end frame in our animation, we still want that frame to finish over that interval, and if we were to just set it to current frame modulo self.
frames, it would end up actually just immediately skipping the last frame because that would be the length of the number of frames.
It would be whatever our length of frames is is going to be that last frame.
So we're essentially modulling by and and adding to one frame past the total number of frames such that it's just just overlaps, loops back to 1 at that point, and then we still get that last frame rendered on on its full interval.
And so as a result, you know, frame the first frame will go, it's interval, then frame 2 will happen, and then it'll loop back to 1, and then 2, or if it were 123, it would do all of that no matter how many frames.
It'll always run the interval and then modulo wrap itself back around to 0 for the timer and then ensure that we always go back to 1 when we module because modulo goes to 0, we want to make sure that we actually modulo to 1 instead of 0.
We've done that.
We've looked at animations or actually, well, rather, we need to, we need to look and see where this is rendered.
I got ahead of myself slightly.
So here we're going to define two animations in Maine.lua.
So we have just essentially we can still think of our idle position as an animation, just a one frame animation.
And it's not going to actually do anything.
We saw there was an if statement to check for more than one frame.
So effectively this is just essentially for consistency for our character, but we do have a moving animation which has 10 and 11 as the two frames into our sprite sheet recall when we looked at the Sprite sheet, they were kind of at the end of 1011 indices into our sprite sheet.
The interval here we set as 0.2, so every 0.2 seconds, the frame will tick and then we'll go to the next frame and then loop back around.
We're gonna set to idle animation and then what we're gonna do, we also want to keep track of the direction we're moving left and right, we're gonna also want to flip our graphic left and right, which is an important detail.
And then let's go ahead and go down to update where we can see that we are indeed updating our animation because it does take DT it does need to know how much time has passed by, so it gets an update just like anything else, much like Timer did last week.
And then here we, if we are pressing the left or right, we want to actually change the direction and if we are, uh, in this case moving, we want to set that to be the moving animation or rather, no matter what what the case is, we want to set the current animation to the moving animation.
And then if we are not doing either of those, then we can just set the current animation to the idle animation and then update it.
The result being here if we come down to love.graphics.draw after the map gets drawn, when we draw our character, we're going to instead of drawing quads at just index 1, which we were doing previously, we're going to whatever our current animation is, we're going to call get current frame, which if we look back here it was literally just an index into self.frames at current frame, so just a glorified getter basically.
And then we're going to essentially set the width and height here by an offset because it's important when we're shifting things in space rather when we're flipping things left or right.
In either on the X or the y axis, a flip will if you're basing your origin on the top left of your character, which is what we do by default, it will actually flip around that origin point and end up having weird behavior.
We want him to actually rotate in place and so to do that we have to actually set.
The origin point of our character right in the center of the sprite instead of the top left corner, which doesn't affect our calculations for physics, but it does affect where things get drawn, which is what we do here.
We actually this last two arguments to love graphics.draw after sort of all these other arguments that we've been used to seeing.
If we set the origin here to character width divided by 2, character height divided by 2, what we're essentially doing is we're shifting in from the top left corner that origin to be slightly or to be directly in the center of the sprite such that scale operations now happen based off of.
That origin point and not this top left corner and the scale operation is how we apply a flip.
If we scale by -1 on the X or the y axis, that's effectively what's going to flip the axis around the origin point and it's going to trigger that sort of flipping aesthetic.
It's a scale technically speaking, and that applies to the origin.
It's always going to be relative to the origin.
And so we also, in order to account for this, we have to draw.
Essentially shift the characters X and Y by that value because now all drawing operations are going to occur based on the center point of the sprite which is going to result in the sprite looking as if it's shifted up towards where it's 00 point was previously at its top left.
So we're essentially shifting the origin into its center and then we can offset that by drawing inwards by that amount.
And then we can now flip its axis of rotation or we can flip along its x axis depending on whether it's moving left or right or whether we have left or right set.
And so ultimately that results in being able to move in place while being able to still have the same collision and things draw just as if they were uh as they are expected to and so we have therefore all that we need in order to draw a sort of.
Flippable translation or flippable animation now character that can rotate or not rotate but can flip on any axis Y or X axis and look as if it's moving left or right and.
They animate as well, so now they actually look as if they are sort of more lively interactive character, which is great, great steps.
The last thing to illustrate just the basics of the character before we move on to other topics is the ability to jump, which is a very core part of Mario.
Mario's sort of signature thing is jumping up and hitting blocks, and so it's only fitting that that's the last thing that we have Mario do in this example, and there's, you know, again another we saw that already alluded to the Sprite that will allow us to do that.
And what's cool is we already sort of looked at that in Flappy Bird.
So let's go ahead and open up Character 4.
I'm going to close these examples.
Open character 4.
And then we have the same thing as before.
I can move the character left right.
Everything works exactly the same.
Now if I had space bar, you'll notice that if I'm staying still, the character is just kind of jumping up and down in place.
If I move right and jump, you'll see the animation is slightly different, but they are indeed jumping with their fists raised up, which is part of the animation, and then left sort of the same thing.
So I'm essentially setting that that frame to be whatever that jump frame was, probably 9 based on my memory of the sprite sheet.
And then if I'm moving left, it'll keep that frame and if it's moving right it'll change to that frame and that's essentially all we need now to feel like we're using a sort of very analogous Mario character with all the same basic features of at least Mario from the original Super Mario Brothers version we can take a look at that here.
It's not a whole lot more that's been added.
We've just added a new animation for the jump animation, which is apparently frame 3.
I thought it was frame 9.
It looks like it's frame 3.
And then if we are, if we come down here just a little bit down into the love.key pressed, if the key is equal to space.
And the DY is zero, meaning that we aren't already jumping because remember this is what we did in Flappy Bird.
We have some gravity amount.
The character can jump up to set their negative gravity, their negative DY to some value like -300 if we assume like a 980 or 900 gravity on the positive Y axis.
And then gravity will bring them back down, but that's essentially what we need to check for here is are we already negative or are we already statically set on the ground such that we're not either jumping or falling down because we don't want to just allow the character to keep jumping and going up and up and up.
We did that in Flappy Bird.
You could just spam Space bar and just keep moving up the screen, which is part of the game, but in Mario you only get to jump when you're touching the ground, so that's what we do here.
Then we can see here we apply gravity very similar to what we did with Flappy Bird and then in here we have just a hard sort of like check for are we at where the tiles begin on the map, which is about tile 7 minus the character's height so that when its feet touch the top of that tile, it won't go any deeper than that.
And then we'll just update whatever the current animation is and then keep moving left and right, so on and so forth.
The same thing should still apply.
We should still be able to move left and right and then still apply a transformation to whatever we're jumping at with current animation.
Nothing changes even if we're jumping, it's still going to be the same thing here, but now our animation can now just be jumping instead of instead of being static or moving to the left and right, depending on whether we press space bar and we are indeed jumping.
And that ultimately wraps up all of the character examples which now we can begin to start to take a look at another key aspect of this distro which is the level generation.
So to sort of bridge us to the distro then what we're gonna want to take a look at, start taking a look at is procedural level generation, which is something that's pretty cool.
I'm, I've always been kind of a fan of it.
It can be something that's, you know, overly done and sort of too relied on sometimes, but in the case of today's example, the benefit is that we can get some really nice looking levels without actually having to by hand create all of them giving us sort of this.
Infinite replayability which we we've explored this idea in concept, at least in terms of breakout, for example, where we have all of these different types of grids of bricks that can be generated that can be broken and then in match 3 the grids have to be randomized in a similar but simpler way.
But in today's example we can sort of sort of start to think about levels similarly in that you essentially have a obviously a grid of what we represent as numbers so far that we've looked at.
And these have some sort of semantic meaning in terms of how they should be placed together.
For example, a ground should be at some baseline level, probably, you know, so far we've been doing it about 6 tiles down, 7 tiles down into the game space, and then you can start to think about, OK, well that's just a flat ground that's infinite in sort of the way that it feels and and pretty simple and not much of a level but if you start to say OK instead of having.
You know, all tiles throughout that whole space from 7 down on the Y axis all the way to the end of the X axis.
All of them always being filled, we can start to imagine, OK, well what if we instead just decide to introduce gaps in there sometimes or we put pillars or we draw pyramids or we do any number of things we start to add new props, new new features instead of tiles maybe there's switches, maybe there's blocks we can jump up and hit, maybe there's sort of decorations like bushes and clouds and very.
Things maybe there's coins, any any sort of thing you can think of, and you start to place these around and you sort of have rules that get more and more elaborate now you've got the system in place that allows you to create these infinite levels and the sky is the limit ultimately for what's possible as long as your logic is such that you never overly put the player into a position that's unwinnable, which is difficult in its own way.
Uh, you end up having a pretty robust system for certainly for testing and even maybe even for playing games that are fun enough at least to uh to play for some people.
So we'll be looking this week we have a quite nice tile sheet that's Creative Commons.
An artist named Kenny did the original version of this which then had a sort of 16 by 16 version done by somebody else, which is really neat, and it has just a plethora of tiles here as you can see, and these are all tile sets we'll only use a couple of these.
Throughout today's lecture, in particular the empty and the full just square version, but you can see there's slopes and rounded versions and all sorts of fun things you can do with that.
I encourage people to explore with the code base, but in addition to that, we not only have tiles, but we also have toppers for them.
So this is a way to sort of add these don't really functionally mean anything in.
of game play, but they do offer a way to differentiate your levels visually in a way that allows us to get the sense that, OK, we're not just in some random plane, maybe we're in like a candy themed place or like this icy area or this foresty place and so this is just a simple change really ultimately it's just drawing a texture on top of another texture which we'll see.
But it does offer a tremendous amount of visual variety that gives your game a lot of pop and makes it feel just fun to play.
In addition, the spreadsheet includes a lot of these other tiles and whatnot that we can use.
There's some hills and some blocks.
We'll use these blocks for sure.
We'll use a, uh, we'll use the, um, there's a flag here for actual distro for folks to use for the problem set.
And then there's some other various examples here you can see that there's even a couple of creature sheets here we'll be using this.
This is where we got our character from, but there's also like an enemy here, a snail enemy, and a bunch of other things the gems here for the power ups, keys and locks for the actual problem set as well.
Here's the tiles again so you can see what it looks like just isolated.
I've gone ahead and isolated most of these as well, so rather than having them be just in one big texture, which is what this is by default, you get instead we have this, we can sort of now if everything's symmetrical recall we can splice it up programmatically a lot more easily.
We don't have to hard coat offsets and do things that are too crazy to figure out where we are.
Same thing for the toppers.
These are now isolated.
These are now programmatically sliceable in the same way.
And then with just these visual elements we now have, as you can see, I try to take a representative selection of levels, but just and I'll do some live illustrations because I have a key bind set up to generate the level textures over and over again, but you can see we have not only are the visuals varied in terms of the we have the topper here we've got like this green.
Texture on top of this darker texture we've got this white texture here on top of this yellow.
All of these kind of have a different version of that.
We not only have that the backgrounds are randomly chosen.
There's only 3 of those, but you can see we have got like gaps in the levels here.
OK, gaps here we've got here we've got a little bush here we've got a little grass looking thing.
And then we've got these pillars as well that stick up and every, every sort of thing if we imagine sort of thinking of our level as this set of tiles that we can place in a line going left to right, top to bottom, you can sort of envision that, you know, maybe we want to say, OK, on this tile I I decide that I don't want any tiles this this particular column and then maybe on the next one it's like, OK, this one I want to just set the ground at normal we'll just define some sort of normal ground elevation.
OK, draw ground from the top to bottom.
OK, draw ground from the top to bottom and then whatever the first tile is, put a topper on that tile.
Mark it with a flag to say, hey, let's have that one be a topper tile because we don't want to draw it on these ones below here.
It'll look like a glitch.
We do want to draw it on whatever the topmost tile is.
Notice, for example, looking at here you can see this one has a topper.
It's the surface, and this one here doesn't where these same elevations are.
So you can do a lot of these sort of calculations looking as if we have like this mark.
Going across the screen placing tiles in sort of that direction you could think of your game in multiple different ways, your level in multiple different ways you can have multiple passes throughout your level to place things after the fact once you've laid your ground, for example, that's an excellent time to decide, OK, now I want coins here and like an arc or something, or I want to put like a block here that you can jump on to get something and there's multiple ways you can conceive of level generation.
We'll take a light look at a lot of these ideas.
And we'll start with level 0.
So let's just get a basic version up.
This is essentially the same thing that we did previously, except we're going to start to use our new graphics here.
Let's go ahead and close out character 4, we'll go to level 0.
And then I'm just gonna run this, see what it looks like.
We do indeed see this is random, so whatever it picks is just gonna be a random tile with a random topper.
The background is just gonna be random color.
And then if I press R, you'll see that I'm shifting through all these different graphics and so you can sort of see lots of really cool combinations here if I do just all of these.
And all it's doing that looks like Sonic, all it's doing is essentially just taking the tiles that we're rendering and so now we're instead of drawing this very primitive brick texture with the transparency layer for the sky that it was doing before, it's now deciding to randomly take, we're going to splice up all of our graphics from the from the tile set we're gonna.
Slice up all of our toppers and then we're just going to pick a random number between the number of tile sets and the number of toppers, grab that block of tiles or toppers, and then just index into it at whatever that ID is for the tile and then just put the same topper on top of it.
So let's go ahead and enter our source code here.
We'll see we've got a bunch of constants here.
These are just the number of tile sets wide and tall, all of our toppers and tiles are.
There were more toppers than there were tile sets.
We won't spend too much time looking at necessarily all of the code for chopping it up.
It's essentially just a calculating a set of offsets into our tile set.
So in the case of our.
We're going to chop up all of our tile sets based on generate quads, but then we're going to pass that into here.
We're going to then just essentially divide up that whole 1d array into a set of 2D or into a 2D array which is going to have each block of tiles within that particular tile set if we look at, for example.
The tiles here.
You can see that we have, you know, a block of tiles here that are all going to be one array, a block of tiles here that are going to be one array, a block of tiles here, and if we zoom in a little bit.
If we look at for example this, we'll look at this one as an example, you can see if we count in 16 blocks of tiles, it's a little bit blurry, so if you can get a little bit crisper, but you can see if we factor if we figure each of these sort of blocks here as VS code is rendering it as as these four tiles together is one tile, 116 pixel by 16 pixel tile.
This would be tile 1, this would be tile 2.
This would be tile 3, then 4, and then 5, and so we can see just looking at this that for example tile 3 here is our solid tile that we're going to use for all generation and then tile 5 is just our empty tile it's just transparent.
There's no pixels being rendered there if folks wanted to they could choose to use some of these tiles here for sort of.
Slopes if they wanted to implement slopes and offsets, vertical offsets for walking up slopes, or if you wanted rounded corners, you could choose to use these for the bottoms of certain things.
A lot of these other tiles are the same tile essentially as the solid tile but put together in a way that makes it easy to visualize sort of what the tile set looks like in a representative example of what the terrain ought to look like.
So it's really neat and we can essentially just index into this with some ID 3 and 53 being solid, 5 being sky essentially a ground and sky, and we can do the same thing for toppers which I've called tile tops, which is laid out in the exact same way by design and if we just zoom in a little bit.
We see that they align such that.
Whoops, if we go to where the purple begins here.
We can see we have 123.
The the topper would perfectly align with that tile 4 and then 5 for transparency.
So things line up perfectly between the tile sets and then it's a matter of just deciding, OK, if I have a tile set and a topper set, we call them topper sets, I can just draw the tile.
And then if I have a flag on that tile that says, OK, you should have a topper, just draw a topper as well right afterwards on top of that tile, and now we get this sort of layered effect of this top of the tile having this element on top of it.
So if you look down here, I'm just going to go ahead and scroll down just a little bit here because we have our, we're going to choose a random tile set and topper set which we can index into.
I'm going to come down to the draw function here which is going to be where we have our our 2D iteration over all of our tiles.
And so when we get our tile, we're going to do our usual draw, which has been previously what we did essentially which is just draw whatever that index into the tile set is.
We were just using the two tile set before but we're going to actually have whatever the tile set that we randomly choose these are just going to be two random numbers and then whatever the ID is at that tile, we're going to store the ID just on the tile now instead of it being a number we'll store the tile ID because we want to flag whether it has a topper, which is what this does right here.
So topper is just a flag, it's just a bully and it's basically going to be OK, is this tile like essentially touching, is this the top surface of the ground essentially when we do our when we create our level?
And if it's if it's true, then we should draw the top or else that means it's going to be below somewhere, somewhere under the ground under that first tile that gets done in our when we actually generate the level.
Here.
At the, I might have put it into a function, let's go ahead and scroll down just a little bit.
Generate level well now instead of just being a tile ID that goes into our table, it's going to actually set top or equal to whether Y is equal to 7, meaning that that's the point at which we begin to draw solid tiles.
And so we do this ternary expression in lua again.
So if Y is 7, then it'll be set to true, else it'll be false.
And then through that we can then check that topper flag in the future and determine whether we should draw a topper at the time we draw the regular tile.
And that's essentially how we can draw now varying tile sets and topper sets to give our levels a cool visual variety and you could semantically flag.
You could set constants equal to OK this particular topper is icy themed or it's fire themed or something, and then you could limit sort of what the actual topper set is that you can choose for your level and have there be meaning there.
We're just running the entire texture in this case and we're just deciding that it doesn't matter, none of that matters at all.
It's just completely random and so you never know what you're going to end up getting.
So that's level 0.
We've got flat levels.
So we talked about this a little bit in terms of how we can start to vary up the terrain, but if we think about the first step being, well, instead of just completely flat levels, what if we had some of the terrain actually jutting up from the ground?
In this example, we won't do anything more complicated than just a few tiles up, but if you sort of imagine, OK, right now we just essentially do a 2D loop which just at the time that Y is equal to 7 in our columns we start to draw tiles.
If we instead just decide per column iterating on the x axis through our our table, we'd say, OK.
There's a 1 in whatever chance that instead of starting the terrain at 7, we'll start it at 4 and then so that's going to result in tile 4 getting to be solid and then all of the tiles below it being ground and then tile 4 there getting set to the top or equals true instead of 7 and then now that's gonna stick up and then we just if we make it a certain percentage chance, then some of them will have pillars and some of them won't.
Let's take a look at what that looks like here.
I'm gonna go ahead and close these.
And then run sorry, level 1, not level 0.
And you can see here we do indeed have our character on the screen.
We can move around a little bit.
We can clip through the walls right now, so we're not actually doing any collision at all, but we can see that we're sort of essentially just there's a random chance between 1 and some relatively low number that instead of starting at 7, the ground just decides to start at 4.
And so we have just some visual variety.
It's not exciting, it's not appealing.
You occasionally get these sort of cool two block thick uh pillars and you could make some code, some logic in your code actually decide, OK, I'm.
Going to draw this column and the next column and the next column for example at 4 so that we guarantee a 3 block or whatever block with pillar, but right now we're just essentially relying on pure randomness and as a result.
Excuse me, sometimes you will get these.
Sort of groupings of these pillars we'll go into main uh of level one here and then we're gonna go down to our way down to our generation function here which is where this is all gonna take place.
This all gets moved into Lemaker.lua in our actual Mario distribution, but we can see here.
That what we're going to do is we just create our entire level with sky tiles to start with.
We just completely populate the whole thing with Sky.
Then we're going to start from the left, going all the way to the right of the screen, vice versa for camera, and what we're going to do is essentially create a 1 in 5 chance to spawn a pillar, and then if we do have that, then we're going to from 4 to 6 go ahead and create that as ground, and then we're going to set top or equal to pillars equal to 4.
And then if rather if pillar is equal to 4, we'll set topper to true on that tile and then always default or fall back to the ground so that no matter whether we draw a pillar or not, we'll always still be drawing the ground below it.
And then in the event that we did spawn a pillar, we don't want to also set the top around 7, but if we, if we didn't spawn a pillar, we do want to set it to true if we are at 7.
And as a result, we now have pillars throughout our level space, which is great.
Things are varying up a little bit.
Things are different, but um, you know, in Mario, one of the big perils is falling into chasms, and I think that that's also a very similar idea that we could explore.
So let's go ahead and take a look at what that means in level 2.
Gonna go ahead and open up level 2, close this.
Again, this is going to be done in our generate function at the very bottom of the program.
Everything is sort of condensed here.
And we're going to do the same thing.
We're going to populate everything with Sky first and then we're going to essentially a chasm is essentially not drawing any tiles at all in that particular part of the level.
And so really it's as simple as just choosing not to do the whole bit of logic that we just added in there.
We're just going to do this continue sort of go to way that LuA does continue.
We're just gonna say, OK, we're just going to go to continue if math.
random 7 is equal to 1.
And continue with this label here at the very bottom of this block below where we actually add tiles.
And so if we just skip the tiles, they'll remain pre-populated as sky and so now we just get chasms for free by just simply not generating anything and as a result, if we run this.
We do indeed have some chasms here.
Now it looks like it looks like there's a bug with the toppers on this particular example which I missed, but as you can see we do have chasms here that are being skipped over by just the pen in our code is essentially saying, OK, 1 in 7 chance, then we're going to show a we're just gonna not generate those tiles and then you know do do as we were doing before.
But it's a little ugly looking, I would, I would argue and what you could do besides the fact that the the texture is glitch, it's ugly in the sense that the actual width of the, the, uh, chasms is so narrow.
Typically in a game like this you might decide, OK, if I want a chasm, I probably want it to be at least 2 or 3 pixels wide, so you could elect to decide to draw.
To skip over from if you're at X is equal to 5, for example, as your chasm, you can just skip right to X is equal to 8 deliberately and then shift over a whole 3 or 4 tiles and then get a larger chasm as part of your design and then just never have single chasmed blocks.
It's up to you.
You could certainly do that.
This I would argue that that's probably good, although it's also arguable whether you might like the fact that there's multiple gaps here, you know, it's sort of intimidating sometimes to have a bunch of gaps in your level that you can just, you can sort of psychologically jump across them, but you might lose your nerve a little bit, so to speak, in the process.
And so that's ultimately going to be a matter of taste for whether or not you want to, how you want to design your levels.
So that's level 2, so chasm levels, and As folks noticed, you know, we had the ability to walk through our level and the levels generated just fine, but collision is a little bit weird.
It's a little bit different.
We're not quite colliding with the level at all except for the ground, and that's essentially a hack in that we're essentially hard coding where we stop on the ground.
We're not actually performing any collision detection.
So here we'll take a second to start to explore how Mario handles its collision detection.
So to begin exploring collision detection, I think it's important to begin to talk about how it's different with a tile map versus how it might have been done previously, at least it can be different for a tile map.
You could certainly use AABB for a tile map, and it works fine enough, but there are, I think, benefits to being able to simplify the collision for something like this, given that there are so many tiles in the space of the game.
And there's complexity involved in actually determining if you were to try to shrink down the tiles that are relevant in terms of figuring out what Mario ought to be testing for a collision with so it's easy actually if we know that we're in a tile map and all the tiles are static in their placement we can essentially just determine at a given pixel where what particular tile is there.
Based on Mario's position on Mario Avatar and determine, oh OK, that's a ground tile.
OK, then that means that that is a collidable tile I should shift Mario to be, you know, to the right of that or left of that tile or whatever relation.
The character has to that tile and it's moving, we can bump it to the outside edge of that tile versus having to do A, BB for maybe every tile in the whole screen, let alone the entire map, which is just, you know, especially when you have other entities that maybe are trying to collide with things like the snails, for example, that we'll be adding.
There's benefit to being able to just isolate your checks to wherever you're moving, whether you're walking, whatever you're doing jumping to just the immediately surrounded tiles based on the character's position we can get a lot of shortcuts and a lot of processing advantage by doing that.
So we won't be using AABB.
We'll be using a function called Point to tile, which will add to the tile map which allows us to just say, OK, at this XY coordinate we're going to divide by the map's sort of tile size and then whatever tile is there at that index in our tile map at YX we can determine based on it's whether it's collidable or not that we have actually collided with a tile, a physical tile, a ground tile, a whatever tile might be that we might use.
There's benefits to this for sure and when we test for collision in this new model, so we're no longer now taking rectangles and comparing our rectangle with those, we we are still doing that for what are called game objects which we'll see in a second, things like the bricks that we can jump up and hit or things like the the gems that come out of those if you were to add coins, if you were to think about, you know, also other entities, for example.
We do still do ABB throughout the code, but in different circumstances, but for our tile map, which has hundreds, maybe thousands of tiles, we're only going to essentially use a sort of conversion between our character's point, our characters's coordinates to whatever the tiles are in the world.
And so for example, if we're colliding with something that's above our head, which is typically only something we're going to do when we're jumping, we can say, OK. At the character's essentially left top left coordinate and top right coordinate, we'll check whatever the tiles exist that are there and so that'll that'll accommodate any particular we don't want to just check the top left because if there's a tile here and we're only checking for top left, well, we'd clip through this right tile essentially.
So we want to check both edges of the of our character to ensure because we can be between two tiles, we want to essentially.
Ensure that we don't collide with whatever both edges are.
Same thing for below us.
If we're falling down or if we're walking or doing anything, we want to check the bottom left and bottom right of our character's avatar so that we don't just isolate collision checks to this, this edge and end up actually clipping through, for example, this tile.
We want to check both, both sides of the character's bottom.
And then here we have on the left side if we're moving to the left, we want to essentially do the exact same thing characters top left, characters bottom left, and then the characters moving to the right, characters top right, and then the bottom right, and these will get applied in the walking state and in the falling state and then the jumping state because we can move left and right when you're jumping and falling and walking.
And so with those.
Assuming that you detect or you calculate, OK, whatever this XY is, whatever this XY is, and then divide by the maps tile size, you'll arrive at the X and Y indices into your table in order to actually calculate, actually figure out what the tile is that's there.
And then you can then say, oh, is that ground tile?
Is it a sky tile?
Sky tiles aren't collidable ground tiles are collidable, and then you're only ever checking pretty much depending on your direction, 2 to 4 tiles at any one time instead of maybe hundreds or thousands of tiles.
It's a very sort of like 0 of 1 type of operation.
Um, and then we have also a discussion about entities that we'll have to talk about.
We can briefly look at the collision before we maybe talk about the entities because that's a whole other subject, entities being will be, for example, the player and the snail and various other things.
Let's take a look just at what the collision, how the collision is implemented in the code base.
And we'll be overall probably skimming over some details and encouraging folks to read some of this because the code base is quite large, but we'll be looking at some of the important things and I'll point to and explain some of the important aspects here.
The player is essentially in Mario the gist of the has a lot of the underlying behavior and functions needed for our actual character, so you'll see that he inherits from Entity, which is sort of like a base class that has an X and a Y and just a collision function and it's kind of just the overall representation for something that should move around and interact in your game and some game engines and frameworks might use entity to be just about everything or game object to be just about anything.
In this particular case, we'll think of it as sort of like moving things that can be agents.
And you'll see that there are a set of collision detection functions.
For example, we have check left collisions, we have check right collisions, we have check object collisions.
So there are also game objects in our world.
Again, I alluded to this being, for example, the blocks that you can jump up and hit or the the gems that you can collect if you were to hit.
One of those and they spawned a gem.
We want the game objects and tiles to be different because the game objects have behavior that is particular to them that is special sort of and you could also therefore put them anywhere you want in your game world and they're not beholden to this static coordinate system that you have in your space and also they can be drawn on top.
Of a tile, for example, you might have a brick in your world that's a game object that you might want to draw on top of sky, and it's not really possible unless you have like multiple tile maps which introduces complexity, but you can't really have that unless your game objects are kind of separate from your tiles like your tiles, if you're using this sort of model we're using today.
And with the advantages that it has with collision and whatnot, you're kind of stuck using a static tile-based system, and then you have to layer on top of that to achieve some of the effects that you want.
But essentially we have to check object collision separately from tiles because the objects using AABB collision are sort of like separate entities and you see this collides function here.
We're sort of using the same AABB that we've used in the past and all of these things.
Have those same functions implemented for them, but this left and right collision detection function is important because it has this self.
map.
Every the player maintains a reference to the game's map in our in our code base and then this point to tile function.
So what we're doing is we're essentially saying, OK, I'm going to take whatever our X and Y is plus 1.
We're doing that just to sort of shrink our hit box just a little bit.
To give us a little bit of a little bit of uh wiggle room flexibility with how we move, move around and don't overly.
I've noticed that if you have it be too firmly set to your boundaries in a game like this, collision is a little bit, it feels too constrained, it feels too you you hit things and you don't feel like you should.
It's better to just kind of it feels better to just kind of take a couple pixels off in the form of our XY and then our width and height.
And so what we can do is essentially say if we're checking for left collisions we'll say, OK, what's the tile to the top left and the bottom left of me which we saw on our our diagram before.
We do that by taking our X and our Y and then our bottom left for the top left, and the bottom left is essentially our X and then our Y plus height.
And then we can use this operation called Point to tile, which if I open up the tile map.
It is just essentially there's a bounced check here just to ensure that our we actually can be within our map.
We don't want to index outside of our our array of tiles, but what we essentially do is we just divide Y by tile size, add one because everything is one indexed in Lua, same thing with our X, and then we therefore can just directly translate pixel space into tile space into our 2D array.
A 2D array.
Nothing is really too fancy here, just a width and height in the tiles array, much like we saw in our other examples.
It gets populated.
There's other places where it's going to actually get generated in our level maker, but as you can see, none none of what we see here is all that different than what we've done before.
But now we can essentially just check any particular pixel in our world, convert that to whatever the tile is there adding one to account for Lewis tile based indices.
And then now, OK, is the tile there solid?
Is it not?
So if we're moving left, let's check whatever the whatever the tiles are to our left or we're moving to the right, let's check whatever they are to the right.
If we're moving up, check whatever is above us.
If we're going down, check whatever's below us.
That's effectively the gist behind getting collision detection to work.
We want to, you know, reset our X value if we end up actually colliding with something, which is what we do here.
And then we're gonna do a little trick here to avoid checking for object collisions.
Uh, we're going to actually shift our Y position up so that if we're walking and we're moving left and right, if we're, if we shift our Y position up by one pixel just temporarily.
Check for collisions and then move our Y position back to normal.
We can still walk because if we're walking we're gonna be right above that object and not colliding with it, but this allows us to just check for collisions in the event that we are, for example, jumping or falling and make sure that we don't actually clip into something.
And then if we actually did end up colliding with something not in that view of that we're walking on top of a block, then we can actually just essentially perform a reset on whatever our walk speed by delta time was moving left and right, which is how we do all of our left right movement here and so that's check left collisions, check right collisions this will end up getting called so if I open up the play state.
We have a play state and a start state as normal folks might notice we have an entity states folder which we'll get to in a second, and this is important, which is where we're gonna start to tie in some of the earlier ideas we talked about with animations and the like, but.
Here we are preserving, we have a level in our play state which is Lemaker.
generate 100 to 10, that's the width and the height of the level.
Folks in part of the assignment will be encouraged to increase the level, increase the size of the level every time the level gets every time that they make it to the end of the level which they have to implement the flag for, they're going to want to increase the size of the level maker.
Increase the size of the level generated by the level maker rather, but skipping all these states and whatnot.
We can go ahead.
We see that there's an update camera function here which just has a camera X and a background X which manipulates the again this is the the overall sort of love.graphics.translate offset that we saw previously and then same thing for the background X as well.
This is just essentially taking whatever the X is divided by 3 mod 256 so that it's going to end up slowly sort of moving to the right as the duration of the uh as they keep moving the camera X throughout the level and it's going to lead to that slow sort of like parallax effect that we saw previously.
Um, we spawn all the enemies here.
The actual collision takes place in.
All of the actual individual player states which then get called so if we actually go to player walking state for example.
So this is a little bit of a a look forward in terms of where things are actually, actually, uh, we're gonna talk about entities and entity states in just a moment, but this is just to kind of illustrate where the collision code is actually taking place.
There's going to be some amount of the left and right collisions going to be checked and certain things are gonna happen depending on which state we're in, but ultimately these defer to the checked left collisions and check right collisions if we're moving left and right because these check left and check right collisions will occur if we're walking, if we're jumping, if we're falling, they kind of are like they transcend any individual entity state or yeah any individual entity state.
And so here is where they end up getting called where ultimately this walking state gets updated within the play state and then as a result we can sort of like isolate our entity behavior a little bit but have general functions that allow us to do calculations and collisions that are going to occur across multiple states and so that's sort of what's happening here now I'm sort of getting ahead of myself a little bit and so I think now it's a good time to maybe look at for example what an entity state is and what we've been.
Sort of alluding to and what that essentially is we have to sort of talk about what entities are to begin with and so an entity is just something that has an XY it does behavior it interacts in your world people have different definitions of what this is and.
The benefit of having entities is that they can be sort of like generalizable in a sense that you can isolate certain types of underlying shared behavior and ideas.
For example, every entity has an X and Y, every entity has a width and a height.
Every entity has some sort of collision detection capability, therefore with another entity, assuming that everything is access aligned.
And these depending on your game, this might be even more behavior than this, but then you can instantiate things that inherit from those behaviors sort of just classical object oriented inheritance in our model, and then have these sort of like more specific types of entities.
For example, a player is an entity that we just happen to be able to steer.
A snail is an entity that is going to be able to target the player with its own states.
And what's cool is that we can also think we can build on this idea and think of entities as a container for sort of these behaviors that now we can visualize in the same way that we visualize the game states as being these containers for what it means to be in a particular part of the game.
We can, we can envision the entities as being their own state machines that are engaged in a particular behavior which we actually had seen in previous lectures.
We saw the overall diagram of like Mario jumping and doing all these different things, and that's essentially what an entity can do is it'll have a maybe an idle state for example in the case of our character, which then if you were to move left or right, this is going to transition you to walking.
You're now walking.
And you're now in that state, but if you were to press space bar, you could go from that state to jumping.
You could go from idle to jumping as well.
So now you can sort of go to that state through two different mechanisms.
And then by just letting go of input, for example, while walking, you go back to idle.
If you get hit, maybe you go into a death animation or something.
And this is in the same way that game state can be taken from these sorts of like convoluted if statements and whatnot as this sort of branching behavior you can just encapsulate these in a state machine and then on your entity on your player on whatever entity just store a state machine that holds all of your states and then all of these states can then be updated one at a time and then rendered and.
You know, interacted with in the same way that we evolved our, our implementation of the game state machine.
We'll do the same thing with entities.
And then uh one other piece that we should look at is game objects real quickly let's look at what the entities look like before we do that.
So again, the entity for transition here.
Is just essentially a wrapper class, so I'm gonna go ahead, close all of these.
Game object is also a rather a a wrapper class, but we have an entity folder for states.
But if I were to go to just the base folder, we have just this entity which takes in a deaf.
You'll see it has a position and a velocity.
Most any entity we've ever interacted with in our games all have an X and a Y and a velocity and a width and a height.
They'll all have a texture.
They'll all have a state machine.
They're gonna have a set of states, a direction, a reference to the map so that they can actually see other aspects of the map and the tiles and whatnot they want to interact with.
And then um the level itself, which is going to have the uh we're going to have a game level object which contains all of the game objects, entities, and the tile map.
So through that we can then see a few functions here.
We've got to change state function which is very similar to that state machine that we had previously, um, the same type of function actually we're using literally the same state machine class for this.
Except that now we are instead of having our game state objects, there are classes that we're implementing with update, render, enter, exit, and all those things we just do that for every entity we want to model.
If we want to model a snail, a snail is gonna have an update, an idle state, a chase state.
The player's gonna have a jump state, so on and so forth.
You get to have ultimately quite a lot of different states depending on what your game, how many creatures and whatnot you have modeled in your world, and so there are ways you might seek to try to reduce the amount of code, explicit code for that, and use what might be called like data driven design to do this, which we might explore a little bit as we get to the Pokemon lecture, for example, and even Zelda's lecture.
But for right now we can model these in code with classes, the snail, the player, whatever creature you could imagine adding.
And then as a result, If we think of these in terms of states, much like we saw previously with the game states, we can just create a new folder in this case just entity.
So we've separated our states sort of semantically between game states and entity states within the entity states we could have probably put this in a player folder, but I just put that in the in the base folder because this is sort of like probably the more important for the sake of lecture set of states, but we also have a snail folder here to separate the snail states from the player states.
You can see, you know, the player walking state is going to encapsulate all the behavior of what it means to be walking.
So if you're, if you're holding left and right, you're going to want to check your left and right collisions while you're walking and ultimately it's, it's, it's pretty simple.
If you're the one key thing is that like if you're not pressing left and right but you're in walking again we need a way to move between states so we're going to want to change our state here to idle instead of to.
Instead of keeping us in the walking state.
Um, and then also if we press jump, we want to go into the jump state because you can jump while you're walking.
You don't, you're not just limited to jumping while you're idle.
If we go into the idle state.
You'll see that if we press left or right, we'll go into walking and then if we press space we'll jump, so kind of the same idea.
If we're in the falling state, so falling just means that gravity is positive and our wipe and we're we're not like on the ground.
So if we end up going here, we'll see.
If our, if we're in the falling state um and then we actually collided with with the ground is what this particular thing is checking for, we're going to want to immediately go into walking else go to idle.
So essentially if we're if we're falling and we hit the ground, we're going to win again we check for point to tile below us, set our Y position up just a little bit so we're we're static set our D to 0.
And then we can just immediately change to whether we're walking or idle depending on whether we're already holding left or right.
If you don't do it, if you're not doing this check here and you jump and you're holding left or right, there'll be like a frame where your character is in the idle state and it feels weird, it's clunky, so you want to just go right into left or right or idle.
And then in this particular instance too, this is where we actually have the death sort of condition for the game.
Like if you in our in our particular game there's really only one way or there's two ways to get killed, and that's to fall below the edge of the the ground or to let a snail touch you while you're on the ground.
And so this is one of those instances which is that the G state machine will change to start, which again this is our for our global state machine, this is our is our game state machine.
We're going to check to see whether our Y is greater than virtual height, meaning that we're below the map, but we've sort of arranged the map so that it's right, uh, we're always rendering it above where that virtual height limit is.
So if we just essentially go below the map, falling into a chasm, the game will go back to the start state which is at the very beginning.
Uh, of the game that we saw and then here we also have some consumable code.
So whenever we're falling or we're walking or we're jumping, we can collide with consumables in the world which are game objects.
We've looked at game objects.
Why don't we transition to that and just have a little brief talk about what game objects are.
So game objects are you could you could in theory put these together with entities in a way just mark entities that are non.
No they're not agents.
They're not moving around.
They're not doing anything that's sort of representative of some sort of sentient thing, but in the case of our game, we're going to say that game objects are this class of item or object that is interactive in the sense that you can collide with it or you can hit it and it will maybe do something for you, but it's not an entity, so it doesn't have states, it doesn't do all of those things.
And so game objects are perfect for things that are relatively static but should have some behavior that triggers when you interact with them or touch them or do whatever.
And in this case the gems here are game objects, as are the blocks when you actually jump up and hit those because those have a behavior that behavior being that when you hit the block, a gem has a chance to spawn up and come up and then be claimable, which is itself a game object.
So the block can spawn.
A game object itself it's an object that can spawn a game object and so we've differentiated between these and you'll see these commonly depending on which environment you're in, which engine you're in some engines will use game object to be literally anything, including an entity.
Some will separate them.
Some will just have entities and they will use what's called an entity component system or ECS where literally you just have everything as an entity.
And then it contains its behavior not through inheriting from some base class and then getting more specific but rather just having some sort of component within it that determines its behavior and then in order for all entities to interact they just iterate through all of their components and then just their behavior is sort of emergent as a combination of all of their interacting components.
Those are called entity component system unity the engine is an entity component system um.
As an illustrative example.
So that brings us, I think, also to the topic of Power Up, which is essentially just a game object that you could think of as just affecting some sort of state on the player.
So in this particular example there we we use a gem, but you could decide to implement a power up, for example, that the gem in our example just improves your score, but you could decide to increase the size of your character or to turn your character invincible, which will be part of the problem set.
So this would be relevant for the problems that have a star or have some sort of object that the player can touch.
Maybe it has a chance to spawn from the brick instead of the gem.
And then particle effect should happen, maybe a rainbow effect on your character, and then right now when you walk into a snail, you will just die if you're not jumping, if you're not falling rather, but you should ideally, if you were invincible, be able to just touch the snail at that point, which is very similar to what Mario gets to do in the game when he gets a superstar and then defeat the snails that way.
So let's take a look now at how game objects are implemented in our code.
If we go ahead and transition over here, this is the perfect place to to show it.
So in our world we have a game level as sort of this container for because we want to have game objects and a tile map and entities all together as sort of this trifecta of things that interact together we're going to put those into what's called a game level, which is this container for all three.
So if I open up game level here.
You'll see it literally just has entities, objects, and then a tile map.
And then what this does is we can just say, OK, we can just iterate through all the objects if we need to, or the entities to see which things collided with each other.
The tile map again, we're doing point to tile operations for collision, so we don't have to worry so much about that.
But all the objects can therefore when we do collision code for depending on which state we're in with the player we can iterate over each object and determine, OK, are we colliding with it?
If we are, is it consumable, meaning did we give it this flag called consumable and then a function maybe that can trigger an anonymous function as we've seen that will do some behavior that affects the player or the map or so on and so forth.
If we go to game object, we'll just take a look at what that actually looks like.
A game object is essentially a shell in the same way that an entity is, except a game object expects a set of things that will trigger, uh, depending on how it's interacted with in our world, and we're sort of arbitrarily choosing how to differentiate and which things it can have and which flags it can have, but this is similar more or less to what you might expect to occur in uh in an engine where you have an on collider on consume callback or function that will trigger.
When your entity or when some entity that is flagged to be capable of consuming those objects indeed does consume those objects.
For example, the snail isn't set up such that it can collide with or consume gems or power ups or whatnot, but you can envision a world where maybe you do have enemies that are vying for resources or vying for power up.
And then maybe they can also collide or interact with or consume these items and so we have that ability should we choose to do that through the functions defined on each entity state.
But as you can see, the game object is a very simple function.
It just has the ability to render itself effectively and collide with another target which is assumed to have an access aligned bounding box.
And it doesn't even have anything set up and update because we don't have any objects currently that should update or any logic for that, but you could very easily extend this to some kind of an updable thing.
You could have maybe game objects that animate or game objects that move around and do various things.
Technically a coin, for example, is going to have some sort of a.
Typically in a game it's gonna have some sort of like oscillating Y position maybe maybe some sort of sinusoidal movement in that way and you could you could put that in update if you wanted to something like that maybe that's based on a flag that it has set in the game object.
What we're mostly concerned about with is it's collidable, consumable, on collide and on consume flags which.
If we go into the level maker, this is where all of that is going to end up being created.
So recall that we showed a very simple version of a level, how to create a level previously in our examples, so level 0 level 1, where we just had chasms and whatnot, but when we're doing a full proper level here, we're going to want to not only maybe add some props which will be game objects that are just not collidable right, which will just render them wherever we place them, but we're going to.
Want for example gems that are consumable and collidable for our score.
We're going to want the blocks so when we jump up and hit them they might spawn a game object or they might spawn a game object that is a gem that then is consumable.
And so all of those things need to be determined sort of in advance and we do that through a iteration of the level maker class which is sort of in spirit to similar to what we did in Breakout.
We had a sort of like level creator there as well.
You'll notice that in some ways it's going to look very similar to the level maker that we were using in our previous examples and just the test code or the lead up code, but we're actually now doing quite a bit more.
If we scroll down here, we're doing all the usual chance to be empty, chance to lay out space, chance to be pillars and whatnot, but when we generate pillars, we can, for example, generate a bush on pillar which will just be, you know, we even saw an example of that in the in the screenshots where we have this objects list.
This is going to be returned.
We're going to return a game level from this level maker and recall it has the objects, the entities, the tile map.
So objects are going to be separate from tiles, so we're going to have a chance to generate 1 and 8 on top of a pillar just if we did generate a pillar.
The texture, the game object expects a texture, so the game object will know to index into whatever the right texture is.
It'll know which quad.
Therefore we should by quad will be it'll be the frame key here and it'll just be a random in this case it's just a random bush in this case from from the list of possible bushes that are there.
The spreadsheet has a ton of varieties and colors and things, so wherever possible we've added some randomization, but essentially it will just be offset based on the top of wherever we are in our list of remember the it generates that index 4 so we want to shift it minus 1 so it's on top of that 4th, that 4th tile.
But then they have it just has a width and a height.
It's going to be able to draw itself.
It's not collidable.
It's collidible's false, so our character will just like when it queries all the game objects that it can collide with, it will just bypass checking collision on it, so it'll just go in front of it.
And then this is just an example of like a static game object that just essentially is a glorified Sprite renderer.
But if you come down, for example, past this other Bush example to the jump block example, which is the more complicated game object in the distro.
See it in a lot of ways is very similar to everything else.
It's just it has a texture, an X and ay, a width and a height, a frame.
It just can be a random jump block.
We saw in our example there were lots of different objects, lots of different games, lots of different jump block tiles for it to choose, but notice that we have collidable is true, so it will perform collision detection when we check, you know, check object collisions in our player class as the function.
Um, it's going to have a hit flag that's false.
So if it's been hit already, we don't want it to generate a gem or do its chance to generate a gem, for example, and you might render this with a different texture in theory.
We didn't go through the paint to do that in this, but you could, if, for example, in Mario, the actual game, if you hit a block that's got a coin in it, typically what happens is it starts off yellow and then it goes into some sort of like faded color after you've hit it, hit it to show that you've hit it.
Um, so solid is true, meaning that it actually has collision detection and pushes us outside of it.
You can be collidable and technically not be solid and still like the collision will trigger, but you won't actually get pushed outside of it.
On collide is the actual function that's going to trigger when we hit it.
And so what this means is that essentially if we imagine that on collide gets called at the moment in this in this case it's when we jump and we hit it.
So on collide will only trigger in our code we're going to specifically trigger it when we actually jump.
If the object has not been hit, we're going to have a random chance to then spawn notice another game object or actually triggering, and this is all also, by the way, in an anonymous in a function, an anonymous function.
It's getting assigned the label on collide, so I guess it's not completely anonymous, but it effectively is anonymous in that we can, we could set this to something else later if we wanted to and swap this out.
But on collide is essentially this function that's going to trigger later will trigger on collide.
It's going to 1 in 20 or 20% chance, generate a new game object and now it's the gem, so it's gonna be the gem, it's going to end up setting itself up to above the actual block.
Um, which we do here, we actually take the gem itself.
We're using timer.
tween, which we saw last week over 0.1 seconds.
We'll set it's Y to be equal to the block height minus 2 times tile size, which will end up shifting it up above the above the actual block height.
And then we're going to play a power, a power up sound and so this is gonna, this is a sort of way to tie in one of the things we saw last week, which is this idea of smooth movement.
So when we actually hit it, we're going to actually spawn the block with a sort of like interpolated Y movement so that it's like the smooth movement versus just like instantly, oh there's the there's the gem.
Um, notice it's got a consumable is true.
It's got an on consume function callback here which assumes that it takes a player and an object.
We'll play a sound here when we do that to show that we've picked it up and then we're going to increase our score by 100.
And then in our actual check object collision function is where these sorts of things get handled in our player.
If we go back to player here.
If we go to check object collisions.
This gets called across various different states across our jump state, our fall state, our walking state because all of those different states you can you can consume a gem or whatnot in all of them.
You can be jumping, you can be falling, you can be moving, just not idle is typically where you would not be consuming unless you had movable movable consumables, then you would want to add that to your idle state because it could move into your hit box in theory, but as you can see.
Every time we call this check object collisions function, we're going to do a check for whether we've collided with it, which is just the AABB collision detection.
If it's solid, we're going to insert it as being like a collided object that we've collided with, and then if it's consumable, what we're going to instead do is we're going to actually call that on consume function object.
On consume.
We're going to pass our ourselves as the first argument, then object itself, and then we're going to remove from the actual objects inside of the inside of level.
objects we maintain a reference to self.level.objects as the player so the player can do these sorts of operations.
We're going to then remove it from the table so that it immediately gets taken out of the world so that we no longer see that anymore when it does the loop to render over all of our game objects, it'll no longer be there, it'll be completely gone.
And again this occurs throughout all of the all of the various player states that we can and potentially uh consume a game object or collide with a game object within.
I think the last thing that I'd like to look at uh in order to illustrate sort of what some of the stuff we've talked about is the snail states which we haven't looked at the snails sort of being this entity that exists sort of that gets spawned at the level maker creation time again based on a random chance.
Just like the game objects are spawned, it's gonna have an X and a Y, it's gonna be placed on a particular tile, but these have their own states, so notice that it has an idle state.
So in the idle state what's kind of cool is that it has its own animation it's just a single frame of it being in the shell.
Which is kind of just a cute thing and then its behavior is such that essentially it has a wait timer or a wait period where it will decide, OK, after that wait period I'm going to change my state to be either moving or chasing and chasing is just essentially if the difference between the player's X and the snail's X is 5 tiles or less, it will set to be chasing the player, which just means wherever the player's X is, go towards that X, which we can see here, snail chasing state.
These these states, by the way, are instantiated, and they take references to in the case of the snail, the snail needs to have a reference to the player always.
So when these states get defined initially, they all get the player as a reference to them.
So this is important if you have any operation that you need to take place within your world, between your entities or the player, often the most expeditious thing to do is to just.
Pass in a reference to the player or the map assuming that you don't have just this gargantuan list of things and to try to have these sort of high level relationships between things you in an ideal sense you want to limit as you know, don't do too much of this between things that don't need to talk to each other, but sometimes the cleanest, easiest way to actually get things to talk to each other is to have them passed as references and held and shared between them.
So here we have a chasing state.
It just always will be looking to see basically is the difference between RX and the players' X 5 tiles, and if so like keep moving in that direction, otherwise there's a chance for it to actually or keep essentially moving left or right based on where the difference is, whether it's left or right, or change it to be just moving, meaning that the moving state essentially just chooses a random destination.
So if we go to here to the moving state, we'll see.
That it has a moving timer which is just going to be, you know, here it's just flipping a coin left or right this initial it's initial moving direction, but it'll always be doing that essentially every time we end up deciding that we should choose a new direction.
It would be based on just the same kind of timer as the wait timer was where we just decide, OK, after X period of time I've been moving to the right, let's move to the left instead or let's keep moving to the right or you know whatever whatever the coin flip decides.
And then there's a 1 in 4 chance for it instead to just go idle for a little bit just to mix up behavior and this is essentially how you can get some interesting sort of like lifelike, you know, sort of like fake lifelike behavior or some interesting interaction in your game without going too crazy.
That's the gist behind overall most of the objectives that people will need for the assignment if we transition here.
Essentially assignment 4 is that we should first off, I don't think we had a chance to even see that it happened in our testing, but right now there isn't really a cap on the player falling or rather there isn't really an assurance that there's going to be solid ground beneath the player when they fall.
So you're going to want to do that.
This is an easy initial thing to also get people used to the level maker code.
The key and lock are in the Sprite sheet, and these are cool.
They're different colors.
Folks will have to choose between one color of the key and the same color of a lock, which will open up the end goal.
So this is just to get people used to using game objects.
So the key will basically enable the player to interact with the lock so that it will unlock the goal existing at the end of the game level, meaning that the flag should then spawn.
That or folks can choose if they want to to spawn the flag just in a state that is empty or not functional.
Either way, the lock has to open the flag has to make it accessible.
And then once they do that, once the player touches the actual flag at the end of the level, they should generate the next level.
So right now there isn't really a way to transition between levels, but take the level that you're in right now and then just generate a new level and then make sure that it's just a little bit bigger than the level that you're currently in.
Additionally, folks might have noticed on the actual Sprite sheet there's an animation for the character to climb up a ladder, and there's a ladder as well.
So the next part would be to generate pillars that are higher than 4 tiles tall, so something that the player cannot jump over.
Currently they can just jump over pillars, but we're going to make it such that maybe jump, maybe generate it 1 or 2 tiles taller so that you can't actually generate or jump over it.
And then add a ladder to the side of it so that you can climb the ladder instead, get to the top of the pillar and then jump down or fall down.
This is implying that you'll need, of course, a new state, a ladder climbing state in order to and therefore model the relationship, model the ways you get out of that state in order to allow this to be possible and therefore allow you to sort of climb on the y axis sort of more arbitrarily.
And then also the last thing would be, which we alluded to briefly, a power up.
So right now we just have gems.
You can, you can interact with the gem to increase your score, but folks should choose like a star or something else that will allow you to become invincible and then therefore interact with a snail if you're just walking, for example, and have Sort of particle effect folks maybe can even add music if they want to, which is what Mario does but add a particle effect of the likes of that when you're walking into a snail then with that new invincibility mode on the superstar mode on, you defeat the snail without having to jump on top of it, and it does not affect you.
You are invincible.
So that was it for Mario.
Next time we're going to do a few things in like a kind of a similar era, which is Zelda, which is one of the, you know, the first game that I remember playing, which was the Super Nintendo Legend of Zelda linked to the past.
In this case we'll do a simple example.
We even included the hearts in there, but you know, so far we haven't really done a top down perspective type game, so we'll explore how to do that movement in two axes, therefore left and right, a little bit different than platformers with gravity, but in some ways simpler, and we'll be looking at a few other cool tricks like stenciling.
Triggers and events will be important as folks might be able to see there's a switch on the ground there, a bunch of enemies also all moving around and interacting.
We we're taking a look at that sort of basic AI type stuff which is kind of an extension of what we did today, but there's a sort of set of doors around the side of the level there.
Folks can hit the switch that will trigger an event which will then open the doors, those will be listening to an event.
Uh, that gets fired that then will get handled through a function that will open the doors and then folks can then move through the dungeon and then go to another randomly generated room with switches and doors and then also really key folks will be able to actually swing a sword and inflict damage, and that's going to require the use of a hurt box.
And then lastly, in particular in relation to the problem set, we'll be exploring the idea of inventory and how to model that in order to show how to, for example, acquire and use a boomerang to attack enemies from a distance.
So that was Mario.
It was probably one of my at least more favorite lectures in terms of I really like the idea of these virtual worlds that we get to explore and use tile maps to represent that.
There's a lot of things we just introduced game objects and entities and all these things, but I think it sets the stage for a lot of really cool things coming forward, so we look forward to that next time.
This is CS52D, Super Mario, we'll see you next time.
Related Videos
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 views•2026-05-28
How agent o11y differs from traditional o11y — Phil Hetzel, Braintrust
aiDotEngineer
450 views•2026-05-28
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation💯✅
LearnwithSahera
1K views•2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 views•2026-05-29
Search Algorithms Explained in 60 Seconds! 🤖💨
samarthtuliofficial
218 views•2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 views•2026-05-30
Introduction to Problem Solving Part - 1 | Lecture 1 | Intermediate DSA
ascensionix
107 views•2026-05-29
So What's Odin Lang Even Good For
TechOverTea
131 views•2026-06-01











