Flappy Bird (with Lua) - CS50's Intro to Game Development

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] hi everybody my name is colton ogden this is gd 50 lecture one and today we'll be covering flappy bird so uh last year we are last week sorry we covered pong which was uh just you know basic shapes and colors today we'll actually be diving into sprites as we can see here we've got some pipes and a bird and we're covering a few other concepts such as gravity and more today the topics that we'll be covering are in a nutshell images and sprites as i just said so loading images from memory from our hard drive and actually drawing them to the screen instead of just uh you know rectangles and whatnot uh we'll be covering infinite scrolling so seeing things like and if you've played the game pipes are infinitely going from right to left how to actually get that going infinitely so that we're not using up also infinite memory we'll be discussing how games and in the similar vein are illusions in the sense that um a lot of the perceived vastness and perceived complexity of games is often just due to camera trickery and more because of limited hardware we'll be covering procedural generation which ties also into infinite scrolling procedures generation is a topic that i'm actually very interested in and we'll be touching on it throughout the course in several locations but in the cons or in the context of today's lecture we'll be using it uh for the pipes because the pipes they spawn from right to left and flappy bird as you're infinitely going through the level but they can spawn at various heights and the gaps are shifting as a result of that therefore creating this sort of infinite level we'll be talking more in detail on state machines so last week we covered state machines in a very abstract sense we used a just basically a string as a variable and then used if conditions today we'll be actually using a state machine class replete with various methods that allow us to transition in and out of these states very cleanly and allow us to break out all of this logic that we previously had in our update and render functions and then put them separately into their own state classes and then lastly we'll also be touching on mouse input and a point that i forgot to mention here whoops is also we'll be talking about music which is just basically sound which we did last week but we'll add that as a polishing touch if you guys want to download the demo code we have a repo up right now on github slash games 50 50 bird that's our take on flappy bird a couple of things i've been asked a couple of times whether we have reading materials for the course uh and there are no formal reading materials but there are a couple of resources that i really enjoyed reading especially as i was getting more into lua and love2d they are two books uh one is an online book actually they're both online books but the latter of which has a physical form as well the four first of these is how to make an rpg by dan scholler which is actually completely written in lua he uses a custom game engine very similar to love2d but it's handwritten by him but a lot of the same ideas apply and it's a great opportunity it's how i cut my teeth on lua and i would encourage you to take a look at that if that's something you're interested in or if you like rpgs and then also game programming patterns by robert nystrom is a very great general purpose game development book that talks about a lot of these sort of more abstract high-level concepts with large-scale game development but yeah beyond that no formal reading those aren't formal reading either those are just if you're curious and you want to read some resources that i found very interesting feel free to do so today's goal is to implement what looks like this this is a this is our version of flappy bird we didn't use the same exact sprites for copyright purposes but we note that we have a bird in the middle of the screen this bird on click or on space bar will jump up and down and your goal is to prevent the bird from touching either the pipes or the ground itself every time you make it past a pair of pipes you will score a point as soon as you touch a pipe or hit the ground game is over and that's that so today we'll be covering uh i'll doing a little bit more live coding so the very first example that i want to cover is the day zero update for flappy bird and a important function that is going to be probably the most noticeable the most visibly obvious function we'll be using throughout this lecture is love.graphics.new image which takes a path this function all it does is load a image file from your disk you specify it as a string and you can then use it as an object and draw anywhere you want at an x y coordinate and we'll see this in practice here so i'm going to go ahead if you're looking in the repo all of these examples are covered 0 through 12. i'm going to start from scratch in a new folder that i've created i'm going to create a brand new main.lua completely fresh and first thing i want to do is because we are going to use a virtual resolution just like we did last week so that we have a more retro aesthetic i'm going to go ahead and require the push library so push equals require push just like that i've pre put push lua into this directory it'll just load by default in the same directory the current working directory of your script when you run love next thing i'm going to do i'm going to define some constants so window width should be 1280 and then window height is going to be 720. those are our physical window dimensions but then we also need a virtual width and we're going to use 512 by 288 this is a resolution that i found worked pretty well for the assets we'll be using today but you can make this most anything you want to as long as it's somewhat in that range it is a 16x9 resolution as well so that it fits comfortably on um modern widescreen 16x9 monitors um what we're going to do is the first goal that we have today is to sort of draw two images to the screen we want a foreground and a background because notice if we go back to the slides we can see in the very background we have a sort of hill landscape and then on the bottom we have a ground and there the two of those are going to eventually scroll at different rates it's going to be called parallax scrolling but just for our very first example we have something very basic i just want to draw two images to the screen so we're going to go ahead and do that here by setting a local variable remember local means that it's just defined to the scope that it's in rather than being global which means we cannot access this variable outside of this file local background gets love.graphic image the function that we just talked about let me go ahead and hide this inspector here so we can have more room to code and then it's just going to take a string so background.png and i realize i actually didn't include those files in the directory so i'm going to need to do that as well same thing for the ground exact same function love.graphics.new image except ground.png and before i forget let's go ahead and do that right now i have the files here ground and background and copy those from the repo the distro repo into my bird zero directory that i'm currently developing in right now and so as soon as we're done with that we're going to go ahead and we're going to define love.load which is the function lov2d calls at the beginning of your program execution in there because we don't want these images to look blurry when they get loaded and upscaled we want to go ahead and set our default filter to nearest on min and mag which means on upscale and downscale apply nearest neighbor filtering which means no blurriness no interpolation of the pixels and then one thing that is just a small little touch love.window.set title 50 bird because uh is gd50 and then we're going to go ahead and set up our screen here with our virtual width virtual height window width window height it's getting a bit long and then it takes in a table recall tables just take in keys like so unlike in python where you might use a colon we use an equal sign in love or in lua i should say resizable to true and that is the end of our load function now does anybody recall how we if we want to resize so notice i said resizable to true do we know how we want how we can send a message to push to resize our screen for us so love 2d defines a function called love.resize which takes in a width and a height and in there all we're going to do is defer that call to push recall the exact same function on push it takes a width and a height and that will take care of dynamically re-scaling the canvas it uses internally it renders to a texture and it's going to render to the texture that we set as the virtual width and virtual height and it's going to scale it to fit our screen and it needs to know our physical screen dimension so that it can actually properly scale that internal canvas appropriately does anybody remember the function that we use to get input from the user so function.love.keypressed recall it takes in a key love is going to call this automatically every time we press a key and that's going to be we're going to have access to that key and we can do any sort of logic that we want on that key or using that key and we're just going to call love.event.quit because i don't like to press command q or click the red x i just want to hit escape be done with it and then what's our render what's love's render function called it's called love.draw so call love.draw and then uh because we're using push does anybody remember what we need to actually do to get push to render our screen to a virtual resolution so recall that there's actually two ways we can do it we can call push start and push finish which we didn't cover last week or we can call and that's actually the new like sort of de facto way to do it or we can do push apply start which is the deprecated way to do it but starting from here on out we're just going to call push start and push finish and then last we have our images we've allocated them as objects up here we have a background and a ground all we need to do now is just draw them to the screen so this is a new function or it's actually uh not a new function it is a new function actually love dot graphics.rectangle is what we used last week for all of the draw calls in this case we want to draw a graph a image object a texture object that we have in memory so we're going to call love.graphics.draw and it takes a drawable which means anything that love has defined as something that can be drawn in this case images are drawables they can be drawn and they can be drawn at any given position that you specify so if we wanted to draw it at the top left corner we would just say love.graphics.dropbackground at zero zero and it has that effect and we're gonna do the exact same thing with our ground the only difference being that obviously we don't want to draw at the top left corner we want to draw at the bottom of the screen so we just call virtual height minus 16 which happens to be the height of our image so if you run this i'm going to go ahead and make sure i'm in the right directory i'm not in the right directory so i'm going to go into a directory i wrote 50 bird scratch going into bird zero and if i run this i should theoretically have just two images layered on top of each other which i do not so let me make sure that it gets saved remember to always save your work and there we go so all we're doing now is it looks infinitely better than last week already but it's very simple very few lines of code all the effort that we've put into it has been in our sprite editor of choice and you can use most any application you want to do this sort of stuff i use a program called a sprite i like a lot but you could do this in which is free you could do it in photoshop you could do it in microsoft paint if you wanted to um godspeed if you do um but yeah so that's that's as simple as it is just to draw images to the screen so we've already made quite a lot of progress in a very short period of time in terms of the visual aspect of our game but it's not interesting to look at beyond the initial sort of honeymoon period of now we have colors on the screen we want to actually get scrolling because the game recall is a scrolling game and actually would anybody be willing to volunteer to come up and play flappy bird just so we can see it live on the stage david do you want to come up and play oh doesn't volunteer stephen you want to come up and play thank you for volunteering i guarantee you're better at this game than i am so i'm going to go ahead and cd into uh bird bird 12 in the directory which is uh the final version of the game complete so i'm going to go ahead and hit enter so already we can see the uh parallax scrolling that i referred to before which is the floor and the background are scrolling at different rates and we'll see this very shortly in the next example we have a prompt we have text we've already used this before with a font so go ahead if you press enter you're going to get a countdown so space is to jump so we have our bird jumping in the middle of the screen we have a score at the top goal is to avoid hitting the pipes oh i got a score of one we'll try again [Music] so it keeps track of his position and every time he gets past the right edge of a pair of pipes as you can see that's when he gets a point so if you recall from last week what do we think is what what's what's detecting the collision if you remember last week what's the term anybody remember access a bound a a b b collision detection access a line bounding box is the same thing that we did with pong except now we're doing it we have graphics but that's the same same as that concept we're just using rectangles and when one rectangle overlaps with another rectangle we trigger depth so one last iteration i think and then we'll oh we'll let you try one more time go ahead i'll give it a shot all right i'm gonna lose on purpose yeah okay here we go so to be to be unfair i i got plenty of practice while i was developing this but we'll see if that actually holds true here all right so notice also the pipes there's their the procedure generation that i oh i lost three points let me explain a little bit more i guess we'll do one more iteration but the pipes themselves every time we start they're spawning at a different location this is procedural generation pretty much the most simplest way possible um and notice that the pipes are shifting gradually so this is sort of like the makeup of our level um and it's just generating bit by bit uh due to some simple algorithm that we have that just says hey respond to the pipe here shift it by some amount and this very simple approach allows us to have an infinite level over and over again it's very efficient we only ever have as many pipes on the screen and as we'll see soon we only have as many pipes in memory as we can see on the screen at one time despite the fact that this level could theoretically go on infinitely and so it's very cost efficient so bird one uh is the example it's the parallax update so parallax scrolling is an important concept in 2d and also 3d but 2d game development it refers to the illusion of movement given two two frames of reference that are that moving at different rates so if you're driving on the highway and you see a fence next to you and you see mountains in the distance you're observing parallax scroll by seeing how fast the fence moves relative to the mountains the mountains are going to move a lot more slowly than the fence is right next to you and we accomplish the same exact illusion in our game by ref by using this sort of uh graphical illusion and so i'm going to go ahead in my directory here in bird one which is a unpopulated direct it's populated with the contents of bird zero the complete contents of bird zero your the version that you'll see will have uh all of the code but i'm gonna go ahead and if we run bird zero uh in that directory so i think right now i'm still in the full distro so let me go ahead go into 50 bird scratch again oops where am i and then i'm gonna go into bird one and run it and i get the exact same image that we had last time so everything is there from before just two images nothing moving no parallax that we can observe i'm gonna go ahead and start implementing the uh basics of this parallax so if i go ahead in my main so i'm gonna go down here to where we have our background so we need a couple of new things so along with our background image we need to keep track of how much it's scrolled because we're going to start drawing this image to the screen but if we're going to scroll it that means that we need to shift its x offset we need to instead of drawing it at 0 0 if we want it to scroll we have to draw it at some negative value instead over time this will have the effect of it moving right to left so i'm going to go ahead and keep track of a i'm going to use a variable to keep track of the scroll amount for both of these images and we're going to call them background scroll and ground scroll and set them to zero so this is going to have the effect of no x offset so i could use this variable right now in this draw call down here which i'm actually going to do i'm going to go ahead and go to i'm just going to find that that is correct i'm going to go ahead and set that to negative background scroll whoops and here i'm going to set this to negative ground scroll so this is not going to change anything yet it's going to be the exact same thing because they're both 0 they were 0 before but we're going to change them over time and in order to do this i'm going to go ahead and go into are uh up here uh one thing before we before we do that actually uh we need to set a speed for this this is going to happen over time but since they need to occur at different rates the background needs to go at a slower rate than the foreground so that we do get this parallax effect we need two separate speed variables generally the norm for something that is not going to change is to write it in caps with underscores this is constant notation this is frequently seen in most programming languages we'll use it here i'm going to set a variable called background scroll speed and i'm just going to set that to 30. i'm going to do the same thing ground scroll speed does this need to be higher or lower than the background scroll speed the ground is going to move the so the background needs to move slower than the ground does so this is going to be higher so we're just going to set it to 60. you can set it to whatever you want to get the effect that you want but this will already be quite noticeable the ground is going to move twice as fast as the background and so what we're going to do also is if we just so what's going to happen if we just let our image scroll infinitely what's going to happen at a certain point it's going to run out of image so how do we fix this problem loop it exactly so we're going to go ahead and set a looping point so another constant background looping point and we're going to set this to 413 which you kind of have to look at your image and determine you you sort of have to set your images up if you want to achieve this effect by having them be a looping image so have either two copies of the exact same thing that's their screen with or just copy the same chunk over and over again there's many ways to do it in this case the looping point of the image of our our background is 413 on the x-axis so we're going to set that to 413. and then we're going to go ahead the next step is we actually have to start uh changing the value so in our our update function which is where this is going to happen i'm going to go ahead and define love.update which recall lov2d will call for you but you must define it yourself i'm going to go ahead and set background scroll to so what this is going to do background scroll gets background scroll to itself plus the speed we set before times delta time so it stays frame rate independent um that'll have the effect of adding the speed to our image but we need to reset it we need to actually perform the reset and to do that we'll just be using modulus which recall from languages like c simply divides basically sets that value to uh the remainder of that division so in this case so 10 modulo 5 would be 0 but 10 modulo 9 would be 1 effectively because we have 0 left over once we divide 10 by 5 we have 1 left over once we divide 10 by 9. so i apologize if that concept is uh not new but we're going to do the same exact thing for our ground only we're going to modulo by the ground or uh of our virtual width in this case i did not set a looping point i do in later examples but the our ground image is very uh it's consistent enough such that you don't even notice it when it loops without using a with just uh without just using the virtual width so we're just gonna use the virtual width in that case it's very um patterned and very small um and aside from that yeah we already have the off the background scrolls here in our render or draw functions so when we run this code we should theoretically have scrolling background so does the images have to be twice the width they do at least twice the width yes there's ways you could effectively tile your image and do it that way to save memory on on a texture size if you have like maybe something that's a quarter of the screen size that you want to loop over and over again you don't want to have that as one big image you'll just draw four copies of that image to fill your screen and then just shift all of them or maybe five actually so you have a little bit beyond the edge of the screen and then just put all of them back to the bottom line the zero is you you wouldn't know if you just restarted showing the image but the the larger background you would have to worry about the mountain getting cut in half when you replaced it right exactly so like we could uh we could actually i could show you right now what that'll look like so if we just take out the looping point here or we set it to some like value that's completely inaccurate like 270 and then we run it after a while should just cut yep right there so are you stacked so are you drawing it twice really like one after another when it runs out no the image is so wide that it always will fill the screen even after it's been uh set back to even after it's gone past the looping point i forget how large the texture is it is yeah 1157 pixels wide so it's more than twice the screen width actually i think it is exactly twice the screen width um no it's not exactly how the screen is but it's more than twice the screen width so that when the amount the 413 pixels has elapsed it's still plenty past the right edge of the screen and the looping part it'll be the exact same appearance on the texture but it's completely been shifted back to the right so that zero zero is now at zero the zero zero of our image is now at zero zero in our screen space it's just taking it your image is here moving and then just instantly back to the beginning and then and moving back to it that's why and the setting it back to zero or however technically how many pixels has gone past the edge of the screen because using modulo yep it's a translation it's an instant translation it takes place over one frame so you don't notice it your human eye can't see it because it literally happens in one frame and it the exact the the image data is the exact same at those two points because we have a texture we've pre-created a texture that has the exact same data so that you have that effect you have to have a texture that allows you to do this or smartly draw four of the same images keep track of all four of them or actually eight of them so that you can move them to the left and then shift them all back to the right when we get to super mario brothers we'll be talking about a concept called tile mapping which is where we take a sprite sheet and then you uh basically chop it up into pieces have a map in that is basically numerical so that a brick is uh like one the value one and then you look through this giant two-dimensional array that you have and then go over it iterate over it and then draw a tile like give at an offset based on your index into that map so it's a little bit more complicated and you actually a lot more memory efficient but slightly different slightly different implementation okay so we have parallax scrolling now um i want to take a moment to because we've touched on we've sort of this is this is a very sort of introductory way of demonstrating that games our illusions by using parallax scrolling we've all we've done really is just set two things to scroll at different rates and this has made us feel like we have depth in our scene but all we're doing we have two images we're scrolling them at different rates um but this is a common theme in game development is taking trying to devise a scene that maybe is very elaborate but doing it on very resource intensive devices uh like your iphone or like you know old consoles like the nintendo 64 these sort of illusions are all over the place and a youtube channel that i um that i recently found that i really like is it's called the name of the channel is she says but the actual show that they have is called boundary break and what they do is they take a camera that it goes beyond what the game developers allowed it to do was they basically hacked the game camera so you can see in places where you weren't supposed to see before and you can see a lot of really cool trickery um i'm about to show you a couple of video clips but here's the youtube url if you're curious to see the exact video it's about 33 minute video um it's on zelda ocarina of time for the n64 uh and i'm i extracted a couple of particularly noteworthy clips that i thought were kind of interesting and also humorous i'm gonna go ahead and show the clip now so if we could dim the lights i'll go ahead and start this is a the first example okay so there's a lot to talk about with the shop owners in ocarina of time so i'm gonna just condense it down to the most interesting and the first one we're going to talk about is the bizarre shop owner in hyrule now in majora's mask this very same character is actually shown with legs but in our current time he did not have those in fact he looks extremely hilarious without his legs so this is a do we do does anybody have an instinct as to why they might have done this this way exactly and beyond that also just saving on memory right like not having to load a character model the vertices and textures associated with it on a such a memory constrained device like the n64 i forget how many how much memory it had like four megabytes of memory i think less than that um and so they were obviously cutting however many corners they could in this case by literally using the illusion of looking at not the illusion but just sort of like the fact that you only could see over the counter and sort of giving you the illusion that there's a fully living talking shopkeeper there but it's just a half a model and another example here uh is more to show how uh ocarina of time used its limited memory the n64 limited memory to give you the sense of being in a very large level when you might not actually have been so if you get in the lights one more time i'll go ahead and show this so this one was apparently a hot suggestion which is free camera on death mountain including our friend big goron the smoke halo looks sort of weird against the black sky and here you can see a nintendo fuldas it's not a full mountain only the cliff face is actually rendered that's the path leading towards the fire temple and if we zoom out we can see the scale of the whole map bigger than i thought it'd be actually the battle music's not quite fitting for an epic panning shot though same idea here really just limited memory space so let's load you know as much as we could possibly ever see from the perspective the camera of link and it's actually very similar to how i guess people create stages in real life to make you feel as if you're in a when you go to a play feel like you're actually in a scene but you know they've clearly cut as many corners as possible but it works in the game you can't tell and that's very common in game development and something if you're trying to achieve a particularly grand effect is something to think about is how can i make it seem like i'm doing something but i'm actually not how can i make it seem like i'm a bird flying through an infinite series of levels but i'm actually not we have a lot of sort of more of that to show coming up soon we have uh so so far we have our background but we don't have the uh title character of our game in the in this case uh 50 bird so i'm going to go ahead and illustrate how we can get a bird actually rendering on the screen so i'm going to go ahead into my bird 2 directory here that i've created note again bird 2 in your directory if you've loaded the code is going to have the complete implementation but in main i'm going to do a couple things so actually first thing i'm going to do we're going to notice that i've included actually i haven't included the class um file so i'm going to do that right now so in bird one or sorry i'm gonna take from bird three the class.lua i'm gonna go ahead and put it into bird two because we're gonna make a bird class recall from last week a class is just a way of taking several variables that we might once have had disparate from one another putting them in together in a package putting functions associated with those variables together so that we can call we we can sort of think of our world our game world more abstractly and more compartmentalized and cleaner so i'm going to go ahead and now i have in bird 2 the class.lua that's just the library we're using to get classes in love2d in lua i'm going to go ahead and i'm going to create a new file this one's called bird.lua so remember the trend is for classes capitalize them to differentiate them from functions and variables this one i'm going to go ahead and just go ahead and use my cheat sheet here poetry my sheets are sticking together okay so this bird class is actually fairly simple uh recall that all we have to do to create a class is just use the class library the capital c with the brackets there to initialize it we're going to go ahead and define our init function so every class has an init function which initializes the object that it's going to refer to later in this case we're going to need a few things so we're going to need an image for our bird because we want to draw to the screen and so what we need to do same thing that we did before love.graphics.new image i'm going to go ahead and hide this really fast and then bird.png simple easy we want the width and the height of our bird so i'm going to go ahead and set that too so uh every image has a set of functions associated with it that love implements for us the image that we get back from love.graphics.new image is itself sort of a class which has a function called get with so this will allow us to achieve the width dynamically of whatever class we whatever image file we happen to allocate and create an object from and then we're going to go ahead and set our x and y because recall we have to draw it somewhere we want to draw our bird in the middle of the screen so we're going to go ahead and just calculate this based on our virtual width so we're going to do virtual width divided by 2. so it's halfway in the middle of the screen but since it draws from the top left corner we want to shift it to the left so we're going to use our width that we just uh instant error we just initialized from the image data and then we're going to call it our do a self.width divided by 2. so we're going to divide the width by 2 shift that to the left on our x-axis that's going to put us in the middle horizontally vertically it's the exact same thing except we're using height instead of width and that's pretty much it except for one last bit here we want to be able to render our bird pretty important so we're going to do love.graphics.draw um our image and then at self.x self.y and so this is uh all we really need just to get a very simple sprite onto the screen now it's not going to do anything because this sort of lives in a vacuum at the moment what we need to do is in our main file we're going to require bird which is going to actually put it into our allow us to use it in our code we're going to create a local bird variable we're just going to call it bird we're going to after that simply render to the screen like that and if all is done and well and if i'm in the right directory uh it did not work make sure make sure you save your work again uh oh i did not require class my bad so also we need to do this since we added that to our directory uh i did not include the bird.png as well so i'm gonna go ahead and do that i'm gonna borrow that from the next directory that should be all we need to do and uh attempt to call method render a nil value interesting did i not save bird i did not save bird there we go we did it so uh not particularly interesting but it's sort of you know we're making steps remember to save your work yes we can see i do not um but we're making progress we have we have our our entity that we will control so uh you know we have visually we have we're getting very close but a lot of important details are missing what should be the next step do we think and we'll do that with the help of a notion that's common in platformers and a lot of games really but gravity how do we how do we think we can simulate gravity in the context of 2d game development just by default fall at a constant rate we could do that certainly um and that's effectively what we will be doing we'll be using something that we used last week which was velocity delta y and applying that velocity uh to our bird's y frame by frame and that will give it the illusion of falling now falling at a constant rate isn't accurate to what gravity actually does what we want to do probably is some gravity over and over again increment our gravity by some sort of constant value so that just like in real life things fall faster and faster and then we want to we want to add that to our y value so i'm going to go ahead and start implementing that now in bird three uh wrong repo so bird three we have everything that we had from before um except now i'm going to go ahead and in main.lua in our update function this is where we're actually going to want to perform the update logic for making the velocity apply to the bird we're going to defer that to the bird class we're going to assume that we have a method called update in our bird class which we're going to implement shortly and that's actually all we need to do in our main class it's sort of the beauty of having classes that you can delegate all this work to your main file though it's still getting quite large it's 108 lines it's not two 300 400 you know thousands of lines of code because we're able to break out this code and sort of encapsulate it elsewhere so i'm going to remember to save it this time and then i'm going to go into the bird.lua file in that directory which is the same with comments because i loaded it from the official repo the same bird code that we wrote before i'm going to go ahead and do a couple of things so the first thing that i'm going to do is define a constant so i mentioned gravity before gravity is going to be a constant value just like it is in real life i'm going to define it to 20. or just some arbitrary value this is the value that i decided felt right but you can tune this however you want there's no right or wrong way to do it the less the gravity is the slower it'll fall and the more you'll feel like you're sort of in outer space or on the moon or whatnot we're going to also go ahead and define recall that we need some way to keep track of how our position is or how our bird is falling we want a velocity a y velocity this is going to update our position each frame and it's going to make it feel like we're falling so we're going to set our initial velocity to zero the burst is going to be in the middle it's not going to be falling yet what we want to do is apply this velocity so remember in our main file we had an up we assumed that we had an update function but we haven't actually implemented it yet so we're going to do that right now we're going to say word update dt it's going to we're going to pass it in the same dt that we use in our main file and we're going to go ahead and just say our velocity is equal to our current velocity plus gravity times delta time we're just going to scale gravity by delta time so it'll move the same amount no matter whether we're running at 10 10 frames per second or 60 frames per second and then we're going to go ahead we have a velocity but it's not actually changing our y value the y value is what ultimately moves us on the screen so we need to apply that uh our new delta y to our y so we're going to go ahead and just do that self.y gets self.y plus self.delta y d y and so if i go back into bird three assuming i saved everything we should just fall straight to the screen which we do not terribly useful but notice it it's kind of it's slightly hard to tell maybe but it does move faster and faster frame by frame because that delta y is increasing as well as our y and that that delta y is getting applied to our y frame by frame i'll do it one more time just let's find a look at all right so we have basic gravity super super basic computation just keep track of some gravity constant a delta y increase that and apply that to your y and that gives you gravity but flappy bird can jump so we need to define a way to defy gravity so we're going to do the in bird 4 we're going to call this the anti-gravity update and we're going to uh talk about how we can actually get that going so i found this diagram which i thought was pretty apt um and it also covers a few of the other concepts we're talking about today but see here this gravity that's the constant we just defined before the 20 or whatever and this gets applied at whatever value you want it to be this gets applied frame by frame to your y what we want is this this vector here this jump velocity we want some value to sort of counteract this gravity that we've been accumulating so how do we think we can go about doing this we can set gravity to some perhaps negative value a high value and that'll have the effect of frame by frame if we if we go from you know some positive value which is taking us down on the y-axis and we go to a negative value it's going to start frame by frame it's going to say let's say we start at negative 5 or we set its velocity to negative five it's going to set y to negative it's going to set it to plus negative 5 pixels plus negative 4.9 pixels 4.8 pixels it's going to shoot us up pretty fast in a series of pixels but since we're applying gravity frame by frame this value that we set before 20 it's going to have the effect 20 times delta time so it gets effectively divided by 60. it's going to counteract this again so we're going to shoot up pretty fast but gravity is going to start taking hold immediately after and we're going to start getting the effect of our bird jumping and then falling down to the ground a couple of other things that this diagram shows which i thought were pretty cool this pipe gap or pipe gap distance here something that we'll be talking about pretty shortly because this needs to be defined so that we can offset our pipes pipe separation that's another thing we'll be talking about and also pipe width which is just an intrinsic value characteristic of the pipe sprite we'll be using but i thought it was a very apt nyu did a a nice little article if you want to look at this about exploring game space they computationally determined uh what would make a flappy bird level difficult or not and rated flappy bird levels that were dynamically generated based on some sort of scale so if you're curious it's in the slides but i thought it was a cool find as i was putting together this lecture so what we need to do is then simply add some negative value to gravity negative sort of anti-gravity so we're going to go ahead and do that so in bird four of the little mini ripa that i have here we're going to go ahead in main first one thing that we want to do is because another part of this is taking input from the user being able to jump we want to be able to detect whether they've pressed space but if we want to detect input for every single entity that we ever like in a game in an instance like this it's not terribly important but let's say we have like 20 or 30 different kinds of entities and they all have their own input handling we don't want to clobber maine with that necessarily so we can dedicate that delegate that i should say to another section of the code in this case we can sort of put our bird's input handling together with our bird class right and sort of expand up upon the model of the class or taking control of the code and data for that particular object in our scene so what we're going to do is in our love dot load i'm gonna go ahead and do something here i'm gonna go ahead and set i'm gonna go ahead and set love.keyboard.keys pressed equals a table and what i'm doing is just adding on to a table that love defines called love dot keyboard i'm adding my own value into it called keys pressed and i'm assigning it to an empty table so what we're going to do this is part of this is now part of what love gives us as part of its sdk but it's something that we've created ourselves and you can do this because in lua basically everything beyond basic variables or just tables and you can manipulate tables however you want in this case love.keyboard is a table i'm just adding a new key called keys press and i'm assigning it to an empty table of my own and we're going to see how this is actually used in just a moment so i'm going to go ahead in our key pressed function here this function gets called every time a user presses a key in the game but i'm going to use it because this because it does that i can go ahead and just do something like this love.keyboard.keys pressed key gets true and what that means is in this table that we've just defined we've created ourselves anytime the user presses any key because love.keypressed gets called for you we can safely rest assured that this is going to get populated no matter what key they've pressed because it's just something that love2d takes care of you but it's not getting stored until now now we're actually going to keep track of it in our own table for reasons that will become apparent very shortly the next part of this code is defining a custom function so the the impetus for this is love defines a couple of functions it defines a function called uh love.keyboard.isdown which takes in some key value and you can use it to test for continuous input which we did in the last the last lecture we're saying hey if up is down right now or down is down then we need to update our y velocity accordingly but it doesn't have a mechanism like this for let's say we want in some file other than main to check for if key was just pressed one time um it has this function love.keypressed which takes a key and that will trigger it but we can't access this outside of this function because if we define this function in bird.lua it's going to overwrite this implementation and we don't necessarily want to have to worry about other files overriding these functions because who knows if you're on a team especially who knows who's overwritten love.keypressed and what module and what order does it get loaded in and what function's actually valid we're going to take care of this problem by giving ourselves the ability to test for whether a key has been pressed on the last frame by implementing a function that we are also adding to the keyboard namespace the keyboard table ourselves called was pressed it's going to take a key and all it's going to do is check that table that we created before it's going to say if love.keyboard.keys pressed key then return true else return false and you could actually just return love.keyboard.keys pressed key and it'll be the exact same thing and so what this has the effect of doing is saying okay because on the update which we're about to see actually i should probably do that before so this all gets tied together um love dot at the end of love.update we're gonna do one last thing and that's reset that table because we wanna just check frame by frame so we have we have a table a global table that we've created to check for whether a key is pressed we have a callback function that love2td gives us that allows us to do that so every time a key gets pressed we're going to just add that key to that table and set it to true now we can just simply query that table anytime we want to with this function that we've created called love.keyboard.was pressed key which means on the last frame was that key pressed basically return whether it's true or false now the only problem is we're not flushing it we're not ever setting that to false that is the effect of if we just press all the keys in our keyboard those will always be false always be true until we reinitialize the table to some empty value which is what we do here on the update which takes place after all inputs been detected we're going to just set that table to an empty table again and on the next frame it's going to whatever keys we pressed those will get set to true and then we can just query that table here as needed and any update henceforth so does anybody anybody have any questions as to how this is operating and so the ultimate driving factor for us as to why we want to do this we want to put in the work to sort of keep track of this global input table is uh so that we can actually query input single key input based on or based in other files outside of main.lua because currently all we can do to check for single key presses is look in main.lua but that's not what we want to do we're going to go ahead and go to our bird.lua and in our update function this is where we actually get to use our efforts and say if love.keyboard.was pressed space which is the key that we want to actually allow us to jump go ahead and set self d y to what should we set self dot d y to when we press space bar should be a positive or a negative value a negative value we'll say it's negative five and we should probably define this as an anti-gravity constant up here but just for the sake of speed we'll say self.dy gets negative five and so and i did save that right i did save that i'm gonna go ahead and go into bird four go ahead and run this example and look at that we're jumping but we can still fall through the ground and we don't have any real gameplay but we've come a long ways now we've taken input single key input that we otherwise didn't have the ability to do in love2d and we've made it possible by just keeping track of our global input state and flushing it every update so does anybody have any questions as to how that works okay so the other big major visual component of flappy bird are these pipes that we see here on the screen we have two pipes there but the screen is filled with infinite pipes so does anybody have any instinct as to how we can implement this well we'll see before long but suffice to say we'll need a new sprite we'll need some sort of way of keeping track of when to spawn them because they sort of spawn you know after a period of time and that'll be sort of our gap and then what happened will happen if we just let it spawn forever and ever we do because if we don't do that after a certain period of time we're allocating memory for each of these pipes not a ton of memory um just you know essentially an x a y width and a height but because they all reference the same they will reference the same sprite image but given enough time eventually you're going to allocate a certain number of bytes that will exceed your computer's memory or the amount of allocated memory and you'll either hang infinitely or crash and so we want to destroy them as they go as well so we're going to go ahead and look at sort of the final live coded example just because from here on out it's going to be a little bit much i'm going to go ahead and go to main.lua first so just get my notes in order the first thing we want to do oh i'm actually in the wrong repo too i apologize i was in the district repo i want to be in the scratch repo so i'm going to go ahead go into main i'm going to require pipe now we don't have a pipe yet but this is a perfect example of how we can sort of keep abstracting our game we have a bird class but we should also probably have a pipe class because a pipe is a distinct type of entity in our game world we can sort of model it as a unit we can give it functions we can give it data and think about it in terms of it being a pipe not being a set of x y with height you know etcetera whatever whatever data you want to ascribe to it we can abstract that out and think in more abstract terms which will allow us to scale a little bit better so we're going to go ahead and assume that we have a pipe class i'm going to go ahead and go ahead and add it to our folder here right now so do a new file pipe.lua and i'm going to go ahead and reference my notes here for just a second go ahead and so the pipe class is actually quite simple just like the bird class was initially we don't need to keep track of a lot of data but we do want to keep track of a few things so the bird there's only ever going to be one bird out at once but with the pipes we're going to be spawning them over and over again and so if we allocate them out for each pipe that we instantiate if we allocate a new image this is probably not super efficient right we're using the same exact data we have a bunch of pipes we only really need one sprite so outside of the init function so just below where we're declaring that pipe is a class we're going to go ahead and create a local variable that is still scoped to this file but there's only ever going to be one copy of this object we're going to go ahead and call it say that we have pipe.png in this folder and this is sort of separated out from the functions that we're going to be defining in here but this has the effect of sort of creating a semi global graphics object even though it's contained within this this class file it's not accessible outside of this class file because we don't need it to be but it's also not being instantiated every single time because recall if we look at bird.lua here we're just setting it as self.image gets love.graphics.newimagebird.png this will have the effect of allocating a new image every time we create a bird object but we only ever create one bird object so it's not really an important design consideration for us to say maybe we should create a you know global semi-global image up here it's not it's not important in this in this context probably good style to do so anyway for larger projects but um just a consideration for here not really something we need to worry about but yes definitely try to take an asset and reference it as reference it rather than allocate it as many times as possible um we want our pipes to scroll so we need some sort of value just like we did with the backgrounds we need some value that keeps track of whether these pipes are scrolling and it can be a constant value we're going to directly call it negative 60 this time and not sort of negate it when we add it to our position later on so pipe scroll negative 60 we can just add it directly to our x or to our well yeah in this case just to our x and then we'll have the times delta time of course and that'll have the effect of shifting it left because it's a negative number we'll define the init function here so pipe init within the init function we're gonna do a couple of things so uh it's x where should the x be what should the x be set to let's say if we want the pipe to spawn beyond the right edge of the screen yep virtual width and you could you could also say virtual width plus some number if you wanted to because it's set to zero zero uh it's going to have the you won't see it on the frame that it gets instantiated but yes virtual width or virtual width plus some constant value or some value that you've uh allocated ahead of time we'll just set it to virtual width so as soon as the pipe gets initialized it will be invisible but it's going to be right on the right edge of the screen what about our y value first of all let's take a look at what the image looks like so we can see it's going to be in our i don't think i have the actual image in that directory so i'm going to come here when i grab the pipe this is what the pipe looks like let's see if i can expand it a little bit so it's kind of tall where should we probably place it if we want it to look sort of similar to flappy bird probably towards like the lower end of the screen we can get fancy with it too and we can even maybe make it randomized just like flappy bird so we'll go ahead and do that i'm going to go ahead and copy this and put it into our scratch folder here back in the init function i'm going to go ahead and set self.y too because we want to talk about procedural generation this will be sort of our first foray into how we randomize this we'll be using the function that we used last week and this is a ubiquitous function you'll see this everywhere and any framework or game engine you use math.random we want it to be the lower half of the screen so let's say virtual height divided by 4 is the upper bound and maybe virtual height minus 10. as the upper bound so that'll have the effect of setting it to roughly a quarter of the screen or sorry virtual height divided by 4 is towards the top end of the screen and then virtual height minus 10 is the lower end of the screen so it's actually going to cover anywhere from the first quarter below that down to about 10 pixels from the bottom i do it in main so in this file i am not sure if i did it for this demonstration it is definitely set in the repo the i don't think i said it in this example but yes you would set the random seed here if you wanted to run every time oh sorry and the question was do we uh should we set the random seed in the bird file or should we set it in main.lua typically you want to set it at the top level of your application so we're going to set it in um we're going to go ahead and set it in the in main and the function itself is here and i think it's starting in bird six onwards so it'll be uh did i not set it i may not have set uh the random seed until later in the repo let's check bird 12. so yes math.randomseed and then seedbyos.time as we used last week in class um i won't i'll set it here um probably will only run it once but it'll it'll have the effect now we can run it several times just to see the the difference in the pipes let's go back to our pipe.lua here and we have the x we have the y so those are set accordingly we also want to set the width does anybody recall uh what the function is to get a the width of a graphics object and the syntax for that so we have our image up here pipe image love.graphics.new image pipe.png exactly so we're going to go ahead and set this to pipe image colon get width and that will become our new that'll allow us to start with for when we um we will use it later um and then we need a few other functions so the pipe will spawn but it won't move because we haven't applied any sort of scrolling to it we have the scrolling variable up on line five but we need to actually apply it to our pipe so we're gonna go ahead and create an update function and then in that update function very similar to what we've seen before already pipe scroll times delta time and then lastly we want to render our pipe so we're going to go ahead and call function that we've seen already today love.graphics.draw we're going to use the pipe image up above and then we're going to go ahead and uh use self.x and self.y and that's all we need for our pipe and let me make sure that that's all we really need so in main.load we got to go back to main.lua too because we actually have to start spawning pipes um so let's go ahead and go to pull up my code here one more time in maine so on line 59 or sorry you won't see it you'll see it line 59 in the actual distro code but um for me it's going to be different slightly different we're going to go ahead and create a new table to keep track of all the pipes that we want to spawn because we need a way to store them in memory we can't just set you know one variable to basically a dynamic almost like a dynamic array in this case but we're going to or a linked list rather we're going to use this table just to hold them we're not going to give them keys we're just going to insert them like we would do with just a linked list like in python for example we're going to go ahead and what do we need to do if we want to sort of have them spawn after a certain period of time probably want to like have some sort of timer we want to like keep track of how much time has passed and maybe have some sort of amount of time that's our like trigger to spawn up a pipe let's say maybe like two seconds so if we set a timer to zero it's gonna start just at zero but we can add to this frame by frame we can just increase this timer by delta time whatever that is frame by frame it'll be about a 60th of a second so um after 60 frames have passed we'll get one second after 120 frames of pass we'll have two seconds at that point we can then decide okay now it's time to spawn a new pipe let's go ahead and do that so i'm going to go ahead and [Applause] in our update function we want to handle the actual increasing of this timer so it's as simple as make sure that i called it spawn timer i don't i just called it timer let's go ahead and call it spawn timer be a little more uh specific about what we want here so our spawn timer and then we're going to go ahead in our update and set spawn timer equal to spawn timer plus delta time and then what we need to do is then check is our spawn timer greater than because it keeps track of time in seconds delta time will give you a fractional amount in seconds so it'll be at .013 or something like that we want to keep track of whether spawn timer's gone past two right so if spawn timer is greater than two we want to add a new pipe does anybody remember the function for how to add to a table in lua so it's table dot insert so table dot will take in a table so in this case we want the pipes table that we allocated before and then we're going to put in a new pipe object we're going to call this is how you instantiate an object or call parentheses that will have the effect of now our pipes table is going to every time we call this it's going to get a new index so it's going to start at 1. lua tables are indexed at 1. first time it happens index 1 is going to be equal to a new pipe object which is going to start its x y at the edge of the screen then index 2 will be the exact same thing a new pipe that's at the edge of the screen and so on and so forth every time we call table.insert once our spine timer has exceeded two if we want this to not spawn a pipe every frame here after which would quickly clog up our uh our world we want to reset our spawn timer to zero so this will have the effect of now it's going to wait another two seconds and then this condition will be true again and then we can add a new pipe to the scene let's go ahead and look at a we're going to need to add a new um set of logic here actually i'm going to put all of this above the bird dot update and then below that i'm going to go ahead and do i'm not sure if we've covered this already don't think we have but if we want to iterate over a table there's a function that lua gives you called pairs it will give you all the key value pairs of a table that you can then use while you're iterating over it similar to enumerate and python if familiar except this will actually give you the keys rather than just the indices so we can do for k pipe in pairs of pipes do some body of code and then we have access to the key and the pipe within this we can just iterate over it and use it so first thing we want to do is we want to update our pipe so for each pipe update it give it the delta time of the current frame and then what was the other important feature so this will have the effect of scrolling it now it's going to get its x shifted but what was the other important thing we need to do with every pipe in our scene yes that is exactly true so what we're going to do is if pipe.x is less than so if we did less than zero what do we think would happen it would have it would we would see it instantly disappear because it's they're based on the top left coordinate so what we need to do is keep track of its width so what we'll do is we'll just say if pipe dot x is less than negative pipe dot width which will allow the pipe to go all the way past the edge of the screen we'll call a function called table.remove which takes a table in this case pipes and then it takes a key and the key we have access to up above on line 124 we can just say k and that'll have the effect of removing that pipe from the scene and then as soon as that's done uh we're good to go the last thing that we need to do is currently we're not actually drawing the pipes to the screen so down below in our render function we're going to go ahead and up above before we do the ground because if we do it normally if we do it after we we render the ground it's going to the pipes are going to look like they're just kind of like layered on top of the ground we want it to look as if they're sticking out from the ground so what we want to do is have a correct render layer a render draw order to the screen we draw the background we draw the pipes then we draw the ground and this will have the effect of looking as if the pipes are sticking out of the ground so what we'll do is we'll do the exact same thing we just did up above by saying for k comma pipe in pairs of pipes do uh pipe and then the render function that we defined in pipe and also the effect of iterating through all the pipes in our scene every draw call and drawing them before it draws the ground and before it draws the bird and that should be all that we need to illustrate this example make sure everything is saved i'm going to go ahead and go into bird five if i did everything correctly this should after a certain period of time draw pipes to the screen that are scrolling and they're randomized their y value is getting set to some value between the top quarter of the screen so about starting right about right where flappy bird is right now down to about 10 pixels above the width of the screen which actually that looks like 10 pixels above so that's a slight bug should probably be something along the lines of 30 or 40. we won't encounter that in the final distro because they're not set to spawn that low but you can see how this is sort of the beginning of our procedural level generation system and we have most all the components of our scene now we get normally in flappy bird we have two pipes we have a pipe that's above and then a pipe that's below and they're sort of in pairs and the next example we're actually going to start illustrating this we're going to have pairs of pipes that are joined together which scroll together that once you fly through them you score a point but for now we sort of have all the pieces that we need in order to get uh you know sort of have the basic visual uh sense of the game completed we're going to take a like a five minute break now and then once we come back we'll actually dive into how we can get pairs of pipes into our scene and may start getting into scoring and some other fun things like music so all right welcome back so the next part so before we establish the bird the background the pipes we have all the visual aspects of our game sorted ready to go the next important piece of the puzzle to really solve is how can we start scoring our game and also how can we get the pipes matching the way that they are implemented in the actual game which recall they're normally in pairs as illustrated here and we also see on the right hand side as we've covered already so far we have the spawn zone for our pipes and on the left we have what i've labeled the dead zone where pipes are sort of de-instantiated once they've gotten past the negative width of themselves but pipes come in pairs they get shifted and the once the bird flies between these gaps is ultimately when they've scored a point and so we need a way to pair pipes together and sort of uh define this sort of logic for how can we tell whether the bird has gone past the the gap and whether or not the pipes have been de-instantiated so we're going to go ahead and i'm going to probably stop live coding for the rest of the demonstrations because they're going to be a little bit more complex but i believe my code editor is over here i'm going to go ahead and open up oh this is my other editor okay so in the base repo now we're going to go ahead and look at the full example so in bird six which is the pair uh pipe pair update our current subfolder that we're looking at we're going to start in main so on line 33 in main we can see that we're requiring pipe pair which is a new class we're defining we're taking the pipe that we had before and we're sort of creating a new composite class we're going to take a class that sort of encapsulates two pipes together a pair of pipes and we're going to use this to think about our problem more abstractly than we already are and this sort of layering of abstractions is a very important um concept in computer science generally speaking but especially in games where you might have objects that are composites of objects that are composites of objects and these abstract hierarchies are sort of what keeps programmers sane when dealing with such um you know large levels of i mean you have thousands of lines of code sort of the only way you can really make sense of it so on line 65 if we look now instead of a table that's called pipes where we've renamed it to pipe pairs we're no longer going to store individual pipes in our scene we're going to sort of take these pipe pairs that we're going to create and store them in our table as well as whole as like individual units on line 71 we need a variable to keep track of the uh we're calling it last y the purpose of this variable is so that we can keep track of where the last set of pipes sort of spawned their gap right because we if we made our gaps completely random it will have the sort of effect of not looking continuous for one and also potentially being impossible to beat we want some sort of smooth so contour to our gap so that we can fly through them reasonably and that it looks as if it was almost pre-made and smooth so we're going to keep track of a variable called last y we're going to start it off at negative pipe height so up past the top of the screen plus some sort of value between between 1 and 80 and 20. so it's going to be it's going to be roughly towards the top of the screen and this is important because last y is going to be we're going to end up flipping our sprite and a flip on the y-axis has the result of the sprite sort of looking as if it's gone its whole height above where its actual y is and we'll see um in more detail shortly why this ends up working the way it does we're going to go down to line 132 and in our sort of condition if our spawn timer is greater than two um what we're going to do is this is where we spawned our pipes before but now we're spawning pairs of pipes so we're going to set a local variable y it's going to be this is the the clamp operation that we talked about sort of last week using math.max and math.min to sort of com like apply some sort of operation in this case we're going to add a random value between negative 20 and 20 to whatever our last y value was which is going to shift the gap effectively by negative 20 or 20 pixels we're going to clamp it between negative pipe height plus 10 so about 10 pixels from the top of the screen and then uh we're gonna set the upper bound to virtual height minus 90 minus pipe height and this minus pipe height is only because we're doing a flip operation on our y-axis for our sprite i'll go into it in a little bit more detail sort of try to make it clear as to why we're doing it and maybe i'll take out some codes to illustrate what it looks like without that operation applied but basically it has the effect of 90 pixels from the bottom is where the pipe could the gap could spawn so basically the pipe at the very bottom recall that this gap is where the this uh value is where the the gap itself begins not necessarily where the pipe starts it'll be between negative pi pipe plus 10 between negative basically effectively between 10 pixels from the top of the screen between negative or between 90 pixels from the bottom of the screen and then we're going to apply a random permutation of this value we're going to add some value between negative 20 and 20 and that will sort of give us a contour and it'll be sort of a randomized contour line 136 we have pipe pairs table insert into that instead of pipes and we're just adding a new pipe pair and we're setting it to uh that the value y and then this the pipe pair takes in a y value and that'll be where the start of the gap is and what what this will have the effect of doing is it's going to flip a sprite above the gap so that we have a pipe right above where the gap starts and then it's going to draw another pipe unflipped about 90 pixels below that and that will be how it sort of puts the two together line 144 is a loop that just updates our pairs instead of our pipes so we've just all we've done here is just renamed it from pipe to pair and we've instead of pipe instead of pipes we're using pipe pairs we're doing the same exact thing here one on line 153 we've done 4k pair in pairs of pipe pairs and then line 150 sorry line 175 is where we are or sorry 170 is where we are rendering each pair instead of each pipe and so if we open up pipe pair here we can take a look at this class from scratch so it's a new class we're going to set our gap height to 90 pixels and so this is just some arbitrary value that i felt was like a pretty fair value in terms of size but you could tune this to whatever you want you could set this to if you want to be really cruel you could set it to something like 50 or if you wanted to be really generous to the player you could set it to something like 150 and make it fairly easy for them to get through or as part of the assignment you could randomize it so that the uh so that it varies uh you know pair by pair and you get more of an organic looking obstacle course still shifted by negative 20 to 20 pixels but now your gap varies and you can also randomize the shift amount if you wanted to as well let's say you wanted maybe the maybe you want the gaps to be up to 40 pixels difference instead of 20 pixels difference on negative and positive value you could easily do that as well on line 18 we're just setting our x to just like we did before virtual width plus 32 so we're setting it to the uh actually before we just set it to virtual width now we're setting it to virtual width plus 32 both are pretty much equal this will just give it a little bit of a delay before it ends up going onto the screen but you could effectively just do this virtual width the on the next line 24 this is where we sort of bundle together the pipes that we're going to end up actually rendering and updating to the screen instead of having just one pipe a pipe pairs two pipes we can easily put this together in a table so we'll just create self.pipes we'll set it to a table that has two keys upper and lower and the upper pipe is just a pipe and notice one thing is different about pipe now before it took no arguments it was just a regular pipe pipes had their own logic they set their own x and y they didn't need any sort of you know parameterization beyond that it was all taken care of for them randomly now they take a string so this top string means that this would be a top pipe so that means that if this pipe is a top pipe there's probably going to be logic and pipe that now checks to see whether it's top or bottom if it's top then we need to render it upside down we need to flip it along the y axis and then we're going to set it to self.y and recall that we set self.y we passed in self.y in main actually i'm not sure if i touched on that let's go back to main here so if we go to figure out where i actually instantiate by the pipes here on line 136 after we've calculated where we want the gap to be for this pipe pair we're going to go ahead and insert into pipe pairs a pipe pair at y why was the calculation um between we basically took the last um y value the last gap that we instantiated and then shifted it by some negative 20 to 20 pixels randomly and made sure it didn't go above or beyond above or below the edges of the screen the back and pipe pair we're going to go ahead and look at line 30 or sorry actually we'll let's take a look a little bit more closely here at line 26. so upper gets top and self.y that's where the gap is and that's the sprite's going to be flipped upon that value the lower value is going to be a shift of that so the lower sprite needs to spawn below the top pipe by the gap amount so that the two are sort of you know top to bottom but there needs to be that space between the two of them so we need to take that pipe shift it down and then draw the next pipe so we're going to take self.y plus pipe height plus gap height and that'll have the effect remember gap height was 90 pixels the pipe height is a result of sort of flipping the uh the y ax the y-axis and having to shift it down the actual um position so if we go back to um line 30. so this is a this is an interesting sort of uh illustration of what happens when you edit a table while you're iterating over a table and i'll show you this in detail shortly but basically on line 30 we're setting a flag called remove to false and what this is going to do is before we were just destroying the objects whenever it got past the edge of the screen we just destroyed it but if we're iterating over a table of values let's say a table of pipe pairs when you do a removal in most programming languages in lua when you do a removal of a table of a table value and it's non-indexed or it's non-keyed which means that it's indexed by you know numerical indices this will shift every other value down and so when you're iterating it and you shift everything down the value you are currently manipulating let's say it's equal to one if you remove that value you shift everything beyond it down by one but then you're going to increment up to two and you're skipping over what was previously just two and is now one so you see effectively skipping over one of your entries and that has buggy behavior and a lot of scenarios in this case it causes the graphics to sort of glitch a little bit because it doesn't apply a uh a pixel shift on one frame and so as pix as whenever a pipe gets removed and i can actually show this visually uh the first pipe left after that table of that pipe gets removed ends up moving a little bit to the right and so you get weird pipes shifting to the left of the bird on each frame so whenever you edit a table in place make sure not to delete while you're iterating over it it's going to cause buggy behavior and like i said i'll show you i'll illustrate this for you very shortly on line 36 we are performing the update logic now a pipe pair has two pipes each with their own render components and their own positions we we're using the code that we wrote before for pipe and we're going to try to expand upon it a little bit so we want to defer we still want to defer a lot of that code to uh the pipe class and we want to update the pipes based on um whether they we want to still keep track of their own x and their render functions and so we're going to see if uh basically if our pipe pair x is greater than negative pipe width which is the same exact logic that we're using before set our own x to um the that minus pipe speed times delta time which is the same operation we were doing before but we are also editing the x of our self.pipes lower and upper and this will allow us to on line 46 render the pipes just as we were doing before because they're getting their x values updated just as they were before so we're effectively deferring the render phase to our pipes and not really needing to add any additional logic for that in our code if we've made changes to pipe.lua as well so i'm going to go ahead and open up pipe here whoops and we've set the height and width of it as constants here so pipe height gets 288 and that happens to be about the size of the screen pipe width gets 70. on 31 we're setting self.orientation gets orientation notice our init function which was previously just empty it took no parameters now takes an orientation and it takes a y value the orientation is going to be going to allow us to say to ask basically is our code a top pipe or a bottom pipe and if it's top pipe we need to flip it draw it and shift it if it's a bottom pipe we're just going to draw normal and not perform any sort of fancy you know sprite flipping or anything like that down here on the render function is where this actually happens so on line 39 we're drawing the pipe image as usual at x but at y because when you flip a sprite it ends up completely uh flipping the white but it basically performs a mirror on it but it not at zero zero it it basically shifts it up by pipe height amount we need to keep track of that and draw it at self.y plus pipe height because if we draw it at just self.y because it's going to be mirrored and it's going to get shifted by pipe height amount it's going to be beyond the top edge of the screen and we we need to account for that account for the fact that we're flipping it on the y-axis and bring it down the code where we the question is where's the code where we flip it so that's actually here on this line on this condition we're saying if self.orientation is equal to top then we want to so the parameters here i'll comment this just for clarification it does uh and i'll show you here so this is zero we've added a few new parameters to our love.graphics.draw function 0 is rotation we're not going to rotate it at all this is the scale on the x axis so x scale and this is the scale on the y-axis so if we apply a scale operation of one it's the same thing as applying no scale like doing no scale at all it's just gonna draw it on the x-axis it's just gonna draw it normally but if it's top if where if this pipe has been set to an orientation of top we need to we're going to set the scale to negative one when you set a sprite its scale factor to negative one it flips it along that axis effectively and so that's how you get mirroring in most engines that allow you to sort of apply scale operations to 2d textures or 2d sprites a negative operation on an axis will mirror it on that axis and so that's what we're doing here so we're mirroring it um if it's a top pipe and we're also shifting its draw location as well because when we mirror it it's going to at 0 0 it's going to do the same it's going to basically draw the same exact thing but mirror it on the y-axis so it's going to need if we want to draw at a given location flipped like still draw it at 0 0 but have it be flipped we need to account for that flip and shift it downwards if that makes sense so that's essentially all that's involved there and i think that's pretty much all of the code so we have our pipes now that are being uh when it's flipped if it's a top pipe it's going to get drawn shifted it's going to have its other pipe shifted down by that amount and set to negative or set and increased uh its y-axis is going to be increased by the gap height so that it gets drawn you know 90 pixels however many pixels you want to set below that pipe so we're going to go into demonstrate this go up to 50 bird the actual repo now and the actual distro code i'm going to go into bird six and i'm going to run it whoops and now we have pipes that are actually rendering but we're missing a couple of important things foremost among them being that now we don't have we don't have collision detection yet so we can just fly through this course you know infinitely but notice that they're being shifted by you know random value between negative 20 and 20 pixels it looks more or less like it's being generated with some sort of goal in mind it's it's not you know haphazard it's not all over the place but you could easily find ways to tweak this such that you know maybe the gap height is some value between you know 60 and 120 and so you have easy and difficult pipes or maybe you have i think i'm so far below the screen that i can't even get back up anymore but uh oh okay i just that's physics error and when you when your value gets to a certain point i think that's actually uh what it's doing is actually overflowing the value and setting it to a negative or underflowing it and saying it to a negative value and then uh incrementing it because it's gotten so large but the uh but you could easily modulate parameters such as the width between the pipes as we saw on the diagram before or the or the height or even the speed at which they move and find ways to you know tune it to make gameplay that actually works for whatever goal you have in mind making it easier or more difficult and that's actually a topic that they talked about in that article that i linked to before where they generated levels programmatically and then tested them programmatically to determine what makes a level in flappy bird difficult or easy and so basically that's those are the parameters you need to sort of weigh as you're thinking of procedural generation and procedure generation ultimately is just taking values that you construct your scene with and just finding ways to just manipulate them randomly math.random some value and that's how you make random levels in a nutshell making good random levels is another question but uh he did he did there was a big controversy around this game back in 2013 but uh i didn't know if i read too much into that but i i was doing a little bit of a little bit of research and was reading about some of that stuff but i mean got to give them props for you know banking on that but yeah that's you know now we have pipe pairs that's arguably the most complex part of the program because going forward now as we get into collision and some more concepts collision is actually something that we touched on last week and it's all basically the same stuff so if we go into bird seven uh the next iteration of our application i'm going to go ahead and open up main.lua and then we're going to go to line 74. and um in order to test collision we don't we don't have scoring in place yet but we need some way to just to determine oh we we collided with the pipe we need some sort of feedback so what we're going to do is i've just decided we should just pause the game so once we once we collide with the pipe let's just pause instantly so we know immediately oh we collided with a pipe so i'm going to set some variable called scrolling at the top of the program in main to true it's going to we're scrolling we're going to start scrolling but when i don't want to scroll anymore when i want to pause the game this should get set to false so on line 120 if scrolling then do all of this update logic that we did before and then at the very end of that we're resetting our input table so we can still take input but no updates will take place if scrolling is set to false all of this stuff is within this if excuse me if scrolling then so very simple just encapsulate it all within some variable that we can turn on and off and then on 152 within that chunk of code that is being sort of contained within that if condition we're just doing a very simple iteration for each pipe it should be for l pair in pairs of uh oh no sorry for every pipe in the pairs of uh it's a nest it's a nested for loop in this case so basically within the the loop that looks over every single pair to update it we're doing another loop that's looping through with the pipes in that pair so it's only a loop of two iterations with the upper and the lower pipe we could just also say if bird collides with upper basically if pair dot upper pair dot lower or peridot pipes upper paired up pipes down lower but this is a little cleaner it's more scalable we can add more pipes if we want to even though it wouldn't happen but for every pipe in peridot pipes we have a function here that we haven't defined yet called bird collides so if bird collides pipe so it takes in a pipe so is going to return a true or false value we know that set scrolling to false so we collide scrolling set to false update logic is going to get shut off completely so we're going to have the effect of pause in the game we're going to go into bird.lua right now we're going to actually see how we implement this and it's going to look very familiar to what we did last week so in bird.lua this function here from 29 down to 45 it's just an aabb collision detection test that we did last week we're just checking to make sure any edges are you know right edge make sure that is to the left of the right edge of the second box bottom edge of box one should be our bottom edge of box one should be above bottom edge or top edge of box two if all these things hold true then return true else return false which means we have a collision and notice that i've shifted everything here by a couple of constant values does anybody have any instincts so why i'm saying self.x plus 2 instead of just self.x or self.with minus 4 why we're checking for that offset for the bird in this case when it is compared with the pipe it's not quite half it's a few pixels smaller do we know why we want to do this why we want to like we're basically shrinking the box why would we want to shrink the box so not quite so there isn't an actual gap between the drawing it's more of a question of how much do we want to frustrate how much do we want to frustrate our users right if if we're pixel perfect colliding with the pipes you know there's there's no give and take it's like you you collide and even if it might even look as if you're not even colliding with the pipe and you're still getting a collision your users are thinking well that's not fair that's really harsh we're shrinking our box so that you know even if they're just like a pixel off they'll still get a little bit of leeway and it'll be a little bit less strict in terms of the collision and this is a very common thing in games when you have characters whose sprites may not necessarily fill the entire box that you've allocated for them even though you're doing box collision just give your users a couple pixels deep however however many you want and they can overlap with whatever they're colliding with just a tiny bit before it actually triggers a true on the collision and it makes your game feel more forgiving and then also more fun as a result of that so that's why we have where instead of testing directly on x0 of that box we're testing x plus two and then self.with minus four because when we shift we add width to a plus two value we need minus four so that we get two off the right edge and same thing goes for the height and the y value and so this just performs aabb collision detection expects a pipe which means that we need to ensure that that pipe has an x and a y a width and a height which it does actually just a constant here we're just checking pipe width and pipe height we probably shouldn't do that it should be pipe dot width pipe dot height in that case because then this couldn't necessarily just be a pipe it could be anything in our scene that has a xy a width and a height it could be a general purpose collision and actually something you can also do if you wanted to is just write a function called collides that takes in two two things that you know have bounding boxes and will allow you to perform closing detection on anything in your scene any between any two entities that would be a more scalable way i guess of dealing with it rather than necessarily having it specifically defined as birds and pipes being the colliders but in this case this is the only thing we're really colliding with except from the ground but when you collab with the ground all you need to do is just check to see whether your y position plus uh your height has gone below the edge of the screen so any questions as to how that so the question was why did we add 2 and subtract 4 instead of just subtract 2 because when you add a because we're doing self.x plus two basically we're shifting the whole box essentially here in this part so self.x plus two brings the beginning of the box that we're that we're colliding with two pixels to the right but if we just do two pixels minus two then the boxes x right edge is still the right edge of the box we want it to be shifted inwards by two by two pixels because we've shifted at the start of our box the x position two pixels over we need to shift it four pixels inwards because that will give us the um that'll have the effect of our box having being uh two pixels uh into the right edge does that make sense okay so i think that's everything for bird seven um we're gonna go ahead and run bird seven now and recall if we hit a pipe we should instantly pause so balancing bouncing bouncing i'm gonna go through one pair of pipes here and then i'm gonna hit this one on purpose oh we paused and notice that we have a little bit of leeway we got a couple pixels there just to give us you know in case we accidentally uh and also it takes into consideration you could move because of your velocity a couple pixels beyond the uh necessarily the strict hard edge of what you're colliding with based on how many you know how many frames have passed and what you're dealt with you're basically essentially what your velocity is and what your position is in this case i think it looks like we're actually like three or four pixels above the edge because our velocity was so high because we jumped but as soon as it detected the collision as soon as we were on that frame where our position was such that we did trigger true for our collision detection it paused the game looping was set to false we no longer ran any update logic and this is our basic way of getting feedback about that however it's not particularly compelling gameplay wise and so we want to get into scoring before we get into scoring though and also associated with that different states of our game so if we get into scoring clearly we want to have a screen that tells us when we lost and how what our score was we should also probably have a title screen because we're just jumping right into the game gameplay we want a screen that lets us play through the game and um as we'll see in a little bit a screen that also gives some time to uh once we start the game to sort of count down sort of say oh three two one go rather than just oh go and oh i don't know what i'm doing i'm bewildered so this is a sort of a diagram that sort of models the state flow that we're going to be using in our program here our game we're going to assume that we start on some sort of title screen state so going left to right a title screen state will transition to the countdown state and then we can define however we want those transitions to be in this case let's just say we press enter title screen state goes to countdown state once countdown state has once the transition is triggered for that we should go to the play state and then once the transition triggers for play state we're going to go down to the score state and then score state should go back into countdown state and this models our entire applications flow uh you know sort of top to bottom left to right chronologically so let's go ahead and take a look at some code as how we're going to accomplish this last week i alluded to taking us and actually earlier in lecture us going from sort of this string based approach to keeping track of our state with if conditions to a class-based approach and that's what we're going to illustrate today so i'm going to go ahead and open up bird eight and in bird eight i'm going to go ahead and start with maine so in main on line 36 we're acquiring a new class called state machine and a few other classes that we're defining called base state play state and title screen state and these are the components of our state machine and they've now instead of being just blocks of code in our update function they're separate blocks separate modules that have their own logic their own update and render logic and we'll see that very shortly on line 78 if you go down here separate from that i'm also instantiating a bunch of fonts we did this last week so love.graphics.new font takes in a file and then a size i've created a few different fonts here because we have a few different ways of giving feedback to the user we want a small font for displaying you know uh press enter to start or something like that we want a medium font for to display the name of the game perhaps or i think actually flappy font's responsible for that medium font i think was for score huge font for our countdown we want a big font right in the middle of the screen that says three two one and then we start and then we're just going to start off by setting it to flappy font which is our gonna be our title font so nothing really new but uh the beginning of our ui so to speak on line 92 this is new and actually this is a demonstration of a type of naming convention you'll see often in game code bases we haven't used it yet but we will start using it in the future we prefix a global variable with a lowercase g this lets you know when you're digging through a bunch of files that oh this is a global variable okay so i should probably know um it's probably not defined in this module maybe it is but i know it's global other things you might see are lowercase m for member which means that this is a sort of a member uh function or a field of a class and you can instantly see it at a glance and know okay if i want to find the definition for this it looks like it's a member function so it's probably in this class here at some line you can easily find it and so in future lectures we'll be using more of this sort of g lowercase g for global variables that we use module to module in this case we're instantiating a state machine so we're using the class that we've that we will take a look at in a second the state machine takes in a table with keys that map to functions that will return our states so we can just call change some some value and it'll have in our state machine it'll basically reference that key in this table here and it'll call that function based on it'll basically set current uh the current state of that state machine to whatever state gets returned by the function at that key so in this case change is going to trigger return new title screen state and we're going to get the state machine is going to be set to the title screen effectively and we'll take a look at what the title screen looks like momentarily on line 96 yep we're changing to title screen on line 134 notice that we don't really have much update logic in this application anymore we're still updating the scrolls because this is behavior we want across all our states no matter what state we're in we want to make sure that our background and our ground scroll so that we have movement we don't need to duplicate this behavior state to state this is a global feature of our game so we're just keeping track of it here just as we would before but anything else in our game that needs to be updated can now be deferred to our state machine class and when we call g state machine update delta time it's going to look and see what's our current state and it's going to update that state and that's going to basically be that chunk that if chunk do this logic that we were doing from before last week when we had a sort of prim more primitive state machine line 46 same exact thing between the background and the ground because those will always render scene to scene we want to render our current active state using our state machine render function and so let's go ahead and just look briefly at our state machine library it's a very simple code it's actually taken from the book i alluded to earlier in the lecture how to make an rpg they give you the state machine which sort of um really cleanly i think handles a state transition basically takes an init and then a series of states sets it has a an empty uh class or empty table so all of these are just empty um if there is no this is a a thing you can do in lua which just lets you initialize a variable if it's not given a value in your function so self.states gets states or some value which means that if states is equal to like a falsie value is equal to nothing just set it to this empty table so it's just a shorthand for instead of saying you know if states equals nothing then set states to empty table self.current is just an empty class so our empty state so this is basically what a state is it's just a set of methods a render update enter and exit function that's a state and then you define all of the behavior in each of these functions and that compiles your state more or less our change function takes in a name and then also some optional parameters that we can use to enter that state um when we set the when we change the state set whatever or call the exit function of whatever state we're in so exit that state maybe your function needs you to de-allocate some memory set the current equal to taking that name and then call whatever function's there so it's going to return in that case we saw earlier it's going to return a new title screen state so that's going to be what current is with self.current we're going to then enter that state machine so we're going to call the enter function that we've defined there with whatever enter parameters we pass into change which are optional and then here state machine update just updates whatever the current state is and render updates whatever the current uh state is as well and so i'm going to start going a little bit quickly just because running short on time base state is a all it does is just implements empty methods so that you can just inherit this state and you can choose which methods you want to define without throwing any errors because it blindly will call all these functions not checking to see whether they're actually implemented and so this is a way for you to just quickly uh avoid a lot of boilerplate code essentially the title screen state here uh this is your way of with the class function class library just including everything that belongs to bay state so inheriting if you're familiar with other languages that use inheritance take an object copy everything from that object or that class put it into this one and then add new stuff to it that's basically what inheritance is we're inheriting from base state so it has all the functions base state has and then on top of that we're defining an update function so if we press enter return change the state machine the global state machine to the play state and then for the render we're just going to render 50 bird and press enter halfway in the middle of the screen and then the play state essentially to some basically what the play state is is all of the code that we ran before only now we're just putting it in the update function here and the render function here and making bird pipe pairs timer and last y member fields of this sort of state object so we'll go ahead and run this really fast and then we have uh this is our title screen state so we at the very beginning we change to title screen state all it does is render and then the scrolling behavior is throughout all classes all states so we'll see that no matter what once you press enter it'll trigger change to play which will return a play state and then now we're back where we were before and we're seeing the difference now and having a couple of different states so quickly i'll go through the score update so this is a little bit um more complicated than the last example but to summarize in uh bird oh sorry we're in bird nine so in bird nine if we go here we're going to go to main so notice that in main down where we define our state machine we're going to go ahead and also note that we require a new score state because now we want to display a score screen down on line 96 score gets a function where we return a score state object so now we can change to score and it'll return that state and we can define all the behavior within a score state that we need to display a score in pipe pair we have a new variable called self.scored set it to true or false we're going to set it to true if the bird has gone past the right edge of the of the pair of pipes that'll have the effect of us scoring a point effectively because all we need to do is just make sure the bird's gone past that pair of pipes because otherwise it'll have collided with it if it does go past it uh set it to true and then add a point to our score and in our play state we can see that we've added a point so if we go to uh our play state 26 is where we actually keep track of our score self.score gets zero in our play state we're going to go ahead and go down to line 56 so for every pair if it's not been scored yet because we don't need to calculate this if it's already been scored it should we should ignore it in terms of scoring once it's been scored if the x plus width is less than our bird.x meaning our bird is beyond the right edge of the pair of pipes increment our score and set that pair to true we will then thereafter because of this condition ignore it and we're also going to increment our score so it's going to be kept track of on 83 notice that if we're colliding with a pipe we should transition to our score state now so and we're also passing in scoregetself.score as a table because remember we can pass in parameters when we call change and this will be passed into our enter function in our state and then score is going to equal self.score we'll have access to the score within that score state we don't have to keep track of it as a global variable to see it in both locations 93 the same exact thing this is collision to check whether we've collided with the bottom of the screen if our y is greater than virtual height minus 15 do the exact same thing transition to the score state and pass it in our current score so another death condition and then 104 we're just going to set flappy font and then we're going to render our score at the top left of the screen at 8 8 and that'll have that effect and so lastly here our score state is pretty simple all it is is we're going to get from those parameters we passed in by a change self.score equals params.score we're going to when we press enter go back to play and then we're going to render you lost and the score which we have access to self.score and then press enter to play again changing fonts along the way and so if we go back to bird9 and then we run this notice that now we have a score in the top left and i'm going to get one point and then die and we go to our score screen now it just remember we passed score into it from uh our play state we passed it as parameters and then we can press enter again go back to playstate and when we fall to the ground we do it as well so we're just taking a look at how to add scoring to our game but what if we want to add a countdown screen maybe we want the users to be prompted three two one before the actual game starts you know throwing pipes at them give them the time to you know sort of get acclimated we're going to go ahead and take a look at how we might do this using another state very similar to the last example we're going to add a new state called countdown state which is shown here on line 38. we're also going to down in our state machine add a new key which returns one of the new countdown states just as before and then we're going to go ahead and take a look at our actual countdown state here so in our countdown state dot lua which is in our states folder as the others it includes from base it uh inherits from base state we have initialized the countdown time to 0.75 this is time in seconds one second is a little long so i made it 0.75 seconds we're going to initialize a count to three and a timer to zero the count is going to start it's going to use a timer once the countdown time has elapsed right here as this logic shows increase the timer once the timer has gone past countdown time we want to go ahead and set it to uh we're in a modulo by countdown time so loop it back to zero plus whatever amount beyond the count on time we went so that we have a smooth track of time we're going to set self.count minus itself by one so that we go three two one and then if our count is zero which means that we've gone all the way down in our account we're going to go ahead and use our state machine and change to the play state and here we're setting our font to a font that we've set huge font and then we're just to string a little function that takes a string or it takes a number converts to a string we're displaying self.count at 0 120 and then our ver it's printf so we're basically starting at 0 y120 virtual width alignment and then we're centering it so the one last piece of that that we need to change is in our title screen state instead of going straight to a play state here on line 15 we're going to a countdown state and what this has the effect of doing if we go into bird 10 is when we press enter notice that we're going 3 2 1 then going into our play state not just going straight into the place as before giving our user a little bit of time to sort of catch their breath and then if we die we go to our score state but once we press enter notice we're doing that as well so in our score state we also are changing to the countdown state so that was how to make a countdown state probably my favorite part of many of these examples and of this example as well is adding audio to our application music and sound effects which really sort of tie everything together so we're going to go ahead and take a look at this it's very simple very similar to what we learned last week when we even when we just did pong so in maine da lua of bird 11 which is what we're going to look at now we're going to take a look at a table of sounds that we've initialized on line 88 we've given them all keys jump explosion hurt score these are all sound effects that i've generated with the bfxer program that we used last week if you recall and then a music track that i found online on free sound which is free to use the link is here if curious uh just a nice sort of happy sound track uh that i found for this game on line 99 to 100 we're going to take do one additional step before we start the music we're going to set looping on that to true because in games that are sort of infinite like this we don't want our music to just go and then stop abruptly we want to you know have it loop so play it after set looping to true initially actually begin the play of that music outside of any of our states because it's going to be a global music track and then that's the music we also need sound effects so if we do in our if we look in our bird file here on line 45 which is where we have the logic for jumping we're also playing a sound of the jump sound effect that we've generated additionally in our play state if we take a look there we can go ahead and see in our states folder here go to playstate and take a look at line 58 this is where we score a point so we should play our score sound effect here simply put and then the same thing on line 80 to collide the sound effect here which is we're actually layering two sounds on top of each other which is a common thing to do in sound design and game design one sound often isn't all you need to accomplish a particular effect so i have an explosion sound which is kind of a white noise effect and then a hurt sound effect is kind of like a sort of like a downward uh like sine wave type of sound we're doing the exact same here on 95 to 96. once we put all these pieces together we're going to run bird 11. we get music [Music] we get a jump sound effect and when we score a point [Music] another sound effect and then if we hit a pipe notice that we have the sort of and a white noise there explosion effect layered together so that sort of brings everything together creatively and artistically as an exercise to the viewer in bird 12 in the github repo we have some code that allows you to actually add mouse clicks to the flappy bird in order to make it a little bit more like the actual game which was an ios game so it relied on taps the function that you might want to use is love.mouse pressed xy button and i would encourage you to think about how we took input and made it global in the context of the keyboard in one of our earlier examples so that we can call this sort of was the mouse just pressed in our bird.lua file as opposed to the as opposed to the main file and so next time we're going to be covering a few new concepts so sprite sheets so taking a large file of images and sort of taking out chunks of that so we don't have to have a million graphic files procedural layouts uh this will be in the context of the game breakout so we want to lay out uh all the bricks in our game sort of procedurally the way that in the sort of the same way that we've procedurally created a sort of pipe level in this game we'll be talking about separate levels and having them stored in memory as opposed to just one continuous level we'll be talking about health we'll be talking about particle systems which is spawning little mini graphics to accomplish various effects that are otherwise difficult to capture in a simple sprite animation a little bit fancier collision detection based on input so that we can sort of drive ball behavior the way we want to and then also persistent save data how can we take a high score and not have it refreshed to zero every time we run the application but rather save it to disk so that every time we run the program thereafter we can see what we've gotten scored in days past the first assignment or rather the second assignment assignment one is going to be a little bit more complicated than last week's but still fairly doable make pipe gaps slightly random being the first component of this so before pipe gap is set to a constant value maybe make it some sort of random value pipe intervals as well so we're spawning every two seconds maybe we want to change that up make pipes spawn a little differently a little more sporadically uh the more complicated aspect of this assignment is going to be awarding players a medal based on their performance so have like a maybe a bronze a silver and a gold medal an image that you display in the score screen in addition to just their score just to give them a little bit of personal feedback and sort of make them feel rewarded for their effort and make them strive to sort of get that last that last medal and then lastly you implement a pause feature which we talked about in class so that when you press for example to keep p the game will stop but unlike that example when we press p again the game should resume just as it was in its prior state so that'll be it uh for flappy bird i'll see you guys next time thanks a lot
Info
Channel: freeCodeCamp.org
Views: 49,076
Rating: undefined out of 5
Keywords: game development, game development for beginners, lua, LÖVE2D, love2d, pong, pong tutorial, game development tutrorial, harvard university, cs50, game programming, lua tutorial, lua tutorial for beginners, gd50, CSCI E-23a, CSCI E-23, harvard
Id: rBHusPevM5k
Channel Id: undefined
Length: 125min 24sec (7524 seconds)
Published: Fri Feb 01 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.