Legend of Zelda Coding Tutorial - CS50's Intro to Game Development

Programming the 6502: A Window into the Past of Game Development in the 80s and 90s

In the 80s and 90s, game development was a challenging task. The processors at that time were relatively slow, with speeds ranging from one to three megahertz. This meant that developers had to be creative and resourceful when it came to optimizing their code for performance. One of the key challenges in developing games during this era was writing efficient assembly code for the 6502 processor.

The 6502 was a popular microprocessor used in many classic consoles, including the NES. To program for the 6502, developers had to learn assembly language, which involved writing low-level code that directly manipulated memory and hardware registers. This required a deep understanding of computer architecture and programming principles, as well as a willingness to work with complex codebases.

In the 90s, as gaming technology improved, the need for specialized knowledge like this began to diminish. The introduction of high-level languages like C and C++ made it easier for developers to write games without needing to learn assembly language. However, for those who were willing to dive into the world of low-level programming, there was still a rich reward.

The 6502's limitations also led to innovative solutions. For example, many games used clever tricks like bit-shifting and masking to optimize graphics rendering. Others employed techniques like horizontal scrolling and tile mapping to create seamless backgrounds. These approaches not only helped developers squeeze more performance out of the 6502 but also laid the groundwork for later game engines.

The rise of consoles in the 90s, such as the N64, PlayStation, and Saturn, brought new challenges to game development. The increasing complexity of graphics processing units (GPUs) required specialized expertise, including knowledge of assembly language programming. In fact, some teams working on these consoles had to write code in low-level languages like assembly just to get their games running smoothly.

One notable example is the PlayStation 3, which has a notoriously difficult graphics processor to program. To tackle this challenge, developers needed to have a deep understanding of computer architecture and programming principles. The resulting games were often breathtakingly beautiful, but the process of getting them there was grueling and time-consuming.

Programming the 6502 for Games: A Brief History

The 6502 processor has played a significant role in shaping the history of game development. From its early days as a microprocessor to its current use in retro gaming hardware, the 6502 has been an integral part of many iconic games.

One key aspect of programming for the 6502 was the need to optimize code for performance. This involved writing efficient assembly language programs that could take advantage of the processor's strengths and limitations. In the early days of game development, compilers were not as sophisticated, so developers had to rely on their own expertise to create high-quality code.

The 80s and 90s saw a surge in popularity of homebrew gaming, with many developers creating games for the NES, Commodore 64, and other retro consoles. These early games often featured simple graphics and gameplay mechanics, but they paved the way for later generations of game developers.

Assignment Five: Creating a Consumable Game Object

In Assignment Four, you created a game object that could be damaged by enemies. For Assignment Five, your task is to create an additional game object that can be consumed by the player. This object should have its own set of properties and behaviors that interact with other objects in the game.

One approach to implementing this object is to use C or Lua programming languages. Both are relatively high-level languages that provide a lot of flexibility when it comes to creating custom game logic. However, if you're looking to write low-level code for performance-critical areas, assembly language may still be your best bet.

Creating the Consumable Object

To create the consumable object, you'll need to define its properties and behaviors using C or Lua programming languages. Start by defining a class or struct that represents the object's state, including its location, health points (HP), and other relevant attributes. Then, implement methods for consuming the object, such as taking damage from enemies or being picked up by the player.

You may also want to consider implementing animations and visual effects for the consumable object, such as a animation that shows the player lifting it out of their inventory or dropping it back down into their pocket.

Tracking the Player's Location

One key aspect of implementing the consumable object is tracking its location relative to the player. This requires using some sort of position calculation or interpolation technique to keep the object in sync with the player's movements. You can use a combination of sprite positioning and collision detection algorithms to achieve this effect.

Walking Animations and Carrying the Object

When the player walks around while carrying the consumable object, you'll need to implement walking animations that take into account their current location and movement direction. This may involve using sprites or other visual effects to create the illusion of movement.

Finally, when the player decides to throw the consumable object away or discard it, you'll need to implement a throwing animation that shows the object flying through the air. You can use physics engines or simple collision detection algorithms to achieve this effect.

Conclusion

Programming for the 6502 processor was a challenging and rewarding experience for many game developers in the 80s and 90s. The process of optimizing code for performance, using low-level languages like assembly language, required creativity and expertise. However, the results were often breathtakingly beautiful and memorable.

In today's gaming landscape, where high-performance graphics engines dominate the scene, it's easy to forget about these early pioneers of game development. Nevertheless, their contributions continue to shape the industry we know and love today.

"WEBVTTKind: captionsLanguage: enall right welcome to gd50 lecture five today's topic is the legend of zelda as you can see on the screen there uh very iconic game last week we did super mario brothers which is arguably the most iconic video game of all time legend of zelda is a close contender even now just like with mario they're producing very wonderful games i think last year breath of the wild took all the i forget which ceremony it was or awards sort of show but it took like every award possible this last year the latest zelda breath of the wild so it feels sort of apropos to talk about legend of zelda in that context as well uh here's a screenshot of the original legend of zelda it was an nes title just like super mario brothers a you know just like most games of its time tile based you can sort of see how the surroundings are sort of layered tiles bit by bit the goal of the game overall was to sort of explore this open world which is kind of a first of its kind you controlled link shown at the very bottom there you had a sword you had bombs you had arrows you had uh you went to go find rupees you went through dungeons you slayed monsters and bosses and then ultimately the goal was to obtain the triforce which is shown at the very top left and right of the slide here here's the screenshot another screenshot legend is all the inside and actual dungeon where you can see at the very top there's got like a map layout maps are sort of arranged in a grid and you could go you know room by room whereby each room was the entire width and height of the screen uh looking for you know solving puzzles looking for items that sort of thing uh you can see there's a monster there you have hearts at the very top right so when you take damage hearts sort of decrement we'll see both of these the hearts and the the dungeon aspect of the game today in lecture as well as you know the the width and height of the room being a dungeon tile and controlling an avatar but this is sort of a representative screenshot of what the game looked like back in the day some topics today that we'll be discussing in order to implement sort of the foundation for what a a game engine like this might look like are things like top down perspective so as you can see in this screenshot we're looking at you know link from the very top and a bird's eye view as opposed to how we used to look at mario from the side where it was sort of like a side scroller here we're actually looking at things from the top down so we have a view of the room in sort of a different perspective there's sort of a there's not really a z-axis technically speaking but there sort of is you can implement things like gravity in a game engine like this by jumping over holes and things like that but there's no gravity like there was in super mario where you know you walk left to right you jump over gaps and it's very easy to see in that sort of sense we'll be talking about infinite dungeon generation so in the context of today's example we'll see how we can go about implementing a dungeon that sort of you can go through forever and ever and how to sort of model that and make it look as if you're traveling through a dungeon over and over again and going through different screens in different rooms that are all different but in reality we'll see it's actually just an illusion like we've seen before in prior lectures we'll talk about hitboxes and hurt boxes and what the difference is between the two hitboxes being uh rectangles on the screen that inflict damage upon other entities in your game world and hurt boxes sort of being the rectangle that models where your player or whether where an entity in the game can be hurt by other hitboxes we'll look at events so events are a way of sort of broadcasting some key or some message that tells the game world oh this thing happened and let me register a function to call when that sort of event is processed so on some event you know dispatch an event and then upon that event being received by whatever's listening for it perform this chunk of code it allows you to sort of decouple aspects of your game engine a little bit and makes for a little bit more readable code and allows you to do some interesting things with like achievements for example where you don't necessarily want to pull every single frame oh did i do this sort of arbitrary list of things this frame rather you can just process all of that within the event and have a listener and a function that you know every time you broadcast oh pick up coin maybe i have an achievement that's like oh uh have i picked up 50 coins in this level you know your achievement callback function in your event can can then look for that and in your game loop you don't have to say every frame oh if player has 850 coins and you sort of take out bloat that would exist in your sort of overall game loop that way and we'll see an example of how we use that in today's lecture we'll look at screen scrolling so a very common uh very uh sort of iconic aspect of legend of zelda is when you're going from one screen to another there's a sort of transition period as one screen loads and the other screen goes away in hardware in the nes uh this was sort of the only way um you could actually render a lot of um sort of more than a screen width of tiles you actually had to dynamically load in tiles and sort of overwrite tiles that existed before that in memory but today we'll see how we can sort of create the illusion of doing that by just drawing a room and then having our main room and then just sort of tweening over and then setting everything back to the origin at zero zero and it makes it look as if we're moving you know back and forth between all these rooms when in reality all we're doing is just doing a shift and then putting everything back to zero zero so i'll sort of illustrate that on the screen if i can and one of the last things we'll look at is data driven design in the context of a lot of types of games particularly rpgs and action games it's often very valuable to be able to model all of your items entities sort of abilities anything you really can as data rather than logic in order to make it easier for you to write one and two for you to sort of allocate the design aspect of your game to other people not just programmers and have an engine that's very versatile and moddable and we'll we'll take a look at how we implement sort of a foundation for that later on but first i'd like to illustrate a demo for today's lecture um a sort of implementation of legend of zelda is there anybody that would like to come up and sort of demo this today in class no awesome thank you so much so whenever you're ready just go ahead and press uh the return key there and so we see on the screen legend of 50 which is our legend of zelda mock uh you know rip off sort of so if you press enter you go into this is the play state of the game so you control an avatar he can walk around he can interact with switches as we saw here when he presses a switch the doors open and once the doors are open you can walk through them and it does as i alluded to before it transitions the screen uh one entire screen with height or width depending on the direction up above and below we see here now we're pressing space spacebar to actually swing our sword it's destroying the entities in the game space so there's a hitbox triggering when you press the space bar that collides with other entities in the game world if that hitbox hits their hurt box then they are flagged as dead and they disappear from the game world and so this goes on ad infinitum is just an infinite dungeon so we he can go uh in between as many floors as we want to all we're doing is every time we go through a doorway spawn a new room delete the old one and just keep going forever and ever and ever in a game like legend of zelda typically doesn't work like this there's a hard set number of rooms and they all exist in sort of a 2d array and when you generate your dungeon you sort of have to take into consideration things like where do i put the keys where do i put the boss where i put treasure that sort of thing we won't get too in depth as to how we can implement a complex algorithm like that but we'll touch up on it a little bit and talk about maybe some ideas that we have and then lastly if we just if we want to demo as we can see at the top left we have hearts there which is uh iconic sort of zelda and when we do take damage from an enemy notice that we flicker a little bit so there's some rendering behavior triggering and we become invulnerable when we take damage and then lastly when we finally take the last hit we get a little game over screen using the zelda font and that's the game in a nutshell and we press enter and loop back so pretty simple all together but there's a lot of pieces here that we haven't really seen before so thank you very much for coming up to demo the game all right so that was a demo of you know legend of 50 legend of zelda it has a lot of the pieces that zelda has it's not as fully fleshed out of course as a full game like zelda which would be monstrously large um and there's a lot of things we need to factor in but the foundation is there we have the dungeon foundation upon which we can build a an actual generator if we wanted to we have entities we have hitboxes we could easily model because we have switches in the game we could model things like treasure chests that open that have different states just like the switch had different states and have different objects in our game space interact with other objects we have a lot here and we'll talk about all of it today so here's a few screenshots just to show you know what we just looked at our goal mainly is the second screenshot on the top right which is the play state that has all of the little pieces we just saw but just to tie it all together we do have a start state here and a game over state which is relevant in regard to the hearts in the top left so the first thing that i'll talk about um as we sort of get the engine built up is where do we go for getting our assets into the game and just like in prior lectures prior to today we just have a sprite sheet like we did with mario where everything is laid out in a fairly even sequence of tile segments in this case the segments are 16 by 16 pixels here i've overlaid a grid just to show that the picture is indeed perfectly 16x16 and this is something that you should take into consideration and consciously do when you're building your game assets make it easy to chop up into little pieces so you can then index into this sprite via some table of quads and then assign an id to whatever object or tile you want to render to the screen and so we see if you look here things like the doors they're not perfectly modeled by one tile and so when you do have things that are larger than 16x16 it's not quite as easy as oh you know have an entity or an object with this frame id and draw it to the screen you actually need to do a little bit more complicated render logic so uh just offhand if i wanted to draw a door does anybody have any suggestions as to how i would model that and or draw it to the screen so if we have a door that's not perfectly a 16 or a you know a 16 by 16 pixel tile offhand the you know what we need to do is just basically store all four of those tiles in this case or at least you know keep track of however many tiles your um object is and then just draw them based on some offset have the you know an x y that represents the top left of that object place it in the right position and instead of drawing just one tile we draw four tiles and then instead of having just one collision box that's 16 by 16 tiles you can do a couple things you can either check collision on all four of these tiles or all at least all two of these door tiles or model your own custom hitbox that then maybe that object that represents a doorway has control of so you can say you know doorway collides player and then the doorway has a height and width of however many pixels it is wide by tall here just this door part and then you know you are able to then build upon not just having one tile that models a an object or entity in your game space but now you have more artistic flexibility you can do things like have doorways that are more than one pixel wide and sort of have a more convincing game world as a result of that and you'll see a lot of things like that in games like rpgs where you have full houses that are obviously not just one tile because that would be i mean some you can model things as one tile if you want to but uh from an artistic perspective and from just a game engine perspective it's a little bit easier to programmatically be able to cut up all your assets into tiles and draw them as such so being able to sort of build upon the just the single sprite and be able to do multiple sprites to represent an object is the key to things like houses things like big trees things that are just more complicated than single tiles as we see here the character sprite sheet is as you can see a little bit more complicated so this is an example of a sprite sheet that has padding sometimes you will get sprite sheets that aren't neatly divided into even segments and for a good reason because sometimes you have tiles that are not perfectly the width and height of whatever your game engine's tiles are in this case the player is actually 20 pixels tall and 16 pixels wide and on top of that he's got different frames of animation we can see here at the bottom left he's got a sword swing animation the sword swing animation is actually stored in a 32 by 32 pixel frame because sometimes depending on which angle he's looking at his sprite can get a little larger or smaller so when you have a sprite with padding what's sort of a way that we can draw this to the screen reliably like how do we how do we take this into consideration how would we render a screen a sprite with padding so let's say this right here our character swinging a sword is in a 32 by 32 pixel box right this sort of box around here this white space but he's only still maybe 16 pixels wide by 20 pixels tall or maybe he's some amount similar to that in order to draw him to the screen which you can look at in the code all we really need to do is assign that sprite and offset just say okay this sprite's offset x and offset y are some value that basically lets us draw the sprite to a negative x and a negative y value and that'll shift the sprite up such that it aligns perfectly with wherever his x y are and we can you can take a look at that in the code to see exactly how that works lastly the actual entities for our game world are different creatures and this is a sprite sheet this is a more ideal sprite sheet where everything is 16 by 16 pixels wide and tall and all we really did here was just divide the sprite sheet using the regular utility function generate quads by 16 16. we don't have to worry about separate you know widths and heights for everything and we can create animations very simply because of this um as a side note something that i like to do when i'm you know parsing or when i'm piecing apart a sprite sheet that's got a lot of individual frames particularly from creating animations or i just need to know it for a tile a particular tile to draw to the screen it's kind of a pain to manually look through each and every tile one by one and say okay this is one but okay if i want to find out which one this the slime is i gotta count okay how many pixels wide is this okay and then it's times four so it's twelve and then it's on the fifth row so 12 times four plus one will get me okay so it's that's it's at index 49. so i've spent you know a non-trivial amount of time sort of hand calculating what all these do i wrote a simple python script that will just go over a file and just add a digit to each individual quad to just show you at a glance what each sprite is and this is included in the distro so you can see what it looks like saves a lot of time and i recommend trying when you're you know sort of working with assets and you find yourself doing something that takes a long time and sort of tedious and mechanical maybe try try to find a way to sort of automate that or at least make it easier to do things at a glance in this case just simply imposing a numerical grid solves that problem i don't have to spend a significant amount of time figuring out which frames of animation the ghost facing left is i know instantly it's 67 68 and 69 and allows me to just crank things up that much faster so the first thing we'll take a look at is a sort of top-down perspective and we mostly talked about this before earlier but all it really is is a tile map which we've seen before uh the only difference is now instead of looking at things from the side we're just looking at things from up above so what's it what's a probably the most obvious consideration when designing a top-down perspective versus a sort of side-scrolling point of view paying it looking at particularly how the tiles are drawn what stands out so the thing that stands out to me is that we have things like shadows on walls here we have also corners and things altogether are skewed such that they are almost like rotated slightly as if they're simulating an angle of rotation relative to the uh camera facing from up above you can see this in like the player for example he sort of looks sort of looks like you're looking at him from kind of backwards and up when you're modeling your assets that way you this it's more convincing and zelda has always done this to make your assets look as if they are slightly tilted and that you're looking up above so when you're designing a top-down game just for convincing the sake of being more convincing try and emulate that the entities here like the skeleton and such are a little bit more straight on the bats and slimes and whatnot even though they still have a little bit of that appearance like the spider it sort of looks as if it's from the top back but modeling your assets from a top-down perspective really that's mainly mainly the thing pay attention to shadows and highlights which adds a lot in terms of convincing us that we're in this room with lighting and also make sure that you're doing things like corners and stuff and making it look as if things are slightly skewed rotation wise the first thing that we're going to look at in terms of the code in the distro is dungeon generation so uh in legend of zelda dungeons are fixed they're completely set in advance by the designers and in most games this is actually the case in our example and in a couple of other examples a primary example of them being that i can think of that i have in the slides a famous game called the binding of isaac dungeons can also be generated so in what what's the sort of like the main unit of a dungeon at least in the in the context of legend of zelda if you had to distill what comprises a dungeon what's like the most fundamental unit yes a room so we can almost look at this if we picture it in terms of a 2d array right we have assuming that this is like index 1 1 and lua 0 0 and other languages going left to right top to bottom basically we have on or off relative to each of these off on on off off off each of the indexes in this 2d array holds a room and so the room has connections implicitly between the other rooms if you wanted to go let's say from this room here to the room up above it what's the offset in terms of the 2d array how are we going from this room up to this room so we're just going up a y level right so this is x level three why level three if we wanted to go up to the next room we need to load in if we're doing it the zelda way right and we're just going we're we're doing a transition between one room to another what we need to do is load in the room at this room minus one on the y and then perform the transition and then set that to the current room such that now we know we're at y level two x level three that's our dungeon in the context of 2d uh dungeons and the legend of zelda that's as simple as it really is you have a 2d grid of dungeon rooms each room has its own collection of entities and objects and connections to other rooms but really all you all you do to sort of fill a dungeon is fill an array in a smart way such that there's no rooms that are for example left by themselves notice that every room in the dungeon has at least one connection to another room and that when you're maybe doing your algorithm to create a convincing dungeon let's say this room here has a door on the right that has a lock right we want to make sure that the key isn't in that room because if it is we're never going to be able to get to it assuming that we come from another direction so when you're designing dungeons procedurally you want to take these sort of things into consideration and then for example the boss room let's say this is the boss room the boss room should have a maybe a boss key or something like that but the boss key should not be obviously in that room it should be somewhere maybe where there's a a couple of rooms before it that have a lock or a key so that you know there's some sort of challenge involved in your dungeon it's not just random as as random as we've done before there has to be a little bit of sort of conscious design on behalf of your algorithms today we're doing things completely random um for illustration just because a system like this is fairly robust and complex but with some effort you could create a simple dungeon generator just using those mechanics just make sure that you have locked doors the lock doors can only open when you have a key make sure the key exists in a place that's accessible and sort of create a chain of like a sort of like a control flow model maybe via a graph of some kind that uh represents your dungeon and the progression thereof so that's that's what a zelda dungeon looks like that's what a 2d dungeon in this sort of perspective looks like and it looks it will look similar to this in other game engines it doesn't necessarily have to be perfectly modeled as a screen with screen height room going into another screen with screen right room you can have arbitrarily complex rooms that have arbitrarily complex sizes and shapes but you still need to make sure that the connections going out of the rooms like if you you could still model you know left right up down if you want to you could model arbitrary numbers of connections between rooms just make sure that you have puzzles that can be solved that's the main sort of obstacle in generating your dungeons here's a game that i really like that uses the old legend of zelda formula to very good effect now it's called the binding of isaac notice already we can instantly see that it's top-down perspective it's the entire width and height of the room is the dungeon of uh the entire width and height of the screen is the dungeon room uh you have a map up here that sort of shows you oh okay i'm in this room right here i can go up i can go left i can go right i can go down this room up here with the yellow crown that's going to be locked behind some door with a key so we need to have keys that spawn in any of these rooms that are just blindly accessible and isaac does things a little bit differently also in that it generates keys and bombs randomly so that you can actually get you don't necessarily have to plant your keys in very specific locations if your algorithm is sufficiently accommodating and complex enough you can just at the end of every room have a chance to spawn a key randomly and you know if you're lucky or if you're not lucky and assuming that the end of the dungeon doesn't exist behind your locked doors you can you have the opportunity to unlock those doors or not and just go on throughout your dungeon as needed in this case they don't lock the boss doors so you can go through the boss door regardless of whether you have a key so they've accommodated for this purely random approach and that just goes to illustrate how you can still take randomization using very complex principles and produce games that are extremely addicting and fun and don't necessarily need to be super elaborate the first thing okay so hitboxes and hurtbox will be the next topic so i want to touch on in the code how we sort of do dungeon generation in the game engine here so i'm going to open up there's a few files so doorway notice in in the source distro there's a world folder we've sort of categorized all of the files that have to do with world generation within that world folder we have a doorway file we have a dungeon file and we have a room file so the dungeon file models the dungeon it's just all of the rooms that form our dungeon just as a very high level top level data structure it's actually very simple the room is as we saw before the individual unit of the dungeon so a dungeon is effectively like a table of rooms and then it models interact mode it holds the code for how we can transition between them but that's sort of how we can think about it when you have a game level in general in this case dungeon is sort of our game level you can model the different subset aspects of your level via some data structure in this case we've decided to make it room but if you have just a platform or level maybe you have regions or zones or anything just little sub segments of your level that you can transition between it's useful to think in terms of that because from an efficiency and performance standpoint you want to dynamically probably load certain levels one at a time certain aspects certain components sub areas of your level one at a time rather than just the entire level at once because depending on how sufficiently complex and larger level is you could get to uh you know exercising your computer's memory constraints so don't want to do that so in this case our algorithm is we have a dungeon we have rooms we have one active room that's only visible at one time that's loaded in memory and then whenever we transition between rooms we want to have another room that gets temporarily loaded so let's look at the dungeon class here dungeon.lua so 14 self.rooms empty table we're going to fill that with rooms just room objects self.current room room and then we're going to pass it in uh the player so that the player has access to all the entities and objects therein and can do things like collision detection which is very important and also so that the entities in the room can look at the player and then decide what they want to do with their ai which you can model arbitrarily complex um the whole init function of room.lua is gonna we're gonna show it here so in room.lua in that same world folder we've instantiated the dungeon at a high level we know the dungeons it's basically a table of rooms so what's a room look like well a room as we saw before what are the what are the pieces that a room needs in order to function what does the room have to to sort of keep control of so doors anything else so doors it has to keep track of right because when we touch a door we should transition to the next room it should keep track of the player so the player can update should keep track of objects in the in the room like switches as we saw in the example such that you know they can update those and see oh has the player stepped on the switch if he has open the doors and then entities right you should keep track of all of the creatures in the in the in the room so that they can update they can interact with each other the player can hit them or they can hit the player um and you can model whatever interactions you want to so we can see here oh and another thing too obviously probably the most one of the more important visual aspects of it we need to also have a set of tiles that model what the room looks like it's a container so we're going to draw you know we have a corner a corner a corner a corner walls on the sides walls on the top and bottom and then a floor and so that's we need to obviously draw the room before we draw all of the other things and update all the other things so tiles doorways entities and objects so we can see here we have tiles self.tiles gets an empty table we have a function called generate walls and floors entities empty table we have a function called generate entities and then objects equals empty table we have a function called generate objects and lastly doorways and then here it's uh because it's not necessarily as complex as we need for a an entire function we just have four doorways and this is static i've chosen in this example to have all the doorways always be in the same place and sort of behave in the same way but you could create a more complex system whereby you especially if you have an algorithm that generates a dungeon dynamically and maybe your rooms aren't necessarily hard set maybe maybe you have a room right if we go back to our slides earlier and let's say we're looking at this room right here in the very center we can see it has a dory on the left a doorway up top and a doorway to the right but there's no doorway on the bottom and that that looks like an arbitrary design decision on behalf of the designers but uh if we look at this room up top here we can see that it sort of behaves in the same way there's a doorway going to this room to the left there's a doorway going to this room down below but it doesn't have a doorway up top and a doorway on the right because there are no rooms going between that room and those directions so when you have a 2d array of dungeon rooms and you want to model your doorways it can be as simple as is there a room in that direction if there is and you'll have your data structure sort of laid out in advance before you generate these doorways if it does if it does not exist don't make a door if it does exist make a door and then make sure that when you transition between the left from let's say from the right to the left or from the bottom to the top that you go to the correct rooms at the correct indices in your 2d sort of room array does that make sense anybody have any questions so far as to like how this works at a high level okay awesome so these doorways here are all because this dungeon's completely random and every time it always has uh doorways going top bottom left and right we're always just going to put four doorways going top bottom left and right note that they take a string denoting which direction they are and this will become important later on in the doorway class which we see here is modeled as a separate class false here just means is the door open so by default we're going to close the door and then self so that we have access to the room from the doorway um the room should have a reference to the player so that it can model uh interactions between the entities and the player as well as the objects on the player that it has a render offset so if we look at the game here the tiles don't completely match up to the width and height of the screen and that's mainly a function of the virtual height not mapping perfectly to 16 tiles divided evenly by 16 tiles so what i've done is i've made the dungeon a little bit smaller and i've and also the fact that the um doorways take up a couple extra tiles of padding on their on their sides this like sort of blank space up here we've shifted everything inwards a little bit we've made that we've made the dungeon one tile smaller or two tiles smaller width and height wise than the screen and then we've just rendered offset by a certain amount such that it's completely centered we calculate how much padding exists between the fully rendered dungeon and whatever blank space is left and just shift by half of that amount and that's our render offset so that's why that's what why render offset is important in this case when you're trying to center anything you typically do it just by calculating an offset calculate the width and height of whatever you're trying to draw calculate whatever your width and height of your screen is minus that divided by two that's your render offset and then this is this these two fields here are interesting self dot adjacent offset x and self.adjacent offset y does anybody have a guess as to um sort of what this is used for yeah yes exactly so drawing itself when it's the adjacent room so when you load the next room you're going to instantiate a room just like this but by default it's going to draw at zero zero however if we want that room to draw you know not right where we are obviously if we're in the current room we don't want the next room drawing right on top of where we are because then it's just gonna layer right on top of the room we're in we want to draw it if we're going to the right we want to draw a screen width to the right if we're drawing it to the left we want to draw a screen width to the left and same thing on the y axis the screen height above or below it so adjacent offset x or y we just add to when we draw the room and that'll have the effect of rendering it separately from uh from the room that we're currently in i can try and have an illustration here so if we have our room here this is our current room so self dot current room and if we let's say we have a doorway here right that's a doorway object the player is here he collides with that object it's going to trigger a transition to a room up above so what we do is we load in a new room right away we always have a pointer called self dot next room and this is all kept track of in the dungeon.lua file but self.nextroom by default is going to be nil but when we transition from the current room to the next room right we should set that to something so self.current room is going to be the same but when we trigger this when we trigger this collision self.nextroom this is going to be equal to just a new room and then we get which doorway we're in we figure it out we figure out which direction we're moving in technically and then if we're moving up then we just pass in a we set it adjacent offset y in this case to negative screen height if it's below we set it to positive screen height if we set it if we go here it's going to be negative screen width the adjacent x will be negative screen width and then positive screen width on the x if we're moving to the right and so uh this sort of adjacent we could see the uh if we do like x y i think that'll be that's that's basically the adjacent offset and so um we end up when we draw our transition going from bottom to up in this case we just tween the camera to this value so we have a camera right this is our camera and it's going to be looking here by default so camera x and camera y those are values in our code as well when we trigger this collision on the doorway and we have current room here which is at zero zero and then we have this room here which is at zero minus or plus our adjacent offset y which is negative screen height so it has the effect of making it negative screen height on the y our camera x recall love.graphics.translate is our camera so all we do is we just tween that we say okay here's our our cam x is going to be here and chem y it's getting a little bit messy i apologize but our camera x and y are here and then over time we're going to tween that up into the next rooms x and y which is calculated it's just x plus adjacent offset x y plus adjacent offset y and then once once this camera has shifted from here up to here or whichever direction we're going whether it's up down left or right once we've completed that we can normalize everything again back to zero zero by doing uh self dot current room equals what's the what do we need to do if we're if we're gonna put everything back to and let's say let's say i want to make this this room the new current room what do i need to do so we have current room and we have next room if i want the current room to become the next room all i need to do is say self dot current room equals self dot next room right and then once that happens adjacent off what happens to the adjacent offset x and y of the next room they get set to zero right i want to take i want to take this room that we've offset up here and i just want to make it the center of the game world again i want to just put it at zero zero so we can do this exact same calculation by just setting adjacent offset x or y to a negative or positive screen height or screen width relative to zero zero so what i'm going to do is just set uh the adjacent offset of x and y of the next room which is now current room to zero and it's gonna draw it right back at zero zero and then camera x and camera y are also going to be set to zero zero and this is gonna have the effect of looking as if we're going up and staying there but in reality we're just going up and then instantly shifting everything back to zero zero including the player entities and switches of that room so it's purely an illusion but it allows us to simulate this sort of infinite exploring a dungeon effect does that make sense is the overall flow of how this works makes sense okay so it's a little bit messy there hopefully i was able to illustrate the overall algorithm for how the infinite dungeon generator works um where did we leave off on we were in oh yeah we were in the init function of the room so let's take a look at a few of the functions that comprise that so generate walls and floors this is very simple very similar to what we've looked at before with uh like tile maps in mario for example where we just go from y to x and then we just pick a random id or not random id well it is random id for some of it but sometimes we need an explicit id so uh remind me what does the id actually map to uh when we're you know drawing tiles to the screen if we want to give a tile an id what is that what should that map to yeah the frame in the sprite sheet that's correct um it doesn't have to for all game engines for all implementations but it's the easiest thing to do is just to give it your tile and id that you can then just draw you can index into your sprite sheet at that id it's just very very simple lightweight clean approach to modeling and we can see here id gets id after we've sort of figured out what id we want now how do we determine like let's say i want to draw let's say for example like this tile here notice it's a it's a corner tile how would i what do i need to do to basically assign i'm going to die before i even have the chance to uh to show you while i avoid enemies if i wanted to draw that top left corner uh what am i sort of looking for i still taking damage anyway relative to x and y what am i looking for what x and y does that does that tile need assuming everything starts 1 1 on the top left and goes down to height and width of the overall dungeon what does the xy need to be of that top left corner needs to be one right so if that's one one basically the top left corner that id should be equal to the corner sprite the top left corner sprite specifically what about the top right corner sprite what should the x be sorry yes width exactly what's the y still going to be one what about the bottom left width comma height and then the bottom right oh sorry bottom left should be one comma height bottom right should be width comma height and so basically checking the position of each tile is how we can infer its id which is what we're doing um in the source code here if x is one and y is one okay top left corner and notice that we've put these into constants that we've put into constants.lua just for readability so we can instantly see oh okay i should set id to tile top left corner not some arbitrary number that's our index into the tile sheet at like maybe like 40 something or whatever it is bottom left is one and height top right is width and one and then uh bottom right is width and height and we can sort it it's readable we can just sort of see at a glance what we're doing we're sort of conditionally generating all of our tiles um if none of those are true and then x is one what's what's that tile if x is one but it's not a corner it's a left-hand wall and the same thing if it's the width it's a right-hand wall and if y is one it's a top wall if y is height it's a bottom wall and then if it's none of those it's got to be a floor exactly so that's that's basically how we generate the overall it's very it's a very easy simple generation algorithm what i've done is for randomization here we have a actually a table of potential left walls potential right walls potential top and bottom and then we just describe it a random value within there based on however large that is and that gives us variable we can see it it's it's we have random whoops every time i generate it so take note of maybe some of the tiles visually that you can see there on the map notice that they changed notice that the switch is also changed notice that they changed again so everything is variable we have a little bit of visual variety this is what we did essentially with mario we changed the tile set and the topper set only now we're just changing we have only one tile sheet but several variations of the same tile they're in so all we need to do is pick a random variation of whatever tile and that involves us looking at the sprite sheet picking out which individual tiles which separate tiles map to the specific type of tile and then just picking a random value from that table that's how that works so a couple things i think left to show relative to the dungeon generation so um 48 we have generate entities so entities are here we have their names right of the entities we saw skeletons bats slimes ghosts spiders it's a very lightweight function like it's not too it's like 15 or 20 lines but we're generating you know a bunch of different kinds of entities because what we've done we take we we basically take a random type from that table we create an entity and then using what we've defined in a global table called entitydefs we take out what sort of matters in terms of generating each individual entity the characteristics thereof and we just put it in simple data form this is sort of like the the segue into data driven design for your game if you look at entity defs here we can just see that all of our entities are a sequence of data so we have up here we have player right on line 10 player gets a table we give them walk speed we have a table of animations so he's got a walk left animation a walk right animation a walk down animation all of those have their frames their interval their texture everything's just clean data right there's no logic here really it's just flags or values simple things you could give this you could give anybody who has sort of the basic knowledge of what we're doing this file and what maybe some textures and say hey i want you to design i want you to lay out basically all of that that's involved and what makes a skeleton sort of rendered to the screen like give them like create all the animations for them give them the give us the give those animations their timing give them the exact frames maybe maybe skeleton should have health and maybe health should be equal to 10 on a skeleton or something like that there's not really any like programming going on here but we're describing everything in our game as data and that's important thing in complex games that have you know fairly complex systems but that can be modeled via some attributes you can just describe your entities and data and then let your engine parse this information and then create your entities programmatically you allow you sort of shift the burden from the programmer to the designer a little bit and you afford your design team you and maybe other people who aren't as comfortable with programming the ability to modify the game engine add things to the game without having to go through the bulk of your engine code and do anything too fancy in this case it's simple all we're doing is just we're creating animations and uh assigning a texture to each of these individual things but i alluded to this last week you could have a file that maybe describes something like a goblin we'll see this at the end of the lecture which has maybe like a flag for is it flammable how much health does it have what are its animations um what skills does it have uh what's its attack strength what's a defense where where does it spawn typically um by putting all these attributes together and having your engine sort of parse this you can create depending on how how many fields you have you can create a very complex uh like potential list of things in your game whether they're entities whether they're weapons whether they're items abilities anything you want to whether they're levels even based solely on just data so this is a holy grail of design and development when you get into especially very complex games like rpgs where you can have skills that are you know have particle effects and do different damage to different things and you have entities that are maybe you want them some to set on fire some to be electrocuted maybe some you don't maybe some uh melt when they touch something create a bunch of flags create a function that parses this and generates entities as a result and you just allow yourself an incredible boost in productivity no longer do you need to create a spider class a ghost class a bat class it's all unnecessary all you need to do is define what sort of attributes does a bat have what attributes does a ghost have what attributes does a skeleton have and anybody can therefore mod your game as a result of this all they need to do is know what attributes a potential entity can have and your design team is all the more productive as a result so that's a sort of spiel on why this is relevant we've implemented a very basic version of this just for the sake of modeling animations but we don't need a separate class for all the different entities in our game world we just need basically and in the context of this game what are their animations so that's entity deaths that's how it's working if we're looking at um room.lua at line 48 all it's doing is getting animations this is by the way your function that takes in these definitions and creates your entities they should be looking for looking into that they pull that definition and then just parse out each individual uh relevant piece of data and then just construct some relevant information or attach a relevant flag to that entity that your game engine can then later parse the flammable example if you do an attack and that attack is of type fire and it collides with an entity and that entity dot flammable is true that should trigger some behavior but you don't need to do anything terribly complex and you can assign this to any rel any arbitrary entity thereafter so just a simple way of getting very complex behavior for your game objects entities whatever you want modeling them as data rather than thinking about it in terms of classes so that's that does anybody have any questions as to like sort of how that works or how it's why it's viable or why it's useful okay a couple last things we'll look at really fast in room so on line 82 generate objects so we did this very very similarly last week where we just had a game object class notice that it takes itself a definition for a switch that definition is in gameobjects.lua switch here's a little bit more sort of data modeling something like a switch it's got its type is switch frame is two by default it's got a width and height of 16. it's not solid player can walk over it its default state is unpressed so when it gets spawned into the game it's unpressed and it's got two potential states unpressed and pressed and each of those states have a frame so all we need to do to render it is say what's its current state render the state dot frame and then now we don't need to do anything that's basically all we need to do in order to render it at the right point your game engine therefore needs to look at your object and look at its state and look at its potential states index into that state and then look at and then pull the frame from that but it affords you sort of infinite flexibility you can now have like uh i don't know glo maybe you have a glowing state and maybe there's a frame in that sprite sheet that allows it to glow and you could just change it to glowing whenever you want and it'll just render appropriately so very flexible approach to modeling data 149 in room so we're almost done with the dungeon here so 149 is our update function all it does basically is iterate through everything and update it including i uh basically all the entities so for every entity sort of if it's less than if it's health is less than or equal to zero it's dead if it's dead then don't render it which we see down in render but if it's not dead and self.player collides with it and the player is not invulnerable then we should damage the player go invulnerable and if the player's health is zero change to game over so notice how readable this is when you model your entities like this as well all you really need to do is just check for certain flags or functions and you can do arbitrarily complex tests like this because entities can be dead or not because entities can be invulnerable or not and then because entities are all xy with height based things in our game and collides just expects them to have that all we just do is play a sound damage the player all damage what what does damage do offhand what do we think it does exactly just take a wild guess and say it subtracts from health it does so self uh entity damage just takes in a number and subtracts that from health that's all it does go invulnerable what does that do prevents you from taking damage for that many seconds it does it takes you it prevents you from taking damage for that many seconds all but all that really does is just set a flag which i've alluded to quite a bit already an arbitrary flag that you can model via data or some other means every entity has an invulnerable flag and if it's invulnerable then the engine should look for that and change the rendering and the mechanics of that entity accordingly and then lastly here just simple if the player health is equal to zero which it might be after we take sufficient damage change to game over that's all it does and then for every object just as we do with every entity update the object and if the player collides with it then trigger its on collide function and this can be a function that you arbitrarily create depending on the needs of your object if we look up here for example every time we create a room we generate objects in the room we insert into objects a switch which we saw before in gameobjects.lua and takes an x and a y in this case we just make sure that the x and y is a random number between the top left of the map and the bottom right of the map within the padding that it has we get a reference to that object and then we define our on collide function so what we do is we say if it's unpressed so recall that we have a state in the game object definition if it's unpressed change it to pressed that's all we really need to do and then notice here for k doorway so for every doorway in doorways self.doorways set that doorway open is true and then play a door sound so our on collide function just interacts with other things in the game world and just very simple things but has a a pretty you know interesting sort of mechanic like we have a i took damage off the bat there because my spawning isn't perfect but it changed from notice that it changed its state from unpressed to pressed because it went from the one sprite to i'll go into another room so we can see that i'll take some damage on the way go to another room notice the sprite it sort of looks like it's unpressed right because the game object's default state is unpressed and then on collide triggers when i go on top of it it changes state which has an effect on what gets rendered and it's on collide function just called which opened up every doorway in the room so simple like 15 lines of code but pretty compelling interesting behavior for the sake of our dungeon like it's it adds a lot as simple as that is now we sort of feel like we're interacting with our game world a little bit so that's that's how that um sort of works and then lastly rendering on line 188 just go through all of our tiles render all the tiles we've seen this before render all the doorways render all the objects render all the entities if they're not dead right if they're dead then don't render them and then this bit here we'll take we'll take a look at at the end which is stenciling so notice that when i walk through doors well first of all notice that i can't walk through doors if they're closed which is important so when you collide with your doorway objects if doorway.open is false shouldn't trigger the broom switch right but if i open the doors they're now all set to recall door.open is now equal to true so they've changed their rendering so now they're they're rendering open doorways if i walk through it notice that it looks as if the player walks underneath the tiles but i'm drawing the tiles before the player and one approach that you might think to do when sort of creating this believable appearance of walking through a doorway is say okay i'll just render the player after i render the doorway or before i render the doorways right so render all the doorways last but it doesn't quite work out because the sprite actually starts right here so what what does somebody think is gonna happen if i were to draw the doorway uh after the player would disappear exactly the player would disappear as soon as he gets to this little bit of line right here which is not very convincing and actually i'll uh try right now to take away the stenciling so we can see what that looks like so if i just take away the stenciling here and then i just render whoops i can't type today so i'm going to go ahead do that well first of all the rendering order is such that now the doorways render after the player or before the player so he just walks right over them right so i'm just walking i'm just walking over the walls that's not compelling and if i were to do something as simple as change the rendering order so right now the doorways render first i'll just render the doorways after the player so right here took that out right no so right now they render before the player i want to render them after the player and i'm going to just run it and then going to step on the switch and then yep notice we get some weird behavior too um like sees heads getting cut off like that's and the other entities as well right in that case uh i couldn't really tell but yeah very weird rendering behavior and what we do to fix that is we create what's called a stencil so basically uh try and get a screenshot here i can i can show first let me fix the changes that i just made right and then if i go back into the code here so a stencil is just and we'll see a slide on this in a little bit but a stencil is just a basically any sort of arbitrary shape that you want that you draw onto the screen it's invisible but it determines whatever whatever gets drawn on top of that stencil it determines whether or not that thing gets rendered so i have a stencil going basically from here onwards to the next room so right about where the doorway hits the archway because that's where we want the player's head to look like it disappears stencil going here i have a stencil going right here and right here and on the right side as well and what that does is i've set the stencil to say whatever passes through this stencil during the sort of stencil testing period which is well all we do is we just draw the player during that time but basically if it's on the stencil don't render it and so what that has the effect of is we draw the we still draw the doorways before the player so the player walks in and he's drawn above this part but as soon as this as he hits the stencil he's not drawn it's just all those basically what it does is it draws the character to the stencil and not uh to the to the actual canvas and so we get convincing the sort of if you want sort of convincing layered weird visual effects like that stenciling is a is an approach okay and then that's all this is so this stencil function love.graphics.stencil takes in a function this is what's going to run during the actual stenciling process it's going to draw i draw four rectangles all those rectangles are just the um those arch ways right perfectly layered over them such that it goes into the next room and then we do notice that it says replace and then one so we replace any pixel that gets drawn to that stencil with the stencil value of one and then we only draw things that are less than one during the stencil test which means anything that didn't get ascribed to value of one which means that if the player went over the stencil got a pixel value of one that's going to be false he's not going to be drawn to the screen yeah question was if you have a stencil because you can do it with an arbitrary shape you could create lighting systems where some areas are dark and some areas are light i want to say possibly the thing is i'm not 100 sure whether stenciling allows you to do like whether love2ds stenciling allows you to do like arbitrary numbers of stuff like what i basically am not sure if it's on or off or a gradient typically if i were to do a lighting system like that i would probably draw like a faux lighting system one you can use a lighting kit like box 2d lights which a lot which does really compelling cool lights for you or sort of a cruder way to do it but possibly realistic would be to draw a shape that fits whatever you want to be your darkness um and then render it at an opacity that's less than 100 so like uh so you'd have like let's see have a room and let's say maybe like two like the sides of it are like kind of shadowy you draw like black rectangles there right but instead of drawing those black rectangles at 255 alpha you draw them at like 200 or 150 or something like that and so you can still see what's underneath them but it looks as if they are uh you know sort of a shadow and you can use gradient effects do the same thing if you want to like have a shadow that's darker going lighter um i believe you can draw rectangles with a gradient effect in left 2d i'd have to look into a little bit more but you would you would look to do something like that draw a gradient of shadow via some rectangle or some arbitrary shape to simulate lighting in that case and you could accomplish something similar to that and there are a lot of other crazy cool ways i've seen lighting done in 2d but that's probably offhand the simplest way that i could think of doing it cool um so that's stenciling and that's basically that's basically it for the dungeon generation which is arguably the most important part and also we we looked earlier at the sort of transition mechanic and we'll look at the transition mechanic a little bit more um in a little bit but let's take a five minute for right now and then uh come back to that and see some more stuff all right welcome back to lecture five legend of zelda so before the break we talked about a bunch of different things uh dungeon generation being foremost among them now we'll actually start talking about things like hitboxes and hurt boxes as we can see here on the screen so a hitbox is a rectangle basically that's how we've implemented it in the distro but we can see here there's a few different sort of rectangles overlapping we have the green rectangles here these are hurt boxes these are where you can get hurt and then this is a hitbox this is where you can hit something and so games like especially with fighting games and a lot of games of you know fairly complex interactions and complex entities that have weapons or particles things like that that all do damage you'll see a lot of sort of complicated overlapping and adjust or arrangement of these rectangles that sort of bring about how things interact with each other in terms of collision and doing damage and affecting other entities in this case he's doing like a an attack that goes uh from the left to the right it's a low attack only his foot essentially a little bit inwards but essentially his foot does damage whereas the rest of him is vulnerable like if someone were to come up to him from up above and attack him it would uh it would do some damage and there's minecraft and it shows you how the same sort of principle applies to 3d games as well in this case the what you see there are all hurt boxes those are all where things can get hurt even though those are a few of those things are items so that only really affects whether or not the player collides with them and picks them up but that's basically the difference between hitboxes and hurt boxes the distro i think i accidentally called the file hurtbox so when i push it up it's going to be renamed to hitbox because that's what we use it for but the uh what is offhand in the in this game what do we need a hitbox for yeah melee attacks from the sword and the reason that we need a hitbox for that is why yes and why can't we just use the player's position x y width and height because it has direction as well has a direction as well and also it needs that's that's essentially it's hurt box right so that's for those two reasons it has a direction that we need to uh sort of act as its reference point for generating a hitbox to inflict damage on other entities and we need to use the player's main hitbox that hurt box that already has to see if something hit it the player maybe from another side or something like that so let's go ahead and take a look here the main bit of code that deals with the hitbox in this case is one gonna be hurt box should be hitbox but we can see this is literally just a rectangle class x y width and a height self.x self.y self.with self.height equals all of those things that's all you need for for a pit box literally box just all you need are those uh fields and then you can do simply collides just like you do entity entity collides hurt box yes no true or false because recall collides expects x y width or height and it's defined in entity.lua so if we go up to the player states so recall last week we introduced the the idea of having the player maintain its own collection of states in a state machine as opposed to just the game world having a state machine that you know influences whether we're at the start screen the place playstate screen whatever we want to divide our game up into we have entity states as well now one of those is the so we have the idle and the walk state those are very similar to last week's where the with an idle state they're just standing still and walking state they're moving and then their animation changes accordingly the swing sword state is a new state and what this does is the player presses space bar it triggers this swing sword state there's a new animation so we go into the swing sword animation relative to which direction we're in and we get that direction and then we calculate the x y width and height of whatever our hurt box it should be hitbox is going to be for the sword when the sword hits something so that hitbox if it's facing to the left it's going to be 8 pixels wide by 16 tall which is roughly the left side of the player and then we just calculate the x and y depending on which position we're in it should be roughly centered based on whatever position the player's direction the player is looking at and so that we then instantiate that hurt box here we call it a self.sword hurt box and then all we really need to do is what in our game loop to check to see if we've hit an entity we just need to loop over the entities in our room the current room right here so every entity in the dungeon's current room dot entities if the entity collides with our sword hurt box sword hitbox then we saw this method before entity damage 1 and then hit enemy play that's as simple as it is and recall in room da lua if an entity's health dropped below one drop to zero it would just trigger it to become dead right and so that's all we need effectively and then um we have some additional logic to make sure that the animation only plays one time and then once it has played one time change its state to idle um and then we can press spacebar repeatedly within that same state just to keep swinging if we want to and it'll just restart the animation and restart the hitbox being instantiated and so that's all we really need to do for that now i have some lines of code here at the bottom of the swordswingstate.lua and playerswords swingswordstate and what these do is sometimes it's useful when you're programming to sort of see where your collision boxes are right because you don't necessarily know if everything's lined up perfectly you know when you're detecting collision between different entities you want to just check to see are the rectangles actually overlapping when this triggers so all you need to do in order to visually see this at a glance and you may have seen this before in other games or debug modes of games is just draw line rectangles and then just give them the xy width and height that you want to look at right so here i'm going to go ahead and save this um and then i'm going to run the i've basically uncommented it because by default i don't want it showing i don't want the they're pink rectangles i don't want them displayed on the screen unless i want to do debugging so i'm going to uncomment them down here notice it says the color to 2550-255 that's magenta i'm going to run this turn it down a little bit nothing looks particularly different but when i swing my sword notice that there's a little rectangle and the rectangles aren't 100 perfect necessarily there they give the player a little bit of an advantage notice that like up above like his hitbox is barely anything up above in the in the actual animation but in the in the collision i give it quite a wide a wide berth and so that's how you can at a glance sort of see whether your things are interact or interacting appropriately there's lines of code that do the same thing for the other states the entity walking and idle states if you want to sort of look at those and see not only the players swinging the sword but also the other entities and the collision boxes they have and just to check whether or not they're overlapping appropriately or to change them or do whatever you want with them but that's a nice way to sometimes it's hard to debug collision if you can't see exactly what's going on because it's often just in terms of xy width and height and so on and so forth with offsets and such it can be kind of a pain and or sort of difficult to track down certain bugs that way so just draw it to the screen you can apply that same logic to a lot of things in your game world that you can vis that are maybe hidden but you want to see sort of visually just draw shapes or draw different things for them so you can see what's going on so that's how the hitbox and hertbox work for our player and you can easily just spawn more hitboxes for other entities if you want to if you wanted to give them states or maybe you want to create projectiles that are offensive maybe just create projectiles and then shoot projectiles in a specific direction but assign a hitbox to that projectile if you want to or just use the projectiles x y width and height and then you can calculate a collision that way just treat it like a game object or an entity it's up to you or a separate uh a separate class altogether but that's hitboxes and hurt boxes in a nutshell anybody have any questions as to how those sort of work okay cool so let's go on to the next topic so events so i spoke of this earlier events are just a nice way to sort of say when something happens do this block of code um and you can do this anywhere and you can decouple it sort of from like maybe two different objects interact with each other but you don't want them to sort of pass references back and forth between each other and to sort of bloat your code maybe you want the code for that check to happen not inside your main loop you want to sort of abstract it out to some sort of other function like instantiate events and then have your main rendering and update logic be sort of free of all this conditional stuff so you create an event you say on some event so maybe like on player walk and then you just update some maybe a label on the top right of the screen that gives the players x y and you just say that x y is equal to that player's x y i mean aside from the fact that you could just literally draw the players x y but it's the same principle you can update some value somewhere separate from another entity and then just wait for an event arbitrarily defined event um that you then broadcast later so let's say i want to broadcast swing sword i have a swing sword event so whenever the player presses space not only do they do all the code that we saw before but they swing sword and then we say when you swing sword pass in the x y of wherever they swung the sword and so then you can say okay on swing sword look at all the entities and see whether or not the hitbox that xy collides with them and you can sort of take out the logic from where you had it before and put it in some other centralized location if you want to more sort of uh representative i think of this model is the idea of an achievement system where instead of every frame and you can have you know games have like a ridiculous number of achievements some games have like a thousand achievements you don't want to put the test for every one of those 1000 achievements necessarily inside of your like update logic right you can instead just broadcast an event for all of the different things that influence whether those achievements are met so whether player needs to get 100 kills in a game or 100 coins whether they need to like jump off some ledge you just have events that model all these interactions event on pick up coin do this increment some time some counter that's stored somewhere else and event dot on kill creature right where kill creature triggers every time you literally a creature is set from dead dead as false to death is true maybe whenever the player jumps you do a on jump function and then you can test to see whether or not in that code you can test to see whether they jumped off that ledge and then if that happens you have your achievement system but you don't have all these if statements and all these tests happening inside your your sort of imperative game loop you can just sort of like pseudo asynchronously check for all of them um given a sufficiently detailed event system in your game and so the library that we'll use just to show this a little bit is in the knife library that we saw before with timer timer.ontimer.everytimer.tween in the knife library there's a sub module called event which allows you to call event dot on give it a name and a function so on player jump off player jump you know function if player.position is you know by some cliff or some area that's relevant then call this block of code increment you know or set the achievement to true event.dispatch dispatch an event when something happens so if player jumps so if you press the spacebar event dot dispatch jump and then just whatever values that your callback function needs like let's say you want to check to see whether it's on the cliff well if you want to check to see whether they've jumped off that specific cliff you're gonna need to check their xy position presumably so you can just pass in optional parameters via event.dispatch you can say okay dispatch the event that the player jumped and say that the player jumped at xy and so this function callback is going to have access to that xy it's going to say okay on jump i see i got i'm getting my callback called with player.x player.y and it happens to oh it's not at the cliff so nothing happens or it is the cliff so the achievement jump off cliff unlocked is true and this test now is not happening every single frame which otherwise would potentially and it's not blocking up your update logic for your player jumping right the player the jumping logic inside player the player jump state doesn't need to know doesn't need to ask whether or not we've jumped off a cliff that's something that we should just delegate to our achievement file or whatever you want and that's sort of the flexibility that events afford you so we use event i'll touch on this a little bit briefly here but basically if we go to the player walk state online uh 21.39 first of all we're checking for input here right we're just saying if if we press left right up or down the direction is that walk left walk right so on and so forth um and then if and then what we do is we call entitywalkstate.update so this base walk state code that exists in entity so that we can let every single entity have this code all it does is just to check to see whether or not the player or entity that it belongs to hit a wall and if it did then set their bumped equals true and that what all that does is just a flag that says okay the player bumped a wall okay why is that relevant because then after we call that we can say okay if we bump the wall and this only happens in the player walk state if we're looking to the left temporarily adjust our position because when you when it bumps you it like knocks you back out back into the world but we're going to re-bump our position in to wherever we collided we're going to look at every doorway and then we're going to say if we collided with that doorway and it's open then uh we're going to shift it to the center of the doorway so that it doesn't look as if the player is walking through the wall we're going to set it to like wherever the wall is or wherever the door is the very center of the doorway and then we're going to call event.dispatch shift and then that direction so shift left shift right shift up and shift down and so all of these these four different cases are going to say okay the player has hit a doorway i have a function somewhere event dot on shift down shift left shift right shift up that's going to trigger what the next room it's going to trigger us moving to the next room and also the next room spawning and the camera sort of shifting position um so the code for this is in dungeon so we're going to go ahead and look at dungeon so right here on line 29-43 event dot on shift left shift right shift up shift down and what that does is we have another function called begin shifting which takes in an x and a y negative virtual width virtual width negative virtual height virtual height does anybody recall why we need to pass those numbers in there if we're shifting left and we're passing a negative virtual width what do you think that number is going to be so recall every room has an adjacent offset x and a y that renders that room with that offset basically added to it it's x and a y so if we begin shifting to the left negative virtual width is going to be the x offset because that room needs to needs to be rendered a screen width to the left right and so on for the right virtual width it adds virtual width to the x offset the adjacent x offset and then virtual height negative and positive for shift up and shift down so begin shifting basically uh it's the gist of this is it's a tween operation so we start here recall timer.tween just takes a value and interpolates it so self we have a camera x and a camera y right going to equal shift x and shift y eventually so shift x and shift y being the adjacent offset that we're going to need to shift the camera by so camera x and camera y are they both start at zero always when the room gets completely finalized so if we if the shift x is virtual width negative virtual width because we're shifting left then it's going to tween the camera going from right to left over the span of a second right and then the player the player x and player y those are basically set up here to the opposite end of the room the next room so if the player is going from the top of the room to the bottom of the next room it needs to get its uh x and y put in that position the bottom part of the next room or the left part the left part of the next room if they're moving from the right right or the right part if they're moving from the left and the bottom part if they're moving up and up part if they're moving down so that's what that does puts them in the right position on the next room tweens both of them the camera and the player because the player needs to keep moving to the next room and the camera needs to shift up um when it's finished we call finish shifting which is just a function that sets basically everything to zero it says the current room to the next room it sets the camera x and all that stuff all to zero and then all the entities and objects there and all get set to zero so we are they're offset to zero so that everything is basically right at zero zero again just like it was at the very beginning um and then last but not least when we get into the new room we start with the doors all open and then when we get into the net and into the room here because this is all done in the finish function recall this will only happen after the tween is completed uh every doorway is going to get set to false open equals false so all the doors will close as soon as we get into the next room after we perform the tween operation and then we'll play a door sound so that's that's the code involved in actually doing the shift operation so we're able to trigger it we don't have to necessarily do that code from the player walk state we just say you know if the players collided with the doorway then dispatch an event that is handled by dungeon because dungeon has access to current room uh next room and all the stuff that it needs to actually perform that transition so we have the two that are sort of decoupled but they work well in tandem with one another so anybody have questions this is sort of how this works okay that's the that's essentially the dungeon scrolling just it's a tween operation it's a camera a temporary room gets rendered we move to the temporary room everything gets set back to zero and then it's just a it's a while loop of that sort of behavior effectively and we use the event library to sort of clean up that whole aspect and sort of uh screen scrolling here just another demonstration we went over it in detail but this is a nice gift i found that sort of encapsulate what that means stenciling is something that we saw earlier and if we look at this as a stencil so uh these we can pretend that these are the circle the heart and that rectangle are a stencil and then we want to draw this gray rectangle on top of those stencils we can use the stencil to mask out specific parts which which would otherwise be pretty tough to do i mean it's hard to necessarily like maybe individually render pixels or create a shape that sort of looks like this much easier for a lot of very interesting visual effects just use a stencil of arbitrary shape and size and you can use images too you can use images as stencils to create some pretty cool effects we went over it before when i talked about the archways so i won't go over in too much more detail but again you can look at that in the where was it exactly it was in the room function room class sorry down here at the bottom so lines 213 to 229 recall they're a stencil function the couple functions in in particular that you need to use are love.graphics.stencil which draws the stencils and then love.graphics.set stencil test which compares the stencil values to some value and draws them whether or not that condition is true so we have uh our function here which draws four rectangles those are all the archways it replaces any pixels that get drawn to those rectangles with the value one the stencil value one so stencil value is just like a hidden value that determines whether stencils get whether a image will get drawn if it's being stenciled or not set stencil test means that we're looking for anything less than one and we'll draw that and because the player is going to be one if it's on any of those uh stencil rectangles uh it will not draw the player if it overlaps with those stencil rectangles and so that's just that's in general how it works and you can do there's a lot of different comparisons you can do greater than one you can do iterative stenciling uh so that you can increment a value it could you can have stencil values go between one and zero and 255. there's a lot of interesting effects you can get with that but the general use that we just saw is you know set the values to one and then check to see whether they're less than one if you want to draw them if you want to not draw them does anybody have questions as to like how stenciling works and how these functions work yeah it's like having that stencil cover up the door the top of the doorway so when you drop it there like you want actually drawn yes so the it's like having the uh the stencil rectangles go over the doorway so that the player will not get drawn when it's underneath them the um i'll go back to a slide that has it the stencils are here right right i had to go through and figure out the exact xy that i needed for them and there is a you can what you can do is you can take that stencil function that draws the rectangles and then you can just like take it out of the stencil test and see you'll you'll actually be able to see where the exact rectangles are because then it'll just literally draw the rectangles um but here basically right over right between here and where it would be on the next room so that when we go from here over into the next room it still stencils them right here the player is gonna go over them it's gonna set its stencil value to one it's checking for anything less than less than one um to to render so it's gonna fail the test and it's going to uh not draw that player at that point so effectively just masking out the player because we're assigning it to the right stencil value or doing the right comparison for it but yeah that's effectively it just using it to because we were in a situation where our tiles didn't cleanly lend themselves to drawing over the player which you could do you could draw the shadow part underneath the archway part and then just change the rendering order but the sprite sheet that we had didn't afford us that flexibility so we went through with a little more interesting stenciling approach to getting it done um we looked at this earlier so game design via data rather than you know a bunch of logic so the more you can sort of get towards modeling your anything in your game like data like this the more flexible your engine is the easier it is for people to mod your game which is hugely important and the easier it is for you to let designers take charge of actually creating assets for your game so here we've created an arbitrary collection of data and you could get as complex with this as you want to the only thing that really matters is whether your game engine supports those fields and acts on those fields accordingly but once it does i mean the more things you add the more possibilities you have for adding new things so goblin we have health strength so strength should be accounted for in our game engine and this is we're talking in just the general abstract sense now let's say we're like making an rpg or something or maybe maybe maybe this is part of our zelda game if we got a little bit more detailed with it but goblin is going to be a creature that has 10 health it's got two strengths so these values will get loaded when it's created as an entity and strength is some value that our engine needs to apply to anything that it decides to attack right you can do the same thing with defense you could create arbitrary things it's got a goblin texture so that's the sprite we've defined some animations here which you've already seen how to instantiate those in our game engine maybe it starts off with a club weapon um and you could also reference other sort of uh lua files that have these definitions if you have maybe a weapons defs file you could index it into weapons def's club and then that club item will maybe have a bunch of its own characteristics that when your game engine is loading this goblin death it loads in the club def as well and instantiates a bunch of qualities onto that entity or that item that then have behavior relevant to your game engine um aggressive is true so if something's a dot you know entity dot aggressive then maybe it's a i will seek out the player right and you can set that to false so now you can differentiate between entities that chase the player and entities that are sort of docile and just do their own random thing uh sleeps at night maybe your game has a day night cycle maybe some entities stay awake and some go into a sleep state right so it'll look and say oh okay it's night time does the entity dot sleeps at night if it does okay then entity change state sleep right and then during the day phase when the day happens you'll do the same thing in reverse you'll say if that entity that sleeps at night uh then you'll say entity not awake is true or whatever it is um change state awake or idle or walking and inflammable maybe you have weapons that shoot damage or shoot fire or maybe you have torches that shoot fire or some maybe you're in a level that's like lava and you know if you get touched the one of the tiles it has a flammable effect and if it's flammable maybe your status equals flammable you take damage over time and you change your animation maybe you get tinted red or something like that but thinking about all these different possibilities and like what your engine is capable of you can create pretty much arbitrarily complex data structures and entities and items and whatever you want in your game without having to create a goblin class or a skeleton class or a arbitrarily complex creature class you just compose your creature with attributes and model its behavior that way composition over inheritance which we talked about last week it's very valuable in game design and that's what unity's game engine sort of approaches because entity is a purely entity component system whereby entities are modeled as collections of components that each do something and we can sort of almost think of these as components too albeit a much simpler sort of representation does anybody have questions as to this or like why it's useful or anything like that okay so the last thing i wanted to talk about today um and i saw uh i think one or two students were mentioning how they wish they knew or wish we talked about a little bit more how programming was done back in the day so i'm not an expert necessarily in sort of programming you know 6502 assembly which is what the nes has written in there are a lot of people that are very good at it but there's a few links here so in the slides if you wanted to look at the slides on the website homebrew is actually quite a popular thing amongst certain communities online and what it is is essentially getting compilers that will compile source code for some arbitrary processor or you know development environment from some arbitrary computer system like the nes which was a 8-bit microprocessor based machine with its own set of interesting hardware it allows you to compile the assembly for that and actually run it via a program called an emulator an emulator just allows you to run rom images or you know just arbitrary data that maps to the machine instructions of some system that the emulator has emulated via a virtual machine and so if you wanted to look at look into it a little bit more in detail the there's a couple links here so nes dev wiki's got a bunch of great links um there's a programming guide that kind of goes over the basics of how to do some 6502 assembly as well as with the nes and its particular hardware and then the compiler that most people use for this is called cc65 which is a 6502 assembly compiler and so this is an example of some source code it's a little bit tough to read but uh i can't zoom in i don't think but the gist of it is you have a lot of in here i can actually pull it up online uh let me try um super mario disassembly so we go here this is on github so somebody decompiled basically or disassembled rather take took the bytes that represent all the machine code and the rom image of super mario brothers and then just converted it back to assembly language and then added comments to it because uh i won't go into too much detail about what assembly is and how it works but essentially it's just a layer directly above machine code where keywords map to essentially what are bytes in memory that are actually program instructions and that influence the behavior of your cpu such that you get programs and that's what c is layered on top of and then every uh language thereof thereafter so if i zoom in here see if i can just being a little slow but it's letting me so here we can see for some reason i think it's lagging because the file is just so large it's like 14 000 lines of code because this thing about assembly is assembly is very very long because there are so many and you need to have just like really slowly zooming in infinitely there are just uh so many steps you need to do in order to achieve the same thing that you get in a high level language like c or or even languages higher than that taking a lot of things for granted especially things like you know loading registers so every cpu has got a series of registers that can store values and then the cpu has logic that knows that you can basically say okay if the value in register a is greater than or equal to some value then branch to this sequence of the assembly code and you often you'll have to do things like and or operations on byte level uh you know just on bytes like right here we can see we're ending uh the value three on uh whatever is in register a like i said not don't intend to get too much into what assembly is but for those curious as to how games were implemented in the 80s and even the early 90s it was all an assembly language because assembly language is you're literally programming the the cpu of whatever machine you're trying to program and that's how you get as much efficiency as possible if you know your computing environment you know what your cpu is capable of the thing about compiling a language like c or c plus or java which is even higher of an abstraction than those is you're allowing sort of algorithms to do the work for you you're allowing algorithms to take your source code and turn it into this basically well a layer lower than this effectively this is an intermediary level that it does generate but suffice to say in the 80s and 90s c compilers were not as good as humans were in creating games especially to do a lot of the tricky things that they needed to do in order to get them working efficiently on processors that were at the time one to three megahertz in speed which now we have like three gigahertz processors and it's no big deal but that is effectively that's that's sort of a window into what it was like developing games in the 80s and 90s and then in the 90s with like things like the n64 playstation 1 going on to playstation 2 and so forth um it was typically done in a language like c or c plus sometimes with some variants um certain consoles like the ps3 has a notoriously difficult graphics processor to program so a lot of ps3 teams needed to program in assembly even you know at the time which was 2007 2009 but that's in a nutshell what it's like programming the 6502 so if you want those things uh those links up above it's a pretty enlightening experience actually digging into that sort of thing and trying to make sense of what the assembly does um it's quite difficult and quite uh it's quite burdensome because of how long these programs are and just how minut each individual instruction is like just checking just loading a value into a register uh just to you know do a loop often will just be iteratively loading a value into a register and then like performing some operation or calculation off of that and then branching to some other loop of code a lot of that can be condensed into like just a couple lines of c or even fewer lines of lua so a lot more of a burden but there is insight definitely some insight into digging a little deeper in sort of looking there so once again those are the links on the nes dev wiki wiki.nesdev.com if you're interested in looking at that in assignment 5 you will like you did an assignment 4 you can create a game object that's consumable and feel free to use code from assignment 4 to do this when like you define your on consume function uh if you're going to adopt the same model as the last assignment then any ideas is what we need to do probably just add uh you know the opposite of damage the entity right we want to add hearts we want to add hp to it um and you'll see in the distro that health is modeled as uh an even number because every one digit is half a heart yeah you can and it'll uh that'll effectively be the same thing too so uh the second part is including pots so there are pots in the sprite sheet so they should these should be solid so when the player interacts with them he should be bumped out uh of where he was uh allow the player to lift them so there's an animation in the sprite sheet that'll actually allow that shows the player lifting up the pot so you probably need a new state player lift pot state walk with pot state whatever you want to define it as but you'll need uh basically to have those two in order to get this to work what's one thing that we'll need to do in order for the player to lift the pot any ideas besides the rendering aspect of it when the player walks around for example what needs to happen if he's holding the pot you could make him slower yeah that's possible that's not it's not required for the assignment more fundamental than that the pod needs to track the player's location right it needs to be relative to the player's location so keep the pot probably above the player to some by some amount um i won't be too picky as to how you implement that but the pot needs to track the player which is this next point actually and the walking animations like i said should change while they're carrying it allow the player lastly to throw the pot so when you throw the pot basically turn the pot into a projectile if it hits one of the walls break it you can either just despawn it instantly or have some animation it's up to you but just if it hits a wall despawn it trigger it to maybe just trigger it to some value is false render is false whatever you want to do if it hits an enemy it should damage the enemy by one just like we've seen already and if it travels farther than four tiles in addition to also hitting one of the walls then destroy it too so those are the main components for assignment five and the ways that which you achieve most of it's fairly flexible but yeah that's assignment five or uh lecture five legend of zelda so thanks for coming i'll see you next time\n"