# CS50 2D - Lecture 5 - Legend of Zelda

## Метаданные

- **Канал:** CS50
- **YouTube:** https://www.youtube.com/watch?v=iwjilbptPFM
- **Дата:** 26.04.2026
- **Длительность:** 2:05:03
- **Просмотры:** 6,007

## Описание

This is Lecture 5 of CS50 2D — recreate the classic Legend of Zelda game mechanics, dungeon generation, hitbox design, screen scrolling, and data-driven game development.

To take the course for a certificate, register at cs50.edx.org/2d. 

***

This is CS50, Harvard University's introduction to the intellectual enterprises of computer science and the art of programming.

***

HOW TO SUBSCRIBE

http://www.youtube.com/subscription_center?add_user=cs50tv

HOW TO TAKE CS50

edX: https://cs50.edx.org/
Harvard Extension School: https://cs50.harvard.edu/extension
Harvard Summer School: https://cs50.harvard.edu/summer
OpenCourseWare: https://cs50.harvard.edu/x

HOW TO JOIN CS50 COMMUNITIES

Bluesky: https://bsky.app/profile/cs50.harvard.edu
Discord: https://discord.gg/cs50
Ed: https://cs50.edx.org/ed
Facebook Group: https://www.facebook.com/groups/cs50/
Faceboook Page: https://www.facebook.com/cs50/
GitHub: https://github.com/cs50
Gitter: https://gitter.im/cs50/x
Instagram: https://instagram.com/cs50
LinkedIn Group: https://www.linkedin.com/groups/7437240/
LinkedIn Page: https://www.linkedin.com/school/cs50/
Medium: https://cs50.medium.com/
Quora: https://www.quora.com/topic/CS50
Reddit: https://www.reddit.com/r/cs50/
Slack: https://cs50.edx.org/slack
Snapchat: https://www.snapchat.com/add/cs50
SoundCloud: https://soundcloud.com/cs50
Stack Exchange: https://cs50.stackexchange.com/
Telegram: https://t.me/cs50x
Threads: https://www.threads.net/@cs50
TikTok: https://www.tiktok.com/@cs50
Twitter: https://twitter.com/cs50
Twitter Community: https://twitter.com/i/communities/1722308663522594923
YouTube: http://www.youtube.com/cs50

HOW TO FOLLOW DAVID J. MALAN

Facebook: https://www.facebook.com/dmalan
GitHub: https://github.com/dmalan
Instagram: https://www.instagram.com/davidjmalan/
LinkedIn: https://www.linkedin.com/in/malan/
Quora: https://www.quora.com/profile/David-J-Malan
Threads: https://www.threads.net/@davidjmalan
TikTok: https://www.tiktok.com/@davidjmalan
Twitter: https://twitter.com/davidjmalan

***

CS50 SHOP

https://cs50.harvardshop.com/

***

LICENSE

CC BY-NC-SA 4.0
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License
https://creativecommons.org/licenses/by-nc-sa/4.0/

David J. Malan
https://cs.harvard.edu/malan
malan@harvard.edu

## Содержание

### [0:00](https://www.youtube.com/watch?v=iwjilbptPFM) Segment 1 (00:00 - 05:00)

Hello world. This is CS52D lecture 5. Today we're going to be looking at The Legend of Zelda, which is a favorite franchise of mine amidst Mario. I like Mario quite a bit as well, but this was one of the first series that I have memory certainly playing. And also we styled it based on the original cartridge for the NES was actually colored gold, so was its sequel, which I thought was kind of a cool thing. This is what the original Zelda looks like. And this is sort of like a famous line that people quote online all the time. It gets referenced in various games. It's a throwback uh and sort of this is the very first thing you see when you go into the game. You start off, you see this cave, you go in, you grab a sword. It at its time was one of the only games that sort of had this early idea of an open world that you could go out and explore. And it accomplished this via having a bunch of sort of discrete separate screens that you could transition in and across. This is a screenshot of a dungeon. You would also go and navigate sort of these labyrinth type structures. And in today's codebase, we'll be primarily focused on this sort of view of what Zelda looks like. Uh looking at a dungeon that gets generated and then we can traverse it. And we won't have all the bells and whistles, per se, but we'll have the sort of famous scrolling and some monsters and the ability to swing your sword and so on and so forth. This was the first Zelda that I played as a kid. This is Link to the Past for the Super Nintendo, which took the formula and sort of expanded on it. Added two different worlds and a bunch of other cool features to it. lot of different items which is part of the problem set will be how to add you know not to get too ahead of myself but a item that's very famous in the franchise being the boomerang which is actually shown here at the top of the screen sort of signified that you can throw this boomerang that will freeze enemies and so on and to this day Zelda is still being produced it's still a very popular very well-selling franchise much like Mario is on the Switch this is Tears of the Kingdom which came out in 2023 very good game which was the sequel to Breath of the Wild also a Switch game in 2017. Very famous, still to this day, one of the top selling franchises. And so some of the things we'll be taking a look at today, we've so far when we've looked at 2D games, we've mostly looked at sort of like the side view of what it means to be in a 2D game. Uh games like Mario are they are called sidescrolling platformers meaning that you scroll from the side and you can jump around and sort of gravity works on the Y ais but in a game like Zelda you're actually taking more of a bird's eye view and actually looking more from the top as if you were you know literally by the name sort of a bird looking down and it's different than a sidescrolling game in that you can actually move across the X and the Y axis unlike well you can in a platformer but in this kind of game you actually have full movement across the X and the Y axis. Your Y-axis isn't just a jump determined by gravity. You can add on to that depending on which game. Even Zelda has done this with things various things like the rock feather in uh various titles. But today, we'll be looking at what this means, and it's really not much of an evolution on what we've already looked at, but there are some site differences to take into consideration. We'll look at dungeon generation, how to actually create a dungeon in code, which allow us to test that our transitions and whatnot work. Hit boxes and hurt boxes, the difference between them. A hitbox being sort of like when you get hit by something and a hurt box being, for example, when you swing a sword. There needs to be a way to differentiate your sword from your character's body and have the sort of semantic difference of your sword inflicting damage onto another rectangle. And that's where the difference comes into play between hitboxes and hurt boxes. We'll look at events briefly. Screen scrolling is sort of like the classic 2D Zelda aesthetic when you think of it. It's where you're moving to the edge of the screen and then the screen pans and that's how you open up and go explore a new area. We'll look at how to do that in our dungeon. And then lastly, we'll take a look at stenciling, which is a sort of way to mask pixels before they're actually drawn to the screen to achieve certain graphical effects before we look at a little bit of datadriven design and how to start representing all of the entities and so forth in our code without having necessarily to upfront define them in code as much as have them in a separate file. something that a designer might be able to sort of take on separately from a dedicated programmer. And so this is what our goal ultimately looks like. You can see things are styled roughly in the vein of Zelda, its font and so on. And the top right in particular shows what the gameplay at its core looks like. All right. So to illustrate this, I think as is tradition, uh it's best that we take a look at an actual example. So if we can get a volunteer from the audience. Yeah. Come on up. — This is Damon, right? — David. — Okay. Nice to see you again. — Thanks for calling. Okay. So, I'm going to go ahead and start the uh game here. — As we can see, we've got a title screen. You press enter. Now, we've got a as you can see a dungeon layout here. — Some enemies. If you press space bar, you'll actually have a sword you

### [5:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=300s) Segment 2 (05:00 - 10:00)

can swing to deal some damage. You also take damage from enemies. — Oh, I see. I lost half a heart. — Yeah, hearts at the top. Indeed. You'll notice that there's a set of doorways. They're all closed currently, but if one were to take notice of the switch there — on the ground, they all open up — and then we get the classic Zelda sort of transition. There's even a utility built in. If you press M, you'll actually be able to see the map sort of laid out there. If we assume that we started on the tile below there, red being the current position, green being other rooms. — I'm not doing very well. — It's hard. the it the hit box on this particular sword uh character is a bit small, but you're doing a great job clearing out this room of all the uh all the enemies nonetheless. — Okay, one heart left. So, we can go this way. — Nailbiting. — All right. SHOULD WE SEE WHAT OH, — AND THEN YES, if you to lose all I think so it could be, but yeah, we have the stylized game over text and we could repeat. Thanks. Thanks so much. — All right. And as we saw, there were a lot of pieces in there that we probably seem that seem a little bit familiar, but also a little bit different. We do have the classic screen scrolling. We haven't quite looked at anything exactly like that, but if we sort of think back to previous lectures, we can imagine that, you know, it does look like an interpolation. Therefore, a tween might be occurring that's taking place. Uh, we do have enemies that are strikable with the hurt box that we talked about, but they're also moving around in a similar way to the snails were in Mario. And all of this can take place again through sort of AAB collision detection. We have the switch as a game object. We've got doorways which are also game objects. And we've got the classic sort of game states that were transitioning to. Also various animations of the player as they're moving left, right, top, bottom. Different states that the player also recall last week we began to look at states on the actual uh entities themselves. In this case, the player has, you know, a walking state, an idle state, a swing sword state. And so we have a lot of the same pieces, but we're using various new techniques to illustrate different ways we can use them to accomplish different goals. And so the very first thing we'll take a look at is the day zero update. This being sort of classic again per prior lectures much like Breakout and otherwise. And here we can just see, you know, what do we have to do to actually get top- down rendering occurring? Well, we need the player. And then just to really nail down the fact that we're in Zelda, we can add some hearts as well. So, why don't we take a look at Zelda 0, the day zero update in code. We transition over here. I'm going to close out the sort of main file for the Zelda demo. And then if we just run main zero or for Zelda Zero here, we can indeed see that my character is moving across the screen. If I just use the arrow keys here, there's an animation taking place. So, he's got sort of a downfacing, upward-f facing, left and right facing perspective. And then the hearts at the top left which we can't really interact with at the moment. There's nothing to really be able to damage us. But the core here of just the movement in 2D space from a bird's eye has been already laid out for us. Now if we transition back here, a top- down perspective often in games entails it depends on the graphical style that you're going for. You can have sort of isometric and then this is a sort of more orthographic sort of style. But as you can see, we have, you know, corner pieces and walls and things of a various highlight of a particular highlight color up here, for example, different color than is down here, which sort of simulates this sort of idea of lighting and perspective. Um, we've got all these different tiles across the board that are being drawn similarly to how they were being drawn with Mario, except now they're actually the floor upon which we're looking from the top. They're not sort of like the side view that we're looking at them. And all of these tiles all have a different texture and therefore a different index. If we were to look at our sprite sheet uh from the top down now, we're going to fixate primarily on just the character for right now. And as you can see, there's a myriad of frames in this texture here that illustrate the different positions that he can walk in and sort of be in. You can see even here some that we're not using yet, which are sort of like a picking up an object, a heavy object, as noted by the sort of strained expression on the character's face. This is part of the problem set which will be to implement pots that the player can lift up and carry above their head and then throw as a projectile. We won't be looking at that just in this uh lecture example, but we will be looking at, for example, the character walking, walking to the right, walking up and walking to the left. In theory, you could just flip these like we looked at last week with the character. But this sprite sheet actually gives it to us there in the sprite. So, we'll just use it as is. We'll just chop up the sprite uh the texture atlas, the sprite sheet, and just use the frames directly. And then you can see here a different sprite sheet where we actually have the character wielding the sword. And because the character uses a sword and it sort of extends his sprite, the character's sprites here are slightly larger. These are actually 32x 32 blocks instead of what these are actually uh sort of 16x6 blocks.

### [10:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=600s) Segment 3 (10:00 - 15:00)

And this involves also us figuring out, okay, what should the hitbox be? What should the hurt box be for the character? How should we offset that from the character and render it? Um well we won't actually render the hurt box but we can for debugging purposes for example take a look at it but this is an example again also of a spreadsheet with varying sizes and sometimes it's more prudent and we've done this in the dro to just chop them up in cases like this for example you have this entire spreadsheet of you know these particular uniform sizes and also uniform semantics this is just walking this is lifting a pot this is walking with a pot those sort of different so we can think of those differently and so it's sometimes prudent and we did this in the problem set to chop these up into separate textures. And so that's what we did. So why don't we take a look at what we're doing here just briefly. Most of it is going to actually be relatively old code by now, but just to illustrate what we're doing. If we bring up the actual main, you'll see that there's not a whole lot going on here other than that we have a play state. So we're just going to have a play state that just for right now just renders the player. A player is an entity. You recall entities were this idea we looked at last week of this container of potential behavior and attributes that allows us to sort of think of all movable or interactable interacting creatures or whatnot. Things that are sort of sentient or moving about the game space, the game world as an object that can then inherit behavior if they get subclassed. For example, an entity could be subclassed to a player, meaning a player is just a more specific version of an entity. You could do the same thing for enemies. any creature that you want to that has its own particular behavior and semantics. But a general entity is going to have things like its own ability to collide and render itself and update itself and so on and so forth and contain its animations that get defined. You can see here the classic uh collision algorithm for AAB. The player update just defers to entity. update. If we take a look at entity again, a lot of the same things we've seen. It has a direction. It has this function that we haven't really seen called create animations which takes a definition a table that contains information about the animations. And this is a sort of as we looked at in the lecture slides an example of datadriven design where we can define as much behavior as much characteristics as possible of whatever it is that we're trying to create. have a function that imports them, parses it, actually creates the thing and then allow our designers and programmers and whatnot to sort of work on separate parts of the problem. The programmers can focus on implementing functionality. The designers can maybe focus on okay, the sprite sheet and the animations and maybe if we expose a high enough level API for how to define behavior within R, we can define a sort of a a DSL, a domain specific language for our entities or for whatever we want to model in our world. we can allow our designers to become ever more powerful without having to get into the weeds with programming. An idea we won't explore tremendously deeply in this lecture, but we will look at the sort of beginnings of that with the data portion of it. But you can see we have all the classical uh definitions. We have a health attribute which is important. That's the hearts again. an invulnerable attribute, which is something unique to the player because if we recall when we get hit by a creature in the game, which we didn't fixate on, but folks might have noticed that the player actually goes invulnerable, meaning that they sort of phase in and out over a period of time and that they look semi-transparent. And during that space of time, they can't actually be affected by another hit. And this is deliberate design such that a character can't just be cornered by several monsters at once and then just go from three hearts to zero in the blink of an eye. We typically want to give the player some sort of a buffer to allow them the chance to escape or to defeat the monsters that are cornering them. And so that's why we have this invulnerability mechanic, which is simply phasing in and out and then ensuring they can't take hits during that span of time. Flashtime is part of that deal. So this create animations thing which we looked at is essentially the key to getting all entities to be able to load a texture and a set of defined animations that then define how they get drawn without us having to do it really manually in the code. If we look at for example entity defs we'll see here that the player is defined and this is a global sort of constant. We've we declare this as a sort of constant global variable with this uppercase camel or uppercase underscore sort of syntax this constant notation for an object that's sort of global constant. We're not going to change it. It's not going to be manipulated too much. It's just static data that we can reference later. And so if we define a key player which then has all of the attributes that essentially we care about at the time of initializing the player or whatever entity. You can see we have here an animations key which has walk left, walk right, walk down, walk up. They all have these frames that have been defined within there, an interval for how fast the animation should occur. Character walk being the texture title. So, we can allow people to actually manipulate these pieces of information

### [15:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=900s) Segment 4 (15:00 - 20:00)

without having to actually involve any code whatsoever. And it's quite bulky if you were to scroll through all of this in terms of there's just a lot of information. There's a lot of sprites. different characters and creatures that we can add to our game, but it's a relatively, I would say, simple, if not mundane and tedious process that somebody could just defer to somebody else or a designer to do. And then if we again empower the designer to also do more with these tables and give them the ability to define sort of simple functions for example that allow for collision behavior or attributes for example what kinds of weapons or what kinds of various other qualities of the entities we want. Uh the designer can sort of spend a tremendous amount of time building up all this stuff while the programmers are out doing uh more uh sort of low-level implementation. So, as you can see, as I scroll down through here, we have all of these different creatures. If we open up the uh graphics here, you'll see that I've split out all of these separate sprite sheets. Character being the base sprite sheet, but then we have character walk. And this is partially for ease because remember, generate quads will actually splice up our sprite sheet based on a uniform size for the actual sprite that we care about. In this case, this looks like it's about 16 by 32 um or 16x 24 perhaps, but they're all the same size and therefore they can be loaded programmatically. We don't have to handin rectangles that then get placed and get futsy. We can just therefore also cleanly just have this indexable, you know, G frames, G quads, character walk or whatnot. And even more simply, because we're defining it in the data now with definitions, there's an actual piece of code that will just look up the texture by a string and then the frames and then just simplify our whole process. And so this is how we can define our animations now without having to really define it in code. We do all the heavy lifting programmatically. And so if we go to the play state here, you'll notice again entity states, game states, these are going to be two different areas where the entity states are these state machines we added last time with onto Mario with the characters having the sort of if you recall jump states, walk states, idle states doing the what we did with games but with entities. Now to sort of simplify how we conceive of them, the player gets defined here with its animations being set to entity defs of player. an animations. it's walk speed player. walksp speed. And so this is how you allow the designers to sort of affect your code or you could do it all yourself as well as if you're just doing all the development and therefore just more I guess isolate and model your data separately which is a good idea as well. But either way you have this flexibility now notice we have six health. We're storing hearts as two health per heart so that we can model half hearts with one health. We've [snorts] got the walk state and idle state update classic stuff that we've seen before. Um, we have this thing that we haven't seen, love. graphics. push. And what this essentially does is recall last week we looked at love. graphics. transate being that function that will actually sort of shift the coordinate space for us. So that we can think about moving a camera through space even though we're not actually necessarily physically doing that. We're sort of mimicking that behavior with an illusion. But we can do that here. we can sort of refresh the state of whatever the camera is set at with this push pop operation whereby push will sort of push a new openg or new graphics state onto this stack that gets maintained in the graphics card and then what this does is any transformations that occur will actually be considered in their own separate space. OpenGL and most graphics APIs are sort of this state of operations. As you've seen that we've we can set the color for example the global color white or whatever we want to draw things and render things but that's globally applicable but sometimes you might want something to draw and then for that to change and you might want to go back to that prior state to draw something else. In this particular case we might want to draw the player and then move the player around but draw the hearts and have the hearts always stay in the exact same spot. And so we accomplish that by treating them as two separate states in this sort of like abstracted over state of the graphics card where we have this one state where okay this is where translations will occur potentially with it will eventually be the dungeon rendering with all of its entities. cuz right now we're just rendering the player, but then once that's finished, we might want to create a whole new state with its own reset translation value effectively where we're not translating anymore by any value. And then therefore, whatever we draw at 0 is always going to be at 0 at the top left, those being the hearts, in a fixed position. So this is a way to do that. It's a way to sort of like have things that get statically drawn no matter what where you've moved the camera, which is applicable in particular for UI elements. And so that's what we're doing here. uh all that to essentially say and

### [20:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=1200s) Segment 5 (20:00 - 25:00)

illustrate how we are getting just a very simple set of things drawn to the screen which I'll run again here. You can see the character moving around. We have state machines which we'll be looking at periodically throughout the rest of the examples. But suffice to say it's not all that different from Mario's walk and idle state from last time in as far as we have an idle state where we're just drawing based on whether we're we have an up or down direction. You can see they have different sprites and then a left or right direction. And then if we're moving, let's update that animation. If we're not moving, then there is no animation to update, per se. So, we've got a character rendering. Um, it's quite, you know, bleak at the moment. But the next update is going to take us closer to a more, I guess, close to final aesthetic for our game, which is going to be the tile set update, which we're actually going to start rendering the tiles of the dungeon, which isn't anything too complicated or something that folks haven't seen yet before, particularly since we've done Mario last week. Tile rendering is fairly straightforward at this point. And in this particular example today, none of these tiles actually have collision. We have, we know for sure all of the tiles in our game are going to be in this exact orientation. This is similar to the original Zelda by design. Folks could definitely implement a version where the tiles did have collision. But since we know all the rooms are going to be the same size and offset the same way, we don't have to actually worry about tilebased collision. We can check instead for boundaries. So in that sense, today's examples are going to be simpler. But there are going to be some other aspects we need to take a look at. The first thing we'll take a look at is just the size of the tile set. And it's not uncommon that tile sets in games like this uh get as complicated as this, if not much more complicated. I mean, last week when we looked at Mario, it was tremendous. We had a ton of tiles to look at. A lot of them were the same, just with a different aesthetic variety. In today's example, we have a lot that are actually functionally different. We're only going to be using a subset of them, but as you can see, looking at this, we can see that there is a sort of general symmetry to the way that things are laid out, a general homogyny to the size of things. Um, if we actually overlay a grid on top of everything, we can really see the 16x6 tile uh sort of delineation. Here we can see the things like the pots over at the top. We can see these sort of corner pieces laid out which are deliberately positioned in a way to sort of lump them all together and see visually how they might organically be able to be composable in a clean way. We have the walls laid out together. You can see there are variants of the walls. They have the these sort of like little details slightly different across all of them and many other pieces. For example, the doorways, which as you can see with the doorways, they're actually modeled with four tiles, which is how we draw the doorways. We have they're slightly larger than the 16x6. So, it's not uncommon that you will draw, you know, 2, four, six, whatever the size of the tiles is, and then model the collision of that however you see fit. I find it really easy in particular to especially when we get into big tile sheets like this to have some way to be able to easily because again we're thinking in terms of indices numbers that we have to index into a table to find these tiles. And so looking at this is one thing but we want to be able to ideally look at whatever number of that tile is that we care about on the fly. For example, I want to know that number four here is the top left corner of the dungeon. I want to know that 24 is the bottom right corner. But it's a kind of a pain to be able to sort of go through and hand count each individual one to know. Recall it just gets sort of added in this linear way left to right, top to bottom. And so there's included in the DRO, which we won't take too much of a look at, a simple Python program that allows you to put in a if you just edit the name of the program or name of the PNG that you care about splicing up or whatever the image is. It uses a library called pillow. But you could also do this in code in Love 2D, for example, if you wanted to just load the texture, render it, and then draw the text overlaid. I would argue that that's slightly less convenient because now you have a the Love runtime up. If you're debugging another game, you're going to run into some issues. It's nicer to just have an image that you can save. And this is included in the repo for folks who want to use the tile set. They can actually see all the numbers pre-added onto the tile sheet for their convenience as they're implementing the problem set. for example, the p the uh the pots at the top and the treasure chest part of the boomerang part of the problem set. Folks are going to want to know what index that is. So, this is a convenient little way to sort of add that. So, let's take a look at the tile set update in code. If we go over here, I'm going to close out all these other images, most or sorry, source files and open up Zelda 1. Now, there's not a whole lot in here that's too fancy or complicated, but [snorts] we do now have a dungeon that is rendering, I should say. Not even a dungeon yet, cuz technically that's the next update. A dungeon being a data structure that has to maintain multiple rooms that are contiguous. This is just a room for right now. We can think of it just as a room, as an object, which is

### [25:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=1500s) Segment 6 (25:00 - 30:00)

defined by a set of tiles that render the top, left, right, and bottom. It will have doorways. It will have switches. It will have entities including the player that it manages, takes care, takes care of, renders, etc. But for right now, it's just a essentially the same code that we had last time, but now we're actually rendering all of these tiles that we care about. And notice they're all random, too. There are multiple variants of the floor tiles, top, bottom, left, and right wall tiles. And this is not uncommon because if you were to just draw the same tile, it would look quite homogeneous in a way that feels very unpolished. So, it's good to use these variants, these sort of randomizing the sort of subtable of uh of tiles you have access to, those being your walls of left and right, top and bottom to affect a sort of better variety, better aesthetic sense for your game. So, let's take a look at how we do that. This will be primarily in the room. You notice that there is a world folder here. So, we have the main source files, then we have the states, and then we have this folder called world. This is how I like to model often when I'm making a game. Separate your main sort of like base logic uh you know animation entity player. Those sort of higher level code files can live at the top of source for example or they could go in a folder called entity or some other folder that is more like generic or general. But I like to also have world that models anything that's in the world because a lot of the world code can kind of be thought of as a separate monolith separate from states separate from utility and general access code. You'll notice that we have so if we go over to play state just to sort of set the stage a little bit here there's a current room that we set in the play state which is going to get rendered and updated and it takes the player as a reference so that it can render the player thereby and this room is in world and what it is effectively not much more than simply a container of tiles that gets generated. You can see there's a function called generate walls and floors and we have a tiles table and a reference to the player. You'll see also like things like these render offsets and an adjacent offset uh couple of fields later that are going to come into play when we start transitioning from one room to another. We're going to need to essentially have both in memory at the same time and rendered and updating, but that's sort of getting ahead of ourselves a little bit. But you'll see generate walls and floors. All it really does, we won't necessarily dive too much into the particulars, uh, at a terribly fine level, but what it essentially does is it iterates through the entire map, Y to X, a 2D crawl, just like we've seen many times, and it will just do comparisons. It'll say, "Okay, I want to fill this tile map with the IDs that map up to what we saw, which is the wall tiles at the top, the or top wall tiles, the sidewall tiles at the sides, and then the bottom wall tiles at the bottom. And then we want to randomize. There's like four three to four variants per at least for the walls that we can choose from. And then uh a higher number that we can choose from for the center. And then you can see all these constants that we're defining. You might wonder where are the constants all these constants for top left tile top left corner tile bottom left corner all being rendered. And simply just in the constants file we've gone ahead and rendered all or defined all of those starting here at tile ids. You can see 4, 5, 23, 24. All things again that I was able to identify because I had written them all out to that texture. If we were to go to graphics here and draw, you'll see it as tile sheet numbered in the DRO. So you'll see here if I just zoom in a little bit here, we do indeed have tile top left, for example, being four, tile top right being five, bottom left 23, bottom right 24. all of these floor variants, there's a lot of them, 7 through 13 and, you know, 26 through 32. So, you know, tedious to an extent, but sort of a necessary evil. And what we can do, therefore, is just hand pick all of those out, define them in constants, and now when we're in the room generating. You'll notice here when we're actually placing the walls left, right, top, bottom, we're actually indexing into these arrays that we defined, these tables that we defined via the math. random random at the length of those variables. And so we get a random set of tiles. Here we're inserting it and then we just render it. We just do a yx crawl and just render all the tiles much like we've done through in Mario and through other examples like uh match 3. Love. graphics. draw takes textures, takes the tiles with the ID of that tile. All things that we've largely seen thus far. All pretty much the same. And then if we go to the entities, the players walk state, you'll see here is where we're sort of doing more of the actual detection for whether we've gone up or below, left or right at a certain point in our room to

### [30:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=1800s) Segment 7 (30:00 - 35:00)

actually detect, okay, are we at the limit of the room? because we don't want to go. We could uh I should say decide to do all the collision in room with tilebased collision and that's fine, but because we're literally just doing a Y and X limit essentially for the character because we know the hard defined limits of the room, we don't have to do tilebased collision anymore. We can simply determine in this particular example, we're using uh actually entity walkstate. update. We're deferring to that. Notice that we're calling entity walkstate. update selfdt. This is a way for us to use a member function a method of a class. In particular, this class is a the player is a subclass of entity. But if we want to use for example the behavior defined in entity walk state as the player, we can't really call like this update function. Now the player's update function is its own function. it gets sort of overrides the entity walk state method, the update method. But if we wanted to call it because we do actually care about that behavior occurring and then we want to add onto it our own behavior, we can manually trigger it using a static invocation here. So we're actually using the class itself. update and then manually passing in self. Recall that if you were to call an object's uh sort of if you were to use the colon operator and then therefore just pass in DT, this is essentially the equivalent only we're invoking it at the class level because that's how we get access to that function. Now we can't actually call like player. update player colon update selfdt and get access to that function. We've already overridden it by inheriting it from entity essentially player over defining it itself. If you were to look up here, you can see includes equals entity walk state essentially means this walk state overrides this walk state. Therefore, this update overrides that update. But we still care about entity walk states update. So we want to do entity walk state. update and then manually pass in self, which is essentially the same thing as calling a colon version a method uh which passes in self for you. But we can't do that if we've inherited and overridden that function. If you were to look at the entity walk state, you'll see it's got various pieces of code here. For example, it actually will change our animation to walk down, assuming that it's every entity that is an entity has a walk down animation in its thing. This is sort of something that gets automatically taken care of for us, for example. But update has the code. All this code here essentially does boundary checking to make sure it it'll flag us as being bumped so we can see, okay, did we collide with a wall? And then if we did, this allows us to like do some checking for doorways a bit later in the code, which is important. But for right now, the important code is here. Depending on which direction we're moving, if we're in the walk state, did we go to a certain essentially these offsets, we have a set of offsets that are constants that define the left and right edges and top and bottom edges of the screen. If we've gone past those offsets in any way, depending on our direction, we should essentially reset our X or Y position by just shifting it right at that offset or minus by our width depending on which side we actually uh as you can see here, if we were moving to the right, we want to shift to the right. Remember, everything's top left based in this codebase, everything is based on the top left. And [snorts] so this is the code we care about that does actual balance checking. the player wants to be able to detect that, do that same thing, but also extend behavior in ways that we'll see later. And so we can essentially invoke that same function manually here by calling it on the class, but passing in self. Self being this player as an object and therefore get that behavior while being able to do some other functionality which is in here sort of penciled in but will be filled in later when we get to the screen transitions. We don't want enemies to do screen transitions. We only want the player That's going to need to occur in the walk state, but we care about the entity walk behavior. So, we want to do that and then extend it with our own sort of behavior. So, that sets us up with a room. So, just right now one single room, which is not quite what we saw previously, which is an entire dungeon, which brings us to the dungeon update, which is Zelda 2. So, this is one of Zelda's more famous mechanics or I guess themes is the idea of going through various dungeons. This is a screenshot of one of the dungeons from the original Legend of Zelda. As you can see, it's got quite an interesting It actually kind of looks like an eagle or a magic lamp or something, but you usually start here at the very bottom center and then the idea is you move up throughout the labyrinth and it can go in a bunch of different directions. And our dungeon generator that we look at will look very similar to this, but as you can see, there are doorways linking each individual chamber. Sometimes, not always, in an actual game, a lot of the time the doors will actually be locked and you'll have to get keys and

### [35:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=2100s) Segment 8 (35:00 - 40:00)

the keys are placed strategically such that you have to kind of solve puzzles to find what they are. And there's hidden chambers. There's walls that are you could bomb them and open them up and do we won't do a lot of those more complicated things. We'll just be looking at a simple generator which creates a simple dungeon that kind of largely functions like this and that it sort of organically evolves upwards from the bottom and can take on various shapes randomly. Um, what's kind of cool is that this idea is even used in a lot of other games, more modern games. For example, The Binding of Isaac, which is a rog light, which is a very popular type of genre, where it sort of behaves very similarly to Zelda, but instead of having man manually sort of created dungeons, it actually creates dungeons randomly and then places various items throughout the dungeon. Another part of Zelda, which is part of the problem set to get people started in that direction, but Isaac's, you know, primary focus is a lot more on the items, and we won't be looking specifically at that, but that is a huge part of uh what makes Zelda as well. And so just to get us started on this, I think it's important for us to look at actually getting a dungeon in code started. We won't look too closely yet at the actual dungeon generation. We'll take a closer look at that in a few more examples, but this example will at least get us set up very lightly looking at everything in terms of not just a room, but in terms of an actual dungeon. So I'm going to close out these examples and then let's open up Zelda 2. I'll just show it. Folks will notice that it is the same pretty much because all this really is a data change. Essentially, we're going to model things slightly differently. [snorts] So, I'm going to open up source. And if we go to our play state here, you'll notice that rather than a room now, we are generating a dungeon. And right now we're not going to show necessarily uh the specifics of that, but we will look at that in in more time down the line in particular because we're not actually yet going to show things like doorways and modeling between rooms. So we can actually show the verification of what the structure looks like. And I want to actually go through the generator sort of visually. But suffice to say, this dungeon maker, which we'll look at in a few moments, generates us a dungeon structure, which is essentially a contiguous assortment of rooms. If we were to look at the dungeon itself, you'll notice it has a rooms table and then there will always be a current room that should be updated. And in this particular instance, we get set to this particular room at the very start. Start X and start Y are passed in. We can set that to whatever we want. If we want to start the player in a particular position, these are manipulable parameters because you might conceive of generating a dungeon such that you've determined the player should start here on the left because everything is puzzle-wise positioned towards the right. And so that might be index 1x and 5 y. We start every we start the player ultimately at five 5x 10 y which is at the bottom of a 10x 10 dungeon in the center roughly. And this is sort of by design to mimic Zelda, but you could approach this in any way that you see fit. You could start the player in the center and have it sort of crawl out from them in myriad directions. There's a lot of genres of games like rogue likes or actual rogue lights or whatever subvariety of that have randomly generated dungeons that do all kinds of various ways of presenting them and generating them. You'll notice we also have a next room. This next room is going to be important because the current room is where the player is currently rendering and entities are doing things, but if we're transitioning, there needs to be a next room. So, the dungeon maintains not only all of the rooms in the dungeon as the sort of two-dimensional grid of rooms that get stored, also the current room gets it gets uh rendered and updated. And then the next room during transitions will get loaded, rendered, and updated as well, just to sort of see things. But ultimately altogether all we're really doing is now just defi or re rendering the updating the rather the player and then rendering the room here whatever the current room is in the dungeon which is the typical model going forward. The player could be rendered in here but for the sake of this example we're still rendering the player in the play state which is fine. Um here down below oh sorry the he might be being rendered in the current room. I apologize he's actually being rendered in the room. So if we look down here, we go down to the room. Player gets updated in room and then room gets rendered as well. The room itself and then the player right after the room. We'll see more extensions on the room as we get into transitions. We're going to look at masking and we'll see the player has to get rendered and the doorways all have there's some work there that's going to actually simulate or provide an illusion for the player passing through doorways. And so it's important that things get rendered in a particular order through here. — [snorts] — So that's essentially the bulk of the dungeon update, which is a very simple just data update. The next update being

### [40:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=2400s) Segment 9 (40:00 - 45:00)

the hitbox update, which as we've seen, hit boxes so far have been the sort of AABB collision detection rectangles that allow us to do simple, you know, the classic algorithm, the classic comparison that tests where the two things are colliding with one another. So far, it's been somewhat of a all or nothing type calculation where if we're colliding with something, we want a particular specific action to occur. If it's the ball with a paddle, we know based on just the way that the game is structured that means a bounce should occur and there should be a sort of inversion of one particular axis of movement. Depending on breakout or pong, it'll be different. Or if it's a ceiling or the sides of the game space, it'll be different. But there's a difference between a hitbox and a hurt box. And folks might use different names to refer to these. They're essentially just rectangles that have different semantic meaning. In the case of a hitbox, as we'll call it, it's essentially shown in green here as being anything that can be struck by a hurt box or potentially can be collided with another hitbox. And all the examples we've used so far have sort of used a hitbox and hurt box as one and the same. But there are many instances, most games, where they're actually separate. In this game, this particular example here, which is Street Fighter, as your character is moving and doing a kick, for example, this part of their body should be open to attack. But if they should collide this part of their rectangle with a green rectangle of their opponent, then you actually get a hit on them and you can start to do a combo and start to hit them more and whittle their health down. In the case of the example shown down here, this is just an illustration of in this case hit boxes which are just in this case cubes in the case of Minecraft where the math is a little bit more complicated for 3D collision. But essentially when you're looking at things in 3D space, everything to collide with it or to pick it up and so on and so forth is going to also have its similar own hitbox with which you could, you know, for example with these things you could collide with them and you could pick them up. In particular, these items here, this is a pig. So if you were to strike this with a hurt box, which you would get with your sword, you know, for example, in the same way as this, then you would deal damage to the cow or the pig or whatever creature, whatever entity, and they have different meaning. They have different purpose. And in Zelda, it's the same way. If you're moving around your character in the dungeon and you collide with an enemy, enemies are all sort of thought of as almost having a hit box that or hurt box that's always active. It being just their physical body. It could be a subpiece of their body as well. You could have creatures that don't hit you if they directly touch you, but maybe if they throw something at you, like the skeletons might throw a bone at you, and then maybe that has a hurt box on it that affects your character, but maybe if you touch them directly, it won't hurt you. Generally, if you touch an enemy with your hitbox, even if it's their hit box, it will still cause damage to you. Therefore implying that they are like one in not only a hitbox but also a hurt box or they're both sort of overlapping each other at the same time and doing the same calculation. But Zelda has the character's ability, which is pertinent to swing a sword and therefore have a separate hitbox from a hurt box. The hurt box is smaller in the case of the character because it's quite large and at least in this particular tile set, the character's sword is pretty small. But you could extend that. You could also change the weapon size of your character depending on the game and so on and so forth. It's a very tweakable sort of set of parameters. So, we're going to take a quick look at how to implement that in Zelda 3. So, if we go over here, I'm going to close these all out and then let's go ahead open up Zelda 3 and let's just show this. So, I'm just moving around. And it's kind of the same as before, but if I hit spacebar, you'll notice that we get a couple of rectangles rendering there. And then I can do this. And you can see it kind of changes a little bit depending on which direction we're moving. And this is deliberate because this the interesting thing about sprite art and depending on perspective and various things is that the rectangle relative to the character will kind of have to be hand adjusted which you saw even in the Street Fighter example, the rectangle that was there on the character's foot was very particularly placed. And that's by design. And people spend a great deal of time focused on identifying the duration of time that hit boxes and hurt boxes are open, as well as their span, their size, their frequency, their number. There's a lot of details that go into making a game more or less balanced because of the fairness or lack thereof of that character's hitboxes. If they're out for too long, for example, then that character is considered or perceived as being broken and kind of overpowered because they the character has to do less to be able to inflict more damage. Or if it's too small, then that character is perceived as being kind of weak and not very fun to play. And so it's something that's very tweakable, something that plays a big role in the balance of your game. You want to make sure that those rectangles

### [45:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=2700s) Segment 10 (45:00 - 50:00)

are adjusted and tuned, that they visually line up, too. Because if your rectangles are such that they don't look like they're quite overlapped in the way of the actual graphics in a way that is not beneficial to you, the player, it will feel very sort of unfair. But a lot of games like shooters, for example, top down shooters will actually shrink the hitbox of the player such that you can navigate through a bunch of projectiles that are flying all over the place potentially, but you'll feel sort of skillful and able to dodge them because their hitbox is actually your hitbox is actually smaller than your character looks. And the projectiles themselves will often have smaller hitboxes than the projectiles as well. while therefore giving you just a little bit of leeway and wiggle room that you might not see at first glance, but leads to the difference between a game being very hard to play and unfair and punishing and a game being very fun to play and more well balanced. And so this is what's illustrated here. We've got debug rectangles specifically to illustrate this. This is very helpful. This is a very difficult thing to visualize if you're ever doing this yourself and trying to implement hurt boxes, hit boxes, etc. and figuring out balance for your game. You want to be able to see them very specifically drawn out so that you can tell, okay? And ideally also frame by frame, maybe go through your game and test to see at what point the collision occurs to see, oh, okay, things are properly working. That's the right size. Okay, now we need to shrink that. No, we need to move that, move it to the left, move it down, so on and so forth. It's a it's a balancing act for sure. So, what we do to accomplish this is we add a new state to the player. I'm going to go ahead and open up in the sources here in the states in the entity in the player from the idle state or the walk state either one. If we press spacebar, notice that we go to this new state, swing sword. So, pretty straightforward there. We're going to go ahead and load this in here. We'll take a look. It takes the player and the dungeon. The reason being that we want to be able to in particular compare this hitbox with all of the entities, the hurt box rather of the sword with all of the entities in the dungeon. And to do this at the time that we create the actual state itself, which is an anonymous function, recall where it's passed in, it's instantiated, it's constructed within that anonymous function within the state machine that's housed on the entity. we pass it in the reference to the dungeon and then therefore anytime this wants to call update or render or whatever it'll have the act it'll have access to the dungeon and therefore all of the entities therein therefore all of its a all of the bounding boxes there of all of those entities with which we can then do a bb on those on that hurt box. So you can see here we have a obviously we have the direction we care about and then we have hitbox Y width and height. Those were all of the variables that go into creating that red rectangle. We've got the direction depending on the direction we set the width and the height differently because if we're looking to the left or the right recall it's more of an elongated sort of vertical rectangle, but if we're looking top or bottom it's more of a horizontal rectangle. And then also just based on where we look, we want to position the rectangle relative to the player in different places. And then we create a hitbox here, which is going to be a rectangle we store on the player. It's a very simple class. Hardly even need to really look at it. But just so folks know, it's literally just a container for a rectangle. Essentially, it's a glorified rectangle. And if we come back here, notice that we are changing our sort of sword animation depending on our direction. Because remember the frames were different in that sprite sheet. We had left looking frames, upwards, downwards, rightwards. And so we have this change animation function on our entity which allows us to in the same way that we can change the state machine's global state, play state, game over state, etc. We can change the player state and then we can just use string concatenation to programmatically change the state depending on which direction we're looking at to save us a set of four conditions. For example, if player E is looking left, if player is looking right, so on and so forth. And then when we enter it, we play some sounds. We refresh whatever the animation's at because we maintain a consistent reference to the animation. Want to make sure that it starts from frame zero. And then when we update, you can see here after we've played the animation once, this is how we actually end the animation. There's a counter on the animation which just increments every time it plays, every time it loops through. And if it's greater than zero, well, okay, we swung the sword once. That's it. We don't need to keep the animation looping and playing over and over again, which is the default for example for walking states. That's how those states work. They'll just loop back to zero. Once this happens, we're just going to change our state to idle and then therefore we'll be able to go back to whatever we're doing. If we're if and we could also just press space again if we wanted to uh and then swing our sword a bunch of times. We can't really like walk and swing consistently. We don't have that logic in here. We're just assuming that when you swing the sword, as is common in these kinds of games, you're sort of locked in place and then you go back to idle state and then you can start moving

### [50:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=3000s) Segment 11 (50:00 - 55:00)

if you hold on to the uh arrow keys or WD. And then here's where we just have our debug rectangle which you could toggle or you could create a flag for this if you wanted to. This is common practice. You can just have a debug flag that just says if uh debug on then render these things. if not for the sake of just having the example ready to go. It's not set to that, but you could absolutely lock this behind a flag or the like. And with that, now we have the ability to swing swords and then inflict sort of damage. We don't have that ability yet cuz we don't have any entities to inflict damage onto, but we have the groundwork laid now with the hitbox in question that will allow us to start doing that. So now that we do have the ability to use the sword, we do want to start adding the next step, the next logical step would be adding entities, things that we saw previously, the skeletons, spiders, so on and so forth, to our actual rooms in the dungeon that will allow us to test that this works properly. So to do that, we're going to go ahead and transition to Zelda 4, which is the monster update, I call it, which as you can see, adds various creatures there and is, as shown by this half a heart. We can collide with them and take damage and inflict damage. And this is the spreadsheet. This is a separate spreadsheet. All the sprite sheets that we've used today were just grabbed off of free sort of public domain Creative Commons assets that are available. There's a lot of different websites that you can grab things like these from. Um, or you could be ambitious and create your own. Certainly, I'm not a great artist, but this is a good enough spreadsheet for today's purposes. And as you can see, it's got various different creatures blocked off into sort of groups of 3x4 sort of rows and columns. And again, it gives itself easily or nice nicely towards a programmatic breaking down using generate quads. but it's got different groups of entities and to keep track of okay slime this is the slime you know just looking downwards and looking left looking right looking backwards so on and so forth we probably want a way to easily tell which ID maps to which and so similarly to the tile set I've gone ahead and preset all of these using the same Python program with an ID and this is just I think a good practice to do but as you can see we have got now a clear way of showing all the individual entities there and we've got the different blocks that they're all grouped up in. So, we could programmatically look through all of these and see, okay, the first three for every of the first four rows are the animations down, left, right, and backwards for this player, and then we care primarily about the skeletons and the slimes, all the actual monsters versus the human humanoid characters. But that's essentially how you can think of splitting apart spreadsheets like this. And you can still do it programmatically. You just have to know what the offsets are. And so we're going to take a look now at what the process looks like for integrating the character with all these entities and the swing sword state that we just added. So we can actually do damage and then we can take damage in turn. So I'm going to go ahead and close all the examples that are open and then let's go ahead and go to Zelda 4 just see what it looks like. So as you can see we're sort of moving around here. If I can still use my sword as usual. All the entities are moving around. You can see they're largely just doing pretty simple ignoring me type behavior, which is by design. We could certainly put path finding on these guys and have them come towards me and do so on and so forth. Similar to what we did with the snail in the last example. It's often the case in Zelda games that en entities enemies are pretty sort of just like amling and not doing a whole lot. So, we kind of just took that approach for this. There's just two states. They essentially have a walking and an idle state much like the player does, except these guys will essentially just look for they'll just randomly choose an interval, move in a direction. If they hit a wall, they'll stop moving and then they'll have a chance to either keep moving in a different direction or they'll be idle. If I bump into one, you'll notice that I take some damage. I start phasing in and out. That's my invulnerability. During that span of time, notice I can't take damage. And then if I go up to one, notice I was able to sort of take it out. You could in theory, if you wanted to, designate or illustrate a slain enemy in this way by maybe having a different sprite sheet or something that's like pile of bones or bat wings or something or slime pile to sort of place there as a game object that might say, "Okay, I defeated an enemy there. " And that's proof that I did that. That's something that was part of the design of Doom, for example, back in the day was all the demons and whatnot that you would take care out. They would exist as a sort of they would have their remains around the game space and then you could sort of track back where you've gone through the level sort of like an unintended pro I mean probably largely intended side effect of uh of that being like good game design for navigation but also just an

### [55:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=3300s) Segment 12 (55:00 - 60:00)

aesthetic choice that was kind of cool. You could do that here too if you wanted to. We didn't make that decision. It wasn't in the sprite sheet, but that's a way that you could sort of verify, oh, okay, I defeated X ghosts, X slimes, X spiders in here. So that shows that the code is working in that way. We'll go to in this particular instance, most of this is managed in the room itself. So if we go over to the also in the swing sword state too, which we'll take a look at. But if we go over to the room, sorry, it's over here. You'll notice that we've got a new function, generate entities. To actually generate them, they actually have to get placed in the room. So, here they are. We've got skeleton, slime, bat, ghost, and spider. Again, all of the sort of creatures that were in the sprite sheet that we looked at. We're going to generate 10. And then we can sort of grab a random type. And then we can do similar to what we did previously with a definition uh whereby we take the animations the walk speed and then we can grab those from a config or sort of a data file which we looked at previously. Again that entitydevs. la has all that information. So we can keep this part relatively short with all that data. We don't have to necessarily dig into the weeds of like okay what's a slimes sprite sheet and its indices or what's the skeletons etc. That's all managed with these keys. These keys are all the same. So we therefore don't have to worry about it too much here. We do want to give them a random X and a Y guarantee that they have a width of 16 and a height of 16 and then a health of one so that when we swing the sword we can deal one unit of damage and then we can check to see for all the entities in the room are any of them less than one health or are they less than or equal to zero health? If they are then we can just remove them from that room and then therefore we have the ability to defeat enemies and remove them from the game state. So again that gets called and then we have also rendering code here from the actual or sorry this is update code. So this is where it actually will do that exact check that I mentioned. There is a process AI which is part of the state machine class that we've looked at before. Um and then that's going to get ultimately deferred to an entity state which you can define process AI and then it takes a couple of arguments here. It takes an options table and then delta time so that it can do whatever you want it to do depending on the entity. And then here we have uh the actual code that's going to detect if the player himself collides with any of the entities here. And then again we have this function damage of one which is going to tick us down half a heart and then go invulnerable for 1. 5 seconds which during that span of time essentially sets a flag and the inability to be uh hit because again here we're saying if not self. player. invulnerable meaning that this won't fire if we're invulnerable. So it just it'll just fail this check and therefore we can't damage ourselves until we are no longer invulnerable. So if we go to the swing sword state that we looked at previously, you'll see in the update function that this is why we needed to take the reference to a dungeon in the constructor of this state. But we are going to iterate over all of the current rooms entities. And then we can do our classic collision. We can check to see if it collides with our hitbox. or hit box again just being a rectangle just a glorified rectangle we'll damage it and then because then it'll its HP will be zero on the next run through all of the entities in the room it will be taken out of that table so altogether pretty simple set of code not nothing too complicated a lot of the things we've looked at already just collision and then now we've just got a hit box we got a few checks you know for invulnerability and the like but altogether things are coming together quite well in terms of our ability to hit enemy enemies hit entities and also to be hit by entities. So, the next sort of phase is going to be the trigger update, the next uh major update. And a big part of Zelda is besides just being in a room using a sword to attack enemies. It's also getting through the dungeon, getting to the end of the dungeon. And so far, we've been in a closed room, but now we'll take a look at actually a doorway or doorways in our dungeon. won't be able to move through them just yet, but just to illustrate the idea of opening a door. Recall in the example that we did at the start of the lecture, they all start closed, which is common in Zelda, you usually go into a room, you usually have to defeat a certain number of entities or enemies. We're not taking that approach, although you certainly could. You could just do for, you know, do a loop over all the entities in the room. If it's equal to zero, then open the doorways. That works fine. Today we're going to just look at using triggers, which is similar to what we did last time when we looked at game objects where we had a game object to represent the block that you could then jump up, hit that would trigger a gem

### [1:00:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=3600s) Segment 13 (60:00 - 65:00)

to spawn. If you collided with that gem, you would then get some score. You' get some an increase to your score and uh so on and so forth. That is essentially the gist of what we're going to do here. We're going to define some game object, a trigger, a switch, and then on collision with it. It's going to have its own on collide function that will trigger the opening of the room's doorways. Pretty much just that simple. So, we'll take a look at how it works here in uh Zelda 5, the trigger update. So, I'm going to go ahead back to my code here. Let's close all these here. Open up Zelda 5. Let's just test this and see what it looks like. So, you'll see it's kind of the same as before. We do have two doorways in this instantiation. This dungeon generator is going to always be random. And so, again, it starts from the sort of bottom of the screen. But if I were to, you know, besides just hitting creatures, I could certainly do that. The doorways are closed. Notice. And right now, even in this example, even when they're open, this won't do anything. But this is just how the behavior would normally be if you're going up to the door and it's closed. You won't be able to move through it. But as soon as I step on this trigger, this switch, notice that it plays a sound and it also opens the doors at which point now it isn't imp it isn't implemented in this example because we're going to actually show it's part of the bigger conversation about how to implement scrolling which is one of the most complicated larger features of this particular lecture. But we do have the foundation now to trigger those sorts of behaviors in our room. So it's similar in spirit to what we looked at last week except we are doing it in a different use case. So if we go over to Zelda 5, this will be in the room. Now I want to take a look for a second at doorway because doorways are now going to be a more important aspect of what we look at. Although the doorway class itself is pretty simple. Doesn't really have a whole lot going for it other than that. Recall when we looked at the sprite sheet, it was four tiles that needed to be rendered to approximate or rather to represent the doorway despite the fact that all of our tiles are 16 x 16. If we were to run this again and pay attention to that fact, you'll see that the doorways actually are larger than the walls. The walls are 16 x 16. So therefore, the doorways represent an object larger than the tile size. And so that's totally fine and valid. In order to do that in a strictly sort of consistently sized tile set, which is common, you'll want to just represent it with multiple tiles. And so therefore, you have the two bottom tiles being the main parts of the doorway. And then that arch, the top of that arch is represented with two semi-transparent tiles on either side that get drawn in conjunction with those tiles. So, if we go back here, rather we're already in doorway. We already have it set up here. You'll notice here that we're going to essentially set our XY width and height based on which direction we set the doorway to. It's going to be part of the constructor here. We'll say left, right, top, bottom. And then down here in render, again, we're going to just get a reference to textures and quads. We have an offset that we can draw at, which is just part of, you know, making sure our map is centered in the game world. So, we just pass in offsets. And also, when we're scrolling rooms together back and forth, those also get an offset added to it, which allows the doorways to sort of move as we're transitioning uh rooms rather the player from one room to another. the as you can see here if we're looking left then we're going to want to if it's open draw a particular set of tiles because recall there were four there's four tiles per doorway and we'll open up the doorways here uh rather texture and I'll show the numbered version again but you can see there are actually in this spreadsheet there are more than uh more than one variant even of doorways there's they even included gated doorways but as you can See, there's a 153 154 being like top doorway closed. But then if we were to look here, 11718 is top doorway open. You could use that for either of these. The great the sort of graded or the wooden doorway. You could have this semantically be, for example, I could envision this being for enemies. If you go into a room and there's a bunch of and there's like an arena or a sort of like you have to defeat all the enemies to leave the room instead of hitting a switch, you could use this doorway just sort of visual way to signify the meaning of that room. But that's presumably why they incorporated it. But it is different. These tiles here are different from these tiles. The shading is different in particular if you notice versus the ones that are sort of like here at the bottom. Again, the shading is different. So they do have slightly different aesthetics to them even though they are very similar on the surface. And so there are four per and

### [1:05:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=3900s) Segment 14 (65:00 - 70:00)

then there are also there's essentially a variant of both sides top left right top bottom that have the door open whether you're using a grate or using a wooden door. And so that's why we have to draw things in this way where we have essentially four tiles. And you can look through the you can consult through the indices of the attached tile sheet to verify these indices yourself for the sake of just keeping it rather condensed here. There's so many of them. We're just using the numbers. But you would probably put these as constants. Um but they would get quite bloated here. So just for the sake of illustration and also just to illustrate the fact that we are indeed using the numbers that we can see here on the tile sheet. they have been included just as the numbers. So folks do I encourage folks to look through all of these and see how we're arriving at these 16 x 16 tiles versus uh when we're using the direction in conjunction with the open flag on the doorway. So that's one detail and that open flag is just what gets set on uh on or off if we were to hit the switch. And so this is a good opportunity to again look at for example the doorway. I'm going to close some of these files here. Sorry, not the doorway. If we were to look at the game object, the game object is not all that different from last time where it is essentially just a container of fields or flags and it has a reference of course to its XY width and height and a uh the ability to render itself. But again, an empty onide function is presumed in here so that we can call on collide of whatever object we want to and then it will just not do anything by default. But we can override this through definitions. So if we were to go to game objects. la Lua for example, this isn't where the function is defined, but this is where you in theory you could have some sort of way of defining functions here for your designers. We're not choosing to do that do so here, but this is where we are again with this theme of like sort of more datadriven design. We're going to try and keep as many of the pieces of information that tie to the switch inside this config, this sort of definition file, as much as possible. It's got a type, a texture. You can put whatever data in here that you want. As long as your code references these flags and these get preserved, then you can the sky's is the limit. You can do whatever you want with these things and then make them as powerful as you wish and give your designers um as much flexibility and creativity uh as you would like. You can even see here there's a states set of uh a table where we can actually index into depending on our state and get the texture the frame for that object depending on what state it's in which recall from last week when we talked about hitting blocks with Mario. This might be how you could do that too. You could have hit and unhit and change the blocks texture accordingly. We didn't do that last week um though I alluded to as much. You could do it accomplishing just this uh exact sort of approach. And then so if we go back to our room again, recall we create the entities and then we also need to create the switch which would be in our game objects. If we have a game objects table, that'd be ideal place to do it. And indeed we do have a generate objects function here that's been added. So we'll just scroll down a little bit here. Pass [snorts] generate entities. And we do indeed have a sort of instantiation here where we're creating the game object, passing it in the game object defs of switch, which is just what we looked at, giving it a random X and Y that we know will be within the bounds of the map, bounds of the room rather, that we're in. And then we're going to define that on collide function ourself here and say if it's pressed or if it's unpressed then we want to set it to pressed and then set all the doorways open to true and then play a sound. That way as soon as our character collides with it triggers that state it triggers the sound effect and then all the rooms actual doorways which it has a reference to will then get opened for us and everything just sort of works nicely in harmony. And so with that, that's essentially all we have to do in order to have entities and objects that now sort of talk to each other. And in as much as like our character can now interact with it by iterating over all the game objects and then triggering the on collide function should we collide with it. And then we now have the ability to open doors, press on switches. You can now do anything you want with any sort of number of game objects as long as they have a reference to the dungeon or the room or whatever that they need to manipulate. So that's that. So this sort of takes us now to the more I guess interesting visual and more complicated aspect of the lecture which is the characteristic Zelda scroll between screens which is a very common uh I think mechanic that when people see it they immediately think oh that's Legend of Zelda that's a Zelda game. And here's an example of the sort of version before we get to the final version where we're seeing a scroll, albeit somewhat visually not quite perfect because the character is clipping through the wall

### [1:10:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=4200s) Segment 15 (70:00 - 75:00)

right here. We don't want that to quite be the case, but we are indeed seeing here a transition between two separate rooms. So, we have whatever the current room was up here, and based on the character's direction, they're facing down. So, it's clear that they're moving down. The movement in this example is automatic. So, this is similar to how the original Zelda worked. And most 2D Zeldas, as far as I'm aware, when you transition screens, your character will sort of move automatically towards the next screen and get placed in a particular destination at that particular point in time. It is ultimately like a little bit of illusion involved here because we have to essentially keep whatever our current room is and then there's usually never a next room loaded until we choose a doorway that we then move towards. And then at that point, both of them being loaded, we then perform a transition, a camera transition from one to the other. And then we sort of instantly flip our position right back to zero. And then that becomes the current room because there needs to be another next room later. And so in order to have the sort of current room and next room, which are functioning very similar to pointers in a language like C or C++, if you want to think about it that way, you have a pointer to a room that can be null and then a pointer to the current room. And then we need to essentially swap pointers such that the next room points to nothing so that it can be set to point to something when we do whatever the next doorway's transition is. And so we get the next room loaded. We move towards it. It'll be off camera, but we're going to move the camera position to it. And then we're essentially set everything hard to zero. And we won't see that happen because it's going to happen right at the second, right at the moment that we get to that position perfectly where the next room is. And so we're essentially almost similar to Flappy Birds looping where we sort of were moving the texture sort of left to right and then we would hard set the texture back to zero at a certain loop point. We're doing a similar idea here, just a bit more complicated bit of a different result, but it similarly captures that idea of making the human believe that they're looking at a movement that is in this sort of larger space, but really they're ultimately ever within zero to largely between zero and virtual width virtual height. And then they might briefly go up towards negative virtual height down to positive virtual height and then left and right virtual width, positive virtual width off camera, but they are never going to stay there. they're always going to then immediately be sent back to 0 so that they can do that next room magic. To accomplish this, we're going to use events. And so this is an opportunity to look at what it means to have an event and an event handler. When we touch a doorway, we're going to want this sort of like actual event to fire, which is, oh, we're it is time to shift bottom, left, or time to shift down, left, right, top in whatever direction that is. and then have the game the camera essentially tween itself up to the next room which is going to live there for a bit and it's going to be in memory and updating and so on and so forth. We're going to want to actually trigger an event that it gets handled by some function that does that behavior and then resets everything for us and then we trigger that event at the time that we touch any open doorway. And so to accomplish that we can just use a essentially a library knife which is what we've been using. But you can also you might hear the term dispatch like you dispatched an event or you emitted an event. We'll be using a function called event. dispatch in this particular example. But there are multiple ways to sort of word these functions and these ideas. But you essentially just have this string for all intents and purposes that gets listened for which is like shift left, shift up, shift down, shift right. And then there's a function that gets associated with that key, that string, which just gets fired. It's always listening essentially. And then at the time that you call event. dispatch, it essentially says, okay, trigger the function that was there. Assuming that we pass a string into dispatch, like event. dispatch shift left, it will listen for okay, it will call whatever function that we said you should listen for that this shift left, shift up, shift right, shift down event and call this function for. We'll see that we'll see this implemented and these again use anonymous functions which we've looked at in many instances already which is a common theme especially in dynamic languages like Lua or like JavaScript. You'll see this a lot. So we'll be looking at event which is another part of the knife library. So we had a knife uh we had the timer class so far knife. time this will be knife. event. And so we have event. on and then event. dispatch dispatch on being the sort of way that we say okay when we hear this string call this call back function. So this is where we essentially set up events that are capable of being listened for and then event. dispatch is our way of broadcasting emitting this event and should it be being listened for there will be some function that gets called this call back that will trigger and

### [1:15:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=4500s) Segment 16 (75:00 - 80:00)

then that behavior will then issue forth at that moment. And in this particular instance, this particular repo, we'll be looking at just handling all of the movements, the shifting with events. Just to illustrate how it works, here's a visual that illustrates what screen scrolling looks like in the original Zelda, which you can see it's very similar to what we saw previously. Notice that Link is moving from the left to the right, and then as soon as he hits just the right edge there, there's a transition, and then he gets placed here. He's on the opposite end, which is important when you move your character over cuz remember we are sort of resetting everything to 0 0. Like you can imagine this being 0 and then it gets there's a camera shift and then this is sort of offscreen but then this now becomes 0 again. This happens to be an infinite infinitely repeating terrain. So, it's it doesn't quite illustrate maybe the fact that you have different screens that get reset to 00, but it does capture the idea where you have an offscreen target screen. You're going to move towards it over some period of time and then now we're at 00 again. It just get hard reset and then just get like we got tricked into thinking that we moved, but in reality we moved and then everything got renormalized, recentered in game space. So that is the gist behind screen scrolling. We're going to take a look now at the implementation and it's going to be a bit of code. We'll look at most of the details, but there might be some amount that folks are encouraged to explore on their own, but I think the gist of it is going to be relatively digestible here. Let me just go ahead and cop close all of these and then make sure this is closed. And then we're just going to run it. And you'll notice that we have everything sort of before we have the switch. We have the slimes and everything and the the enemies. If I hit this switch here, we'll do the classic sort of switch opening the doors. But if I move here, you'll notice that we do indeed now transition from that bottom room up to the room that was above it. If I hit this switch here, a lot of monsters in here. So I hit this switch and then I move down here. The same thing sort of happens. And essentially all we're doing is we're just moving we are loading in the room that we're moving to relative to the current room. The current room is always at 0 0. It's always the center of the world, the origin. And then we're loading in the next world. Sorry, the next room. And then we're going to transition to it. The camera's going to move towards where it is going to be offset by some virtual height or width depending on which axis we're moving towards. And then that room is going to have everything in it including the player being reset relative to 0 at which point then we can then do another move to a different adjacent room which will have a different offset. We'll do the same thing. That room will get set to 0 0. But everything is functioning as intended. The only thing that is not currently working properly is when we move through the wall we do indeed just kind of clip through the wall which is an ideal behavior. And so we're gonna have to fix that. That'll be the next example. But let's go ahead and take a look at what we're doing here to accomplish that transition. So we are only triggering it when we're walking. So we'll actually end up triggering the animation when we're in the walk state of the player. The entities can't do this. So we're going to go ahead and look at the walk state here really quickly. You'll notice that we have this big block of code in player walk state towards the bottom of the update function where after we perform that sort of static dispatch to the entity walk state's update function, we're going to then sort of say, okay, if self. bumpted, bumped, which gets flagged if we did collide with a wall. We're going to pretend as if we're colliding backwards in the direction we're moving to test if we're actually walking into a doorway, which is because when we get bumped, we end up getting shifted out of the wall. So, we're going to look in the look to see are we in a doorway? And if we are, we're then going to dispatch the event shift and then left, right, up, down, whichever direction we end up actually walking towards. And this is going to also need us to make sure that our Y and X get shifted so that our character is directly centered with the doorway because we can in theory collide with the doorway just a little bit off of it, like a couple pixels. But if we do this next transition that we're about to do, this walking of the player from one spot, one part of this room to the next room, and he's not directly centered in the doorway in this in a way that we're going to stencil in on the next example, he's going to look as if he's just like fading kind of clipping through the side of the doorway, which we don't want. So, we're going to force the character's Y or X, depending on which direction they're moving into the doorway, to actually be directly lined up with the doorway. And so that's going to dispatch the event shift left which if we go to dungeon not doorway but dungeon rather you'll

### [1:20:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=4800s) Segment 17 (80:00 - 85:00)

notice that at the in the constructor we do set these four event on functions these event sort of like handlers here. They're anonymous functions again but we have this function this deferred call to self begin shifting at negative virtual width zero or zero negative virtual height. And these are like full screen offsets because these model the transition between this room and then a full screen's width or height away to a different room. And so that's what this begin shifting function will actually allow us to do. So if you scroll down, we'll see begin shifting here takes in an X and a Y. And then we're going to get whatever the next X and Y is in the sort of this the table structure of the dungeon to determine which room is there because it's going to have entities and we're going to transition towards it. All of its doorways will be flagged as true to start with when we are moving inside of it. It doesn't make sense if we're moving into the next room and his doors are closed but we're clipping through them. So they're going to start open and then it has this adjacent offset which gets added to all of its render operations such that when it gets rendered as a room including all the entities, the doorways, the tiles, it gets added this offset, this adjacent offset. It's called adjacent offset because adjacent to the main the current room at the time that it has this value. And then what we can do is uh also figure out okay when we are rendering that room or at the time or the value we should tween the player to specifically ought to end up in the opposite room that he is sort of moving through as we perform the transition. So if the player is moving up for example then the player is going to end up at the lower part of the next room right? If you have two rooms player moves up he's going to end up here at the bottom of the sort of top room. And so that's what all this code does. It just inverts whichever direction the player is moving. It's going to place the player's end final XY at the opposite part of the next room when it finishes. There's a tween here. This is doing a lot of the heavy lifting for us with the actual movement of not only the uh camera which is done here. Recall timer. between takes in these tables or it takes in a as a table. These sort of values that have the these are assumed to be tables and their member sort of fields here have to get actually set to whatever their destination value is that we want to tween. In the in this case we want to tween over 1 second. You could make this however long you want. You can make it 2 seconds or faster 0. 5. That's going to ultimately affect the speed at which the transition takes place. But the player gets their X and Y set to whatever that XY we just looked we just talked about is which is going to have been perfectly set by our little shift that I just talked about when we actually collide with the doorway. And then the self camera X and camera Y are also going to be tween towards whatever the shift X and shift Y are which is going to be again it's going to be one value is going to stay the same as is right now zero and the other is going to get set to the positive or negative virtual height or width depending on it's left or right top or bottom that we're going to move to. We can only move orthogonally in one direction. Once it's finished, recall that timer. Tween can take a call back. It can be given a call back of do or function or invoked to call the function finish which takes a call back function at the time that the tween finishes which is self finish shifting. This is the illusion part of what we talked about. If we look down here just a little bit, finish shifting just sets all these everything to zero in the current room. It essentially sets the current room to the next room here and then it just sets the next room to nil and then sets all of the XY values that we've been interpolating to move with love. graphics. t translate to whatever the next position is. Everything gets set to zero. And so we've tricked the player into thinking they've moved some distance into the dungeon when in reality they've gone to that room and then that room gets rendered and set at 0 0. And so if we come up here just a little bit, um this is that shift sort of uh code as well, which gets reset once we finish. Once we've actually finished, we're going to want to set the player based on where they were when we finalized that last shift. We're going to actually set their direction to left, right, up, down, depending on where they ended up in the map. And then we're finally going to close the doors and then reset switches because if you go were to go back to a previous room, all the doors close automatically, but the switches don't get reset. Then you can't actually leave the room. This was a bug that I actually encountered and I thought it was kind of funny. Um, so yeah, you want to make sure that you reset all your switches and close all your doors and then you essentially re-trigger the puzzle that was there in that last room uh when you initially did the uh transition. And then as you can see here, if we are shifting, we do perform that

### [1:25:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=5100s) Segment 18 (85:00 - 90:00)

love. graphics. transate of that camera X and camera Y, which again get interpolated with timer. between which allows us to perform the transition effect. And so with that, all of that logic, we then have this ability to detect a doorway collision, move towards the opposite point of the adjacent room. Once we've finished that with timer tween doing the heavy work of actually interpolating the player's position, notice the play, remember the player was moving during the animation and the dungeon itself was shifting c the perspective was because of the camera. Once it's all been finished with timer. ween, the thing we looked at previously in other lectures, we can then say, okay, once that's done, set everything to zero, normalize it, and then prepare our room or our dungeon such that we can then collide with another doorway and set next room to be some other door, some other room in the dungeon, allowing us to repeat this process, add infinite item until we just decide, you know, we're done with the dungeon or we reach the end or whatever. But that's the process by which we can implement scrolling. So the last sort of phase that we can look at in our implementation of Zelda before we also take a second just to look at the dungeon generator is going to be the stencil update which just allows us to effectively accomplish exactly what I pointed to previously. Notice that in this example in the screenshot I've purposefully taken the screenshot such that the character is transitioning between this room and this room. You can sort of see him little peeking out just a little bit there. But you can see there's, you know, no overlap with this archway. He's just being drawn right there as is. And we're effectively accomplishing this with a stencil what's called the stencil buffer or masking, which is where we're essentially setting some pixels behind the scenes to be this sort of invisible mask that prevents pixels from being drawn should we do a certain set of calculations with that stencil buffer, which we'll look at in a second. But we can effectively just draw a an invisible rectangle that's here that is essentially flipping some of the pixels in that buffer. The sort of pixels quote unquote these values in this invisible buffer to say okay if we try to draw on top of any of those flagged pixels don't draw anything. Therefore, if we draw on top of draw those first we draw these archways, these actual graphics we want to render and then do the stencil operation, we can then say we can then ensure that like nothing can be drawn on top of wherever that rectangle is that we drew. Therefore, trick the user into thinking that we are actually going through this hallway with depth when in reality we're just masking out the pixels that character is trying to draw onto the screen. And so this is essentially just an image of what a stencil really is. And you can sort of think of the stencil buffer. You can use the stencil buffer in many different ways. This is just sort of illustrating the con the concept of what a stencil is, which is just some sort of non-transparent material through which you can look through, for example, and then draw within a particular bound. In this case, you know, circle, heart, and a rectangle. But if you were to try to draw on this white, for example, assuming this is some sort of non nonpermissible material, you would not actually draw pixels. That's effectively what we're doing except they're just sort of invisible to us. We determine how they get drawn and we can show a de I'll show you a debugging tool to sort of illustrate that. But another way to look at it too in a more technical terms and this is taken from a useful blog on DirectX but this applies to OpenGL and Vulcan and any other graphics API but you essentially at the time that you're going to try to process pixels by the either the pixel shader or the fragment shader which is a stage of the GPU's pipeline where it calculates color value there's a step after that before it actually renders it to the screen or to a texture to some device where this thing called the stencil buffer which also has depth buffer gets tested and you can test it in various different ways. You can test for equality for less than classic boolean type operations and you can perform draw operations onto it as well to affect those values. So we can set for example this ring of ones in the stencil buffer. If we assume these are pixels, this is the same range of pixels but these are the stencil indices over those pixels. And we were to set these to one and then set the stencil operation to the comparison function of equals, meaning that anything that equals one will allow through pixels. you end up with this result here which is that now you've drawn a ring of pixels to the screen because those passed the function the stencil test as it's called and which love 2D has the same API for um that we've defined at the time of the intermediate stage between the pixel shader the ver or the fragment shader and the render target as it's called in DirectX. Here's another example of how to sort of use the stencil buffer for cool effect.

### [1:30:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=5400s) Segment 19 (90:00 - 95:00)

This is a common effect used in various games and there's virtually unlimited ways you can use depth and stencil testing to do certain really cool things. But in this case, if you imagine drawing these cubes first and you wanted to draw an outline around the cubes that in this sort of abstract way using this color the it's normally quite a difficult thing to do. But if you just also at the same time that you draw these cubes also draw to the stencil buffer which again there's a pixel there's a there's an index on the buffer on top of your screen and you were to set these to one and do some sort of comparison of uh of less than for example on that then anything that you draw at the next stage that is going to and set those to one. So if it's not less than one, effectively it's not going to draw anything with the time that these rectangles or these cubes get drawn. You just scale the cube out a little bit, right? You keep the cube where it is, scale it a little bit, and then draw again with the stencil preventing the prior pixels from being written to because you just don't pass that stencil test. But you set the shader to draw this green color. Now you can get this outline because these the stencil buffer is effectively preventing you from drawing where the old cubes previously drew even though normally this green rectangle would be drawing over those. Right? If you think about drawing just over whatever's already written to the screen. Portal uses this to a really cool effect in that through stencil testing they ensure that the current scene doesn't get drawn over wherever this particular texture is here. Instead, it get there's a stencil operation that then will try to draw another scene such that the stencil buffer will only allow in that scene where the pixels that were previously written to the stencil buffer permit that operation. And so therefore, you get this sense that you're actually looking through looking at two scenes at once effectively. You have this main scene and then you're rendering another scene again, but you're only rendering it through this portal here, allowing you this sort of sense that you're defining uklitian space and able to go through different places, which is a really cool effect. They used a combination of stencil and depth testing to achieve this. And so with love, this is also an interesting time to talk about love 11 versus love 12. They have two different ways of doing this. sort of love most of love 12's changes which is coming out soon are easy to sort of create a version of that permit compatibility with love 11 and love 12 love 12's stencil changes are not backwards compatible with a love with love 11 so we've included both a love 11 and love 12 version of the code in the same files and we're going to be looking at both but essentially they operate very similarly where you have a lovegraphics stencil function in 11 which takes a function that draws to the buffer whatever you determine with the operation that you specify and then a set stencil test which then does whatever the test is that you care about to determine do the pixels get to draw to the screen depending on what the sort of state of the stencil buffer is or the operation is in love 12 there is no longer a function that you pass to a love. graphics graphics. stencil, you have a set stencil state which takes it's a bit more of a lower level version of doing it where you specify an action, a mode, and then a value, an optional value. And we'll take a look at these examples. And then and you don't have to use the um set stencil test or stencil here. You just have the one function set stencil state and then you draw whatever you want. And then depending on what you've set the stencil state to, it'll be either a stencil operation or a regular operation. But there's also this love. graphics set color mask, which is important because now that we don't have this dedicated drawing function that handled this for us, it handled color masking for us. We actually have to prevent drawing to the screen while we're drawing when we're trying to set bits in the stencil buffer such that we don't draw and also set the stencil buffer which will have the effect of drawing for example like white rectangles onto the screen which is not what we want. We don't want to draw anything physical to the screen. We just want to draw effectively affect the bits in the stencil buffer and then perform an operation that tests for that and allows us to draw or sort of preclude us from drawing pixels of our character onto the screen based on those rectangles that we saw. which is a lot to essentially just say that we now have a buffer in memory that we can manipulate to mask colors that we draw onto the screen in other draw operations which we'll take a look at here now with Zelda 7. So let's go ahead and open this up here. Going to up Zelda 7. And then we're just going to test it so we can see it in operation.

### [1:35:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=5700s) Segment 20 (95:00 - 100:00)

So we have the usual just going to take a bunch of damage. Now I'm going to move through the hallway here to the left. And you'll notice as I'm moving through, we do indeed sort of have this illusion that we're moving through a corridor because our character is being masked out while he's moving through the corridor. So if I do it again and then we'll move up, you'll notice just clips does not clip through the wall. just actually gets completely not drawn. He's still moving. Everything is still there. And there the draw operation is still happening, but the stencil buffer is essentially not allowing those pixels to affect the screen, the render target at the final stage of rendering. So, if we go over to Zelda 7, this is going to take place in the room. So, if we go to room here and we're going to scroll down just a little bit here, there is a render function. And so, you'll notice that we have we've included two different versions of this code so that you can see how it works between the two. And then as love 12 comes out, the love 11 versions are technically deprecated. So you will get a notice saying that you're using a deprecated version. You're going to want to use the new version in love 12. We've deliberately turned off the deprecation error which you can do through this code here. Love set deprecation output false. But just for folks that might be using love 12 in the future. That's something that they might notice. So we've included both versions. The love 12 version uh I would recommend folks move to that version over the level 11 version. But just for right now, since love 12 is technically not out just at this moment, I'm going to go ahead and reopen up room. So the level 11 version, it takes in a function and then replace is what's going to happen to the stencil buffer and then one is what it's going to replace it with. So we're going to replace whatever we draw here in this function, which is four rectangles. They're going to be at the doorways between essentially the tops or the just the edges of the doorways and the next where the next doorway would be when we do a dungeon transition. So that's what they're going to be set to. That's these all these functions here. All these rectangles rather in this function are going to be performing this replace one operation. You can do this with shapes. images if you want more fine-tuned specific per pixel image arch fancy sort of masking. You can do that with uh images, but we're just doing it with um rectangles cuz that's all we effectively need to accomplish the goal. And then when we set the stencil test to less than one, then that means that anything that passes less than one as when that character or when whatever gets rendered is being compared against the stencil buffer will go through. Meaning that because we replaced all of the rectangles with one in the stencil buffer at the doorways, it's going to fail the test. It's going to be equal to one. Therefore, when it tries to draw the player and the player's pixels are on top of those parts of the stencil buffer that are set to one, that failure means that essentially skip render is skip that it won't skip the rendering process, but it will skip allowing those pixels through to the final result. Therefore, making it look as if the player is going underneath that sort of dark tile, a dark archway. And then the Love 12 version, I'm just going to go ahead and comment out all of this here. the Love 12 version. If we uncomment all of this, Whoops, didn't mean to hit that. Meant to hit that, you'll see that it's pretty similar except we are setting the color mask to all falses because now when you draw things to the stencil buffer, you need to actually make sure that you're not also drawing because it does it in one pass. Now it will draw to the stencil buffer and also draw to the screen during that span of time. And so if we want to draw rectangles that are stencil affecting but not like actually drawn to the screen, then set false to all four components of RGBA color here means that don't let those through when we try to draw that color. It'll just draw completely nothing. You could set just red to false, meaning that no red will draw, which will sort of change how your color gets colors get rendered. You can turn off individual channels this way and affect different color effects. But this is essentially just ensuring that we don't draw anything to the screen when we render things. And then this set stencil state is performing the same thing as that stencil function was previously where we're setting replace to always of one. So we're going to replace every pixel. There's no condition. There's no checking of anything. You can do a lot of various functions to do certain tests and different more complicated ways of affecting the stencil buffer, but in this case, we're just going to always replace the pixel that's there in the stencil buffer pixel with one. And then

### [1:40:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=6000s) Segment 21 (100:00 - 105:00)

we're going to draw those rectangles, the same rectangles. Again, our mask is on false on all four components of color. Nothing's getting drawn. We're going to set it back on. And then we're going to keep whatever, and this is the default operation that previously love 11 was doing. We're going to keep all pixels that are less than one when we draw. Hereafter that the stencil buffer is going to say is this is the value of the stencil buffer less than one if so permit drawing if not you know forbid drawing. Therefore when we render the player and we try to draw over those rectangle affected parts of the stencil buffer the character is not going to draw where those pixels interact with the stencil buffer in that way. And then set stencil state here just resets it to a blank slate effectively. So there is no more stencil uh operation occurring. And that's the difference between level 11 and LE 12. They function identically. And there's even some debug drawing here at the very bottom. And so I added a flag that you can test for if you wanted to see the rectangles actually drawn to the screen and what they look like. U I believe I set it to D or R. Let me just confirm which key I used to draw. So in the update function here should be which is a s for stencil. That makes a lot more sense. So let's go ahead and render this. And then if I hit S, you'll notice that there are four rectangles that get drawn there. Those are debug rectangles just so we can visualize where we're affecting the stencil buffer because again we're not trying to draw these to the screen. Um that's why we set the color mask in the Love 12 version. But if we are not sure exactly where the mask is being set properly and we want to just debug for ourselves and again similar in spirit to the collision rectangles for the sword state and whatnot and for our hitboxes. This is a good practice to have a debug way to sort of do this on a keystroke. I can just set this global flag this boolean that says uh you know if some flag then just draw literally draw these transparent red rectangles and then if I hit this switch here and then we just go up to the top you'll roughly see although it's kind of slow to see but right as soon as we go underneath that rectangle we are indeed not allowing the players pixels to draw forth onto the screen. Uh and so that is how the stencil operation works effectively between level 11 and love 12. I recommend folks using love to go to level 12 though level 11 will still work probably for a while but it is officially deprecated at the time of this lecture. And so the final thing to look at I think is just we haven't really looked at the dungeon generator too much but that's something I also wanted to spend a couple of minutes just looking at very briefly before a couple of little side topics. And so we can do that through I have a separate implementation in here called dungeon. And the purpose of dungeon is just to provide a visual way for us to see how the dungeon generator actually works. So I'm going to fire this up. And we'll see that it's a pretty simple UI, but we have a 10x10 grid and we're using a queue in order to do this. There are many different ways you can generate mazes and dungeons and whatnot with stacks and cues. We're using a Q because it gives us kind of some nice properties that align well with the sort of aesthetic of Zelda. But if we look at some of the information around the screen, we can see that we're in the start phase. We have our head set to one. We have a Q length of zero. The head is going to be set to the index that we're always going to be looking at. So our Q is essentially just an array of these coordinates that we're going to look at that get randomly decided and we're just going to iterate through them one by one. We're not going to do any like removing from the queue. We're just going to keep adding and then keep incrementing the head. And then what that's going to allow us to do is eventually the head if we depending on how we want to implement things. Typically you'll never get to the head reaching the end of the queue in this example. Sometimes you will, but effectively the theory is you want to aim for a certain number of rooms to generate or for so many rooms to eventually generate and then your head to crawl through all of them and test for neighbors to generate contiguous rooms that you eventually determine, okay, we've reached the end of the queue. Now we can just consider it the end of our dungeon generation. If I press enter, we'll see the very first room gets is going to get placed kind of at the bottom center, which is at 510. And so I have a red cursor there that's just going to illustrate that that's where we're currently looking. There's a visited array table at the bottom right which is just going to be a way for us to confirm that we have already generated rooms at a particular index so that two rooms that try to spawn new rooms don't try to both write to the same spot. They'll check an array and they'll say, "Okay, we've already visited 510. We don't need to add a room to 510. We have a Q length of one right now. Right now

### [1:45:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=6300s) Segment 22 (105:00 - 110:00)

what we're going to do, and this is what's going to happen every time we iterate through each room, is we're going to check all of the orthogonal neighbors, top, left, right, and bottom. And unless we're at the edge, and we're not going to check bottom here, but we can see we're at current 510. We're going to check, for example, 410. We're going to check 59, and we're going to check 610. And what this is going to do is if we randomize which of those we decide we want to actually place in the map, we could place all of them every time. And what that's going to do is just going to grow in this sort of symmetrical uniform way this dungeon out. But that's not ideal behavior because everything's just going to always end up being the exact same sort of layout. We want an organic varying aesthetic and distribution of rooms and this sort of feeling like every dungeon is different. So there's a weighted chance. There's a 40% chance that a direction is skipped. Meaning a 60% chance that any one direction will be chosen such that there is a randomness to choosing different paths for every room that gets added. Now there are some caveats to that approach being that like if it is random purely then you could theoretically just at the very outset not generate any rooms and then have just a single room dungeon and that would be a bug in the implementation and so therefore you know this particular instance is going to use that implementation but the code will ship with a version that actually is fixed in Zelda 8 whereby we actually do ensure a certain number of rooms get generated and then if we don't have 10 rooms generated by the time that even that runs out then we can just crawl through our structure and add rooms wherever there aren't any rooms that have been generated yet. But for right now, we're just kind of naively doing an approach where we're going to look at all directions and randomly choose of the four or three if we're at the edge which one we should choose. So if I press enter, notice that I'm able to step through this by the way in sequence. This operation was happening linearly this as just one generation previously uh right at the outset. And we're going to take a look at sort of why that's interesting in a moment. But if I press enter, you'll see that we chose left and right, but we did not go up. So, our dungeon generator has already chosen not to go up with its sort of prospective next rooms. These have gotten added to the queue. The queue is actually visible here at the bottom left. You'll see we have 610 is where our head's at. Head's always going to be at the bottom. And then 410 is the node that's right above that in the queue. So what our code is now going to do is it's going to go to each of those in turn and it's going to create a room there and then it's going to do the same exact operation where it's going to say okay up down left right unless there's a room already there and then 40% chance to skip that direction. So we're going to go to 610 first. That's at the head. So that's on the right. Notice that they're also being numbered based on their order generation just for our ease of being able to see the process. So then what we can do is uh press enter again. Notice that we had two options. We could have gone to up and to the right. We only chose the right. So okay, now we're going to press enter. We went here. Okay, we got two directions. Okay, left. Okay, we're going to do the same thing. Now we're going to go to the right. Okay. No directions there. And then we're going to go this one. K1. K1. K2, K1, and then so now, interestingly, we have sort of this very leftward bias dungeon that kind of goes up a little bit. And you know, if you were looking at this and implementing things a little bit more uh I guess with an eye towards the design of the dungeon itself, you could envision a way to sort of gauge distance between the starting node and some of the further nodes and then sort of heristically assume that further in points of the dungeon are where you would put treasure or you would put the boss. And then you would also lay keys and determine through some sort of like algorithm that can determine that you can get to any particular door, assuming that you get the keys in the order that they're generated, that the dungeon is traversible. You could certainly do that after this point. We're just sort of naively creating all the rooms and then we're going to do a final phase which is called the doorways phase which is going to be right here. [snorts] So you can see the phase got denoted as doorways. It's going to do a left to right, top to bottom crawl of every door, every room, and it's going to place doorways for us across all the rooms in the dungeon. And then once we're in the done phase and we press enter, we're going to go into the dungeon that we just generated. And then I can press M and see that we do indeed have this exact dungeon that we created. I can go onto the uh switch there and I can move to the right. And if I move to the right, you'll see the red cursor move to the right. I'm going to move one more to the right and

### [1:50:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=6600s) Segment 23 (110:00 - 115:00)

we'll see we'll get to a dead end because there were no there's no rooms to the right or to the up top of that. So, we do indeed have a dead end here. And the only way we can now go is backwards to the dungeon that we generated. So, pretty ultimately straightforward, right? Just a queue. We're kind of crawling through it. We're saying, "Okay, there's a random chance. 40% that we won't do a direction, but 60% that we will. " And it largely works. It does have bugs if you were to not ensure that a certain number of rooms get created, which we've already seen uh a couple of times. And then the fix for that is just ensure that no matter what, you do have rooms that get generated. So, we're just going to quickly look through what that looks like and show a couple of cool things about it. So the actual dungeon maker which we haven't looked at too much is here we have a couple of different versions of it. So there's a generate version that is wrapped in what are called co- routines and then we have a version that is being run in the actual regular code. So in this particular example this is just one function in the dungeon repo which is just the co- routine version. And if we were to go back to for example the uh let's say Zelda 7 and we were to look at the dungeon maker you'll see that we have a couple of versions of that function. We have a generate gen function which is a generate a generator function um which is another sort of way of thinking of or classifying co- routines which we haven't talked about yet. We'll briefly just talk about that. But there's a version down here that we've been using throughout all of the implementation otherwise which is the same code but this version does not have any of the co- routine associations with it. And this is just the generate function but it's essentially just a Q. It gets an XY set of XY pairs that get looked at. A room gets instantiated there added to our overall uh rooms 2D array here 2D table. Then we just essentially do some assertions that we're less than or equal to the length of the queue and that we haven't gone over or that we are still within the range of the number of max rooms we want to generate. You could tweak this number as much as you want to. It's a parameter to the generate function. We will then uh essentially just you know do that verified check. Have we visited the room yet? Generated something there. If not create a new room right here player being passed in which is an argument to the generate function. So the room has a reference to the player. Also order for our generator visited at a string key that maps up to the CX and CY for current X, current Y. Setting that to true so that we can reference that later when we want to see if we've already generated something there. And then this is where we essentially do like a little shuffle algorithm that's going to shuffle up, down, left, or right. Can be one of the four. We're going to then essentially here's where we are skipping generation for that direction with the 40% thing that go to continue thing. We saw that in the Mario example of the dro when generating levels to skip uh sort of generating tiles in a particular x slice of the world. I y slice but a particular xindex y slice in the world. And then uh what we're going to do is essentially just do the same thing here. check if it's not um uh check if it's within bounds not visited yet add it to the que and that Q is just going to keep you know it's going to have all of the floors we have a 60% chance to add it in this example that our Q is going to then just iterate through in this sort of like bread first manner if you were to use a stack or something similar where you're just adding to some sort of stack structure and taking off from the top you'll end up with a very snaky generator for example a very linear generator. Um there's many different ways that you can use different data structures to accomplish this. We're going with a stack because or with a Q because it accomplishes the sort of like more organic wider sort of building of dungeons versus the snaky sort of linear approach that a stack would give us. And then lastly, we have a generate doorways phase. And then the last important thing we'll look at here and something that was really important when I was building this out especially is you know something like a dungeon generator it can be hard to debug uh especially if it's a very recursive sort of algorithm or uses complicated data structures and to be able to visualize it well is why we've included this tool that allows you to do it in the dungeon example. But to do that, you know, you're thinking about, okay, I have this one function that does all these things and how do I split it up so that I can like take a snapshot and pause and look at every individual part of it. And the way that you can accomplish that is with this thing called a co-outine. And so a co-ine is just built into a lot of languages, a lot of environments. Lua has its own sort of co-ine function implementation with this co-outine namespace which has a few functions. wrap being a function that returns a co- routine that you can then call over and over again in whatever calling context you want to use

### [1:55:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=6900s) Segment 24 (115:00 - 120:00)

it in. In the case of our particular example, folks can look at in the dungeon code. We won't look at it too much here, but if you look at the actual dungeon test state, you'll be able to see all of the code that we used to draw the grid, all the colors, and you'll see that if we go to any instance of self. gen gen there anytime you press enter you'll be able to see the next instance of the que such that should be in here I'm used to having the sorry used to having this particular setup doesn't have easy highlights viewable here but you can see that we have a sort of loop that over the course of time in our update phase once we press enter is going to just call self. gen gen which is getting a reference back to the return value of that generate function which is returning a wrapped co- routine and so what that means is that so a co-outine is essentially just this function that you can essentially think of as being pausible within a sort of context it exists in the same process as Lua itself and it's similar to a thread but it operates within Lua's own stack where you can essentially just call this function And then it's going to have a set of pause points or yields within it where it will then return back to you a set of values at whatever point you want while preserving its current sort of state, its current call stack, its current existence at that state in time to allow you to if you were to call it again, it's just going to resume operation wherever it left off on until it's exhausted all of its potential yields. You can run into an infinite scenario where this never happens. So it's something to watch out for. But assuming that your co- routine does end eventually, you give yourself the ability to sort of manually trigger however much of your function you want to run at once, assuming that you spaced out these yields. So if we were to go back again to the dungeon maker here, uh to the one that is in dungeon, you'll see that sprinkled throughout all of the implementation of this function are these co-outine. yields yields that return a table of just all the information that we care about. And this is at the very end. So it's going to return the dungeon itself. We're going to instantiate the dungeon, return that as part of the final done phase. But throughout in other places, you can see for example here, this is the doorways phase. That's where we can sort of signal to the calling context. Okay, we're yielding. This is at doorways at these sort of states in time. This is where current is. This is the state of the rooms, the state of visited. all the variables that are essentially these complicated things to pay attention to during the generation of your dungeon, you can pass them back to a calling context as a co- routine and then step through it piece by piece such that you could use like keyboard input in the case of love 2D to see step by step something that otherwise would be synchronous and really hard to visualize bugs and weird behavior. And so this is a practice that I highly recommend people abide by or use when they're debugging complicated algorithms or generations of things like dungeons or levels or whatnot. Co- routines can be your friend and Lua makes it super easy to use them. Um very handy and folks can explore the dungeon example to see that in practice. So that brings us to just a few last topics to talk about that are kind of just fun aides more than anything. um sort of lighter things to look at uh on the heels of quite a lot of complicated code. But we've talked periodically today about game design via data. I think this is an important idea that particularly applies very well to large complicated and dynamic games where you have lots of entities that might interact in weird ways. And entity component system architectures are also very good for this too. And these going to kind of go hand in hand for that which you've alluded to previously. But here's an example of what a more fleshed out version of a goblin or something a creature in your game might look like. Besides just the animation frames, the texture, simple things like that, you could imagine being able to specify that they should have certain types of weapons, whether they're aggressive, which affects their AI, uh whether they sleep at night, whether they're flammable. You have all these different things that can affect very dynamic game spaces. And I think it's very important to try to think in terms of this when you're modeling games like that. Um, at least that's in particular a lot of the games that I'm interested in have this sort of philosophy and this sort of requisite. I think um games that have a lot of items and enemies and things like that, they get complicated. And so doing it in data and having sort of emergent behavior, emerging gameplay and quickly iterable design or gameplay uh is a is a blessing. I think we've talked about the last couple of weeks. I think on the heels of that, this is kind of a fun thing. I've

### [2:00:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=7200s) Segment 25 (120:00 - 125:00)

dabbled in this a little bit occasionally. I'm not an expert by any stretch, but we've looked at Love 2D throughout the course of the term, and it's very high level. It uses Lua. It's, you know, it's modern. It's not something that takes a tremendous amount to get started with. It's it certainly is a very vast and deep set of things to do, but it's ultimately relatively high level, which is why I like it for teaching an introductory game development class, but NES game development and Super Nintendo and many generations even thereafter were a lot more low-level and difficult and very different. And here's a few links that you could go and explore, for example, with NES homebrew to see some of that. Here's a an excerpt of what like Super Mario Brothers looks like for the NES and from a disassembly. And it's written in assembly code. um 6502 for the NES and it's not nearly as readable or easy to understand or grock because assembly is just a very different world but it is very close to the CPU. It is literally using op codes on the CPU which are affecting the hardware on the CPU at that level to affect the behavior that you want in your game. And so you have to write a lot more code to do a lot less unfortunately. But this was how it all got done back in the day. And the paradigms and ways of looking at those problems were very different. And I think it's worth exploring that sometimes and just sort of seeing and respecting how far we've come in this space across all spaces in programming. But in games is the space that I'm the most familiar with. And that brings us sort of to the close of uh this lecture and then to what we will be looking at for assignment five which is namely extending what the player is capable of doing such that right now the player can move around swing a sword which we illustrate sort of how to add a state and to be able to affect behavior and do things in the game besides just moving around and even adding new hitboxes. So let's extend that idea a little bit and now actually add pots um which is this big part. So, be able to grab a pot, throw it, have that be a projectile, affect enemies, hit the wall, so on and so forth. We saw pots in the sprite sheet. But also, right now, folks might have noticed all too well that the game's pretty hard and that when you get hit a certain number of times, you're kind of out of luck because there are no ways to recover HP. But in Zelda the game, it is very much an expected thing that you have hearts that drop from enemies or from pots or from anything that replenish a full heart, which is equivalent to two damage in this game. So that's another big part of the game as well. So hearts, lift pots, pots can be throwable, and then damage enemies. And then lastly, sort of another big thing and very important and I think iconic and we alluded to it previously is in Zelda there are treasure chests. And within those treasure chests are any number of things. In this problem set, the expectation is that the player will find a boomerang. There should only be one per dungeon. When the player finds the boomerang, they should have an icon to show that they do have it. Some inventory icon similar to what we saw even in the uh original Zelda image. The player should be able to throw that boomerang. The boomerang's behavior should be such that it goes out a certain distance and then we'll come back and track the player no matter where they are and then they'll be able to throw it again. while it's out, they should not be able to throw it. And then while they throw it, it should do damage to enemies or bounce off of walls. So, a few different things related primarily to projectiles, but also just to other big pieces of the Zelda formula, at which point really you'd only have like maybe a boss and then keys, and then you'd kind of almost have the full base game there. But that's essentially just behind assignment 5. And so next week we're going to look at Box 2D and kind of take ourselves out of the NES era and back to the modern era, which has been kind of a theme back and forth. Next we'll be looking at an Angry Birds implementation uh using this sort of uh public domain tile set and looking at a very different way to do 2D game development with physics compared to anything we've done so far. Box 2D ships with love. It ships with many different environments. is very easily embeddible into almost any engine and is a 2D way to sort of do a lot more interesting types of collision and mechanics and dynamics than what we've seen thus far. So that was Zelda. That was week five. We did a lot in terms of looking at a bunch of code. We looked at even some relatively sophisticated dungeon generation and co- routines there at the very end. But we've run the gamut largely of what's possible with 2D in terms of sides scrolling top down. Not a whole lot of I guess visual or aesthetic domains left per se. Even next week we'll be looking at sidescrolling graphics. We could look at isometric at some point perhaps, but that would be one of the few left to really explore. Otherwise, most of the fundamentals I think now have been communicated. And so now it's time to explore things like physics and box 2D. And then eventually with Pokémon, we'll be looking at things like turn-based systems and uh and guey elements, graphical user interface elements. But, you know, until then, this was Legend of Zelda and the close sort of the NES era. And we'll see

### [2:05:00](https://www.youtube.com/watch?v=iwjilbptPFM&t=7500s) Segment 26 (125:00 - 125:00)

you next time.

---
*Источник: https://ekstraktznaniy.ru/video/49634*