WEBVTT X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:900000 00:00:16.660 --> 00:00:18.000 COLTON OGDEN: Hi, everybody. 00:00:18.000 --> 00:00:21.740 My name is Colton Ogden, and this is GD50 lecture one, 00:00:21.740 --> 00:00:24.480 and today we'll be covering Flappy Bird. 00:00:24.480 --> 00:00:27.810 So last year or last week, sorry, we covered Pong, 00:00:27.810 --> 00:00:30.730 which was just basic shapes and colors. 00:00:30.730 --> 00:00:32.640 Today we'll actually be diving into sprites. 00:00:32.640 --> 00:00:35.010 As we can see here, we've got some pipes, and a bird, 00:00:35.010 --> 00:00:39.540 and we're covering a few other concepts such as gravity, and more. 00:00:39.540 --> 00:00:43.140 Today, the topics that we'll be covering are in a nutshell, images and sprites, 00:00:43.140 --> 00:00:43.860 as I just said. 00:00:43.860 --> 00:00:46.530 So loading images from memory from our hard drive, 00:00:46.530 --> 00:00:50.040 and actually drawing them to the screen instead of just rectangles, 00:00:50.040 --> 00:00:50.825 and whatnot. 00:00:50.825 --> 00:00:52.560 We'll be covering infinite scrolling. 00:00:52.560 --> 00:00:55.170 So seeing things like-- and if you've played the game, 00:00:55.170 --> 00:00:57.690 pipes are infinitely going from right to left. 00:00:57.690 --> 00:01:00.240 How to actually get that going infinitely so that we're not 00:01:00.240 --> 00:01:02.910 using up also infinite memory. 00:01:02.910 --> 00:01:05.489 We'll be discussing how games, and in the similar vein, 00:01:05.489 --> 00:01:10.710 are illusions in the sense that a lot of the perceived vastness and perceived 00:01:10.710 --> 00:01:14.520 complexity of games is often just due to camera trickery, 00:01:14.520 --> 00:01:17.650 and more because of limited hardware. 00:01:17.650 --> 00:01:19.860 We'll be covering procedural generation, which 00:01:19.860 --> 00:01:21.854 ties also into infinite scrolling. 00:01:21.854 --> 00:01:24.770 Procedure generation is a topic that I am actually very interested in, 00:01:24.770 --> 00:01:27.720 and will be touching on it throughout the course in several locations. 00:01:27.720 --> 00:01:32.190 But in the context of today's lecture, we'll be using it for the pipes 00:01:32.190 --> 00:01:34.966 because the pipes, they spawn from right to left in Flappy Bird 00:01:34.966 --> 00:01:36.840 as you're infinitely going through the level, 00:01:36.840 --> 00:01:39.900 but they can spawn at various heights, and the gaps 00:01:39.900 --> 00:01:44.640 are shifting as a result of that, therefore creating this infinite level. 00:01:44.640 --> 00:01:46.744 We'll be talking more in detail on state machines. 00:01:46.744 --> 00:01:49.410 So last week we covered state machines in a very abstract sense. 00:01:49.410 --> 00:01:53.040 We used just basically a string as a variable, and then used if conditions. 00:01:53.040 --> 00:01:55.170 Today we'll be actually using a state machine 00:01:55.170 --> 00:01:57.870 class replete with various methods that allow 00:01:57.870 --> 00:02:00.210 us to transition in and out of these states very 00:02:00.210 --> 00:02:02.700 cleanly, and allow us to break out all of this logic 00:02:02.700 --> 00:02:05.700 that we previously had in our update and render functions, 00:02:05.700 --> 00:02:09.600 and then put them separately into their own state classes. 00:02:09.600 --> 00:02:12.060 And then lastly, we'll also be touching on mouse input. 00:02:12.060 --> 00:02:15.010 And a point that I forgot to mention here, 00:02:15.010 --> 00:02:20.070 whoops, is also we'll be talking about music, which is just basically 00:02:20.070 --> 00:02:21.960 sound, which we did last week. 00:02:21.960 --> 00:02:24.369 But we'll add that as a polishing touch. 00:02:24.369 --> 00:02:26.160 If you guys want to download the demo code. 00:02:26.160 --> 00:02:31.410 We have a repo up right now on GitHub/games50/fiftybirds. 00:02:31.410 --> 00:02:34.440 It's our take on Flappy Bird. 00:02:34.440 --> 00:02:36.700 A couple of things, I've been asked a couple of times 00:02:36.700 --> 00:02:39.170 whether we have reading materials for the course. 00:02:39.170 --> 00:02:40.920 And there are no formal reading materials, 00:02:40.920 --> 00:02:42.961 but there are a couple of resources that I really 00:02:42.961 --> 00:02:46.890 enjoyed reading, especially as I was getting more into Lua and Love2D. 00:02:46.890 --> 00:02:48.720 They are two books. 00:02:48.720 --> 00:02:49.800 One is an online book. 00:02:49.800 --> 00:02:52.470 Actually, they're both online books, but the latter of which 00:02:52.470 --> 00:02:54.180 has a physical form as well. 00:02:54.180 --> 00:02:57.360 The first of these is How to Make an RPG by Dan Schoeller, which 00:02:57.360 --> 00:02:59.820 is actually completely written in Lua. 00:02:59.820 --> 00:03:03.330 He uses a custom game engine very similar to Love2D, 00:03:03.330 --> 00:03:04.910 but it's handwritten by him. 00:03:04.910 --> 00:03:07.629 But a lot of the same ideas apply, and it's a great opportunity. 00:03:07.629 --> 00:03:09.420 It's how I cut my teeth on Lua, and I would 00:03:09.420 --> 00:03:12.669 encourage you to take a look at that if that's something you're interested in, 00:03:12.669 --> 00:03:14.310 or if you like RPGs. 00:03:14.310 --> 00:03:17.070 And then also Game Programming Patterns by Robert Nystrom 00:03:17.070 --> 00:03:20.220 is a very great general purpose game development 00:03:20.220 --> 00:03:23.820 book that talks about a lot of the sort of more abstract high level concepts 00:03:23.820 --> 00:03:25.440 with large scale game development. 00:03:25.440 --> 00:03:27.006 But beyond that, no formal reading. 00:03:27.006 --> 00:03:28.380 Those aren't from reading either. 00:03:28.380 --> 00:03:29.730 Those are just if you're curious, and you 00:03:29.730 --> 00:03:32.146 want to read some resources that I found very interesting. 00:03:32.146 --> 00:03:33.300 Feel free to do so. 00:03:33.300 --> 00:03:36.660 Today's goal is to implement what looks like this. 00:03:36.660 --> 00:03:39.150 This is our version of Flappy Bird. 00:03:39.150 --> 00:03:41.790 We didn't use the same exact sprites for copyright purposes, 00:03:41.790 --> 00:03:45.640 but we note that we have a bird in the middle of the screen. 00:03:45.640 --> 00:03:49.410 This bird, on click, or on spacebar, will jump up and down, 00:03:49.410 --> 00:03:51.660 and your goal is to prevent the bird from touching 00:03:51.660 --> 00:03:55.380 either the pipes or the ground itself. 00:03:55.380 --> 00:03:58.920 Every time you make it past a pair of pipes, you will score a point. 00:03:58.920 --> 00:04:01.590 As soon as you touch a pipe or hit the ground, the game is over, 00:04:01.590 --> 00:04:03.700 and that's that. 00:04:03.700 --> 00:04:05.907 So today we'll be covering-- 00:04:05.907 --> 00:04:07.740 I'll be doing a little bit more live coding. 00:04:07.740 --> 00:04:09.750 So the very first example that I want to cover 00:04:09.750 --> 00:04:12.660 is the day zero update for Flappy Bird. 00:04:12.660 --> 00:04:15.540 And a important function that is going to be probably 00:04:15.540 --> 00:04:18.149 the most noticeable, the most visibly obvious function we'll 00:04:18.149 --> 00:04:23.010 be using throughout this lecture, is love.graphics.newImage, 00:04:23.010 --> 00:04:24.360 which takes a path. 00:04:24.360 --> 00:04:28.320 This function, all it does is load a image file from your disk. 00:04:28.320 --> 00:04:32.640 You specify it as a string, and you can then use it as an object, 00:04:32.640 --> 00:04:35.250 and draw anywhere you want at an xy coordinate, 00:04:35.250 --> 00:04:37.210 and we'll see this in practice here. 00:04:37.210 --> 00:04:38.550 So I'm going to go ahead. 00:04:38.550 --> 00:04:42.280 If you're looking in the repo, all of these examples are covered-- 00:04:42.280 --> 00:04:44.640 0 through 12. 00:04:44.640 --> 00:04:48.210 I'm going to start from scratch in a new folder that I've created. 00:04:48.210 --> 00:04:53.250 I'm going to create a brand new main.lua, completely fresh. 00:04:53.250 --> 00:04:57.000 And the first thing I want to do is because we 00:04:57.000 --> 00:04:59.970 are going to use a virtual resolution just like we did last week, 00:04:59.970 --> 00:05:01.860 so that we have a more rhetoric, I'm going 00:05:01.860 --> 00:05:03.568 to go ahead and require the push library. 00:05:03.568 --> 00:05:05.570 So push equals require. 00:05:05.570 --> 00:05:06.720 Push just like that. 00:05:06.720 --> 00:05:09.780 I've pre put push.lua into this directory. 00:05:09.780 --> 00:05:12.240 It'll just load by default in the same directory-- 00:05:12.240 --> 00:05:15.724 the current working directory of your script when you run Love. 00:05:15.724 --> 00:05:18.515 The next thing I'm going to do, I'm going to define some constants. 00:05:18.515 --> 00:05:23.910 So window with should be 1280, and then window height is going to be 720. 00:05:23.910 --> 00:05:26.100 Those are our physical window dimensions, 00:05:26.100 --> 00:05:30.960 but then we also need a virtual width, and we're going to use 512 by 288. 00:05:30.960 --> 00:05:35.040 This is a resolution that I found worked pretty well for the assets we'll 00:05:35.040 --> 00:05:38.970 be using today, but you can make this most anything you want to as long 00:05:38.970 --> 00:05:40.570 as it's somewhere in that range. 00:05:40.570 --> 00:05:43.260 It is a 16 by 9 resolution as well, so that it fits comfortably 00:05:43.260 --> 00:05:48.362 on modern wide screen 16 by 9 monitors. 00:05:48.362 --> 00:05:51.390 What we're going to do is the first goal that we have today 00:05:51.390 --> 00:05:55.530 is to draw two images to the screen. 00:05:55.530 --> 00:05:58.020 We want a foreground and a background because notice, 00:05:58.020 --> 00:06:01.540 if we go back to the slides, we can see in the very background 00:06:01.540 --> 00:06:08.700 we have a hill landscape, and then on the bottom we have a ground. 00:06:08.700 --> 00:06:12.150 The two of those are going to eventually scroll at different rates. 00:06:12.150 --> 00:06:14.396 It's going to be called parallax scrolling, but just 00:06:14.396 --> 00:06:16.770 for our very first example, we want something very basic. 00:06:16.770 --> 00:06:18.760 I just want to draw two images to the screen. 00:06:18.760 --> 00:06:22.500 So we're going to go ahead and do that here by setting a local variable. 00:06:22.500 --> 00:06:25.560 Remember, local means that it's just defined to the scope that 00:06:25.560 --> 00:06:29.310 it's in, rather than being global, which means we cannot access this variable 00:06:29.310 --> 00:06:31.590 outside of this file. 00:06:31.590 --> 00:06:34.895 Local background gets love.graphics.newImage, 00:06:34.895 --> 00:06:36.520 the function that we just talked about. 00:06:36.520 --> 00:06:40.821 Let me go ahead and hide this inspector here so we can have more room code. 00:06:40.821 --> 00:06:42.570 And then it's just going to take a string. 00:06:42.570 --> 00:06:44.536 So background.png. 00:06:44.536 --> 00:06:47.410 And I realize I actually didn't include those files in the directory. 00:06:47.410 --> 00:06:50.080 So I'm going to need to do that as well. 00:06:50.080 --> 00:06:52.225 Same thing for the ground, exact same function, 00:06:52.225 --> 00:06:55.590 love.graphics.newImage except ground.png. 00:06:55.590 --> 00:06:59.280 And before I forget, let's go ahead, and do that right now. 00:06:59.280 --> 00:07:02.055 I have the files here. 00:07:02.055 --> 00:07:03.660 So ground and background. 00:07:03.660 --> 00:07:09.150 We're going to copy those from the distro repo into my bird0 directory 00:07:09.150 --> 00:07:11.589 that I'm currently developing in right now. 00:07:11.589 --> 00:07:14.130 And as soon as we're done with that, we're going to go ahead, 00:07:14.130 --> 00:07:16.230 and we're going to define love.load, which 00:07:16.230 --> 00:07:20.500 is the function Love2D calls at the beginning of your program execution. 00:07:20.500 --> 00:07:25.560 In there, because we don't want these images to look blurry 00:07:25.560 --> 00:07:27.510 when they get loaded and upscaled, we want 00:07:27.510 --> 00:07:31.030 to go ahead and set our default filter to nearest on min and mag, 00:07:31.030 --> 00:07:34.950 which means on upscale and downscale, apply nearest neighbor filtering, which 00:07:34.950 --> 00:07:41.100 means no blurriness, no interpolation of the pixels. 00:07:41.100 --> 00:07:47.120 And then one thing that is just a small little touch, 00:07:47.120 --> 00:07:51.390 love.window.setTitle fifty bird because it's GD50, 00:07:51.390 --> 00:07:54.960 and then we're going to go ahead and set up our screen here 00:07:54.960 --> 00:08:01.670 with our virtual width, virtual height, window width, window height. 00:08:01.670 --> 00:08:05.730 It's getting a bit long, and then it takes in the table. 00:08:05.730 --> 00:08:09.180 Recall tables, just take in keys like so. 00:08:09.180 --> 00:08:14.750 Unlike in Python where you might use a colon, we use an equal sign in Love, 00:08:14.750 --> 00:08:16.820 or in Lua, I should say. 00:08:16.820 --> 00:08:23.740 Resizable to true, and that is the end of our load function. 00:08:23.740 --> 00:08:27.600 Now, does anybody recall how if we want to resize-- 00:08:27.600 --> 00:08:29.120 so notice I set resizable to true. 00:08:29.120 --> 00:08:34.169 Do we know how we can send a message to push to resize our screen for us? 00:08:37.240 --> 00:08:41.110 So Love2D to defines a function called love.resize, 00:08:41.110 --> 00:08:43.510 which takes in a width and a height. 00:08:43.510 --> 00:08:48.005 And in there, all we're going to do is defer that call to push. 00:08:48.005 --> 00:08:49.630 Recall the exact same function on push. 00:08:49.630 --> 00:08:51.463 It takes a width and a height, and that will 00:08:51.463 --> 00:08:55.390 take care of dynamically rescaling the canvas it uses internally. 00:08:55.390 --> 00:08:58.157 It renders to a texture, and it's going to render to the texture 00:08:58.157 --> 00:09:00.490 that we set as the virtual width and the virtual height, 00:09:00.490 --> 00:09:02.890 and it's going to scale it to fit our screen. 00:09:02.890 --> 00:09:05.320 And it needs to know our physical screen dimensions 00:09:05.320 --> 00:09:11.007 so that it can actually properly scale that internal canvas appropriately. 00:09:11.007 --> 00:09:14.090 Does anybody remember the function that we use to get input from the user? 00:09:17.380 --> 00:09:23.370 So function.love.keyPressed, recall, takes in a key. 00:09:23.370 --> 00:09:26.310 Love is going to call this automatically every time we press a key, 00:09:26.310 --> 00:09:27.555 and that's going to be-- 00:09:27.555 --> 00:09:31.170 and we're going to have access to that key, and we can do any sort of logic 00:09:31.170 --> 00:09:36.180 that we want on that key, using that key, 00:09:36.180 --> 00:09:38.550 and we're just going to call love.event.quit 00:09:38.550 --> 00:09:41.100 because I don't like to press Command Q or click the red x. 00:09:41.100 --> 00:09:44.380 I just want to hit escape, be done with it. 00:09:44.380 --> 00:09:47.130 And then what's our render-- what's Love's render function called? 00:09:49.810 --> 00:09:52.630 it's called love.draw. 00:09:52.630 --> 00:09:55.300 So call love.draw. 00:09:55.300 --> 00:09:58.210 And then because we're using push, does anybody 00:09:58.210 --> 00:10:01.180 remember what we need to actually do to get push to render 00:10:01.180 --> 00:10:03.321 our screen to a virtual resolution? 00:10:06.030 --> 00:10:11.200 So recall that there's actually two ways we can do it. 00:10:11.200 --> 00:10:16.950 We can call push start and push finish, which we didn't cover last week. 00:10:16.950 --> 00:10:22.150 Or we can call-- and that's actually the new de facto way to do it. 00:10:22.150 --> 00:10:27.660 Or we can do push apply start, which is the deprecated way to do it. 00:10:27.660 --> 00:10:31.790 But starting from here on out, we're going to call push start 00:10:31.790 --> 00:10:34.740 and push finish. 00:10:34.740 --> 00:10:37.110 And then last, we have our images. 00:10:37.110 --> 00:10:40.680 We've allocated them as objects up here. 00:10:40.680 --> 00:10:44.190 We have a background and a ground. 00:10:44.190 --> 00:10:47.230 All we need to do now is just draw them to the screen. 00:10:47.230 --> 00:10:49.470 So this is a new function. 00:10:49.470 --> 00:10:52.650 Or it's actually not a new function. 00:10:52.650 --> 00:10:55.080 It is a new function, actually. 00:10:55.080 --> 00:10:59.320 Love.graphics.rectangle is what we used last week for all of the draw calls. 00:10:59.320 --> 00:11:02.640 In this case, we want to draw an image object, 00:11:02.640 --> 00:11:04.660 a texture object that we have in memory. 00:11:04.660 --> 00:11:06.810 So we're going to call love.graphics.draw, 00:11:06.810 --> 00:11:09.240 and it takes a drawable, which means anything 00:11:09.240 --> 00:11:11.760 that Love has defined as something that can be drawn. 00:11:11.760 --> 00:11:13.860 In this case, images are drawables. 00:11:13.860 --> 00:11:18.396 They can be drawn, and they can be drawn at any given position that you specify. 00:11:18.396 --> 00:11:20.520 So if you wanted to draw it at the top left corner, 00:11:20.520 --> 00:11:24.400 we would just say love.graphics.drawBackground at 0,0, 00:11:24.400 --> 00:11:25.670 and it has that effect. 00:11:25.670 --> 00:11:30.437 And we're going to the exact same thing with our ground. 00:11:30.437 --> 00:11:32.520 The only difference being that obviously, we don't 00:11:32.520 --> 00:11:34.100 want to draw at the top of corner. 00:11:34.100 --> 00:11:35.980 We want to draw at the bottom of the screen. 00:11:35.980 --> 00:11:41.447 So we just call virtual height minus 16, which 00:11:41.447 --> 00:11:43.030 happens to be the height of our image. 00:11:43.030 --> 00:11:43.990 So if you run this-- 00:11:43.990 --> 00:11:48.690 I'm going to go ahead, and make sure I'm in the right directory. 00:11:48.690 --> 00:11:52.500 I'm not in the right directory, so I'm going to go into a directory I wrote, 00:11:52.500 --> 00:11:55.020 fifty bird scratch. 00:11:55.020 --> 00:11:56.830 Go into bird0. 00:11:56.830 --> 00:12:00.630 And if I run this, I should theoretically have just two images 00:12:00.630 --> 00:12:03.150 layered on top of each other, which I do not. 00:12:06.570 --> 00:12:11.070 So we just make sure that it gets saved. 00:12:11.070 --> 00:12:15.150 Remember to always save your work, and there you go. 00:12:15.150 --> 00:12:18.900 So all we're doing now, it looks infinitely better than last week 00:12:18.900 --> 00:12:21.242 already, but it's very simple, very few lines of code. 00:12:21.242 --> 00:12:22.950 All the effort that we've put into it has 00:12:22.950 --> 00:12:25.470 been in our sprite editor of choice, and you 00:12:25.470 --> 00:12:28.140 can use most any application you want to do this sort of stuff. 00:12:28.140 --> 00:12:30.480 I use a program called Aseprite, I like a lot, 00:12:30.480 --> 00:12:33.090 but you could do this in Gimp, which is free, you could do it in Photoshop, 00:12:33.090 --> 00:12:35.256 you could do it in Microsoft Paint if you wanted to. 00:12:37.660 --> 00:12:40.790 Godspeed if you do. 00:12:40.790 --> 00:12:43.900 But yeah, so that's as simple as it is just to draw images to the screen. 00:12:43.900 --> 00:12:48.000 So we've already made quite a lot of progress in a very short period of time 00:12:48.000 --> 00:12:50.280 in terms of the visual aspect of our game. 00:12:50.280 --> 00:12:53.889 But it's not interesting to look at beyond the initial sort of honeymoon 00:12:53.889 --> 00:12:55.680 period of now we have colors on the screen. 00:12:55.680 --> 00:13:01.320 We want to actually get scrolling because the game, recall, is 00:13:01.320 --> 00:13:02.130 a scrolling game. 00:13:02.130 --> 00:13:05.490 And actually, would anybody be willing to volunteer 00:13:05.490 --> 00:13:09.360 to come up, and play Flappy Bird just so we can see it live on the stage? 00:13:13.830 --> 00:13:15.390 David, you want to come up and play? 00:13:15.390 --> 00:13:19.110 Does someone volunteer? 00:13:19.110 --> 00:13:20.880 Stephen, you want to come up and play? 00:13:20.880 --> 00:13:23.360 STEPHEN: Sure. 00:13:23.360 --> 00:13:25.068 COLTON OGDEN: Thank you for volunteering. 00:13:30.690 --> 00:13:34.410 I guarantee you're better at this game than I am. 00:13:34.410 --> 00:13:41.670 I'm going to go ahead and cd into bird12 in the directory, which is 00:13:41.670 --> 00:13:43.735 the final version of the game complete. 00:13:43.735 --> 00:13:45.360 So I'm going to go ahead and hit Enter. 00:13:45.360 --> 00:13:47.220 [VIDEO GAME MUSIC PLAYING] 00:13:47.220 --> 00:13:52.200 So already, we can see the parallax scrolling 00:13:52.200 --> 00:13:54.504 that I referred to before, which is the floor 00:13:54.504 --> 00:13:56.670 and the background are scrolling at different rates, 00:13:56.670 --> 00:13:59.412 and we'll see this very shortly in the next example. 00:13:59.412 --> 00:14:00.120 We have a prompt. 00:14:00.120 --> 00:14:00.661 We have text. 00:14:00.661 --> 00:14:02.660 We've already used this before with the font. 00:14:02.660 --> 00:14:05.740 So go ahead, if you press Enter, you're going to get a count down. 00:14:05.740 --> 00:14:06.720 So space is to jump. 00:14:06.720 --> 00:14:08.507 [BEEPING] 00:14:08.507 --> 00:14:10.840 So we have our bird jumping in the middle of the screen. 00:14:10.840 --> 00:14:12.255 We have a score at the top. 00:14:12.255 --> 00:14:15.101 The goal is to avoid hitting the pipes. 00:14:15.101 --> 00:14:16.412 [CRASH] 00:14:16.412 --> 00:14:17.470 OK, a score of one. 00:14:17.470 --> 00:14:18.386 You want to try again? 00:14:18.386 --> 00:14:20.012 Go ahead. 00:14:20.012 --> 00:14:21.988 [BEEPING] 00:14:23.470 --> 00:14:25.990 So it keeps track of his position, and every time 00:14:25.990 --> 00:14:29.380 he gets past the right edge of a pair of pipes, as you can see, 00:14:29.380 --> 00:14:32.710 that's when he gets a point. 00:14:32.710 --> 00:14:37.149 So if you recall from last week, what do we think is-- 00:14:37.149 --> 00:14:38.440 what's detecting the collision? 00:14:38.440 --> 00:14:40.430 If we remember last week, what's the term? 00:14:40.430 --> 00:14:42.580 Anybody remember? 00:14:42.580 --> 00:14:46.654 Axis aligned bound aabb collision detection, axis-aligned bounding box. 00:14:46.654 --> 00:14:49.570 It's the same thing that we did with Pong, except now we're doing it-- 00:14:49.570 --> 00:14:52.240 we have graphics, but it's the same exact concept. 00:14:52.240 --> 00:14:54.000 We're just using rectangles. 00:14:54.000 --> 00:14:58.690 And when one rectangle overlaps with another rectangle, we trigger death. 00:15:02.155 --> 00:15:04.585 So one last iteration I think, and then we'll. 00:15:04.585 --> 00:15:06.590 [CRASH] 00:15:06.590 --> 00:15:09.110 We'll let you try one more time. 00:15:09.110 --> 00:15:09.980 Go ahead. 00:15:09.980 --> 00:15:11.410 I'll give it a shot. 00:15:11.410 --> 00:15:12.720 I'm going to lose on purpose. 00:15:12.720 --> 00:15:15.230 OK, here we go. 00:15:15.230 --> 00:15:18.440 To be unfair, I got plenty of practice when I was developing this, 00:15:18.440 --> 00:15:20.830 but we'll see if that actually holds true here. 00:15:23.455 --> 00:15:25.730 So notice also, the pipes-- 00:15:25.730 --> 00:15:27.870 the procedure generation that I-- oh, I lost. 00:15:27.870 --> 00:15:29.050 Three points. 00:15:29.050 --> 00:15:31.820 Let me explain a little bit more, while I do one more iteration. 00:15:31.820 --> 00:15:34.362 But the pipes themselves, every time we start, 00:15:34.362 --> 00:15:36.070 they're spawning at a different location. 00:15:36.070 --> 00:15:39.700 This is proceeded generation in pretty much the most simplest 00:15:39.700 --> 00:15:45.160 way possible, and notice that the pipes are shifting gradually. 00:15:45.160 --> 00:15:48.370 So this is sort of the make up of our level, 00:15:48.370 --> 00:15:51.910 and it's just generating bit by bit do to some simple algorithm 00:15:51.910 --> 00:15:54.670 that we have that just says hey, spawn another pipe here, shift it 00:15:54.670 --> 00:15:55.840 by some amount. 00:15:55.840 --> 00:16:00.340 And this very simple approach allows us to have an infinite level over and over 00:16:00.340 --> 00:16:01.760 again, and it's very efficient. 00:16:01.760 --> 00:16:05.665 We only ever have as many pipes on the screen, and as we'll see soon-- 00:16:05.665 --> 00:16:09.362 we'll only have as many pipes in memory as we can see on the screen 00:16:09.362 --> 00:16:12.070 at one time, despite the fact that this level could theoretically 00:16:12.070 --> 00:16:16.460 go on infinitely, and so it's very cost efficient. 00:16:16.460 --> 00:16:18.250 So bird1 is the example. 00:16:18.250 --> 00:16:19.420 It's the parallax update. 00:16:19.420 --> 00:16:23.410 So parallax scrolling is an important concept in 2D 00:16:23.410 --> 00:16:26.160 and also 3D, but 2D game development. 00:16:26.160 --> 00:16:31.750 It refers to the illusion of movement given two frames of reference 00:16:31.750 --> 00:16:33.680 that are moving at different rates. 00:16:33.680 --> 00:16:37.120 So if you're driving on the highway, and you see a fence next to you, 00:16:37.120 --> 00:16:39.160 and you see mountains in the distance, you're 00:16:39.160 --> 00:16:41.857 observing parallax scroll by seeing how fast the fence moves 00:16:41.857 --> 00:16:42.940 relative to the mountains. 00:16:42.940 --> 00:16:46.330 The mountains are going to move a lot more slowly than the fences right 00:16:46.330 --> 00:16:47.170 next to you. 00:16:47.170 --> 00:16:50.140 And we accomplish the same exact illusion in our game 00:16:50.140 --> 00:16:54.310 by using this sort of graphical illusion. 00:16:54.310 --> 00:16:56.860 And so I'm going to go ahead in my directory 00:16:56.860 --> 00:17:02.380 here, in bird1, which is an unpopulated-- 00:17:02.380 --> 00:17:07.329 it's populated with the contents of bird0, the complete contents of bird0. 00:17:07.329 --> 00:17:10.490 The version that you'll see will have all of the code, 00:17:10.490 --> 00:17:16.700 but I'm going to go ahead, and if we run bird0 in that directory. 00:17:16.700 --> 00:17:19.900 So I think right now I'm still in the full distro. 00:17:19.900 --> 00:17:23.355 So let me go ahead, go into fifty bird scratch again. 00:17:26.609 --> 00:17:27.400 Whoops, where am I? 00:17:31.970 --> 00:17:34.562 And then I'm going to go into bird1, and run it, 00:17:34.562 --> 00:17:36.770 and I get the exact same image that we had last time. 00:17:36.770 --> 00:17:40.030 So everything is there from before, just two images, nothing moving, 00:17:40.030 --> 00:17:41.980 no parallax that we can observe. 00:17:41.980 --> 00:17:44.140 I'm going to go ahead, and start implementing 00:17:44.140 --> 00:17:47.540 the basics of this parallax. 00:17:47.540 --> 00:17:51.040 So if I go ahead in my main. 00:17:51.040 --> 00:17:55.810 So I'm going to go down here to where we have our background. 00:17:55.810 --> 00:17:58.580 So we need a couple of new things. 00:17:58.580 --> 00:18:00.400 So along with our background image, we need 00:18:00.400 --> 00:18:02.890 to keep track of how much it's scrolled because we're 00:18:02.890 --> 00:18:04.780 going to need to start drawing this image to the screen, 00:18:04.780 --> 00:18:06.580 but if we're going to scroll it, that means 00:18:06.580 --> 00:18:08.891 that we need to shift its x offset. 00:18:08.891 --> 00:18:11.140 Instead of drawing it at 0,0, if we want it to scroll, 00:18:11.140 --> 00:18:15.190 we have to draw it at some negative value instead. 00:18:15.190 --> 00:18:18.680 Over time, this will have the effect of it moving right to left. 00:18:18.680 --> 00:18:20.680 So I'm going to go ahead, and keep track-- 00:18:20.680 --> 00:18:22.570 I'm going to use a variable to keep track of the scroll 00:18:22.570 --> 00:18:24.550 now, for both of these images, and we're just 00:18:24.550 --> 00:18:27.549 going to call them backgroundScroll and groundScroll, and set them to 0. 00:18:27.549 --> 00:18:30.370 So this is going to have the effect of no x offset. 00:18:30.370 --> 00:18:34.780 So I could use this variable right now in this draw call down here, 00:18:34.780 --> 00:18:36.110 which I'm actually going to do. 00:18:36.110 --> 00:18:38.180 I'm going to go ahead, and go to-- 00:18:38.180 --> 00:18:40.460 I'm just going to find out if that is correct. 00:18:40.460 --> 00:18:46.681 I'm going to go ahead and set that to negative backgroundScroll. 00:18:49.330 --> 00:18:53.170 And here, I'm going to set this to negative groundScroll. 00:18:53.170 --> 00:18:55.160 So this is not going to change anything yet. 00:18:55.160 --> 00:18:57.701 It's going to be the exact same thing because they're both 0. 00:18:57.701 --> 00:19:02.870 They were 0 before, but we're going to change them over time. 00:19:02.870 --> 00:19:14.110 And in order to do this, I'm going to go ahead, and go into up here. 00:19:14.110 --> 00:19:17.600 One thing before we do that actually, we need to set a speed for this. 00:19:17.600 --> 00:19:19.660 This is going to happen over time, but since they 00:19:19.660 --> 00:19:21.580 need to occur at different rates, the background 00:19:21.580 --> 00:19:23.580 needs to go at a slower rate than the foreground 00:19:23.580 --> 00:19:28.300 so that we do get this parallax effect, we need to separate speed variables. 00:19:28.300 --> 00:19:32.500 Generally, the norm for something that is not going to change 00:19:32.500 --> 00:19:34.690 is to write it in caps with underscores. 00:19:34.690 --> 00:19:35.900 This is constant notation. 00:19:35.900 --> 00:19:38.350 This is frequently seen in most programming languages. 00:19:38.350 --> 00:19:39.102 We'll use it here. 00:19:39.102 --> 00:19:41.560 I'm going to set a variable called BACKGROUND_SCROLL_SPEED, 00:19:41.560 --> 00:19:44.130 and I'm just going to set that to 30. 00:19:44.130 --> 00:19:48.580 I'm going to do the same thing, GROUND_SCROLL_SPEED. 00:19:48.580 --> 00:19:54.520 Does this need to be higher or lower than the BACKGROUND_SCROLL_SPEED? 00:19:54.520 --> 00:19:56.280 The ground is going to move-- 00:19:56.280 --> 00:19:59.341 so the background needs to move slower than the ground does. 00:19:59.341 --> 00:20:00.590 So this is going to be higher. 00:20:00.590 --> 00:20:01.930 So we're just going to set it to 60. 00:20:01.930 --> 00:20:03.850 You can set it to whatever you want to get the effect that you want, 00:20:03.850 --> 00:20:05.680 but this will already be quite noticeable. 00:20:05.680 --> 00:20:09.670 The ground is going to move twice as fast as the background. 00:20:09.670 --> 00:20:14.590 And so what we're going to do also is if we 00:20:14.590 --> 00:20:19.180 just-- so what's going to happen if we just let our image scroll infinitely? 00:20:19.180 --> 00:20:21.147 What's going to happen at a certain point? 00:20:21.147 --> 00:20:22.460 AUDIENCE: Run out of image? 00:20:22.460 --> 00:20:24.168 COLTON OGDEN: It's going run at an image. 00:20:24.168 --> 00:20:26.621 So how do we fix this problem? 00:20:26.621 --> 00:20:27.370 AUDIENCE: Loop it. 00:20:27.370 --> 00:20:28.894 COLTON OGDEN: Loop it, exactly. 00:20:28.894 --> 00:20:31.060 So we're going to go ahead, and set a looping point. 00:20:31.060 --> 00:20:35.230 So another constant background looping point, 00:20:35.230 --> 00:20:39.340 and we're going to set this to 413, which you kind of have 00:20:39.340 --> 00:20:43.330 to look at your image, and determine-- you sort of have to set your images up, 00:20:43.330 --> 00:20:47.050 if you want to achieve this effect, by having them be a looping image. 00:20:47.050 --> 00:20:51.040 So have either two copies of the exact same thing that's your screen width, 00:20:51.040 --> 00:20:53.226 or just copy the same chunk over, and over again. 00:20:53.226 --> 00:20:54.350 There's many ways to do it. 00:20:54.350 --> 00:20:57.520 In this case, the looping point of the image of our background 00:20:57.520 --> 00:20:59.100 is 413 on the x-axis. 00:20:59.100 --> 00:21:02.307 So we're going to set that to 413. 00:21:02.307 --> 00:21:05.140 And then we're going to go ahead-- the next step is we actually have 00:21:05.140 --> 00:21:07.130 to start changing the value values. 00:21:07.130 --> 00:21:09.820 So in our update function, which is where 00:21:09.820 --> 00:21:12.820 this is going to happen, I'm going to go ahead, 00:21:12.820 --> 00:21:16.270 and define love.update, which recall, Love2D will call for you, 00:21:16.270 --> 00:21:19.240 but you must define it yourself. 00:21:19.240 --> 00:21:22.325 I'm going to go ahead, and set backgroundScroll too. 00:21:32.366 --> 00:21:34.240 So what this is going to do, backgroundScroll 00:21:34.240 --> 00:21:40.140 gets backgroundScroll to itself plus the speed we set before times delta time. 00:21:40.140 --> 00:21:42.035 So it stays frame rate independent. 00:21:45.020 --> 00:21:48.400 That will have the effect of adding the speed to our image, 00:21:48.400 --> 00:21:49.420 but we need to reset it. 00:21:49.420 --> 00:21:51.070 We need to actually perform the reset. 00:21:51.070 --> 00:21:54.730 And to do that, we'll just be using Modulus, which recall 00:21:54.730 --> 00:21:59.860 from languages like C, simply divides-- 00:21:59.860 --> 00:22:04.700 basically, sets that value to the remainder of that division. 00:22:04.700 --> 00:22:10.990 So in this case-- so 10 modulo 5 would be 0, 00:22:10.990 --> 00:22:18.040 but 10 modulo 9 would be 1, effectively, because we have 0 00:22:18.040 --> 00:22:20.050 left over once we divide 10 by 5. 00:22:20.050 --> 00:22:22.690 We have 1 left over once we divide 10 by 9. 00:22:26.240 --> 00:22:30.950 So I apologize if that concept is not new. 00:22:30.950 --> 00:22:40.930 But we're going to do the same exact thing for our ground, 00:22:40.930 --> 00:22:47.500 only we're going to modulo by our virtual width in this case. 00:22:47.500 --> 00:22:48.880 I did not set a looping point. 00:22:48.880 --> 00:22:52.250 I do in later examples, but our ground image is very-- 00:22:52.250 --> 00:22:54.280 it's consistent enough such that you don't even 00:22:54.280 --> 00:22:57.984 notice it when it loops without just using the virtual with. 00:22:57.984 --> 00:23:00.400 So we're just going to use the virtual width in that case. 00:23:00.400 --> 00:23:05.260 It's very patterned, and very small. 00:23:05.260 --> 00:23:09.550 And aside from that, we already have the background scrolls here 00:23:09.550 --> 00:23:11.840 in our draw functions. 00:23:11.840 --> 00:23:13.990 So when we run this code, we should theoretically 00:23:13.990 --> 00:23:17.010 have scrolling background. 00:23:17.010 --> 00:23:20.440 AUDIENCE: So does it even just have to be twice the width 00:23:20.440 --> 00:23:21.910 or something so they don't run out? 00:23:21.910 --> 00:23:24.118 COLTON OGDEN: They do, at least twice the width, yes. 00:23:24.118 --> 00:23:27.280 There's ways you could effectively tile your image, 00:23:27.280 --> 00:23:30.180 and do it that way to save memory on texture size. 00:23:30.180 --> 00:23:32.942 If you have maybe something that's a quarter of the screen size 00:23:32.942 --> 00:23:34.900 that you want to loop over, and over again, you 00:23:34.900 --> 00:23:36.775 don't want to have that as one big image, 00:23:36.775 --> 00:23:39.400 you'll just draw four copies of that image to fill your screen, 00:23:39.400 --> 00:23:40.694 and then to shift all of them. 00:23:40.694 --> 00:23:42.610 Maybe five, actually, so you have a little bit 00:23:42.610 --> 00:23:48.272 beyond the edge of the screen, and then just put all of them back to 0. 00:23:48.272 --> 00:23:51.218 AUDIENCE: So the bottom one, the ground is-- 00:23:51.218 --> 00:23:56.610 you wouldn't know if you just restarted showing the image with the larger 00:23:56.610 --> 00:23:57.110 background. 00:23:57.110 --> 00:24:00.547 You wouldn't have to worry about the mountain getting cut in half 00:24:00.547 --> 00:24:02.040 when you replaced the right-- 00:24:02.040 --> 00:24:03.370 COLTON OGDEN: Exactly. 00:24:03.370 --> 00:24:05.144 So we could actually-- 00:24:05.144 --> 00:24:07.310 I could show you right now what that will look like. 00:24:07.310 --> 00:24:10.330 So if we just take out the looping point here, 00:24:10.330 --> 00:24:15.390 or we set it to some value that's completely inaccurate like 270, 00:24:15.390 --> 00:24:24.670 and then we run it, after a while it should just cut. 00:24:24.670 --> 00:24:26.480 Yeah, right there. 00:24:26.480 --> 00:24:28.686 AUDIENCE: So are you drawing it twice, really? 00:24:28.686 --> 00:24:31.060 Like one after another one when it runs out or something? 00:24:31.060 --> 00:24:34.420 COLTON OGDEN: No, the image is so wide that it always will fill the screen, 00:24:34.420 --> 00:24:38.940 even after it's been set back to-- even after it's gone past the looping point. 00:24:38.940 --> 00:24:41.080 I forget how large the texture is. 00:24:41.080 --> 00:24:44.102 It is 1157 pixels wide. 00:24:44.102 --> 00:24:45.810 So it's more than twice the screen width. 00:24:45.810 --> 00:24:48.257 Actually, I think it is exactly twice the screen width. 00:24:48.257 --> 00:24:50.090 No, it's not exactly twice the screen width, 00:24:50.090 --> 00:24:51.820 but it's more than twice the screen width 00:24:51.820 --> 00:24:54.820 so that when the amount-- the 413 pixels has elapsed, 00:24:54.820 --> 00:24:59.410 it's still plenty past the right edge of the screen, and the looping part, 00:24:59.410 --> 00:25:02.420 it'll be the exact same appearance on the texture, 00:25:02.420 --> 00:25:06.190 but it's completely been shifted back to the right. 00:25:06.190 --> 00:25:10.150 The 0,0 of our image is now at 0,0 in our screen space. 00:25:10.150 --> 00:25:13.690 AUDIENCE: So the looping is just reloading [INAUDIBLE]?? 00:25:13.690 --> 00:25:16.660 COLTON OGDEN: Your image is here, moving, and then just instantly 00:25:16.660 --> 00:25:20.340 back to the beginning, and then moving back to-- 00:25:20.340 --> 00:25:23.470 the setting it back to 0 or technically, how many 00:25:23.470 --> 00:25:28.190 pixels it's gone past the edge of the screen because using modulo. 00:25:28.190 --> 00:25:29.606 AUDIENCE: So it's just one image. 00:25:29.606 --> 00:25:32.147 It's like you just instantaneously flip it at the right time. 00:25:32.147 --> 00:25:33.130 COLTON OGDEN: Yeah. 00:25:33.130 --> 00:25:33.940 It's a translation. 00:25:33.940 --> 00:25:34.900 It's an instant translation. 00:25:34.900 --> 00:25:36.010 It takes place over one frame. 00:25:36.010 --> 00:25:36.968 So you don't notice it. 00:25:36.968 --> 00:25:40.370 Your human eye can't see it because it literally happens in one frame. 00:25:40.370 --> 00:25:43.780 The image data is the exact same at those two points 00:25:43.780 --> 00:25:45.010 because we have a texture. 00:25:45.010 --> 00:25:48.122 We've pre-created a texture that has the exact same data 00:25:48.122 --> 00:25:49.330 so that you have that effect. 00:25:49.330 --> 00:25:53.560 You have to have a texture that allows you to do this, or smartly 00:25:53.560 --> 00:25:55.390 draw four of the same images. 00:25:55.390 --> 00:25:57.100 Keep track of all four of them-- 00:25:57.100 --> 00:26:00.130 or actually, eight of them-- so you can move them to the left, 00:26:00.130 --> 00:26:03.020 and then shift them all back to the right. 00:26:03.020 --> 00:26:05.445 AUDIENCE: I assume when we do Super Mario Bros., 00:26:05.445 --> 00:26:11.032 we're going to have multiple images that get stacked one after another. 00:26:11.032 --> 00:26:12.990 COLTON OGDEN: When we get to Super Mario Bros., 00:26:12.990 --> 00:26:15.750 we'll be talking about a concept called tile mapping, which 00:26:15.750 --> 00:26:19.830 is where we take a sprite sheet, and then you basically chop it up 00:26:19.830 --> 00:26:25.740 into pieces, have a map that is basically numerical so that a brick is 00:26:25.740 --> 00:26:28.950 value one, and then you look through this giant two dimensional array 00:26:28.950 --> 00:26:31.740 that you have, and then go over it, iterate over it, 00:26:31.740 --> 00:26:35.945 and then draw a tile at an offset based on your index into that map. 00:26:35.945 --> 00:26:38.820 So it's a little bit more complicated, and actually a lot more memory 00:26:38.820 --> 00:26:43.316 efficient, but slightly different implementation. 00:26:48.740 --> 00:26:53.740 OK, so we have parallax scrolling now. 00:26:53.740 --> 00:26:55.420 I want to take a moment to-- 00:26:55.420 --> 00:26:58.510 because we've touched on-- 00:26:58.510 --> 00:27:01.930 this is a very introductory way of demonstrating that games are illusions, 00:27:01.930 --> 00:27:03.160 by using parallax scrolling. 00:27:03.160 --> 00:27:07.084 All we've done, really, is just set two things to scroll at different rates, 00:27:07.084 --> 00:27:09.500 and this has made us feel like we have depth in our scene, 00:27:09.500 --> 00:27:11.416 but all we're doing, we have two images, we're 00:27:11.416 --> 00:27:13.780 scrolling them at different rates. 00:27:13.780 --> 00:27:15.790 But this is a common theme in game development, 00:27:15.790 --> 00:27:20.530 is trying to devise a scene that maybe is very elaborate, 00:27:20.530 --> 00:27:24.790 but doing it on very resource intensive devices like your iPhone, 00:27:24.790 --> 00:27:27.520 or like an old console like the Nintendo 64. 00:27:27.520 --> 00:27:34.672 These illusions are all over the place, and a YouTube channel 00:27:34.672 --> 00:27:37.830 that I recently found that I really like is 00:27:37.830 --> 00:27:41.320 it's called-- the name of the channel is She Says, but the actual show that they 00:27:41.320 --> 00:27:42.910 have is called Boundary Break. 00:27:42.910 --> 00:27:46.670 And what they do is they take a camera that 00:27:46.670 --> 00:27:49.060 goes beyond what the game developers allowed it to do, 00:27:49.060 --> 00:27:52.729 which they basically hack the game camera so you can see in places where 00:27:52.729 --> 00:27:54.520 you weren't supposed to see before, and you 00:27:54.520 --> 00:27:57.314 can see a lot of really cool trickery. 00:27:57.314 --> 00:27:59.230 I'm about to show you a couple of video clips, 00:27:59.230 --> 00:28:02.480 but here's the YouTube URL if you're curious to see the exact video. 00:28:02.480 --> 00:28:04.940 It's about a 33 minute video. 00:28:04.940 --> 00:28:08.440 It's on Zelda, Ocarina of Time for the N64. 00:28:08.440 --> 00:28:12.040 And I extracted a couple of particularly noteworthy clips 00:28:12.040 --> 00:28:15.127 that I thought were kind of interesting, and also humorous. 00:28:15.127 --> 00:28:16.960 I'm going to go ahead and show the clip now. 00:28:16.960 --> 00:28:21.160 So if we could dim the lights, I'll go ahead and start. 00:28:21.160 --> 00:28:23.802 This is the first example. 00:28:23.802 --> 00:28:26.260 SPEAKER 1: OK, so there's a lot to talk about with the shop 00:28:26.260 --> 00:28:27.627 owners in the Ocarina of Time. 00:28:27.627 --> 00:28:30.210 So I'm going to just condense it down to the most interesting, 00:28:30.210 --> 00:28:32.001 and the first one we're going to talk about 00:28:32.001 --> 00:28:34.090 is the bizarre shop owner in Hyrule. 00:28:34.090 --> 00:28:38.980 Now, in Majora's Mask, the very same character is actually shown with legs, 00:28:38.980 --> 00:28:42.430 but in Ocarina of Time, he did not have those. 00:28:42.430 --> 00:28:46.950 In fact, he looks extremely hilarious without his legs. 00:28:50.530 --> 00:28:52.445 COLTON OGDEN: So this is a-- 00:28:52.445 --> 00:28:55.570 does anybody have an instinct as to why they might have done this this way? 00:28:55.570 --> 00:28:57.670 AUDIENCE: You're not going to see it anyway. 00:28:57.670 --> 00:29:01.685 COLTON OGDEN: Exactly, and beyond that, also just saving on memory. 00:29:01.685 --> 00:29:04.810 Not having to load a character model-- the vertices and textures associated 00:29:04.810 --> 00:29:05.680 with it-- 00:29:05.680 --> 00:29:08.830 on such a memory constrained device like the N64. 00:29:08.830 --> 00:29:10.510 I forget how much memory it had. 00:29:10.510 --> 00:29:13.790 Like four megabytes of memory, I think less than that. 00:29:13.790 --> 00:29:16.540 And so they are obviously cutting however many corners they could. 00:29:16.540 --> 00:29:19.690 In this case, by literally using the illusion 00:29:19.690 --> 00:29:23.470 of looking-- not the illusion, but just the fact that you only could see over 00:29:23.470 --> 00:29:26.890 the counter, and giving you the illusion that there's a fully living, 00:29:26.890 --> 00:29:30.000 talking shop keeper there, but it's just half a model. 00:29:30.000 --> 00:29:35.260 And other example here is more to show how Ocarina of Time 00:29:35.260 --> 00:29:39.550 used the N64's limited memory to give you 00:29:39.550 --> 00:29:43.250 the sense of being in a very large level when you might not actually have been. 00:29:43.250 --> 00:29:46.400 So if we could dim the lights one time, we'll go ahead and show this. 00:29:46.400 --> 00:29:48.850 SPEAKER 1: So this one was apparently a hot suggestion, 00:29:48.850 --> 00:29:50.570 [CHUCKLES] 00:29:51.860 --> 00:29:56.340 which is free camera on Death Mountain, including our friend, Big Goran. 00:29:56.340 --> 00:29:59.920 The smoke halo looks sort of weird against the black sky, 00:29:59.920 --> 00:30:02.440 and here you can see Nintendo fooled us. 00:30:02.440 --> 00:30:05.430 It's not full mountain, only the cliff face is actually rendered, 00:30:05.430 --> 00:30:09.000 and that's the path leading towards the Fire Temple. 00:30:09.000 --> 00:30:13.060 And if we zoom out, we can see the scale of the whole mop. 00:30:13.060 --> 00:30:15.240 Bigger than I thought it would be actually. 00:30:15.240 --> 00:30:19.990 The battle music's not quite fitting for an epic panning shot, though. 00:30:19.990 --> 00:30:23.090 COLTON OGDEN: Same idea here, really, just limited memory space. 00:30:23.090 --> 00:30:25.840 So let's load you know as much as we could possibly ever 00:30:25.840 --> 00:30:29.550 see from the perspective of the camera of Link, 00:30:29.550 --> 00:30:32.560 and it's actually very similar to how, I guess, people 00:30:32.560 --> 00:30:36.230 create stages in real life to make you feel as if you're in a-- 00:30:36.230 --> 00:30:38.920 when you go to a play, feel like you're actually in a scene. 00:30:38.920 --> 00:30:43.090 But they've clearly cut as many corners as possible, but it works. 00:30:43.090 --> 00:30:47.520 In the game, you can't tell, and that's very common in game development. 00:30:47.520 --> 00:30:50.260 If you're trying to achieve a particularly grand effect, 00:30:50.260 --> 00:30:52.090 it's something to think about is how can I 00:30:52.090 --> 00:30:55.300 make it seem like I'm doing something, but I'm actually not. 00:30:55.300 --> 00:30:58.540 How can I make it seem like I'm a bird flying through an infinite series 00:30:58.540 --> 00:31:01.540 of levels, but I'm actually not. 00:31:01.540 --> 00:31:07.020 We have a lot of more of that to show coming up soon. 00:31:07.020 --> 00:31:10.750 So far we have our background, but we don't 00:31:10.750 --> 00:31:16.870 have the title character of our game, and in this case, fifty bird. 00:31:16.870 --> 00:31:20.980 So I'm going to go ahead, and illustrate how we can get a bird actually 00:31:20.980 --> 00:31:23.390 rendering on the screen. 00:31:23.390 --> 00:31:27.800 So I'm going to go ahead into my bird2 directory here, that I've created. 00:31:27.800 --> 00:31:31.000 And note again, bird2 in your directory if you've loaded the code, 00:31:31.000 --> 00:31:34.210 is going to have the complete implementation. 00:31:34.210 --> 00:31:38.096 But in main, I'm going to do a couple of things. 00:31:38.096 --> 00:31:39.970 So actually, the first thing I'm going to do, 00:31:39.970 --> 00:31:42.310 we're going to-- notice that I've included-- 00:31:42.310 --> 00:31:48.010 actually, I haven't included the class file. 00:31:48.010 --> 00:31:49.580 So I'm going to do that right now. 00:31:49.580 --> 00:31:53.770 So in bird1-- sorry, I'm going to take from bird3, the class.lua. 00:31:53.770 --> 00:31:56.590 I'm going to go ahead, and put it into bird2 because we're 00:31:56.590 --> 00:31:57.746 going to make a bird class. 00:31:57.746 --> 00:31:59.620 Recall, from last week, a class is just a way 00:31:59.620 --> 00:32:03.010 of taking several variables that we might once have had disparate from one 00:32:03.010 --> 00:32:04.989 another, putting them together in a package, 00:32:04.989 --> 00:32:07.030 putting functions associated with those variables 00:32:07.030 --> 00:32:09.160 together so that we can call-- 00:32:09.160 --> 00:32:13.660 we can sort of think of our game world more abstractly, and more 00:32:13.660 --> 00:32:16.060 compartmentalized, and cleaner. 00:32:16.060 --> 00:32:19.000 So I'm going to go ahead-- and now I have in bird2, the class.lua. 00:32:19.000 --> 00:32:23.394 That's just the library we're using to get classes in Love2D in Lua. 00:32:23.394 --> 00:32:25.810 I'm going to go ahead, and I'm going to create a new file. 00:32:25.810 --> 00:32:27.490 This one's called bird.lua. 00:32:27.490 --> 00:32:31.660 So remember, the trend is for classes, capitalize them to differentiate them 00:32:31.660 --> 00:32:35.920 from functions and variables. 00:32:35.920 --> 00:32:41.020 This one I'm going to go ahead, and just go ahead and use my cheat sheet here. 00:32:51.636 --> 00:32:53.940 My sheets are sticking together. 00:32:53.940 --> 00:32:58.232 OK, so this bird class is actually fairly simple. 00:32:58.232 --> 00:33:00.190 Recall that all we have to do to create a class 00:33:00.190 --> 00:33:03.220 is just use the class library, the capital C with the brackets 00:33:03.220 --> 00:33:05.332 there to initialize it. 00:33:05.332 --> 00:33:07.540 We're going to go ahead and define our init function. 00:33:07.540 --> 00:33:13.120 So every class has an init function, which initializes the object 00:33:13.120 --> 00:33:16.420 that it's going to refer to later. 00:33:16.420 --> 00:33:18.800 In this case, we're going to need a few things. 00:33:18.800 --> 00:33:20.932 So we're going to need an image for our bird 00:33:20.932 --> 00:33:22.640 because we want it to draw to the screen, 00:33:22.640 --> 00:33:25.056 and so what we need to do, same thing that we did before-- 00:33:25.056 --> 00:33:26.140 love.graphics.newImage. 00:33:26.140 --> 00:33:28.480 I'm going to go ahead, and hide this really fast. 00:33:28.480 --> 00:33:31.270 And then bird.png. 00:33:31.270 --> 00:33:33.280 Simple, easy. 00:33:33.280 --> 00:33:37.600 We want the width in the height of our bird. 00:33:37.600 --> 00:33:40.640 So I'm going to go ahead, and set that too. 00:33:40.640 --> 00:33:45.130 So every image has a set of functions associated 00:33:45.130 --> 00:33:46.930 with it that Love implements for us. 00:33:46.930 --> 00:33:49.665 The image that we get back from love.graphics.newImage 00:33:49.665 --> 00:33:54.400 is itself, sort of a class, which has a function called getwidth. 00:33:54.400 --> 00:33:58.600 So this will allow us to achieve the with, dynamically, of whatever 00:33:58.600 --> 00:34:00.250 class we-- 00:34:00.250 --> 00:34:04.917 whatever image file we happen to allocate, and create an object from. 00:34:04.917 --> 00:34:07.750 And then we're going to go ahead and set our x and y because recall, 00:34:07.750 --> 00:34:09.310 we have to draw it somewhere. 00:34:09.310 --> 00:34:11.690 We want to draw our bird in the middle of the screen. 00:34:11.690 --> 00:34:14.126 So we're going to go ahead, and just calculate this 00:34:14.126 --> 00:34:15.250 based on our virtual width. 00:34:15.250 --> 00:34:17.250 So we're going to do VIRTUAL_WIDTH divided by 2. 00:34:17.250 --> 00:34:19.083 So it's halfway in the middle of the screen, 00:34:19.083 --> 00:34:21.007 but since it draws from the top left corner, 00:34:21.007 --> 00:34:22.340 we want to shift it to the left. 00:34:22.340 --> 00:34:25.060 So we're going to use our width that we just-- 00:34:27.719 --> 00:34:30.730 instant error-- we just initialized from the image data, 00:34:30.730 --> 00:34:34.060 and then we're going to do a self.width divided by 2. 00:34:34.060 --> 00:34:38.469 So we're going to divide the width by 2, shift that to the left on our x-axis. 00:34:38.469 --> 00:34:41.650 That's going to put us in the middle, horizontally. 00:34:41.650 --> 00:34:46.210 Vertically, it's the exact same thing, except reusing height instead of width. 00:34:46.210 --> 00:34:51.670 And that's pretty much it, except for one the last bit here. 00:34:51.670 --> 00:34:54.050 We want to be able to render our bird, pretty important. 00:34:54.050 --> 00:35:03.580 So we're going to do love.graphics.draw our image, and then at self.x, self.y. 00:35:03.580 --> 00:35:05.920 And so this is all we really need just to get 00:35:05.920 --> 00:35:08.140 a very simple sprite onto the screen. 00:35:08.140 --> 00:35:10.930 Now, it's not going to do anything because this sort of lives 00:35:10.930 --> 00:35:12.580 in a vacuum at the moment. 00:35:12.580 --> 00:35:15.640 What we need to do is in our main file, we're 00:35:15.640 --> 00:35:19.150 going to require bird, which is going to actually put it into our-- 00:35:19.150 --> 00:35:20.560 allow us to use it in our code. 00:35:23.180 --> 00:35:32.790 We're going to create a local bird variable. 00:35:32.790 --> 00:35:35.180 We're just going to call it bird. 00:35:35.180 --> 00:35:47.270 We're going to, after that, simply render to the screen like that, 00:35:47.270 --> 00:35:51.560 and if all is done and well, and if I'm in the right directory-- 00:35:55.210 --> 00:35:56.270 it did not work. 00:35:59.810 --> 00:36:02.950 Make sure you save your work, again. 00:36:02.950 --> 00:36:07.291 Oh, I did not require class. 00:36:07.291 --> 00:36:07.790 My bad. 00:36:07.790 --> 00:36:15.280 So also, we need to do this since we added that to our directory. 00:36:15.280 --> 00:36:17.762 And I did not include the bird.png as well. 00:36:17.762 --> 00:36:19.345 So I'm going to go ahead, and do that. 00:36:19.345 --> 00:36:22.100 I'm going to borrow that from the next directory. 00:36:22.100 --> 00:36:27.740 That should be all we need to do, and attempt to call method. 00:36:27.740 --> 00:36:29.990 Render a null value. 00:36:29.990 --> 00:36:32.720 Interesting. 00:36:32.720 --> 00:36:33.710 Did I not save bird? 00:36:33.710 --> 00:36:36.350 I did not save bird. 00:36:36.350 --> 00:36:37.180 There we go. 00:36:37.180 --> 00:36:38.240 We did it. 00:36:38.240 --> 00:36:42.440 So not particularly interesting, but we're making steps. 00:36:42.440 --> 00:36:44.300 Remember to save your work. 00:36:44.300 --> 00:36:47.960 As we can see, I do not. 00:36:47.960 --> 00:36:49.400 But we're making progress. 00:36:49.400 --> 00:36:51.500 We have our entity that we will control. 00:36:54.110 --> 00:36:59.540 Visually, we're getting very close, but a lot of important details are missing. 00:36:59.540 --> 00:37:03.170 What should be the next step, do we think? 00:37:08.127 --> 00:37:09.960 AUDIENCE: Let's get him jumping and falling. 00:37:09.960 --> 00:37:12.210 COLTON OGDEN: Exactly, and we'll do that with the help 00:37:12.210 --> 00:37:14.970 of a notion that's common in platformers, and a lot of games, 00:37:14.970 --> 00:37:16.680 actually, but gravity. 00:37:16.680 --> 00:37:21.858 How do we think we can simulate gravity in the context of 2D game development? 00:37:21.858 --> 00:37:25.200 AUDIENCE: Just by default, falling at a constant rate. 00:37:25.200 --> 00:37:27.180 COLTON OGDEN: We could do that, certainly, 00:37:27.180 --> 00:37:29.220 and that's effectively what we will be doing. 00:37:29.220 --> 00:37:32.820 We'll be using something that we used last week, which 00:37:32.820 --> 00:37:42.150 was velocity, delta y, and applying that velocity to our birds y, 00:37:42.150 --> 00:37:46.740 frame by frame, and that will give it the illusion of falling. 00:37:46.740 --> 00:37:51.120 Now, falling at a constant rate isn't accurate to what gravity actually does. 00:37:51.120 --> 00:37:55.420 What we want to do, probably, is some gravity over and over again. 00:37:55.420 --> 00:37:58.330 Increment our gravity by some sort of constant value 00:37:58.330 --> 00:38:02.320 so that just like in real life, things fall faster, and faster, 00:38:02.320 --> 00:38:05.470 and then we want to add that to our y value. 00:38:05.470 --> 00:38:08.630 So I'm going to go ahead, and start implementing that now in bird3. 00:38:08.630 --> 00:38:09.610 Wrong repo. 00:38:09.610 --> 00:38:16.670 So bird3, we have everything that we had from before, 00:38:16.670 --> 00:38:33.721 except now, I'm going to go ahead, and in main.lua, in our update function, 00:38:33.721 --> 00:38:35.470 this is where we're actually going to want 00:38:35.470 --> 00:38:44.230 to perform the update logic for making the velocity apply to the bird. 00:38:44.230 --> 00:38:46.120 We're going to defer that to the bird class. 00:38:46.120 --> 00:38:49.286 We're going to assume that we have a method called update in our bird class, 00:38:49.286 --> 00:38:54.430 which we're going to implement shortly, and that's actually 00:38:54.430 --> 00:38:57.210 all we need to do in our main class. 00:38:57.210 --> 00:39:01.150 And the beauty of having classes that you can delegate all this work 00:39:01.150 --> 00:39:06.300 to, your main file, though it's still getting quite large-- it's 108 lines-- 00:39:06.300 --> 00:39:11.470 it's not 200 , 300, 400, thousands of lines of code because we're able 00:39:11.470 --> 00:39:16.660 to break out this code, and encapsulate it elsewhere. 00:39:16.660 --> 00:39:18.580 So I'm going to remember to save it this time, 00:39:18.580 --> 00:39:23.500 and then I'm going to go into the bird.lua file 00:39:23.500 --> 00:39:26.890 in that directory, which is the same with comments 00:39:26.890 --> 00:39:31.390 because I loaded it from the official repo, the same bird code 00:39:31.390 --> 00:39:33.310 that we wrote before. 00:39:33.310 --> 00:39:35.390 I'm going to go ahead, and do a couple of things. 00:39:35.390 --> 00:39:38.440 So the first thing that I'm going to do is define a constant. 00:39:38.440 --> 00:39:42.760 So I mentioned gravity before. 00:39:42.760 --> 00:39:47.800 Gravity is going to be a constant value just like it is in real life. 00:39:47.800 --> 00:39:49.040 I'm going to define it to 20. 00:39:49.040 --> 00:39:50.331 It's just some arbitrary value. 00:39:50.331 --> 00:39:52.656 This is a value that I decided felt right, 00:39:52.656 --> 00:39:54.280 but you can tune this however you want. 00:39:54.280 --> 00:39:56.060 There's no right or wrong way to do it. 00:39:56.060 --> 00:39:58.794 The less the gravity is, the slower it'll fall, 00:39:58.794 --> 00:40:01.960 and the more you'll feel like you're sort of in outer space, or on the moon, 00:40:01.960 --> 00:40:02.460 or whatnot. 00:40:05.230 --> 00:40:10.030 We're going to also go ahead, and define-- 00:40:10.030 --> 00:40:11.950 recall that we need some way to keep track 00:40:11.950 --> 00:40:16.010 of how our how our bird is falling. 00:40:16.010 --> 00:40:17.800 We want a velocity, a y velocity. 00:40:17.800 --> 00:40:19.870 This is going to update our position each frame, 00:40:19.870 --> 00:40:22.000 and it's going to make it feel like we're falling. 00:40:22.000 --> 00:40:24.000 So we're going to set our initial velocity to 0. 00:40:24.000 --> 00:40:25.749 The bird's just going to be in the middle. 00:40:25.749 --> 00:40:27.130 It's not going be falling yet. 00:40:27.130 --> 00:40:31.300 What we don't want to do is apply this velocity. 00:40:31.300 --> 00:40:35.650 So remember, in our main file we assumed that we had an update function, 00:40:35.650 --> 00:40:37.480 but we haven't actually implemented it yet. 00:40:37.480 --> 00:40:38.980 So we're going to do that right now. 00:40:38.980 --> 00:40:42.490 We're going to say birdupdate dt. 00:40:42.490 --> 00:40:47.530 We're going to pass it in the same dt that we use in our main file, 00:40:47.530 --> 00:40:51.370 and we're going to go ahead, and just say our velocity 00:40:51.370 --> 00:40:59.630 is equal to our current velocity plus gravity times delta time. 00:40:59.630 --> 00:41:01.720 We're just going to scale gravity by delta time. 00:41:01.720 --> 00:41:04.210 So it will move the same amount no matter 00:41:04.210 --> 00:41:07.960 whether we're running at 10 frames per second or 60 frames per second. 00:41:07.960 --> 00:41:09.716 And then we're going to go ahead-- 00:41:09.716 --> 00:41:12.340 we have a velocity, but it's not actually changing our y value. 00:41:12.340 --> 00:41:14.590 The y value is what ultimately moves us on the screen. 00:41:14.590 --> 00:41:18.130 So we need to apply our new delta y to our y. 00:41:18.130 --> 00:41:21.190 So we're going to go ahead, and just do that. 00:41:21.190 --> 00:41:26.310 Self.y gets self.y way plus self dot delta y, dy. 00:41:26.310 --> 00:41:32.540 And so if I go back into bird3, assuming I saved everything, 00:41:32.540 --> 00:41:34.900 we should just fall straight to the screen, which we do. 00:41:34.900 --> 00:41:40.840 Not terribly useful, but notice it's slightly hard to tell, maybe, 00:41:40.840 --> 00:41:44.470 but it does move faster, and faster, frame by frame because that delta 00:41:44.470 --> 00:41:48.190 y is increasing as well as our y, and that delta y is getting 00:41:48.190 --> 00:41:49.939 applied to our y, frame by frame. 00:41:49.939 --> 00:41:50.980 I'll do it one more time. 00:41:50.980 --> 00:41:52.150 It's just funny to look at. 00:41:52.150 --> 00:41:55.210 All right, so we have basic gravity. 00:41:55.210 --> 00:41:56.620 Super basic computation. 00:41:56.620 --> 00:42:01.120 Just keep track of some gravity constant, delta y, 00:42:01.120 --> 00:42:05.740 increase that, and apply that to your y, and that gives you gravity. 00:42:05.740 --> 00:42:07.670 But Flappy Bird can jump. 00:42:07.670 --> 00:42:09.970 So we need to find a way to defy gravity. 00:42:09.970 --> 00:42:12.280 So we're going to do the-- in bird4, we're 00:42:12.280 --> 00:42:14.440 going to call this the anti-gravity update, 00:42:14.440 --> 00:42:19.690 and we're going to talk about how we can actually get that going. 00:42:19.690 --> 00:42:22.540 So I found this diagram, which I felt was pretty apt, 00:42:22.540 --> 00:42:25.581 and it also covers a few of the other concepts we're talking about today. 00:42:25.581 --> 00:42:29.290 But see here, this gravity, that's the constant we had just defined before, 00:42:29.290 --> 00:42:31.780 the 20 or whatever, and this gets applied 00:42:31.780 --> 00:42:33.340 at whatever value you want it to be. 00:42:33.340 --> 00:42:36.670 This gets applied frame by frame to your y. 00:42:36.670 --> 00:42:37.810 What we want is this. 00:42:37.810 --> 00:42:40.360 This vector here is jump velocity. 00:42:40.360 --> 00:42:46.420 We want some value to counteract this gravity that we've been accumulating. 00:42:46.420 --> 00:42:48.400 So how do we think we can go about doing this? 00:42:51.640 --> 00:42:57.070 We can set gravity to some, perhaps, negative value, a high value. 00:42:57.070 --> 00:42:59.740 And that will have the effect of frame by frame, 00:42:59.740 --> 00:43:04.270 if we go from some positive value, which is taking us down on the y-axis, 00:43:04.270 --> 00:43:07.330 and we go to a negative value, frame by frame, 00:43:07.330 --> 00:43:09.770 it's going to say-- let's say that we start at negative 5. 00:43:09.770 --> 00:43:12.970 We set it's velocity to negative 5 it's going 00:43:12.970 --> 00:43:17.590 to set y to negative-- it's going to set it to plus negative 5 pixels 00:43:17.590 --> 00:43:20.350 plus negative 4.9 pixels, 4.8 pixels. 00:43:20.350 --> 00:43:22.930 It's going to shoot us up pretty fast in a series of pixels, 00:43:22.930 --> 00:43:26.700 but since we're applying gravity frame by frame, this value 00:43:26.700 --> 00:43:30.210 that we set before, 20, it's going to have the effect-- 00:43:30.210 --> 00:43:31.160 20 times delta time. 00:43:31.160 --> 00:43:33.570 So it gets, effectively, divided by 60. 00:43:33.570 --> 00:43:36.060 It's going to counteract this again. 00:43:36.060 --> 00:43:39.371 So we're going to shoot up pretty fast, but gravity 00:43:39.371 --> 00:43:41.370 is going to start taking hold immediately after, 00:43:41.370 --> 00:43:45.570 and we're going to start getting the effect of our bird jumping, 00:43:45.570 --> 00:43:47.429 and then falling down to the ground. 00:43:47.429 --> 00:43:49.470 A couple of other things that this diagram shows, 00:43:49.470 --> 00:43:54.032 which I thought were pretty cool, this pipe gap 00:43:54.032 --> 00:43:56.490 distance here, something that we'll be talking about pretty 00:43:56.490 --> 00:43:59.972 shortly because this needs to be defined so that we can offset our pipes. 00:43:59.972 --> 00:44:02.430 Pipe separation, it's another thing we'll be talking about. 00:44:02.430 --> 00:44:05.100 And also pipe width, which is just an intrinsic value 00:44:05.100 --> 00:44:07.400 characteristic of the pipe sprite we'll be using, 00:44:07.400 --> 00:44:09.290 but I thought it was very apt. 00:44:09.290 --> 00:44:11.970 NYU did a nice article, if you want to look 00:44:11.970 --> 00:44:13.470 at this, about exploring game space. 00:44:13.470 --> 00:44:17.190 They computationally determined what would make a Flappy Bird 00:44:17.190 --> 00:44:20.340 level difficult or not, and rated Flappy Bird levels that were dynamically 00:44:20.340 --> 00:44:22.562 generated based on some sort of scale. 00:44:22.562 --> 00:44:24.270 So if you're curious, it's in the slides, 00:44:24.270 --> 00:44:28.510 but I thought it was a cool find as I was putting together this lecture. 00:44:28.510 --> 00:44:34.200 So what we need to do is then, simply, add some negative value to gravity. 00:44:34.200 --> 00:44:35.922 Negative sort of anti-gravity. 00:44:35.922 --> 00:44:37.380 So we're going to go ahead do that. 00:44:37.380 --> 00:44:46.590 So in bird4 of the little mini repo that I have here, 00:44:46.590 --> 00:44:48.715 we're going to go ahead in main, first. 00:44:55.540 --> 00:45:01.120 One thing that we want to do is because another part of this 00:45:01.120 --> 00:45:04.390 is taking input from the user, being able to jump, 00:45:04.390 --> 00:45:08.050 we want to be able to detect whether they've pressed space. 00:45:08.050 --> 00:45:15.260 But if we want to detect input for every single entity that we ever-- 00:45:15.260 --> 00:45:19.070 in an instance like this, it's not terribly important, 00:45:19.070 --> 00:45:22.840 but let's say we have 20 or 30 different kinds of entities, 00:45:22.840 --> 00:45:25.180 and they all have their own input handling, 00:45:25.180 --> 00:45:27.670 we don't want to clog our main with that, necessarily. 00:45:27.670 --> 00:45:30.760 So we can dedicate that-- 00:45:30.760 --> 00:45:34.700 delegate that, I should say, to another section of the code. 00:45:34.700 --> 00:45:37.120 In this case, we can sort of put our birds input 00:45:37.120 --> 00:45:40.080 handling together with our bird class, right, 00:45:40.080 --> 00:45:45.940 and expound upon the model of the class, taking control of the code and data 00:45:45.940 --> 00:45:49.850 for that particular object in our scene. 00:45:49.850 --> 00:46:01.420 So what we're going to do is in our love.load, 00:46:01.420 --> 00:46:05.190 I'm going to go ahead, and do something here. 00:46:05.190 --> 00:46:09.130 I'm going to go ahead and set love.keyboard.keysPressed 00:46:09.130 --> 00:46:10.130 equals a table. 00:46:10.130 --> 00:46:15.385 And what I'm doing is just adding onto a table that Love defined. 00:46:15.385 --> 00:46:16.810 It's called love.keyboard. 00:46:16.810 --> 00:46:20.890 I'm adding my own value into it called keysPressed, 00:46:20.890 --> 00:46:23.060 and I'm assigning it to an empty table. 00:46:23.060 --> 00:46:24.520 So what we're going to do-- 00:46:24.520 --> 00:46:29.230 this is now part of what Love gives us as part of its SDK, 00:46:29.230 --> 00:46:32.050 but it's something that we've created ourselves, 00:46:32.050 --> 00:46:36.190 and you can do this because in Lua, basically everything 00:46:36.190 --> 00:46:39.440 beyond basic variables are just tables, and you can manipulate tables 00:46:39.440 --> 00:46:40.360 however you want. 00:46:40.360 --> 00:46:42.150 In this case, love.keyboard is a table. 00:46:42.150 --> 00:46:43.720 I'm just adding a new key called keysPressed, 00:46:43.720 --> 00:46:45.835 and I'm assigning it to an empty table of my own. 00:46:45.835 --> 00:46:50.120 And we're going to see how this is actually used in just a moment. 00:46:50.120 --> 00:46:55.060 So I'm going to go ahead in our keyPressed function here-- 00:46:58.540 --> 00:47:03.190 this function is called every time a user presses a key in the game, 00:47:03.190 --> 00:47:06.850 but I'm going to use it. 00:47:06.850 --> 00:47:11.320 Because it does that, I can go ahead, and just do something like this. 00:47:11.320 --> 00:47:19.540 Love.keyboard.keysPressed key gets true, and what that means is in this table 00:47:19.540 --> 00:47:23.500 that we've just defined, we've created ourselves, anytime the user 00:47:23.500 --> 00:47:27.220 presses any key, because love.keyPressed gets called for you, 00:47:27.220 --> 00:47:30.220 we can safely rest assured that this is going to get populated no matter 00:47:30.220 --> 00:47:32.290 what key they press because it's just something 00:47:32.290 --> 00:47:33.850 that Love2D takes care for you. 00:47:33.850 --> 00:47:35.810 But it's not getting stored until now. 00:47:35.810 --> 00:47:38.440 Now we're actually going to keep track of it 00:47:38.440 --> 00:47:44.180 in our own table for reasons that will become apparent very shortly. 00:47:44.180 --> 00:47:49.900 The next part of this code is defining a custom function. 00:47:49.900 --> 00:47:55.420 So the impetus for this is Love defines a couple of functions. 00:47:55.420 --> 00:48:00.190 It defines a function called love.keyboard.isdown, 00:48:00.190 --> 00:48:02.020 which takes in some key value, and you can 00:48:02.020 --> 00:48:06.700 use it to test for continuous input, which we did in the last lecture. 00:48:06.700 --> 00:48:10.600 We were saying hey, if up is down right now, or down is down, 00:48:10.600 --> 00:48:13.820 then we need to update our y velocity accordingly. 00:48:13.820 --> 00:48:16.540 But it doesn't have a mechanism like this for let's say, 00:48:16.540 --> 00:48:18.850 we want in some file other than main, to check 00:48:18.850 --> 00:48:22.090 for if a key was just pressed one time. 00:48:22.090 --> 00:48:24.880 It has this function, love.keyPressed, which takes a key, 00:48:24.880 --> 00:48:28.820 and that will trigger it, but we can't access this outside of this function 00:48:28.820 --> 00:48:31.870 because if we define this function in bird.lua, 00:48:31.870 --> 00:48:34.660 it's going to overwrite this implementation. 00:48:34.660 --> 00:48:37.900 And we don't necessarily want to have to worry about other files 00:48:37.900 --> 00:48:40.000 overwriting these functions because who knows-- 00:48:40.000 --> 00:48:41.380 if you're on a team, especially, who knows 00:48:41.380 --> 00:48:43.780 who's overwritten love.keyPressed, and what module in, 00:48:43.780 --> 00:48:47.620 and what order does it get loaded in, and what functions actually valid. 00:48:47.620 --> 00:48:51.220 We're going to take care of this problem by giving ourselves the ability 00:48:51.220 --> 00:48:54.520 to test for whether a key has been pressed on the last frame 00:48:54.520 --> 00:49:00.040 by implementing a function that we are also adding to the keyboard namespace, 00:49:00.040 --> 00:49:04.420 the keyboard table ourselves, called wasPressed. 00:49:04.420 --> 00:49:09.100 And it's going to take a key, and all it's going to do 00:49:09.100 --> 00:49:11.040 is check that table that we created before. 00:49:11.040 --> 00:49:18.250 It's going to say if love.keyboard.keysPressed key, then 00:49:18.250 --> 00:49:23.140 return true, else return false. 00:49:23.140 --> 00:49:27.220 And you could actually just return love.keyboard.keysPressed key, 00:49:27.220 --> 00:49:29.689 and it will be the exact same thing. 00:49:29.689 --> 00:49:31.480 And so what this has the effect of doing is 00:49:31.480 --> 00:49:35.860 saying, OK, because on the update, which we're about to see-- 00:49:35.860 --> 00:49:39.055 actually, I should probably do that before so this all gets tied together. 00:49:42.600 --> 00:49:45.100 At the end of love.update, we're going to do one last thing, 00:49:45.100 --> 00:49:50.960 and that's reset that table because we want to just check frame by frame. 00:49:50.960 --> 00:49:55.470 So we have a table, a global table, that we've created 00:49:55.470 --> 00:49:57.330 to check for whether a key is pressed. 00:49:57.330 --> 00:50:00.615 We have a callback function that Love2D gives us that allows us to do that. 00:50:00.615 --> 00:50:02.490 So every time a key gets pressed, we're going 00:50:02.490 --> 00:50:07.230 to just add that key to that table, and set it to true. 00:50:07.230 --> 00:50:10.740 Now, we can just simply query that table anytime we want to with this function 00:50:10.740 --> 00:50:14.160 that we've created called love.keyboard.wasPressed key, which 00:50:14.160 --> 00:50:18.210 means on the last frame, was that key pressed? 00:50:18.210 --> 00:50:20.152 Basically return whether it's true or false. 00:50:20.152 --> 00:50:22.110 Now, the only problem is we're not flushing it. 00:50:22.110 --> 00:50:24.550 We're not ever setting that to false. 00:50:24.550 --> 00:50:27.540 That's has the effect of if we just press all the keys on our keyboard, 00:50:27.540 --> 00:50:32.370 those will always be true until we re-initialize the table 00:50:32.370 --> 00:50:35.040 to some empty value, which is what we do here. 00:50:35.040 --> 00:50:39.930 On the update, which takes place after all inputs been detected, 00:50:39.930 --> 00:50:44.580 we're going to just set that table to an empty table again. 00:50:44.580 --> 00:50:48.880 And on the next frame, it's going to-- whatever keys we pressed, those 00:50:48.880 --> 00:50:52.620 will get set to true, and then we can just query that table here as needed, 00:50:52.620 --> 00:50:55.320 and any update henceforth. 00:50:55.320 --> 00:51:00.262 So does anybody have any questions as to how this is operating? 00:51:02.940 --> 00:51:06.542 And so the ultimate driving factor for us 00:51:06.542 --> 00:51:08.250 as to why we want to do this, why we want 00:51:08.250 --> 00:51:13.320 to put in the work to keep track of this global input table, 00:51:13.320 --> 00:51:23.550 is so that we can actually query input, single key input based in other files 00:51:23.550 --> 00:51:25.560 outside of main.lua because currently, all 00:51:25.560 --> 00:51:29.010 we can do to check for single key presses is look in main.lua, 00:51:29.010 --> 00:51:31.020 but that's not what we want to do. 00:51:31.020 --> 00:51:38.790 We're going to go ahead and go to our bird.lua, and in our update function, 00:51:38.790 --> 00:51:42.690 this is where we actually get to use our efforts, 00:51:42.690 --> 00:51:49.800 and say if love.keyboard.wasPressed space, 00:51:49.800 --> 00:51:54.190 which is the key that we want to actually allow us to jump, 00:51:54.190 --> 00:51:56.510 go ahead and set self dy to-- 00:51:56.510 --> 00:51:59.040 what should we set self.dy to when we press spacebar? 00:52:01.990 --> 00:52:04.379 Should it be a positive or a negative value? 00:52:04.379 --> 00:52:05.170 AUDIENCE: Negative. 00:52:05.170 --> 00:52:06.910 COLTON OGDEN: A negative value. 00:52:06.910 --> 00:52:09.380 We'll set it to negative 5, and we should probably 00:52:09.380 --> 00:52:12.230 define this as an anti-gravity constant up here. 00:52:12.230 --> 00:52:16.010 But just for the sake of speed, we'll say self.dy gets negative 5. 00:52:19.567 --> 00:52:20.650 And I did say that, right? 00:52:20.650 --> 00:52:21.750 I did say that. 00:52:21.750 --> 00:52:25.210 I'm going to go ahead, and go into bird4. 00:52:25.210 --> 00:52:28.472 Go ahead and run this example. 00:52:28.472 --> 00:52:32.950 And look at that, we're jumping. 00:52:32.950 --> 00:52:34.870 But we can still fall through the ground, 00:52:34.870 --> 00:52:36.866 and we don't have any real game play. 00:52:36.866 --> 00:52:37.990 But we've come a long ways. 00:52:37.990 --> 00:52:41.410 Now we've taken single key input that we otherwise 00:52:41.410 --> 00:52:43.750 didn't have the ability to do in Love2D, and we've 00:52:43.750 --> 00:52:46.570 made it possible by just keeping track of our global input state, 00:52:46.570 --> 00:52:49.780 and flushing it every update. 00:52:49.780 --> 00:52:54.050 So does anybody have any questions as to how that works? 00:52:54.050 --> 00:52:55.660 OK. 00:52:55.660 --> 00:53:00.552 So the other big major visual component of Flappy Bird 00:53:00.552 --> 00:53:02.510 are these pipes that we see here on the screen. 00:53:02.510 --> 00:53:07.930 We have two pipes there, but the screen is filled with infinite pipes. 00:53:07.930 --> 00:53:12.460 So does anybody have any instinct as to how we can implement this? 00:53:16.036 --> 00:53:22.580 Well, we'll see before long, but suffice to say we'll need a new sprite. 00:53:22.580 --> 00:53:25.710 We'll need some sort of way of keeping track 00:53:25.710 --> 00:53:32.520 of when to spawn them because they'd sort of spawn after a period of time, 00:53:32.520 --> 00:53:34.320 and that will be our gap. 00:53:34.320 --> 00:53:40.354 And then what will happen if we just let it spawn forever and ever? 00:53:40.354 --> 00:53:42.270 AUDIENCE: You have to destroy them as they go? 00:53:42.270 --> 00:53:43.500 COLTON OGDEN: We do because if we don't do 00:53:43.500 --> 00:53:45.780 that, after a certain period of time, we're allocating memory 00:53:45.780 --> 00:53:46.950 for each of these pipes. 00:53:46.950 --> 00:53:52.200 Not a ton of memory, just essentially, an x, a y, a width, and a height. 00:53:52.200 --> 00:53:54.480 But because they all reference the same-- 00:53:54.480 --> 00:53:57.639 they will reference the same sprite image, but given enough time, 00:53:57.639 --> 00:54:00.180 eventually you're going to allocate a certain number of bytes 00:54:00.180 --> 00:54:03.900 that will exceed your computer's memory, or the amount of allocated memory, 00:54:03.900 --> 00:54:06.810 and you'll either hang infinitely, or crash. 00:54:06.810 --> 00:54:11.950 And so we want to destroy them as they go as well. 00:54:11.950 --> 00:54:16.650 So we're going to go ahead, and look at the final live coded example 00:54:16.650 --> 00:54:20.120 just because from here on out, it's going to be a little bit much. 00:54:20.120 --> 00:54:25.785 I'm going to go ahead, and go to main.lua first. 00:54:30.300 --> 00:54:35.730 So just get my notes in order. 00:54:35.730 --> 00:54:37.365 The first thing that we want to do-- 00:54:37.365 --> 00:54:38.990 oh, I'm actually in the wrong repo too. 00:54:38.990 --> 00:54:39.850 I apologize. 00:54:39.850 --> 00:54:40.920 I was in the distro repo. 00:54:40.920 --> 00:54:43.260 I want to be in the scratch repo. 00:54:43.260 --> 00:54:52.510 So I'm going to go ahead, go into main, and I'm going to require pipe. 00:54:52.510 --> 00:54:56.200 Now, we don't have a pipe yet, but this is a perfect example of how 00:54:56.200 --> 00:54:58.990 we can keep abstracting our game. 00:54:58.990 --> 00:55:02.110 We have a bird class, but we should also probably 00:55:02.110 --> 00:55:05.810 have a pipe class because a pipe is a distinct type of entity in our game 00:55:05.810 --> 00:55:06.310 world. 00:55:06.310 --> 00:55:09.310 We can model it as a unit, we can give it functions, 00:55:09.310 --> 00:55:13.280 we can give it data, and think about it in terms of it being a pipe, 00:55:13.280 --> 00:55:17.770 not being a set of xy, width, height, et cetera. 00:55:17.770 --> 00:55:20.530 Whatever data you want to ascribe to it, we 00:55:20.530 --> 00:55:24.130 can abstract that out, and think in more abstract terms, which will 00:55:24.130 --> 00:55:25.812 allow us to scale a little bit better. 00:55:25.812 --> 00:55:28.520 So we're going to go ahead, and assume that we have a pipe class. 00:55:28.520 --> 00:55:32.320 I'm going to go ahead, and add it to our folder here right now. 00:55:32.320 --> 00:55:39.460 So do a new file, pipe.lua, and I'm going 00:55:39.460 --> 00:55:42.370 to go ahead, and reference to notes here for just a second. 00:55:52.650 --> 00:55:56.900 So the pipe class is actually quite simple, 00:55:56.900 --> 00:55:59.115 just like the bird class was initially. 00:55:59.115 --> 00:56:00.990 We don't need to keep track of a lot of data, 00:56:00.990 --> 00:56:03.770 but we do want to keep track of a few things. 00:56:03.770 --> 00:56:08.510 So the bird-- there's only ever going to be one bird out 00:56:08.510 --> 00:56:11.900 at once, but with the pipes, we're going to be spawning them over, 00:56:11.900 --> 00:56:13.170 and over again. 00:56:13.170 --> 00:56:15.680 And so if we allocate them-- 00:56:15.680 --> 00:56:20.030 for each pipe that we instantiate, if we allocate a new image, 00:56:20.030 --> 00:56:22.550 this is probably not super efficient, right? 00:56:22.550 --> 00:56:23.930 We're using the same exact data. 00:56:23.930 --> 00:56:25.550 We have a bunch of pipes. 00:56:25.550 --> 00:56:28.040 We only really need one sprite. 00:56:28.040 --> 00:56:32.180 So outside of the init function-- so just below where 00:56:32.180 --> 00:56:34.882 we're declaring that pipe is a class, we're 00:56:34.882 --> 00:56:37.340 going to go ahead and create a local variable that is still 00:56:37.340 --> 00:56:40.100 scoped to this file, but there's only ever going 00:56:40.100 --> 00:56:44.280 to be one copy of this object. 00:56:44.280 --> 00:56:46.750 We're going to go ahead and call it-- 00:56:46.750 --> 00:56:50.120 say that we have pipe.png in this folder, 00:56:50.120 --> 00:56:52.290 and this is separated out from the functions 00:56:52.290 --> 00:56:54.331 that we're going to be defining in here, but this 00:56:54.331 --> 00:56:59.180 has the effect of creating a semi global graphics object, 00:56:59.180 --> 00:57:02.124 even though it's contained within this class file. 00:57:02.124 --> 00:57:04.040 It's not accessible outside of this class file 00:57:04.040 --> 00:57:05.390 because we don't need it to be. 00:57:05.390 --> 00:57:09.290 But it's also not being instantiated every single time because recall, 00:57:09.290 --> 00:57:16.430 if we look at bird.lua here, we're just setting it 00:57:16.430 --> 00:57:20.600 as self.image gets love.graphics.newImage bird.png. 00:57:20.600 --> 00:57:24.440 This will have the effect of allocating a new image every time 00:57:24.440 --> 00:57:25.880 we create a bird object. 00:57:25.880 --> 00:57:29.570 But we only ever create one bird object, so it's not really an important design 00:57:29.570 --> 00:57:36.080 consideration for us to say, maybe we should create a semi global image up 00:57:36.080 --> 00:57:37.250 here. 00:57:37.250 --> 00:57:39.050 It's not important in this context. 00:57:39.050 --> 00:57:43.670 Probably good style to do so anyway for larger projects, but just 00:57:43.670 --> 00:57:44.837 a consideration for here. 00:57:44.837 --> 00:57:46.670 Not really something we need to worry about. 00:57:46.670 --> 00:57:51.020 But yes, definitely try to take an asset, 00:57:51.020 --> 00:57:56.045 and reference it rather than allocate it as many times as possible. 00:57:59.090 --> 00:58:00.950 We want our pipes to scroll. 00:58:00.950 --> 00:58:02.330 So we need some sort of value. 00:58:02.330 --> 00:58:04.039 Just like we did with the backgrounds, we 00:58:04.039 --> 00:58:06.954 need some value that keeps track of whether these pipes are scrolling, 00:58:06.954 --> 00:58:08.390 and it can be a constant value. 00:58:08.390 --> 00:58:11.510 We're going to directly call it negative 60 this time, 00:58:11.510 --> 00:58:18.360 and not negate it when we add it to our position later on. 00:58:18.360 --> 00:58:24.590 So PIPE_SCROLL negative 60, we can just add it directly to our x, or to our-- 00:58:24.590 --> 00:58:26.455 yeah, in this case, just to our x, and it 00:58:26.455 --> 00:58:28.730 will have the-- times delta time, of course, 00:58:28.730 --> 00:58:30.890 and that will have the effect of shifting 00:58:30.890 --> 00:58:33.990 it left because it's a negative number. 00:58:33.990 --> 00:58:35.490 We'll define the init function here. 00:58:35.490 --> 00:58:37.760 So pipe init. 00:58:37.760 --> 00:58:40.460 Within the init function, we're going to do a couple of things. 00:58:40.460 --> 00:58:43.130 So it's x. 00:58:43.130 --> 00:58:45.360 Where should the x be? 00:58:45.360 --> 00:58:49.340 What should the x be set to, let's say, if we want the pipe to spawn 00:58:49.340 --> 00:58:51.702 beyond the right edge of the screen? 00:58:51.702 --> 00:58:55.630 AUDIENCE: [INAUDIBLE] 00:58:55.630 --> 00:58:58.540 COLTON OGDEN: Virtual width, and you could also 00:58:58.540 --> 00:59:01.420 say virtual width plus some number if you wanted to. 00:59:01.420 --> 00:59:04.600 Because it's set to 0,0, it's going to have 00:59:04.600 --> 00:59:07.660 the-- you won't see it on the frame that it gets instantiated, 00:59:07.660 --> 00:59:12.310 but yes, virtual width or virtual width plus some constant value, or some 00:59:12.310 --> 00:59:14.260 value that you've allocated ahead of time. 00:59:14.260 --> 00:59:15.847 We'll just set it to virtual width. 00:59:15.847 --> 00:59:18.430 So as soon as the pipe gets initialized, it will be invisible, 00:59:18.430 --> 00:59:22.390 but it's going to be right on the right edge of the screen. 00:59:22.390 --> 00:59:25.150 What about our y value? 00:59:25.150 --> 00:59:29.260 First of all, let's take a look at what the image looks like so we can see. 00:59:29.260 --> 00:59:31.210 It's going to be in our-- 00:59:31.210 --> 00:59:33.775 I don't think I have the actual image in that directory. 00:59:33.775 --> 00:59:34.960 So I'm going to come here. 00:59:34.960 --> 00:59:36.520 I'm going to grab the pipe. 00:59:36.520 --> 00:59:38.390 This is what the pipe looks like. 00:59:38.390 --> 00:59:40.446 Let's see if I can expand it a little bit. 00:59:44.795 --> 00:59:45.670 So it's kind of tall. 00:59:45.670 --> 00:59:48.680 Where should we probably place it if we wanted 00:59:48.680 --> 00:59:54.420 it to look similar to Flappy Bird? 00:59:54.420 --> 00:59:58.050 Probably towards the lower end of the screen. 00:59:58.050 --> 01:00:00.420 We can get fancy with it too, and we can even maybe 01:00:00.420 --> 01:00:02.919 make it randomized just like Flappy Bird. 01:00:02.919 --> 01:00:04.210 So we'll go ahead, and do that. 01:00:04.210 --> 01:00:07.290 I'm going to go ahead, and copy this, and put it into our scratch folder 01:00:07.290 --> 01:00:10.170 here. 01:00:10.170 --> 01:00:14.190 Back in the init function, I'm going to go ahead, and set self.y too. 01:00:14.190 --> 01:00:17.100 Because we want to talk about procedural generation, 01:00:17.100 --> 01:00:19.850 this will be sort of our first foray into how we randomize this. 01:00:19.850 --> 01:00:23.394 We'll be using the function that we used last week, 01:00:23.394 --> 01:00:24.810 and this is a ubiquitous function. 01:00:24.810 --> 01:00:29.440 You'll see this everywhere in any framework or game engine you use-- 01:00:29.440 --> 01:00:31.900 math.random. 01:00:31.900 --> 01:00:34.690 We want it to be the lower half of the screen. 01:00:34.690 --> 01:00:38.700 So let's say virtual height divided by 4 is the upper bound, and maybe 01:00:38.700 --> 01:00:44.950 virtual height minus 10 as the upper bound. 01:00:44.950 --> 01:00:49.110 So that will have the effect of setting it to roughly a quarter of the screen. 01:00:49.110 --> 01:00:52.694 Sorry, virtual height divided by 4 is towards the top end of the screen, 01:00:52.694 --> 01:00:55.360 and then virtual height minus 10 is the lower end of the screen. 01:00:55.360 --> 01:00:59.220 So it's actually going to cover anywhere from the first quarter below that, down 01:00:59.220 --> 01:01:00.780 to about 10 pixels from the bottom. 01:01:00.780 --> 01:01:03.902 AUDIENCE: Do you have to set the random seed in this [INAUDIBLE] 01:01:03.902 --> 01:01:04.800 or do you do it main? 01:01:04.800 --> 01:01:06.050 COLTON OGDEN: I do it in main. 01:01:06.050 --> 01:01:11.430 So in this file, I am not sure if I did it for this demonstration. 01:01:11.430 --> 01:01:13.395 It is definitely set in the repo. 01:01:16.245 --> 01:01:18.750 I don't think I set it in this example, but yes, you 01:01:18.750 --> 01:01:21.570 would set the random seed here if you wanted it to run every time. 01:01:21.570 --> 01:01:22.080 Sorry. 01:01:22.080 --> 01:01:26.850 And the question was should we set the random seed in the bird file, 01:01:26.850 --> 01:01:29.130 or should we set it in main.lua? 01:01:29.130 --> 01:01:32.590 Typically, you want to set it at the top level of your application. 01:01:32.590 --> 01:01:36.210 So we're going to set it in-- 01:01:36.210 --> 01:01:41.610 we're going to go ahead, and set it in main. 01:01:41.610 --> 01:01:54.880 And the function itself is here, and I think it's starting in bird6, onwards. 01:01:54.880 --> 01:01:55.610 So it will be-- 01:02:00.030 --> 01:02:01.300 did I not set it? 01:02:01.300 --> 01:02:04.254 I may not have set the random seed until later in the repo. 01:02:04.254 --> 01:02:05.045 Let's check bird12. 01:02:09.810 --> 01:02:13.170 So yes, math.randomseed, and then seed by os.time, 01:02:13.170 --> 01:02:15.300 as we used last week in class. 01:02:18.160 --> 01:02:20.850 I'll set it here. 01:02:20.850 --> 01:02:23.370 Probably, we'll only run it once, but it'll have the effect. 01:02:23.370 --> 01:02:27.780 Now we can run it several times just to see the difference in the pipes. 01:02:27.780 --> 01:02:37.200 Let's go back to our pipe.lua here, and we have the x, we have the y. 01:02:37.200 --> 01:02:39.700 So those are set accordingly. 01:02:39.700 --> 01:02:41.340 We also want to set the width. 01:02:41.340 --> 01:02:50.210 Does anybody recall what the function is to get a width of a graphics object 01:02:50.210 --> 01:02:53.370 and the syntax for that? 01:02:53.370 --> 01:02:56.420 So we have our image up here, pipe image. 01:02:56.420 --> 01:02:58.700 It's love.graphic.newImage pipe.png. 01:03:01.480 --> 01:03:02.944 AUDIENCE: [INAUDIBLE] 01:03:02.944 --> 01:03:03.920 COLTON OGDEN: Exactly. 01:03:03.920 --> 01:03:08.700 So we're going to go ahead, and set this to PIPE_IMAGE colon getWidth, 01:03:08.700 --> 01:03:13.785 and that will become our new-- that will allow us to store our width for when 01:03:13.785 --> 01:03:14.940 we will use it later. 01:03:18.430 --> 01:03:20.770 And then we need a few other functions. 01:03:20.770 --> 01:03:24.000 So the pipe will spawn, but it won't move because we haven't 01:03:24.000 --> 01:03:26.700 applied any sort of scrolling to it. 01:03:26.700 --> 01:03:29.520 We have the scrolling variable up on line five, 01:03:29.520 --> 01:03:31.680 but we need to actually apply it to our pipe. 01:03:31.680 --> 01:03:35.650 So we're going to go ahead, and create an update function. 01:03:35.650 --> 01:03:38.580 And then in that update function, very similar to what 01:03:38.580 --> 01:03:43.690 we've seen before already, PIPE_SCROLL times delta time. 01:03:43.690 --> 01:03:46.560 And then lastly, we want to render our pipe. 01:03:46.560 --> 01:03:50.640 So we're going to go ahead, and call a function that we've seen already today, 01:03:50.640 --> 01:03:57.060 love.graphics.draw We're going to use the pipe image up above, 01:03:57.060 --> 01:04:02.040 and then we're going to go ahead, and use self.x, 01:04:02.040 --> 01:04:07.080 and self.y, and that's all we need for our pipe. 01:04:07.080 --> 01:04:09.949 And let me make sure that that's all we really need. 01:04:09.949 --> 01:04:12.990 So in main.lua-- we've got to go back to main.lua too because we actually 01:04:12.990 --> 01:04:14.198 have to start spawning pipes. 01:04:17.790 --> 01:04:20.990 So let's go ahead, and go to-- 01:04:20.990 --> 01:04:23.790 let me pull up my code here one more time. 01:04:26.540 --> 01:04:30.510 In main-- so on line 59-- 01:04:30.510 --> 01:04:31.910 sorry, you won't see it. 01:04:31.910 --> 01:04:34.200 You'll see it in line 59 in the actual distro code, 01:04:34.200 --> 01:04:38.460 but for me, it's going to be slightly different. 01:04:38.460 --> 01:04:41.210 We're going to go ahead, and create a new table 01:04:41.210 --> 01:04:44.060 to keep track of all the pipes that we want to spawn because we 01:04:44.060 --> 01:04:45.518 need a way to store them in memory. 01:04:45.518 --> 01:04:48.080 We can't just set one variable to-- 01:04:48.080 --> 01:04:53.690 basically, almost like a dynamic array in this case, or a linked list rather. 01:04:53.690 --> 01:04:55.844 We're going to use this table just to hold them. 01:04:55.844 --> 01:04:57.260 We're not going to give them keys. 01:04:57.260 --> 01:04:59.270 We're just going to insert them like we would 01:04:59.270 --> 01:05:03.830 do with just a linked list like in Python, for example. 01:05:03.830 --> 01:05:07.880 We're going to go ahead, and what do we need 01:05:07.880 --> 01:05:13.295 to do if we want to have them spawn after a certain period of time? 01:05:17.030 --> 01:05:19.000 Probably want to have some sort of timer. 01:05:19.000 --> 01:05:21.410 We want to keep track of how much time has passed, 01:05:21.410 --> 01:05:23.720 and maybe have some sort of amount of time 01:05:23.720 --> 01:05:26.340 that's our trigger to spawn of a pipe. 01:05:26.340 --> 01:05:28.290 Let's say maybe 2 seconds. 01:05:28.290 --> 01:05:32.700 So if we set a timer to 0, it's just start just at 0, 01:05:32.700 --> 01:05:35.390 but we can add to this frame by frame. 01:05:35.390 --> 01:05:40.370 We can just increase this timer by delta time, whatever that is, frame by frame. 01:05:40.370 --> 01:05:42.560 It'll be about 1/60 of a second. 01:05:42.560 --> 01:05:47.600 So after 60 frames have passed, we'll get one second. 01:05:47.600 --> 01:05:50.480 After 120 frames have passed, we'll have two seconds. 01:05:50.480 --> 01:05:54.180 At that point, we can then decide OK, now it's time to spawn a new pipe. 01:05:54.180 --> 01:05:56.100 Let's go ahead and do that. 01:05:56.100 --> 01:06:06.780 So I'm going to go ahead, and in our update function, 01:06:06.780 --> 01:06:12.900 we want to handle the actual increasing of this timer. 01:06:12.900 --> 01:06:16.290 So it's as simple as-- 01:06:16.290 --> 01:06:18.915 and I make sure that I called it spawnTimer. 01:06:18.915 --> 01:06:20.130 No, I just called it timer. 01:06:20.130 --> 01:06:21.755 Let's go ahead, and call it spawnTimer. 01:06:21.755 --> 01:06:24.670 Be a little more specific about what we want here. 01:06:24.670 --> 01:06:25.702 So our spawnTimer. 01:06:25.702 --> 01:06:27.660 And then we're going to go ahead in our update, 01:06:27.660 --> 01:06:34.740 and set spawnTimer equal to spawnTimer plus delta time. 01:06:34.740 --> 01:06:39.390 And then what we need to do is then check 01:06:39.390 --> 01:06:42.277 is our spawn timer greater than-- because it 01:06:42.277 --> 01:06:44.610 keeps track of time in seconds, delta time will give you 01:06:44.610 --> 01:06:45.990 a fractional amount in seconds. 01:06:45.990 --> 01:06:49.510 So it will be at 0.013, or something like that. 01:06:49.510 --> 01:06:54.120 We want to keep track of whether spawnTimer has gone past two, right? 01:06:54.120 --> 01:07:02.580 So if spawnTimer is greater than 2, we want to add a new pipe. 01:07:02.580 --> 01:07:05.840 Does anybody remember the function for how to add to a table in Lua? 01:07:09.450 --> 01:07:12.520 So it's table.insert. 01:07:12.520 --> 01:07:15.600 So table.insert will take in a table. 01:07:15.600 --> 01:07:19.770 So in this case, we want the pipes table that we allocated before. 01:07:19.770 --> 01:07:23.450 And then we're going to put in a new pipe object. 01:07:23.450 --> 01:07:27.040 This is how you instantiate an object for call, parentheses. 01:07:27.040 --> 01:07:29.480 That will have the effect of now our pipes 01:07:29.480 --> 01:07:33.094 table is going to-- every time we call this, it's going to get a new index. 01:07:33.094 --> 01:07:34.260 So it's going to start at 1. 01:07:34.260 --> 01:07:37.222 Lua tables are indexed at 1. 01:07:37.222 --> 01:07:39.320 The first time it happens, index1 is going 01:07:39.320 --> 01:07:42.810 to be equal to a new pipe object, which is going to start 01:07:42.810 --> 01:07:45.530 its xy at the edge of the screen. 01:07:45.530 --> 01:07:49.430 Then index2 will be the exact same thing, a new pipe that's 01:07:49.430 --> 01:07:52.370 at the edge of the screen, and so on, and so forth every time we 01:07:52.370 --> 01:07:53.540 call table.insert. 01:07:56.180 --> 01:07:59.750 Once our spawn Timer has exceeded 2, if we want this to not 01:07:59.750 --> 01:08:06.020 spawn a pipe every frame here after, which would quickly clog up our world, 01:08:06.020 --> 01:08:07.880 we want to reset our spawn timer to 0. 01:08:07.880 --> 01:08:11.360 So this will have the effect of now, it's going to wait another 2 seconds, 01:08:11.360 --> 01:08:13.457 and then this condition will be true again, 01:08:13.457 --> 01:08:15.290 and then we can add a new pipe to the scene. 01:08:19.180 --> 01:08:23.654 Let's go ahead and look at-- 01:08:23.654 --> 01:08:29.240 we're going to need to add a new set of logic here. 01:08:29.240 --> 01:08:39.040 Actually, I'm going to put all of this above the bird.update, 01:08:39.040 --> 01:08:42.119 and then below that, I'm going to go ahead, and do-- 01:08:42.119 --> 01:08:43.910 I'm not sure if we've covered this already. 01:08:43.910 --> 01:08:46.835 I don't think we have, but if we want to iterate over a table, 01:08:46.835 --> 01:08:48.960 there's a function that Lua gives you called pairs. 01:08:48.960 --> 01:08:52.750 It will give you all the key value pairs of a table 01:08:52.750 --> 01:08:55.149 that you can then use while you're iterating over it. 01:08:55.149 --> 01:08:57.700 Similar to enumerate in Python, if familiar, 01:08:57.700 --> 01:09:02.120 except this will actually give you the keys rather than just the indices. 01:09:02.120 --> 01:09:11.890 So we can do for k, pipe in pairs of pipes do some body of code, 01:09:11.890 --> 01:09:14.890 and then we have access to the key and the pipe within this. 01:09:14.890 --> 01:09:17.510 We can just iterate over it, and use it. 01:09:17.510 --> 01:09:20.580 So the first thing we want to do is we want to update our pipe. 01:09:20.580 --> 01:09:22.330 So for each pipe, update it. 01:09:22.330 --> 01:09:26.770 Give it the delta time of the current frame. 01:09:26.770 --> 01:09:28.929 And then what was the other important feature? 01:09:28.929 --> 01:09:30.970 So this will have the effect of scrolling it now. 01:09:30.970 --> 01:09:33.370 It's going to get its x shifted, but what 01:09:33.370 --> 01:09:36.861 was the other important thing we needed to do with every pipe in our scene? 01:09:36.861 --> 01:09:42.410 AUDIENCE: When x is less than 0, we have to [INAUDIBLE] 01:09:42.410 --> 01:09:44.090 COLTON OGDEN: Yes. 01:09:44.090 --> 01:09:45.600 That is exactly true. 01:09:45.600 --> 01:09:50.680 So what we're going to do is if pipe.x is less than-- 01:09:50.680 --> 01:09:55.331 so if we did less than 0, what do we think would happen? 01:09:55.331 --> 01:09:58.850 AUDIENCE: [INAUDIBLE] 01:09:58.850 --> 01:10:01.940 COLTON OGDEN: We would see it instantly disappear because they're 01:10:01.940 --> 01:10:03.590 based on the top left coordinates. 01:10:03.590 --> 01:10:07.830 So what we need to do is keep track of its width. 01:10:07.830 --> 01:10:09.980 So what we'll do is we'll just say if pipe.x 01:10:09.980 --> 01:10:15.500 is less than negative pipe.width, which will allow the pipe 01:10:15.500 --> 01:10:19.860 to go all the way past the edge of the screen, 01:10:19.860 --> 01:10:23.450 we'll call a function called table.remove, which takes a table, 01:10:23.450 --> 01:10:26.600 in this case, pipes, and then it takes a key. 01:10:26.600 --> 01:10:29.480 And the key we have access to up above on line 124. 01:10:29.480 --> 01:10:32.090 We can just say k, and that will have the effect 01:10:32.090 --> 01:10:36.560 of removing that pipe from the scene. 01:10:36.560 --> 01:10:40.370 And then as soon as that's done, we're good to go. 01:10:40.370 --> 01:10:42.817 The last thing that we need to do is currently, 01:10:42.817 --> 01:10:44.900 we're not actually drawing the pipe to the screen. 01:10:44.900 --> 01:10:50.750 So down below in our render function, we're going to go ahead, and up above-- 01:10:50.750 --> 01:10:55.310 before we do the ground, because if we do it normally-- 01:10:55.310 --> 01:10:57.764 if we do it after we render the ground, the pipes 01:10:57.764 --> 01:11:00.680 are going look like they're just kind of layered on top of the ground. 01:11:00.680 --> 01:11:03.620 We want it to look as if they're sticking out from the ground. 01:11:03.620 --> 01:11:06.830 So what we want to do is have a correct render layer, 01:11:06.830 --> 01:11:08.780 a render draw order to the screen. 01:11:08.780 --> 01:11:11.810 We draw the background, we draw the pipes, then we draw the ground, 01:11:11.810 --> 01:11:14.120 and this will have the effect of looking as if the pipes are sticking out 01:11:14.120 --> 01:11:15.140 of the ground. 01:11:15.140 --> 01:11:18.170 So what we'll do is we'll do the exact same thing we just did up 01:11:18.170 --> 01:11:31.050 above by saying for k, pipe in pairs of pipes do pipe, 01:11:31.050 --> 01:11:33.480 and then the render function that we defined in pipe. 01:11:33.480 --> 01:11:35.230 And this will have the effect of iterating 01:11:35.230 --> 01:11:37.530 through all the pipes in our scene every draw call, 01:11:37.530 --> 01:11:41.160 and drawing them before it draws the ground, and before it draws the bird, 01:11:41.160 --> 01:11:44.534 and that should be all that we need to illustrate this example. 01:11:44.534 --> 01:11:46.075 Let me make sure everything is saved. 01:11:46.075 --> 01:11:50.660 I'm going to go ahead, and go into bird5. 01:11:50.660 --> 01:11:56.610 If I did everything correctly, this should, after a certain period of time, 01:11:56.610 --> 01:11:59.820 drop pipes to the screen that are scrolling, and they're randomized. 01:11:59.820 --> 01:12:02.760 Their y value is getting set to some value 01:12:02.760 --> 01:12:05.610 between the top quarter of the screen. 01:12:05.610 --> 01:12:09.190 So starting about right where Flappy Bird is right now, down 01:12:09.190 --> 01:12:12.017 about 10 pixels above the width of the screen, which actually, 01:12:12.017 --> 01:12:13.350 that looks like 10 pixels above. 01:12:13.350 --> 01:12:14.570 So that's a slight bug. 01:12:14.570 --> 01:12:18.030 It should probably be something along the lines of 30 or 40. 01:12:18.030 --> 01:12:19.890 We won't encounter that in the final distro 01:12:19.890 --> 01:12:22.980 because they're not set to spawn that low, 01:12:22.980 --> 01:12:27.510 but you can see how this is sort of the beginning of our procedural level 01:12:27.510 --> 01:12:31.480 generation system, and we have most all the components of our scene. 01:12:31.480 --> 01:12:35.040 Now, normally in Flappy Bird, we have two pipes. 01:12:35.040 --> 01:12:38.730 We have a pipe that's above, and then a pipe that's below, 01:12:38.730 --> 01:12:39.666 and they're in pairs. 01:12:39.666 --> 01:12:42.540 In the next example, we're actually going to start illustrating this. 01:12:42.540 --> 01:12:45.360 We're going to have pairs of pipes that are joined together, 01:12:45.360 --> 01:12:46.620 which scroll together. 01:12:46.620 --> 01:12:51.240 That once you fly through them, you score a point. 01:12:51.240 --> 01:12:53.130 But for now, we have all the pieces that we 01:12:53.130 --> 01:13:00.310 need in order to have the basic visual sense of the game completed. 01:13:00.310 --> 01:13:04.450 We're going to take like a five minute break now, and then once we come back, 01:13:04.450 --> 01:13:07.850 we'll actually dive into how we can get pairs of pipes into our scene, 01:13:07.850 --> 01:13:10.790 and start getting into scoring, and some other fun things like music. 01:13:14.050 --> 01:13:15.250 All right, welcome back. 01:13:15.250 --> 01:13:16.600 So the next part-- 01:13:16.600 --> 01:13:20.790 so before we establish the bird, the background, the pipes, 01:13:20.790 --> 01:13:23.940 we have all the visual aspects of our game ready to go. 01:13:23.940 --> 01:13:26.190 The next important piece of the puzzle to really solve 01:13:26.190 --> 01:13:29.550 is how can we start scoring our game, and also how can we 01:13:29.550 --> 01:13:33.330 get the pipes matching the way that they're implemented in the actual game? 01:13:33.330 --> 01:13:38.430 Which, recall, they're normally in pairs, as illustrated here. 01:13:38.430 --> 01:13:41.430 And we also see on the right-hand side, as we've covered already so far, 01:13:41.430 --> 01:13:44.130 we have the spawn zone for our pipes, and on the left, 01:13:44.130 --> 01:13:46.050 we have what I've labeled the dead zone, where 01:13:46.050 --> 01:13:49.980 pipes are de-instantiated once they've gone 01:13:49.980 --> 01:13:52.410 past the negative width of themselves. 01:13:52.410 --> 01:13:55.380 But pipes come in pairs, they get shifted, 01:13:55.380 --> 01:13:58.941 and once the bird flies between these gaps, 01:13:58.941 --> 01:14:00.690 is ultimately when they've scored a point. 01:14:00.690 --> 01:14:07.620 And so we need a way to pair pipes together, and define this logic for how 01:14:07.620 --> 01:14:11.010 can we tell whether the bird has gone past the gap, 01:14:11.010 --> 01:14:14.500 and whether or not the pipes have been de-instantiated. 01:14:14.500 --> 01:14:17.760 So we're going to go ahead, and I'm going to probably 01:14:17.760 --> 01:14:20.760 stop live coding for the rest of the demonstrations 01:14:20.760 --> 01:14:23.730 because they're going to be a little bit more complex. 01:14:23.730 --> 01:14:28.190 But I believe my code editor is over here. 01:14:28.190 --> 01:14:30.690 I'm going to go ahead, and open up-- 01:14:30.690 --> 01:14:32.680 oh, this is my other editor. 01:14:32.680 --> 01:14:37.030 So in the base repo now, we're going to go ahead, and look at the full example. 01:14:37.030 --> 01:14:40.930 So in bird6, which is the pipe pair update-- 01:14:40.930 --> 01:14:44.440 our current subfolder that we're looking at-- 01:14:44.440 --> 01:14:45.660 we're going to start in main. 01:14:45.660 --> 01:14:51.390 So on line 33 in main. 01:14:51.390 --> 01:14:53.820 We can see that we're acquiring pipe pair, which 01:14:53.820 --> 01:14:55.440 is a new class we're defining. 01:14:55.440 --> 01:14:58.350 We're taking the pipe that we had before, 01:14:58.350 --> 01:15:00.500 and we're creating a new composite class. 01:15:00.500 --> 01:15:04.440 So we're going to take a class that encapsulates two pipes together, 01:15:04.440 --> 01:15:08.400 a pair of pipes, and we're going to use this to think about our problem more 01:15:08.400 --> 01:15:10.050 abstractly than we already are. 01:15:10.050 --> 01:15:13.669 And this layering of abstractions is a very important concept 01:15:13.669 --> 01:15:15.960 in computer science, generally speaking, but especially 01:15:15.960 --> 01:15:19.470 in games where you might have objects that are composites of objects that 01:15:19.470 --> 01:15:22.350 are composites of objects, and these abstract hierarchies are 01:15:22.350 --> 01:15:24.600 sort of what keeps programmers sane when dealing 01:15:24.600 --> 01:15:28.800 with such large levels of-- when you have thousands of lines of code, 01:15:28.800 --> 01:15:32.550 it's sort of the only way you can really make sense of it. 01:15:32.550 --> 01:15:35.430 So on line 65, if we look-- 01:15:35.430 --> 01:15:39.060 now, instead of a table that's called pipes, we've renamed it to pipe pairs. 01:15:39.060 --> 01:15:41.670 We're no longer going to store individual pipes in our scene. 01:15:41.670 --> 01:15:44.100 We're going to take these pipe pairs that we're 01:15:44.100 --> 01:15:51.270 going to create, and store them in our table as well as individual units. 01:15:51.270 --> 01:15:56.390 On line 71, we need a variable to keep track of the-- 01:15:56.390 --> 01:15:58.500 we're calling it last y. 01:15:58.500 --> 01:16:00.930 The purpose of this variable is so that we 01:16:00.930 --> 01:16:04.620 can keep track of where the last set of pipes spawned their gap. 01:16:04.620 --> 01:16:08.060 Because if we made our gaps completely random, 01:16:08.060 --> 01:16:12.840 it will have the effect of not looking continuous for one, 01:16:12.840 --> 01:16:15.450 and also potentially being impossible to beat. 01:16:15.450 --> 01:16:19.580 We want some sort of smooth contour to our gaps 01:16:19.580 --> 01:16:21.640 so that we can fly through them reasonably, 01:16:21.640 --> 01:16:24.810 and that it looks as if it was almost pre-made, and smooth. 01:16:24.810 --> 01:16:27.270 So we're going to keep track of a variable called last y. 01:16:27.270 --> 01:16:30.180 We're going to start it off at negative pipe height. 01:16:30.180 --> 01:16:37.830 So up past the top of the screen, plus some sort of value between 1 and 80 01:16:37.830 --> 01:16:39.030 and 20. 01:16:39.030 --> 01:16:42.340 It's going to be roughly towards the top of the screen. 01:16:42.340 --> 01:16:45.900 And this is important because the last y is 01:16:45.900 --> 01:16:49.976 going to be-- we're going to end up flipping our sprite. 01:16:49.976 --> 01:16:55.020 A flip on the y-axis has the result of the sprite looking 01:16:55.020 --> 01:16:59.580 as if it's gone its whole height above where its actual y is, 01:16:59.580 --> 01:17:02.400 and we'll see in more detail, shortly, why 01:17:02.400 --> 01:17:05.690 this ends up working the way it does. 01:17:05.690 --> 01:17:15.150 We're going to go down to line 132, and in our condition, 01:17:15.150 --> 01:17:20.220 if our spawnTimer is greater than 2, what we're going to do 01:17:20.220 --> 01:17:22.830 is this is where we spawned our pipes before, 01:17:22.830 --> 01:17:26.070 but now we're spawning pairs of pipes. 01:17:26.070 --> 01:17:29.460 So we're going to set a local variable y. 01:17:29.460 --> 01:17:31.230 It's going to be-- 01:17:31.230 --> 01:17:34.050 this is the clamp operation that we talked about last week 01:17:34.050 --> 01:17:38.730 using math.max and math.min to apply some sort of operation. 01:17:38.730 --> 01:17:42.060 In this case, we're going to add a random value between negative 20 01:17:42.060 --> 01:17:44.850 and 20 to whatever our last y value was, which 01:17:44.850 --> 01:17:49.690 is going to shift the gap effectively by negative 20 or 20 pixels. 01:17:49.690 --> 01:17:52.530 We're going to clamp it between negative pipe height plus 10. 01:17:52.530 --> 01:17:56.100 So about 10 pixels from the top of the screen, 01:17:56.100 --> 01:18:02.520 and then we're going to set the upper bound to virtual height minus 90 01:18:02.520 --> 01:18:03.420 minus pipe height. 01:18:03.420 --> 01:18:07.800 And this minus pipe light is only because we're doing a flip operation 01:18:07.800 --> 01:18:09.750 on our y-axis for our sprite. 01:18:09.750 --> 01:18:11.850 I'll go into it in a little bit more detail 01:18:11.850 --> 01:18:14.880 to try to make it clear as to why we're doing it, 01:18:14.880 --> 01:18:17.760 and maybe I'll take out some codes to illustrate what it looks 01:18:17.760 --> 01:18:20.190 like without that operation applied. 01:18:20.190 --> 01:18:23.400 But basically, it has the effect of 90 pixels from the bottom 01:18:23.400 --> 01:18:26.550 is where the gap could spawn. 01:18:26.550 --> 01:18:29.540 So basically, the pipe at the very bottom. 01:18:29.540 --> 01:18:31.600 Recall that this gap is where-- 01:18:31.600 --> 01:18:35.370 this value is where the gap itself begins, not necessarily 01:18:35.370 --> 01:18:38.680 where the pipe starts. 01:18:38.680 --> 01:18:42.690 It'll be between negative pipe height plus 10. 01:18:42.690 --> 01:18:47.820 Basically, effectively, between 10 pixels from the top of the screen, 01:18:47.820 --> 01:18:50.560 between 90 pixels from the bottom of the screen, 01:18:50.560 --> 01:18:55.530 and then we're going to apply a random permutation of this value. 01:18:55.530 --> 01:18:58.590 We're going to add some value between negative 20 and 20, 01:18:58.590 --> 01:19:03.000 and that will give us a contour, and it'll be a randomized contour. 01:19:06.540 --> 01:19:09.960 Line 136, we have pipe pairs. 01:19:09.960 --> 01:19:14.070 Table insert into that instead of pipes, and we're just adding a new pipe pair, 01:19:14.070 --> 01:19:18.030 and we're setting it to the value y. 01:19:18.030 --> 01:19:21.820 And then the pipe pair takes in a y value, 01:19:21.820 --> 01:19:23.930 and that will be where the start of the gap is. 01:19:23.930 --> 01:19:25.980 And what this will have the effect of doing 01:19:25.980 --> 01:19:29.790 is it's going to flip a sprite above the gap 01:19:29.790 --> 01:19:32.880 so that we have a pipe right above where the gap starts, and then it's 01:19:32.880 --> 01:19:37.380 going to draw another pipe unflipped about 90 pixels below that, 01:19:37.380 --> 01:19:39.735 and that will be how it puts the two together. 01:19:42.850 --> 01:19:49.380 Line 144 is a loop that just updates our pairs instead of our pipes. 01:19:49.380 --> 01:19:55.110 So all we've done here is just renamed it from pipe to pair, and instead 01:19:55.110 --> 01:19:57.030 of pipes, we're using pipe pairs. 01:19:57.030 --> 01:20:01.390 We are doing the same exact thing here on line 153. 01:20:01.390 --> 01:20:05.490 We've done for k pair in pairs of pipe pairs. 01:20:05.490 --> 01:20:08.310 And then line 150-- 01:20:08.310 --> 01:20:12.210 sorry, line 175 is where we are-- 01:20:12.210 --> 01:20:17.790 sorry, 170 is where we are rendering each pair instead of each pipe. 01:20:17.790 --> 01:20:22.980 And so if we open up pipe pair here, we can take a look 01:20:22.980 --> 01:20:25.060 at this class from scratch. 01:20:25.060 --> 01:20:26.700 So it's a new class. 01:20:26.700 --> 01:20:29.122 We're going to set our gap height to 90 pixels, 01:20:29.122 --> 01:20:31.080 and so this is just some arbitrary value that I 01:20:31.080 --> 01:20:33.879 felt was a pretty fair value in terms of size, 01:20:33.879 --> 01:20:35.670 but you can tune this to whatever you want. 01:20:35.670 --> 01:20:37.570 You could set this to-- 01:20:37.570 --> 01:20:40.520 if you want to be really cruel, you could set it to something like 50. 01:20:40.520 --> 01:20:42.420 Or if you wanted to be really generous to the player, 01:20:42.420 --> 01:20:44.430 you could set it to something like 150, and make 01:20:44.430 --> 01:20:46.410 it fairly easy for them to get through. 01:20:46.410 --> 01:20:50.430 Or as part of the assignment, you could randomize it 01:20:50.430 --> 01:20:54.420 so that it varies pair by pair, and you get more 01:20:54.420 --> 01:20:56.445 of an organic looking obstacle course. 01:20:56.445 --> 01:20:59.010 It's still shifted by negative 20 to 20 pixels, 01:20:59.010 --> 01:21:02.939 but now your gap varies, and you can also randomize the shift amount 01:21:02.939 --> 01:21:03.980 if you wanted to as well. 01:21:03.980 --> 01:21:05.460 Let's say you wanted-- 01:21:05.460 --> 01:21:08.970 maybe you want the gaps to be up to 40 pixels 01:21:08.970 --> 01:21:11.190 difference instead of 20 pixels difference 01:21:11.190 --> 01:21:12.755 on negative and positive value. 01:21:12.755 --> 01:21:14.130 You could easily do that as well. 01:21:16.840 --> 01:21:20.760 On line 18 we're just setting our x to just like 01:21:20.760 --> 01:21:23.010 we did before, virtual width plus 32. 01:21:23.010 --> 01:21:24.990 So we're setting it to the-- 01:21:24.990 --> 01:21:27.090 actually, before we just set it to virtual width. 01:21:27.090 --> 01:21:29.880 Now we're setting it to virtual width plus 32. 01:21:29.880 --> 01:21:31.474 Both are pretty much equal. 01:21:31.474 --> 01:21:33.390 This will just give it a little bit of a delay 01:21:33.390 --> 01:21:36.780 before it ends up going onto the screen, but you can effectively just 01:21:36.780 --> 01:21:38.190 do this, virtual width. 01:21:41.680 --> 01:21:47.130 On the next line, 24, this is where we sort of bundled together 01:21:47.130 --> 01:21:51.300 the pipes that we're going to end up actually rendering and updating 01:21:51.300 --> 01:21:52.100 to the screen. 01:21:52.100 --> 01:21:55.560 Instead of having just one pipe, a pipe pair is two pipes. 01:21:55.560 --> 01:21:58.090 We can easily put this together in a table. 01:21:58.090 --> 01:21:59.860 So we'll just create self.pipes. 01:21:59.860 --> 01:22:03.420 We'll set it to a table that has two keys, upper and lower, 01:22:03.420 --> 01:22:06.390 and the upper pipe is just a pipe. 01:22:06.390 --> 01:22:08.490 And notice one thing is different about pipe. 01:22:08.490 --> 01:22:10.890 Now, before, it took no arguments. 01:22:10.890 --> 01:22:12.180 It was just a regular pipe. 01:22:12.180 --> 01:22:13.740 Pipes had their own logic. 01:22:13.740 --> 01:22:15.600 They set their own x and y. 01:22:15.600 --> 01:22:19.620 They didn't need any sort of parametrization beyond that. 01:22:19.620 --> 01:22:22.750 It was all taken care of for them randomly. 01:22:22.750 --> 01:22:24.910 Now, they take a string. 01:22:24.910 --> 01:22:28.140 So this top string means that this would be a top pipe. 01:22:28.140 --> 01:22:30.330 So that means that if this pipe is a top pipe, 01:22:30.330 --> 01:22:33.840 there's probably going to be logic in pipe that now checks 01:22:33.840 --> 01:22:35.940 to see whether it's top or bottom. 01:22:35.940 --> 01:22:39.180 If it's top, then we need to render it upside down. 01:22:39.180 --> 01:22:44.400 We need to flip it along the y-axis, and then we're going to set it to self.y. 01:22:47.160 --> 01:22:49.740 And recall that we set self.y-- 01:22:49.740 --> 01:22:52.510 we passed in self.y in main. 01:22:52.510 --> 01:22:54.390 Actually, I'm not sure if I touched on that. 01:22:54.390 --> 01:22:56.770 Let's go back to main here. 01:22:56.770 --> 01:22:58.153 So if we go to-- 01:23:02.320 --> 01:23:07.580 I need to figure out where I actually instantiate the pipes. 01:23:07.580 --> 01:23:11.090 Here on line 136, after we've calculated where 01:23:11.090 --> 01:23:13.220 we want the gap to be for this pipe pair, 01:23:13.220 --> 01:23:18.170 we're going to go ahead, and insert into pipe pairs, a pipe pair at y. 01:23:18.170 --> 01:23:24.950 Y was the calculation between-- we basically took the last y value, 01:23:24.950 --> 01:23:29.570 the last gap that we instantiated, and then shifted it by some negative 20 01:23:29.570 --> 01:23:33.500 to 20 pixels randomly, and made sure it didn't go above or beyond-- 01:23:33.500 --> 01:23:35.150 above or below the edges of the screen. 01:23:38.570 --> 01:23:45.770 Back in pipe pair, we're going to go ahead, and look at line 30. 01:23:45.770 --> 01:23:49.250 I'm sorry, actually let's take a look a little bit more closely here 01:23:49.250 --> 01:23:50.840 at line 26. 01:23:50.840 --> 01:23:53.360 So upper gets top and self.y. 01:23:53.360 --> 01:23:59.250 That's where the gap is, and the sprite is going to be flipped upon that value. 01:23:59.250 --> 01:24:02.990 The lower value is going to be a shift of that. 01:24:02.990 --> 01:24:09.880 So the lower sprite needs to spawn below the top pipe by the gap amount 01:24:09.880 --> 01:24:12.492 so that the two are top to bottom, but there 01:24:12.492 --> 01:24:14.450 needs to be that space between the two of them. 01:24:14.450 --> 01:24:17.840 So we need to take that pipe, shift it down, and then draw the next pipe. 01:24:17.840 --> 01:24:25.760 So we're going to take self.y plus pipe height plus gap height, 01:24:25.760 --> 01:24:29.630 and that'll have the effect-- remember, gap height was 90 pixels. 01:24:29.630 --> 01:24:36.300 The pipe height is a result of flipping the y-axis, 01:24:36.300 --> 01:24:41.160 and having to shift it down the actual position. 01:24:41.160 --> 01:24:48.085 So if we go back to line 30. 01:24:51.241 --> 01:24:56.890 This is an interesting illustration of what 01:24:56.890 --> 01:25:00.072 happens when you edit a table while you're iterating over a table, 01:25:00.072 --> 01:25:01.780 and I'll show you this in detail shortly. 01:25:01.780 --> 01:25:05.620 But basically, on line 30, we're setting a flag called remove to false. 01:25:05.620 --> 01:25:09.170 And what this is going to do is before we were just destroying the objects. 01:25:09.170 --> 01:25:11.920 Whenever it got past the edge of the screen, we just destroyed it. 01:25:11.920 --> 01:25:15.580 But if we're iterating over a table of values, 01:25:15.580 --> 01:25:22.810 let's say a table of pipe pairs, when you do a removal in most programming 01:25:22.810 --> 01:25:28.870 language-- in Lua, when you do a removal of a table value, and it's not indexed, 01:25:28.870 --> 01:25:33.640 or it's non-keyed, which means that it's indexed by numerical indices, 01:25:33.640 --> 01:25:36.470 this will shift every other value down. 01:25:36.470 --> 01:25:38.470 And so when you're iterating it, and you shift 01:25:38.470 --> 01:25:41.530 everything down, the value you are currently manipulating, 01:25:41.530 --> 01:25:45.970 let's say it's equal to 1, if you remove that value, 01:25:45.970 --> 01:25:48.760 you shift everything beyond it down by 1. 01:25:48.760 --> 01:25:50.650 But then you're going to increment up to 2, 01:25:50.650 --> 01:25:55.510 and you're skipping over what was previously just 2, and is now 1. 01:25:55.510 --> 01:25:59.160 So you're effectively skipping over one of your entries, 01:25:59.160 --> 01:26:02.560 and it has buggy behavior in a lot of scenarios. 01:26:02.560 --> 01:26:06.070 In this case, it causes the graphics to glitch a little bit 01:26:06.070 --> 01:26:10.090 because it doesn't apply a pixel shift on one frame, 01:26:10.090 --> 01:26:16.840 and so whenever a pipe gets removed-- and I can actually show this visually, 01:26:16.840 --> 01:26:20.292 the first pipe left after that pipe gets removed 01:26:20.292 --> 01:26:22.000 ends up moving a little bit to the right, 01:26:22.000 --> 01:26:26.120 and so you get weird pipes shifting to the left of the bird on each frame. 01:26:26.120 --> 01:26:29.380 So whenever you edit a table in place, make 01:26:29.380 --> 01:26:32.100 sure not to delete while you're iterating over it. 01:26:32.100 --> 01:26:34.870 It's going to cause buggy behavior. 01:26:34.870 --> 01:26:40.030 And like I said, I'll illustrate this for you very shortly. 01:26:40.030 --> 01:26:46.000 On line 36, we are performing the update logic. 01:26:46.000 --> 01:26:50.740 Now, a pipe pair has two pipes, each with their own render components, 01:26:50.740 --> 01:26:52.480 and their own positions. 01:26:52.480 --> 01:26:55.030 We're using the code that we wrote before for pipe, 01:26:55.030 --> 01:26:57.370 and we're going to try to expand upon it a little bit. 01:26:57.370 --> 01:27:04.200 So we still wanted to defer a lot of that code to the pipe class, 01:27:04.200 --> 01:27:10.060 and we want to update the pipes based on whether-- 01:27:10.060 --> 01:27:13.630 We want to still keep track of their own x, and their render functions, 01:27:13.630 --> 01:27:18.310 and so we're going to see, basically, if our pipe pair 01:27:18.310 --> 01:27:22.300 x is greater than negative pipe width, which is the same exact logic that we 01:27:22.300 --> 01:27:24.670 were using before. 01:27:24.670 --> 01:27:33.160 Set our own x to that minus pipe speed times 01:27:33.160 --> 01:27:35.800 delta time, which is the same operation we were doing before. 01:27:35.800 --> 01:27:42.320 But we are also editing the x of our self.pipes lower and upper, 01:27:42.320 --> 01:27:45.910 and this will allow us to-- on line 46-- 01:27:45.910 --> 01:27:47.920 render the pipes just as we were doing before 01:27:47.920 --> 01:27:51.440 because they're getting their x values updated just as they were before. 01:27:51.440 --> 01:27:55.180 So we're effectively deferring the render phase to our pipes, 01:27:55.180 --> 01:27:58.820 and not really needing to add any additional logic for that in our code. 01:28:01.360 --> 01:28:04.830 We've made changes to pipe.lua as well, so I'm 01:28:04.830 --> 01:28:08.910 going to go ahead, and open up pipe here. 01:28:14.710 --> 01:28:20.710 And we've set the height and width of it as contents here. 01:28:20.710 --> 01:28:23.020 So pipe height gets 288, and then happens 01:28:23.020 --> 01:28:24.640 to be about the size of the screen. 01:28:24.640 --> 01:28:25.750 Pipe width gets 70. 01:28:28.270 --> 01:28:32.230 On 31, we're sitting self.orientation gets orientation. 01:28:32.230 --> 01:28:34.420 Notice our init function, which was previously just 01:28:34.420 --> 01:28:38.710 empty, it took no parameters, now takes an orientation, and it takes a y value. 01:28:38.710 --> 01:28:44.980 The orientation is going to allow us to ask, basically, is our code 01:28:44.980 --> 01:28:46.190 a top or a bottom pipe? 01:28:46.190 --> 01:28:50.050 And if it's top pipe, we need to flip it, draw it, and shift it. 01:28:50.050 --> 01:28:53.800 If it's a bottom pipe, we're just going to draw it normal, 01:28:53.800 --> 01:28:58.240 and not perform any sort of fancy sprite flipping or anything like that. 01:28:58.240 --> 01:29:02.060 Down here in the render function is where this actually happens. 01:29:02.060 --> 01:29:05.620 So on line 39, we're drawing the pipe image as usual 01:29:05.620 --> 01:29:12.490 at x, but at y, because when you flip a sprite 01:29:12.490 --> 01:29:18.250 it ends up completely flipping the y-- it basically performs a mirror on it, 01:29:18.250 --> 01:29:20.020 but it not at 0,0. 01:29:20.020 --> 01:29:24.040 It basically shifts it up by pipe height amount. 01:29:24.040 --> 01:29:29.910 We need to keep track of that, and draw it at self.y plus pipe height. 01:29:29.910 --> 01:29:34.324 Because if we draw it at just self.y, because it's going to be mirrored, 01:29:34.324 --> 01:29:36.490 and it's going to get shifted by pipe height amount, 01:29:36.490 --> 01:29:38.760 it's going to be beyond the top edge of the screen. 01:29:38.760 --> 01:29:41.470 We need to account for that, account for the fact 01:29:41.470 --> 01:29:45.125 that we're flipping it on the y-axis, and bring it down. 01:29:45.125 --> 01:29:47.222 AUDIENCE: Where's the code where you flip it? 01:29:47.222 --> 01:29:49.930 COLTON OGDEN: The question is where is the code where we flip it. 01:29:49.930 --> 01:29:52.660 So that's actually here on this line. 01:29:52.660 --> 01:29:57.250 On this condition, we're saying if self.orientation is equal to top, 01:29:57.250 --> 01:29:58.350 then we want to-- 01:29:58.350 --> 01:30:02.722 so the parameters here, I'll comment this just for clarification. 01:30:02.722 --> 01:30:05.320 AUDIENCE: So the draw function has a flip function? 01:30:05.320 --> 01:30:07.240 COLTON OGDEN: It does, I'll show you here. 01:30:07.240 --> 01:30:09.610 So this 0, we've added a few new parameters 01:30:09.610 --> 01:30:11.950 to our love.graphics.drawfunction. 01:30:11.950 --> 01:30:13.120 Zero is rotation. 01:30:13.120 --> 01:30:14.930 We're not going to rotate it at all. 01:30:14.930 --> 01:30:17.560 This is the scale on the x-axis. 01:30:17.560 --> 01:30:21.590 So x scale, and this is the scale on the y-axis. 01:30:21.590 --> 01:30:24.070 So if we apply a scale operation of one, it's 01:30:24.070 --> 01:30:28.250 the same thing as doing no scale at all. 01:30:28.250 --> 01:30:30.230 It's going to draw it on the x-axis. 01:30:30.230 --> 01:30:31.640 It's just going draw it normally. 01:30:31.640 --> 01:30:39.340 But if it's top, if this pipe has been set to an orientation of top, 01:30:39.340 --> 01:30:41.710 we're going to set the scale to negative 1. 01:30:41.710 --> 01:30:43.540 When you said a sprite-- 01:30:43.540 --> 01:30:48.770 its scale factor to negative 1, it flips it along that axis, effectively. 01:30:48.770 --> 01:30:51.130 And so that's how you get mirroring. 01:30:51.130 --> 01:30:56.470 Most engines that allow you to apply scale operations to 2D textures or 2D 01:30:56.470 --> 01:31:01.120 sprites, a negative operation on an axis will mirror it on that axis, 01:31:01.120 --> 01:31:02.620 and so that's what we're doing here. 01:31:02.620 --> 01:31:05.680 So we're mirroring it if it's a top pipe, 01:31:05.680 --> 01:31:07.660 and we're also shifting its draw location 01:31:07.660 --> 01:31:10.710 as well because when we mirror it, it's going to-- 01:31:10.710 --> 01:31:12.970 at 0,0 it's going to do the same-- it's going 01:31:12.970 --> 01:31:17.112 to basically draw the same exact thing, but mirrored on the y-axis. 01:31:17.112 --> 01:31:18.070 So it's going to need-- 01:31:18.070 --> 01:31:21.100 if we want to draw at a given location, flipped-- 01:31:21.100 --> 01:31:23.410 still draw it at 0,0, but have it be flipped, 01:31:23.410 --> 01:31:27.040 we need to account for that flip, and shift it downwards if that makes sense. 01:31:29.740 --> 01:31:33.970 So that's essentially all that's involved there. 01:31:33.970 --> 01:31:38.150 And I think that's pretty much all of the code. 01:31:38.150 --> 01:31:42.100 So we have our pipes now that are being flipped. 01:31:42.100 --> 01:31:44.460 If its a top pipe, it's going to get drawn shifted. 01:31:44.460 --> 01:31:48.040 It's going to have its other pipe shifted down by that amount, 01:31:48.040 --> 01:31:54.100 and its y-axis is going to be increased by the gap height 01:31:54.100 --> 01:31:56.680 so that it gets drawn 90 pixels, however many pixels you 01:31:56.680 --> 01:31:59.260 want to set below that pipe. 01:31:59.260 --> 01:32:02.100 So we're going to go into demonstrate this. 01:32:02.100 --> 01:32:10.360 Go up to fifty bird, the actual repo now, and the actual distro code. 01:32:10.360 --> 01:32:13.510 I'm going to go into bird6, and I'm going to run it. 01:32:16.450 --> 01:32:19.750 And now we have pipes that are actually rendering. 01:32:19.750 --> 01:32:24.700 But we're missing a couple of important things. 01:32:24.700 --> 01:32:28.120 Foremost, among them being that now we don't have collision detection yet. 01:32:28.120 --> 01:32:30.430 So we can just fly through this course infinitely, 01:32:30.430 --> 01:32:35.110 but notice that they're being shifted by a random value between negative 20 01:32:35.110 --> 01:32:37.210 and 20 pixels. 01:32:37.210 --> 01:32:41.980 It looks more or less like it's being generated with some sort of goal 01:32:41.980 --> 01:32:42.780 in mind. 01:32:42.780 --> 01:32:44.199 It's not haphazard. 01:32:44.199 --> 01:32:46.240 It's not all over the place, but you could easily 01:32:46.240 --> 01:32:50.380 find ways to tweak this such that maybe the gap height is 01:32:50.380 --> 01:32:53.470 some value between 60 and 120. 01:32:53.470 --> 01:32:57.413 And so you have easy and difficult pipes, or maybe you have-- 01:32:57.413 --> 01:33:02.760 I think I'm so far below the screen that I can't even get back up anymore. 01:33:02.760 --> 01:33:06.490 Oh, OK, that's a physics error. 01:33:06.490 --> 01:33:10.570 When your value gets to a certain point, I think that's actually what it's doing 01:33:10.570 --> 01:33:13.839 is it's actually overflowing the value, and setting it to a negative. 01:33:13.839 --> 01:33:16.130 Or underflowing it, and setting it to a negative value, 01:33:16.130 --> 01:33:21.850 and then incrementing it because it's gotten so large. 01:33:21.850 --> 01:33:26.480 But you could easily modulate parameters such as the width between the pipes 01:33:26.480 --> 01:33:29.570 as you saw in the diagram before, or the height, 01:33:29.570 --> 01:33:32.030 or even the speed at which they move, and find 01:33:32.030 --> 01:33:37.880 ways to tune it to make game play that actually works for whatever goal 01:33:37.880 --> 01:33:40.230 you have in mind-- making it easier or more difficult. 01:33:40.230 --> 01:33:42.260 And that's actually a topic that they talked about in that article 01:33:42.260 --> 01:33:44.270 that I linked to before, where they generated levels 01:33:44.270 --> 01:33:46.561 programmatically, and then tested them programmatically 01:33:46.561 --> 01:33:50.410 to determine what makes level in Flappy Bird difficult or easy. 01:33:50.410 --> 01:33:55.190 And so basically, those are the parameters 01:33:55.190 --> 01:33:58.160 you need to way as you're thinking of procedural generation. 01:33:58.160 --> 01:34:00.259 And procedural generation, ultimately, is just 01:34:00.259 --> 01:34:02.300 taking values that you construct your scene with, 01:34:02.300 --> 01:34:05.460 and just finding ways to just manipulate them randomly. 01:34:05.460 --> 01:34:11.780 Math.random some value, and that's how you make random levels in a nutshell. 01:34:11.780 --> 01:34:16.344 Making good random levels is another question. 01:34:16.344 --> 01:34:18.719 AUDIENCE: This guy made a lot of money doing very little. 01:34:18.719 --> 01:34:19.927 COLTON OGDEN: He did, he did. 01:34:19.927 --> 01:34:22.430 There was a big controversy around this game back in 2013. 01:34:22.430 --> 01:34:25.580 AUDIENCE: He had like a nervous breakdown, too, right? 01:34:25.580 --> 01:34:27.913 COLTON OGDEN: I don't know if I read too much into that, 01:34:27.913 --> 01:34:31.310 but I was doing a little bit of research, and was reading about some 01:34:31.310 --> 01:34:32.890 of that stuff. 01:34:32.890 --> 01:34:35.300 I've got to give him props for banking on that. 01:34:38.692 --> 01:34:39.650 Now we have pipe pairs. 01:34:39.650 --> 01:34:42.920 That's arguably the most complex part of the program 01:34:42.920 --> 01:34:46.740 because going forward now, as we get into collision, 01:34:46.740 --> 01:34:49.460 and some more concepts, collision is actually something 01:34:49.460 --> 01:34:54.840 that we touched on last week, and it's all basically the same stuff. 01:34:54.840 --> 01:35:00.750 So if we go into bird7, the next iteration of our application. 01:35:00.750 --> 01:35:07.420 I'm going to go ahead, and open up main.lua, 01:35:07.420 --> 01:35:10.680 and then we're going to go to line 74. 01:35:14.040 --> 01:35:19.510 And in order to test collision, we don't have scoring in place yet, 01:35:19.510 --> 01:35:22.960 but we need some way to determine oh, we collided with a pipe. 01:35:22.960 --> 01:35:24.730 We need some sort of feedback. 01:35:24.730 --> 01:35:29.340 So what we're going to do is I've just decided we should just pause the game. 01:35:29.340 --> 01:35:31.980 So once we collide with a pipe, let's just pause instantly 01:35:31.980 --> 01:35:34.577 so we know immediately, oh, we collided with a pipe. 01:35:34.577 --> 01:35:36.660 So I'm going to set some variable called scrolling 01:35:36.660 --> 01:35:39.239 at the top of the program in main to true. 01:35:39.239 --> 01:35:39.905 We're scrolling. 01:35:39.905 --> 01:35:42.339 We're going to start scrolling, but when I 01:35:42.339 --> 01:35:44.880 don't want to scroll any more, when I want to pause the game, 01:35:44.880 --> 01:35:47.580 this should get set to false. 01:35:47.580 --> 01:35:55.950 So on line 120, if scrolling, then do all of this update logic 01:35:55.950 --> 01:35:58.091 that we did before. 01:35:58.091 --> 01:36:00.840 And then at the very end of that, we're resetting our input table. 01:36:00.840 --> 01:36:03.330 So we can still take input, but no updates 01:36:03.330 --> 01:36:05.940 will take place if scrolling is set to false. 01:36:05.940 --> 01:36:09.030 All of this stuff is within this if-- 01:36:09.030 --> 01:36:10.710 excuse me, if scrolling, then. 01:36:10.710 --> 01:36:11.477 So very simple. 01:36:11.477 --> 01:36:13.560 We'll just encapsulate it all within some variable 01:36:13.560 --> 01:36:16.990 that we can turn on and off. 01:36:16.990 --> 01:36:21.870 And then on 152, within that chunk of code 01:36:21.870 --> 01:36:25.280 that is being contained within that if condition, 01:36:25.280 --> 01:36:28.920 we're just doing a very simple iteration. 01:36:28.920 --> 01:36:33.420 For each pipe, this should be for l pair in pairs of-- 01:36:33.420 --> 01:36:34.590 oh no, sorry. 01:36:34.590 --> 01:36:39.870 For every pipe in the pairs of-- 01:36:39.870 --> 01:36:41.490 it's a nested for loop in this case. 01:36:41.490 --> 01:36:46.824 So basically, within the loop that looks over every single pair, to update it, 01:36:46.824 --> 01:36:49.990 we're doing another loop that's looping through with the pipes in that pair. 01:36:49.990 --> 01:36:53.820 So it's only a loop of two iterations with the upper and the lower pipe. 01:36:53.820 --> 01:36:57.930 We could just also say if bird collides with upper-- 01:36:57.930 --> 01:37:00.960 basically, if pair.upper or pair.lower, or pair.pipes 01:37:00.960 --> 01:37:03.960 to upper pair.pipes.lower, but this is a little cleaner. 01:37:03.960 --> 01:37:04.847 It's more scalable. 01:37:04.847 --> 01:37:07.680 We can add more pipes if we want to, even though it wouldn't happen. 01:37:07.680 --> 01:37:12.480 But for every pipe in pair.pipes, we have a function here that we 01:37:12.480 --> 01:37:14.940 haven't defined yet called bird:collides. 01:37:14.940 --> 01:37:19.030 So if bird collides pipe-- so it takes in a pipe. 01:37:19.030 --> 01:37:21.990 So it's going to return a true or false value, we know that-- 01:37:21.990 --> 01:37:23.220 set scrolling to false. 01:37:23.220 --> 01:37:26.590 So we collide, scrolling set to false, update logic 01:37:26.590 --> 01:37:28.090 is going to get shut off completely. 01:37:28.090 --> 01:37:31.620 So we're going to have the effect of pausing the game. 01:37:31.620 --> 01:37:34.230 We're going to go into bird.lua right now, 01:37:34.230 --> 01:37:36.700 and we're going to actually see how we implement this, 01:37:36.700 --> 01:37:40.600 and it's going look very familiar to what we did last week. 01:37:40.600 --> 01:37:47.750 So in bird.lua, this function here, from 29 down to 45, 01:37:47.750 --> 01:37:51.350 it's just an aabb collision detection test that we did last week. 01:37:51.350 --> 01:37:54.740 We're just checking to make sure any edges are-- 01:37:54.740 --> 01:37:58.310 right edge, make sure that is to the left 01:37:58.310 --> 01:38:00.440 of the right edge of the second box. 01:38:00.440 --> 01:38:02.740 Bottom edge of box one should be-- 01:38:02.740 --> 01:38:05.570 or bottom edge of box one should be above bottom edge or top 01:38:05.570 --> 01:38:06.590 edge of box two. 01:38:06.590 --> 01:38:10.520 If all of these things hold true, then return true. 01:38:10.520 --> 01:38:11.440 Else, return false. 01:38:11.440 --> 01:38:13.070 That means we have a collision. 01:38:13.070 --> 01:38:18.107 And notice that I've shifted everything here by a couple of constant values. 01:38:18.107 --> 01:38:19.940 Does anybody have any instinct as to why I'm 01:38:19.940 --> 01:38:26.060 saying self.x plus 2 instead of just self.x self.width minus 4? 01:38:26.060 --> 01:38:30.620 Why we're checking for that offset for the bird in this case 01:38:30.620 --> 01:38:32.240 when it is compared with the pipe. 01:38:32.240 --> 01:38:34.004 AUDIENCE: It's half the size of the bird? 01:38:34.004 --> 01:38:35.420 COLTON OGDEN: It's not quite half. 01:38:35.420 --> 01:38:37.160 It's a few pixels smaller. 01:38:37.160 --> 01:38:39.350 Do we know why we want to do this? 01:38:39.350 --> 01:38:41.450 We're basically shrinking the box. 01:38:41.450 --> 01:38:45.120 Why would we want to shrink the box? 01:38:45.120 --> 01:38:49.405 AUDIENCE: There's a gap between the actual drawing [INAUDIBLE] 01:38:49.405 --> 01:38:50.530 COLTON OGDEN: So not quite. 01:38:50.530 --> 01:38:53.740 So there isn't an actual gap between the drawing. 01:38:53.740 --> 01:39:00.355 It's more a question of how much we want to frustrate our users. 01:39:00.355 --> 01:39:06.070 If we're pixel perfect colliding with the pipes, there's no give and take. 01:39:06.070 --> 01:39:09.010 It's like you collide, and even if-- it might even 01:39:09.010 --> 01:39:11.369 look as if you're not even colliding with the pipe, 01:39:11.369 --> 01:39:12.910 and you're still getting a collision. 01:39:12.910 --> 01:39:14.990 Your users are thinking, well, that's not fair. 01:39:14.990 --> 01:39:16.570 That's really harsh. 01:39:16.570 --> 01:39:19.812 We're shrinking our box so that even if they're just a pixel off, 01:39:19.812 --> 01:39:21.520 they'll still get a little bit of leeway, 01:39:21.520 --> 01:39:25.630 and it'll be a little bit less strict in terms of the collision, 01:39:25.630 --> 01:39:29.890 and this is a very common thing in games when you have characters whose sprites 01:39:29.890 --> 01:39:33.820 may not necessarily fill the entire box that you've allocated for them, 01:39:33.820 --> 01:39:35.530 even though you're doing box collision. 01:39:35.530 --> 01:39:39.250 Just give your users a couple of pixels deep, however many 01:39:39.250 --> 01:39:42.220 you want, and they can overlap with whatever 01:39:42.220 --> 01:39:44.980 they're colliding with just a tiny bit before it actually 01:39:44.980 --> 01:39:48.610 triggers a true on the collision, and it makes your game feel more forgiving, 01:39:48.610 --> 01:39:51.770 and then also more fun as a result of that. 01:39:51.770 --> 01:39:53.200 So that's why we have-- 01:39:53.200 --> 01:39:57.940 instead of testing directly on x0 of that box, we're testing x plus 2, 01:39:57.940 --> 01:40:00.490 and then self.width minus 4 because when we shift, 01:40:00.490 --> 01:40:03.480 we add width to a plus 2 value, we need minus 4 01:40:03.480 --> 01:40:06.370 so that we get 2 off the right edge, and same thing 01:40:06.370 --> 01:40:10.010 goes for the height, and the y value. 01:40:10.010 --> 01:40:13.120 And so this just performs aabb collision detection. 01:40:13.120 --> 01:40:17.230 Expects a pipe, which means that we need to ensure 01:40:17.230 --> 01:40:20.470 that the pipe has an x and a y, a width and a height, which it does. 01:40:20.470 --> 01:40:22.090 Actually, just a constant here. 01:40:22.090 --> 01:40:24.261 We're just checking pipe width and pipe height. 01:40:24.261 --> 01:40:25.510 We probably shouldn't do that. 01:40:25.510 --> 01:40:29.071 It should be pipe.width, pipe.height in that case, 01:40:29.071 --> 01:40:31.320 because then this couldn't necessarily just be a pipe. 01:40:31.320 --> 01:40:34.990 It could be anything in our scene that has a xy, a width, and a height. 01:40:34.990 --> 01:40:37.090 It could be a general purpose collision. 01:40:37.090 --> 01:40:39.895 And actually, something you could also do if you wanted to is 01:40:39.895 --> 01:40:44.260 just write a function called collides that takes in two things 01:40:44.260 --> 01:40:46.354 that you know have bounding boxes, and will 01:40:46.354 --> 01:40:48.520 allow you to perform collision detection on anything 01:40:48.520 --> 01:40:50.890 in your scene between any two entities. 01:40:50.890 --> 01:40:54.022 That would be a more scalable way, I guess, 01:40:54.022 --> 01:40:56.230 of dealing with it, rather than necessarily having it 01:40:56.230 --> 01:40:59.422 specifically defined as birds and pipes being the colliders. 01:40:59.422 --> 01:41:01.630 But in this case, this is the only thing we're really 01:41:01.630 --> 01:41:03.950 colliding with, except for the ground. 01:41:03.950 --> 01:41:06.283 But when you collide with the ground, all you need to do 01:41:06.283 --> 01:41:10.090 is just check to see whether your y position plus your height 01:41:10.090 --> 01:41:12.700 has gone below the edge of the screen. 01:41:12.700 --> 01:41:15.197 So any questions as to how that? 01:41:15.197 --> 01:41:20.560 AUDIENCE: Why did you add 2n and subtract 4 instead of just subtract 2? 01:41:20.560 --> 01:41:23.740 COLTON OGDEN: So the question was why did we add 2 and subtract 01:41:23.740 --> 01:41:26.590 4 instead of just subtract 2? 01:41:26.590 --> 01:41:31.090 Because when you add a-- 01:41:31.090 --> 01:41:34.270 because we're doing self.x plus 2, basically we're 01:41:34.270 --> 01:41:36.930 shifting the whole box, essentially, here in this part. 01:41:36.930 --> 01:41:40.960 So self.x plus 2 brings the beginning of the box 01:41:40.960 --> 01:41:43.930 that we're colliding with 2 pixels to the right. 01:41:43.930 --> 01:41:49.780 But if we just do 2 pixels minus 2, then the box's right edge 01:41:49.780 --> 01:41:51.460 is still the right edge of the box. 01:41:51.460 --> 01:41:55.060 We want it to be shifted inwards by 2 pixels. 01:41:55.060 --> 01:41:59.740 Because we've shifted at the start of our box, the x position, 2 pixels over, 01:41:59.740 --> 01:42:04.790 we need to shift it 4 pixels inwards because that 01:42:04.790 --> 01:42:10.221 will have the effect of our box being 2 pixels into the right edge. 01:42:10.221 --> 01:42:11.694 Does that make sense? 01:42:16.120 --> 01:42:18.895 OK, so I think that's everything for bird7. 01:42:23.960 --> 01:42:28.070 We're going to go ahead, and run bird7 now. 01:42:28.070 --> 01:42:33.920 And recall, if we hit a pipe, we should instantly pause. 01:42:33.920 --> 01:42:36.870 So bouncing, bouncing, bouncing. 01:42:36.870 --> 01:42:39.694 I'm going to go through one pair of pipes here, 01:42:39.694 --> 01:42:41.610 and then I'm going to hit this one on purpose. 01:42:41.610 --> 01:42:42.364 Oh, we paused. 01:42:42.364 --> 01:42:44.280 And notice that we had a little bit of leeway. 01:42:44.280 --> 01:42:47.330 We got a couple of pixels there just to give us-- in case we 01:42:47.330 --> 01:42:50.330 accidentally-- and also, it takes into consideration you could move, 01:42:50.330 --> 01:42:52.490 because of your velocity, a couple of pixels 01:42:52.490 --> 01:42:57.530 beyond necessarily the strict hard edge of what you're colliding with 01:42:57.530 --> 01:43:00.900 based on how many frames of passed. 01:43:00.900 --> 01:43:04.177 Basically, essentially what your velocity is, and what your position is. 01:43:04.177 --> 01:43:06.260 In this case, I think it looks like we're actually 01:43:06.260 --> 01:43:08.759 three or four pixels above the edge because our velocity was 01:43:08.759 --> 01:43:13.640 so high because we jumped, but as soon as it detected the collision, as 01:43:13.640 --> 01:43:17.060 soon as we were on that frame where our position was such 01:43:17.060 --> 01:43:19.830 that we did trigger true for our collision detection, 01:43:19.830 --> 01:43:20.750 it paused the game. 01:43:20.750 --> 01:43:22.040 Looping was set to false. 01:43:22.040 --> 01:43:26.060 We no longer ran any update logic, and this is our basic way 01:43:26.060 --> 01:43:27.960 of getting feedback about that. 01:43:27.960 --> 01:43:33.230 However, it's not particularly compelling, gameplay wise, 01:43:33.230 --> 01:43:37.430 and so we want to get into scoring. 01:43:37.430 --> 01:43:41.390 Before we get to scoring though, and also associated with that, 01:43:41.390 --> 01:43:42.690 different states of our game. 01:43:42.690 --> 01:43:44.440 So if we get into scoring, clearly we want 01:43:44.440 --> 01:43:47.489 to have a screen that tells us when we lost, and what our score was. 01:43:47.489 --> 01:43:49.280 We should also probably have a title screen 01:43:49.280 --> 01:43:51.830 because we're just jumping right into the gameplay. 01:43:51.830 --> 01:43:54.710 We want a screen that lets us play through the game, 01:43:54.710 --> 01:43:57.890 and as we'll see in a little bit, a screen that also gives us 01:43:57.890 --> 01:44:01.340 some time, once we start the game, to count down. 01:44:01.340 --> 01:44:04.290 Sort of say, oh, three, two, one, go, rather than just oh, 01:44:04.290 --> 01:44:06.250 go, and oh, I don't know what I'm doing. 01:44:06.250 --> 01:44:06.950 I'm bewildered. 01:44:06.950 --> 01:44:10.220 So this is a diagram that sort of models the state 01:44:10.220 --> 01:44:15.180 flow that we're going to be using in our program here, our game. 01:44:15.180 --> 01:44:18.200 We're going to assume that we start on some sort of title screen state. 01:44:18.200 --> 01:44:19.540 So going left to right. 01:44:19.540 --> 01:44:22.860 A title screen state will transition to the count down state, 01:44:22.860 --> 01:44:26.297 and then we can define, however we want, those transitions to be. 01:44:26.297 --> 01:44:28.130 In this case, let's just say we press Enter. 01:44:28.130 --> 01:44:30.290 The title screen state goes to countdown state. 01:44:30.290 --> 01:44:32.550 Once countdown state has-- 01:44:32.550 --> 01:44:36.872 once the transition has triggered for that, we should go to the play state. 01:44:36.872 --> 01:44:39.080 And then once the transition triggers for play state, 01:44:39.080 --> 01:44:40.970 we're going to go down to the score state. 01:44:40.970 --> 01:44:44.630 And then score state should go back into countdown state, and this models 01:44:44.630 --> 01:44:48.950 our entire application's flow-- 01:44:48.950 --> 01:44:53.090 top to bottom, left to right, chronologically. 01:44:53.090 --> 01:44:55.190 So let's go ahead, and take a look at some code 01:44:55.190 --> 01:44:56.898 as to how we're going to accomplish this. 01:44:56.898 --> 01:45:00.260 Last week, I alluded to taking us-- and actually earlier in lecture, us 01:45:00.260 --> 01:45:04.220 going from this string based approach, to keeping track of our state with if 01:45:04.220 --> 01:45:06.470 conditions, to a class based approach, and that's 01:45:06.470 --> 01:45:08.140 what we're going to illustrate today. 01:45:08.140 --> 01:45:11.080 So I'm going to go ahead, and open up bird8. 01:45:11.080 --> 01:45:15.110 And in bird8, I'm going to go ahead, and start with main. 01:45:15.110 --> 01:45:23.240 So in main, on line 36, we're acquiring a new class called state machine, 01:45:23.240 --> 01:45:26.600 and a few other classes that we're defining called bay state, play 01:45:26.600 --> 01:45:28.920 state, and title screen state. 01:45:28.920 --> 01:45:31.630 And these are the components of our state machine, 01:45:31.630 --> 01:45:35.120 and they've now, instead of being just blocks of code in our update function, 01:45:35.120 --> 01:45:37.670 they're separate blocks, separate modules that 01:45:37.670 --> 01:45:40.310 have their own logic, their own update and render logic, 01:45:40.310 --> 01:45:41.890 and we'll see that very shortly. 01:45:41.890 --> 01:45:45.560 On line 78, if we go down here-- 01:45:45.560 --> 01:45:48.290 separate from that, I'm also instantiating a bunch of fonts. 01:45:48.290 --> 01:45:49.610 We did this last week. 01:45:49.610 --> 01:45:53.164 So love.graphics.newfont takes in a font file and then a size. 01:45:53.164 --> 01:45:56.330 I've created a few different fonts here because we have a few different ways 01:45:56.330 --> 01:45:58.490 of giving feedback to the user. 01:45:58.490 --> 01:46:01.220 We want a small font for displaying press Enter 01:46:01.220 --> 01:46:02.860 to a start, or something like that. 01:46:02.860 --> 01:46:06.214 We want a medium font to display the name of the game, perhaps. 01:46:06.214 --> 01:46:08.630 Or, I think actually, Flappy Font is responsible for that. 01:46:08.630 --> 01:46:10.550 Medium font, I think, was for score. 01:46:10.550 --> 01:46:12.170 Huge font for our countdown. 01:46:12.170 --> 01:46:14.170 We want a big font right in the middle of screen 01:46:14.170 --> 01:46:16.220 that says three, two, one, and then we start. 01:46:16.220 --> 01:46:19.303 And then we're just going to start off by setting it to Flappy Font, which 01:46:19.303 --> 01:46:20.750 is going to be our title font. 01:46:20.750 --> 01:46:25.520 So nothing really new, but the beginning of our UI, so to speak. 01:46:25.520 --> 01:46:29.240 On line 92, this is new, and actually this 01:46:29.240 --> 01:46:32.660 is a demonstration of a type of naming convention 01:46:32.660 --> 01:46:35.240 you'll see often in game code bases. 01:46:35.240 --> 01:46:38.420 We haven't used it yet, but we will start using it in the future. 01:46:38.420 --> 01:46:42.290 We prefix a global variable with a lower case g. 01:46:42.290 --> 01:46:45.260 This lets you know when you're digging through a bunch of files 01:46:45.260 --> 01:46:46.940 that oh, this is a global variable. 01:46:46.940 --> 01:46:51.770 OK, so I should probably know it's probably not defined in this module. 01:46:51.770 --> 01:46:54.892 Maybe it is, but I know it's global. 01:46:54.892 --> 01:46:56.600 Other things you might see are lower case 01:46:56.600 --> 01:47:03.770 m for member, which means that this is a member function, or a field of a class, 01:47:03.770 --> 01:47:06.684 and you can instantly see at a glance, and know 01:47:06.684 --> 01:47:08.600 OK, if I want to find the definition for this, 01:47:08.600 --> 01:47:10.141 it looks like it's a member function. 01:47:10.141 --> 01:47:13.280 So it's probably in this class, here at some line. 01:47:13.280 --> 01:47:15.530 You can easily find it. 01:47:15.530 --> 01:47:17.960 And so in future lectures, we'll be using 01:47:17.960 --> 01:47:23.140 more of this lowercase g for global variables that we use module to module. 01:47:23.140 --> 01:47:25.700 In this case, we're instantiating a StateMachine. 01:47:25.700 --> 01:47:29.710 So we're using the class that we will take a look at in a second. 01:47:29.710 --> 01:47:32.200 The StateMachine takes in a table with keys 01:47:32.200 --> 01:47:36.890 that map to functions that will return our states. 01:47:36.890 --> 01:47:43.910 So we can just call change some value, and it'll have in our state machine, 01:47:43.910 --> 01:47:48.340 it will basically reference that key in this table here, 01:47:48.340 --> 01:47:51.160 and it'll call that function based on-- it'll 01:47:51.160 --> 01:47:57.340 basically set the current state of that StateMachine to whatever state 01:47:57.340 --> 01:48:00.050 gets returned by the function at that key. 01:48:00.050 --> 01:48:04.717 So in this case, change is going to trigger return new TitleScreenState, 01:48:04.717 --> 01:48:06.550 and we're going to get-- the StateMachine is 01:48:06.550 --> 01:48:08.620 going to be set to the title screen, effectively, 01:48:08.620 --> 01:48:12.950 and we'll take a look at what the title screen looks like momentarily. 01:48:12.950 --> 01:48:16.350 On line 96, yeah, we're changing to title screen. 01:48:16.350 --> 01:48:20.290 On line 134, notice that we don't really have much update 01:48:20.290 --> 01:48:22.060 logic in this application anymore. 01:48:22.060 --> 01:48:25.480 We're still updating the scrolls because this is 01:48:25.480 --> 01:48:27.280 behavior we want across all our states. 01:48:27.280 --> 01:48:29.404 No matter what state we're in, we want to make sure 01:48:29.404 --> 01:48:32.830 that our background and our ground scroll so that we have movement. 01:48:32.830 --> 01:48:35.380 We don't need to duplicate this behavior state to state. 01:48:35.380 --> 01:48:37.340 This is a global feature of our game. 01:48:37.340 --> 01:48:40.960 So we're just keeping track of it here just as we would before, 01:48:40.960 --> 01:48:44.110 but anything else in our game that need to be updated 01:48:44.110 --> 01:48:47.620 can now be deferred to our StateMachine class. 01:48:47.620 --> 01:48:51.490 And when we call gStateMachine update delta time, 01:48:51.490 --> 01:48:53.745 it's going to look and see what's our current state, 01:48:53.745 --> 01:48:55.870 and it's going to update that state. 01:48:55.870 --> 01:48:59.270 And that's going to basically be that chunk, that if chunk 01:48:59.270 --> 01:49:02.080 do this logic that we were doing from before last week when 01:49:02.080 --> 01:49:05.360 we had a more primitive StateMachine. 01:49:05.360 --> 01:49:08.140 Line 46, same exact thing. 01:49:08.140 --> 01:49:11.470 Between the background and the ground, because those will always render scene 01:49:11.470 --> 01:49:15.400 to scene, we want to render our current active state 01:49:15.400 --> 01:49:19.120 using our StateMachine render function. 01:49:19.120 --> 01:49:23.410 And so let's go ahead, and just look briefly at our state machine library. 01:49:23.410 --> 01:49:25.420 It's a very simple code. 01:49:25.420 --> 01:49:29.710 It's actually taken from the book I alluded to earlier in the lecture-- 01:49:29.710 --> 01:49:31.000 How to Make an RPG. 01:49:31.000 --> 01:49:34.900 They give you this state machine, which really cleanly, I think, 01:49:34.900 --> 01:49:37.330 handles state transition. 01:49:37.330 --> 01:49:40.840 Basically, it takes an init, and then a series of states. 01:49:40.840 --> 01:49:45.440 It has an empty class, or empty table. 01:49:45.440 --> 01:49:46.750 So all of these are just empty. 01:49:50.680 --> 01:49:52.840 If there is no-- this is a thing you can do 01:49:52.840 --> 01:49:56.110 in Lua, which just lets you initialize a variable if it's not 01:49:56.110 --> 01:49:57.820 given a value in your function. 01:50:01.870 --> 01:50:05.090 So self.states gets states or some value. 01:50:05.090 --> 01:50:08.770 Which means that if states is equal to a false value, it's equal to nothing, 01:50:08.770 --> 01:50:10.330 just set it to this empty table. 01:50:10.330 --> 01:50:14.380 So it's just a shorthand for instead of saying if states equals nothing, 01:50:14.380 --> 01:50:16.950 then set states to empty table. 01:50:16.950 --> 01:50:23.680 Self.current is just an empty class or empty state. 01:50:23.680 --> 01:50:26.670 So this is basically what a state is, it's just a set of methods-- 01:50:26.670 --> 01:50:28.780 a render, update, enter, and exit function. 01:50:28.780 --> 01:50:31.960 That's a state, and then you define all of the behavior 01:50:31.960 --> 01:50:36.060 in each of these functions, and that compiles your state more or less. 01:50:36.060 --> 01:50:39.430 Our change function takes in a name, and then also some optional parameters 01:50:39.430 --> 01:50:41.080 that we can use to enter that state. 01:50:44.280 --> 01:50:48.201 When we change the state, or call the exit function of whatever state 01:50:48.201 --> 01:50:48.700 we're in. 01:50:48.700 --> 01:50:50.260 So exit that state. 01:50:50.260 --> 01:50:53.660 Maybe your function needs you to de-allocate some memory. 01:50:53.660 --> 01:50:56.530 Set the current equal to taking that name, 01:50:56.530 --> 01:50:58.155 and then call whatever functions there. 01:50:58.155 --> 01:50:59.154 So it's going to return. 01:50:59.154 --> 01:51:02.530 In that case, we saw earlier, it's going to return a new title screen state. 01:51:02.530 --> 01:51:05.050 So that's going to be what current is. 01:51:05.050 --> 01:51:09.116 With self.current, we're going to then enter that state machine. 01:51:09.116 --> 01:51:11.740 So we're going to call the Enter function that we defined there 01:51:11.740 --> 01:51:17.180 with whatever enter parameters we pass into change, which are optional. 01:51:17.180 --> 01:51:22.600 And then here, StateMachineUpdate just updates whatever the current state is, 01:51:22.600 --> 01:51:27.140 and render updates whatever the current state is as well. 01:51:27.140 --> 01:51:29.962 And so I'm going to start going a little bit quickly 01:51:29.962 --> 01:51:31.670 just because we're running short on time. 01:51:31.670 --> 01:51:36.010 BaseState, all it does is just implements empty methods 01:51:36.010 --> 01:51:38.809 so that you can just inherit this state, and you 01:51:38.809 --> 01:51:40.600 can choose which methods you want to define 01:51:40.600 --> 01:51:44.046 without throwing any errors because it blindly will call all these functions, 01:51:44.046 --> 01:51:46.420 not checking to see whether they're actually implemented. 01:51:46.420 --> 01:51:50.530 And so this is a way for you to just quickly avoid 01:51:50.530 --> 01:51:52.960 a lot of boilerplate code, essentially. 01:51:52.960 --> 01:52:00.760 The TitleScreenState here, this is your way of with the class library, 01:52:00.760 --> 01:52:04.210 just including everything that belongs to BaseState. 01:52:04.210 --> 01:52:06.700 So inheriting, if you're familiar with other languages 01:52:06.700 --> 01:52:09.730 that use inheritance-- take an object, copy 01:52:09.730 --> 01:52:12.760 everything from that object or that class, put it into this one, 01:52:12.760 --> 01:52:14.170 and then add new stuff to it. 01:52:14.170 --> 01:52:15.711 That's basically what inheritance is. 01:52:15.711 --> 01:52:19.090 We're inheriting from BaseState so it has all the functions BaseState has, 01:52:19.090 --> 01:52:22.800 and then on top of that, we're defining an update function. 01:52:22.800 --> 01:52:27.410 So if we press Enter or Return, change the global state machine 01:52:27.410 --> 01:52:28.670 to the play state. 01:52:28.670 --> 01:52:31.400 And then for the render, we're just going to render fifty bird, 01:52:31.400 --> 01:52:34.740 and press Enter halfway in the middle of the screen. 01:52:34.740 --> 01:52:36.620 And then the PlayState, essentially to some-- 01:52:36.620 --> 01:52:40.850 basically, what the PlayState is is all of the code that we ran before, only 01:52:40.850 --> 01:52:44.330 now we're just putting it in the update function here, 01:52:44.330 --> 01:52:49.760 and the render function here, and making bird, pipe pairs, timer, 01:52:49.760 --> 01:52:53.642 and lastY member fields of this state object. 01:52:53.642 --> 01:52:55.475 So we'll go ahead, and run this really fast. 01:53:01.010 --> 01:53:02.410 This is our title screen state. 01:53:02.410 --> 01:53:05.661 So at the very beginning, we change to title screen state. 01:53:05.661 --> 01:53:07.910 All it does is render, and then the scrolling behavior 01:53:07.910 --> 01:53:10.136 is throughout all classes, all states. 01:53:10.136 --> 01:53:11.510 So we'll see that no matter what. 01:53:11.510 --> 01:53:13.460 Once you press Enter, it'll trigger change 01:53:13.460 --> 01:53:17.210 to play, which will return a play state, and then 01:53:17.210 --> 01:53:21.820 now we're back where we were before, and we're seeing the difference now 01:53:21.820 --> 01:53:24.140 in having a couple of different states. 01:53:24.140 --> 01:53:27.480 So quickly, I'll go through the score update. 01:53:27.480 --> 01:53:32.510 So this is a little bit more complicated than the last example. 01:53:32.510 --> 01:53:36.230 But to summarize, in bird-- 01:53:36.230 --> 01:53:38.360 sorry, we're in bird9. 01:53:38.360 --> 01:53:45.350 So in bird9, if we go here, we're going to go to main. 01:53:45.350 --> 01:53:50.810 So notice that in main, down where we define our StateMachine, 01:53:50.810 --> 01:53:54.080 we're going to go ahead, and also note that we require a new score 01:53:54.080 --> 01:53:57.810 state because now we want to display a score screen. 01:53:57.810 --> 01:54:05.130 Down on line 96, score gets a function where we return a score state object. 01:54:05.130 --> 01:54:08.960 So now we can change to score, and it will return that state, 01:54:08.960 --> 01:54:12.320 and we can define all the behavior within ScoreState 01:54:12.320 --> 01:54:16.160 that we need to display a score. 01:54:16.160 --> 01:54:24.140 In PipePair, we have a new variable called self.scored. 01:54:24.140 --> 01:54:25.766 Set it to true or false. 01:54:25.766 --> 01:54:27.640 We're going to set it to true if the bird has 01:54:27.640 --> 01:54:31.060 gone past the right edge of the pair of pipes. 01:54:31.060 --> 01:54:34.910 That will have the effect of us scoring a point, effectively, 01:54:34.910 --> 01:54:37.460 because all we need to do is just make sure the birds got 01:54:37.460 --> 01:54:39.305 past that pair of pipes because otherwise it 01:54:39.305 --> 01:54:40.430 will have collided with it. 01:54:40.430 --> 01:54:45.980 If it does go past it, set it to true, and then add a point to our score. 01:54:45.980 --> 01:54:53.130 And in our play state, we can see that we've added a point. 01:54:53.130 --> 01:54:57.380 So if we go to our play state, 26 is where we actually 01:54:57.380 --> 01:54:58.510 keep track of our score. 01:54:58.510 --> 01:55:00.980 Self.score gets 0 in our play state. 01:55:00.980 --> 01:55:03.530 We're going to go ahead, and go down to line 56. 01:55:03.530 --> 01:55:06.637 So for every pair, if it's not been scored yet-- 01:55:06.637 --> 01:55:09.470 because we don't need to calculate this if it's already been scored. 01:55:09.470 --> 01:55:13.460 We should ignore it in terms of scoring once it's been scored. 01:55:13.460 --> 01:55:19.520 If the x plus width is less than our bird.x, 01:55:19.520 --> 01:55:22.820 meaning our bird is beyond the right edge of the pair of pipes, 01:55:22.820 --> 01:55:25.190 increment our score, and set that pair to true. 01:55:25.190 --> 01:55:28.237 We will then thereafter, because of this condition, ignore it, 01:55:28.237 --> 01:55:30.070 and we're also going to increment our score. 01:55:30.070 --> 01:55:33.000 So it's going to be kept track of. 01:55:33.000 --> 01:55:36.350 On 83, notice that if we're colliding with a pipe, 01:55:36.350 --> 01:55:38.920 we should transition to our score state now. 01:55:38.920 --> 01:55:42.380 And we're also passing in score gets self.score as a table 01:55:42.380 --> 01:55:45.350 because remember, we can pass in parameters when we call change, 01:55:45.350 --> 01:55:49.110 and this will be passed into our enter function in our state, 01:55:49.110 --> 01:55:51.260 and then score is going to equal self.score. 01:55:51.260 --> 01:55:54.170 We'll have access to the score within that score state. 01:55:54.170 --> 01:55:56.480 We don't have to keep track of it as a global variable 01:55:56.480 --> 01:55:59.810 to see it in both locations. 01:55:59.810 --> 01:56:02.360 And on 93, the same exact thing. 01:56:02.360 --> 01:56:05.810 This is collision to check whether we've collided with the bottom of the screen. 01:56:05.810 --> 01:56:09.530 If our y is greater than virtual height minus 15, do the exact same thing. 01:56:09.530 --> 01:56:12.440 Transition to the score state, and pass it in our current score. 01:56:12.440 --> 01:56:14.540 So another death condition. 01:56:14.540 --> 01:56:17.390 And then 104, we're just going to set Flappy Font, 01:56:17.390 --> 01:56:21.206 and then we're going to render our score at the top left of the screen at 8,8, 01:56:21.206 --> 01:56:23.280 and that will have that effect. 01:56:23.280 --> 01:56:26.735 And so lastly, here, our scores state is pretty simple. 01:56:29.197 --> 01:56:32.030 All it is is we're going to get-- from those parameters we passed in 01:56:32.030 --> 01:56:35.540 by a change, self.score equals params.score. 01:56:35.540 --> 01:56:38.270 We're going to, when we press Enter, go back to play, 01:56:38.270 --> 01:56:42.170 and then we're going to render 'you lost', and the score, 01:56:42.170 --> 01:56:45.320 which we have access to-- self.score, and then press Enter to play again, 01:56:45.320 --> 01:56:46.980 changing fonts along the way. 01:56:46.980 --> 01:56:51.650 And so if we go back to bird9, and we run this, 01:56:51.650 --> 01:56:54.380 notice that now we have a score in the top left. 01:56:54.380 --> 01:57:01.220 And I'm going to get one point, and then die. 01:57:01.220 --> 01:57:03.220 Then we go to our score screen now. 01:57:03.220 --> 01:57:07.180 Remember, we passed score into it from our play state. 01:57:07.180 --> 01:57:13.240 We passed it in as parameters, and then we 01:57:13.240 --> 01:57:16.550 can press Enter again, go back to play state, and when we fall to the ground, 01:57:16.550 --> 01:57:17.540 we do it as well. 01:57:17.540 --> 01:57:21.610 So we've just taken a look at how to add scoring to our game, 01:57:21.610 --> 01:57:24.070 but what if we want to add a count down screen? 01:57:24.070 --> 01:57:28.810 Maybe we want the users to be prompted three, two, one before the actual game 01:57:28.810 --> 01:57:30.660 starts throwing pipes at them. 01:57:30.660 --> 01:57:32.752 Give them the time to sort of get acclimated. 01:57:32.752 --> 01:57:34.710 We're going to go ahead, and take a look at how 01:57:34.710 --> 01:57:38.870 we might do this using another state very similar to the last example. 01:57:38.870 --> 01:57:41.350 We're going to add a new state called CountdownState, 01:57:41.350 --> 01:57:44.350 which is shown here on line 38. 01:57:44.350 --> 01:57:47.590 We're also going to, down in our state machine, 01:57:47.590 --> 01:57:51.899 add a new key, which returns one of the new countdown states, just as before, 01:57:51.899 --> 01:57:54.940 and then we're going to go ahead, and take a look at our actual countdown 01:57:54.940 --> 01:57:56.180 state here. 01:57:56.180 --> 01:58:04.510 So in our CountdownState.lua, which is in our states folder, as the others, 01:58:04.510 --> 01:58:06.460 it inherits from BaseState. 01:58:06.460 --> 01:58:09.160 We have initialized a countdown time to 0.75. 01:58:09.160 --> 01:58:10.700 This time in seconds. 01:58:10.700 --> 01:58:14.170 One second is a little long, so I made it 0.75 seconds. 01:58:14.170 --> 01:58:16.810 We're going to initialize a count to three, and a timer to 0. 01:58:16.810 --> 01:58:18.130 The count's going to start. 01:58:18.130 --> 01:58:22.540 It's going to use a timer once the countdown time has elapsed, 01:58:22.540 --> 01:58:24.040 right here, as this logic shows. 01:58:24.040 --> 01:58:27.530 Increase the timer once the timer has gone past countdown time. 01:58:27.530 --> 01:58:29.960 We want to go ahead, and set it to-- 01:58:29.960 --> 01:58:31.630 we're going to modulo by countdown time. 01:58:31.630 --> 01:58:34.930 So loop it back to 0 plus whatever amount beyond the countdown time 01:58:34.930 --> 01:58:38.740 we went so that we have a smooth track of time. 01:58:38.740 --> 01:58:41.650 We're going to set self.count minus itself 01:58:41.650 --> 01:58:44.290 by 1 so that we go three, two, one. 01:58:44.290 --> 01:58:46.632 And then if our count is 0, which means that we've 01:58:46.632 --> 01:58:48.340 gone all the way down in our count, we're 01:58:48.340 --> 01:58:51.640 going to go ahead and use our state machine, and change to the play state. 01:58:51.640 --> 01:58:54.098 And here, we're setting our font to a font that we've set-- 01:58:54.098 --> 01:58:56.770 hugeFont-- and then we're just twostring, 01:58:56.770 --> 01:58:59.320 a little function that takes a string, or takes a number, 01:58:59.320 --> 01:59:00.420 converts to a string. 01:59:00.420 --> 01:59:05.110 We're displaying self.count at 0, 120, and then our-- 01:59:05.110 --> 01:59:06.115 it's printf. 01:59:06.115 --> 01:59:10.814 So we're basically starting at 0, y 120, virtual width alignment, 01:59:10.814 --> 01:59:11.980 and then we're centering it. 01:59:11.980 --> 01:59:15.520 So the one last piece of that that we need to change 01:59:15.520 --> 01:59:21.010 is in our title screen state, instead of going straight to a play state 01:59:21.010 --> 01:59:24.070 here on line 15, we're going to a countdown state. 01:59:24.070 --> 01:59:29.710 And what this has the effect of doing, if we go into bird10, 01:59:29.710 --> 01:59:34.300 is when we press Enter, notice that we're going three, two, one, 01:59:34.300 --> 01:59:35.769 then going into our play state. 01:59:35.769 --> 01:59:37.560 Not just going straight into the play state 01:59:37.560 --> 01:59:40.990 as before, giving our user a little bit of time to catch their breath. 01:59:40.990 --> 01:59:45.370 And then if we die, we go to our score state, but once we press Enter, 01:59:45.370 --> 01:59:48.320 notice we're doing that as well. 01:59:48.320 --> 01:59:53.450 So in our score state, we also are changing to the countdown state. 01:59:53.450 --> 01:59:55.510 So that was how to make a countdown state. 01:59:55.510 --> 01:59:58.720 Probably my favorite part of many of these examples, and of this example 01:59:58.720 --> 02:00:02.680 as well, is adding audio to our application. 02:00:02.680 --> 02:00:05.306 Music and sound effects, which really ties everything together. 02:00:05.306 --> 02:00:07.430 So we're going to go ahead and take a look at this. 02:00:07.430 --> 02:00:08.290 It's very simple. 02:00:08.290 --> 02:00:12.400 Very similar to what we learned last week, even when we just did Pong. 02:00:12.400 --> 02:00:18.490 So in main.lua of bird11, which is what we're going to look at now, 02:00:18.490 --> 02:00:22.390 we're going to take a look at a table of sounds 02:00:22.390 --> 02:00:24.580 that we've initialized on line 88. 02:00:24.580 --> 02:00:26.230 We've given them all keys. 02:00:26.230 --> 02:00:28.160 Jump, explosion, hurt, score. 02:00:28.160 --> 02:00:31.870 These are all sound effects that I've generated with the BFX program 02:00:31.870 --> 02:00:33.610 that we used last week, if you recall. 02:00:33.610 --> 02:00:35.650 And then a music track that I found online 02:00:35.650 --> 02:00:38.440 on FreeSound, which is free to use. 02:00:38.440 --> 02:00:40.345 The link is here, if curious. 02:00:40.345 --> 02:00:46.450 It's just a nice, happy soundtrack that I found for this game. 02:00:46.450 --> 02:00:49.510 On line 99 to 100, we're going to do one additional step 02:00:49.510 --> 02:00:50.890 before we start the music. 02:00:50.890 --> 02:00:52.930 We're going to set looping on that to true 02:00:52.930 --> 02:00:55.930 because in games that are infinite like this, 02:00:55.930 --> 02:00:59.200 we don't want our music to just go, and then stop abruptly. 02:00:59.200 --> 02:01:03.370 We want to have it loop. 02:01:03.370 --> 02:01:05.220 Set looping to true. 02:01:05.220 --> 02:01:08.620 I actually begin the play of that music outside of any of our states 02:01:08.620 --> 02:01:14.060 because it's going to be a global music track, and then that's the music. 02:01:14.060 --> 02:01:15.430 We also need sound effects. 02:01:18.340 --> 02:01:22.480 If we look in our bird file here line 45, which 02:01:22.480 --> 02:01:26.020 is where we have the logic for jumping, we're 02:01:26.020 --> 02:01:30.400 also playing the jump sound effect that we've generated. 02:01:30.400 --> 02:01:34.080 Additionally, in our play state, if we take a look there, 02:01:34.080 --> 02:01:36.520 we can go ahead and see in our states folder here. 02:01:36.520 --> 02:01:42.040 Go to play state, and take a look at line 58. 02:01:42.040 --> 02:01:43.570 This is where we score a point. 02:01:43.570 --> 02:01:46.630 So we should play our score sound effects here, simply put. 02:01:46.630 --> 02:01:49.900 And then the same thing on line 80 to 81, 02:01:49.900 --> 02:01:52.720 collide, the sound effect here, which is we're actually 02:01:52.720 --> 02:01:55.840 layering two sounds on top of each other, which is a common thing 02:01:55.840 --> 02:01:57.940 to do in sound design, and game design. 02:01:57.940 --> 02:02:01.857 One sound, often, isn't all you need to accomplish a particular effect. 02:02:01.857 --> 02:02:04.690 So I have an explosion sound, which is kind of a white noise effect, 02:02:04.690 --> 02:02:10.190 and then a hurt sound effect, which is sort of like a downward sine wave 02:02:10.190 --> 02:02:10.960 type of sound. 02:02:10.960 --> 02:02:14.140 It would be the exact same here on 95 to 96. 02:02:14.140 --> 02:02:17.560 Once we put all these pieces together, we're going to run bird11. 02:02:17.560 --> 02:02:18.520 [MUSIC PLAYING] 02:02:18.520 --> 02:02:19.230 We get music. 02:02:23.038 --> 02:02:24.040 [BEEPING] 02:02:24.040 --> 02:02:28.165 We get a jump sound effect. 02:02:28.165 --> 02:02:31.362 And when we score a point, 02:02:31.362 --> 02:02:32.760 [DING] 02:02:32.760 --> 02:02:34.110 we get another sound effect. 02:02:34.110 --> 02:02:35.020 [CRASH] 02:02:35.020 --> 02:02:38.330 And then if we hit a pipe, notice that we have a sort of 02:02:38.330 --> 02:02:39.002 [MIMICS NOISE] 02:02:39.002 --> 02:02:42.790 and a white noise or an explosion effect layered together. 02:02:42.790 --> 02:02:47.710 So that sort of brings everything together, creatively and artistically. 02:02:47.710 --> 02:02:50.970 As an exercise to the viewer, in bird12-- 02:02:50.970 --> 02:02:52.900 in the GitHub repo, we have some code that 02:02:52.900 --> 02:02:55.652 allows you to actually add mouse clicks to the Flappy Bird 02:02:55.652 --> 02:02:58.360 in order to make it a little bit more like the actual game, which 02:02:58.360 --> 02:02:59.380 was an iOS game. 02:02:59.380 --> 02:03:01.720 So it relied on taps. 02:03:01.720 --> 02:03:06.040 The function that you might want to use is love.mousepressed x, y, button, 02:03:06.040 --> 02:03:09.010 and I would encourage you to think about how we took input, and made 02:03:09.010 --> 02:03:12.970 it global in the context of a keyboard in one of our earlier examples 02:03:12.970 --> 02:03:20.140 so that we can call this was the mouse just pressed in our bird.lua file, 02:03:20.140 --> 02:03:22.150 as opposed to the main file. 02:03:22.150 --> 02:03:24.920 And so next time, we're going to be covering a few new concepts. 02:03:24.920 --> 02:03:26.140 Or sprite sheets. 02:03:26.140 --> 02:03:30.910 So taking a large file of images, and taking out chunks of that 02:03:30.910 --> 02:03:33.250 so we don't have to have a million graphic files. 02:03:33.250 --> 02:03:34.379 Procedural layouts. 02:03:34.379 --> 02:03:36.420 This will be in the context of the game Breakout. 02:03:36.420 --> 02:03:40.082 So we want to lay out all the bricks in our game, procedurally, 02:03:40.082 --> 02:03:42.040 in sort of the same way that we've procedurally 02:03:42.040 --> 02:03:45.220 created a pipe level in this game. 02:03:45.220 --> 02:03:47.530 We'll be talking about separate levels, and having 02:03:47.530 --> 02:03:50.950 them stored in memory as opposed to just one continuous level. 02:03:50.950 --> 02:03:52.384 We'll be talking about health. 02:03:52.384 --> 02:03:55.300 We'll be talking about particle systems, which is spawning little mini 02:03:55.300 --> 02:03:59.170 graphics to accomplish various effects that are otherwise difficult to capture 02:03:59.170 --> 02:04:01.780 in simple sprite animation. 02:04:01.780 --> 02:04:04.570 A little bit fancier collision detection based on input 02:04:04.570 --> 02:04:07.792 so that we can drive ball behavior the way we want to, 02:04:07.792 --> 02:04:09.250 and then also persistent save data. 02:04:09.250 --> 02:04:11.440 How can we take a high score, and not have 02:04:11.440 --> 02:04:15.670 it refresh to 0 every time we run the application, but rather save it to disk 02:04:15.670 --> 02:04:18.310 so that every time you run the program thereafter, we can see 02:04:18.310 --> 02:04:21.400 what we've gotten scored in days past. 02:04:21.400 --> 02:04:24.910 The first assignment, or other the second assignment, assignment one, 02:04:24.910 --> 02:04:27.520 is going to be a little bit more complicated than last weeks, 02:04:27.520 --> 02:04:28.970 but still fairly doable. 02:04:28.970 --> 02:04:31.940 Make pipe gaps slightly random, being the first component of this. 02:04:31.940 --> 02:04:34.240 So before, a pipe gap was set to a constant value. 02:04:34.240 --> 02:04:36.190 Maybe make it some sort of random value. 02:04:36.190 --> 02:04:37.360 Pipe intervals as well. 02:04:37.360 --> 02:04:38.860 So we're spawning every two seconds. 02:04:38.860 --> 02:04:40.651 Maybe we want to change that up, make pipes 02:04:40.651 --> 02:04:44.020 spawn a little differently, a little more sporadically. 02:04:44.020 --> 02:04:46.330 The more complicated aspect of this assignment 02:04:46.330 --> 02:04:49.150 is going to be awarding players a medal based on their performance. 02:04:49.150 --> 02:04:52.060 So have maybe a bronze, a silver, and a gold medal-- 02:04:52.060 --> 02:04:55.930 an image that you display in the score screen in addition to just their score 02:04:55.930 --> 02:04:58.602 just to give them a little bit of personal feedback, 02:04:58.602 --> 02:05:01.060 and make them feel rewarded for their effort, and make them 02:05:01.060 --> 02:05:03.640 strive to get that last medal. 02:05:03.640 --> 02:05:06.640 And then lastly, you'll implement a pause feature, which we talked about 02:05:06.640 --> 02:05:11.170 in class, so that when you press, for example, the key p, the game will stop. 02:05:11.170 --> 02:05:13.720 But unlike that example, when we press p again, 02:05:13.720 --> 02:05:16.990 the game should resume just as it was in its prior state. 02:05:16.990 --> 02:05:19.154 So that will be it for Flappy Bird. 02:05:19.154 --> 02:05:20.320 I'll see you guys next time. 02:05:20.320 --> 02:05:22.170 Thanks a lot.