WEBVTT X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:900000 00:00:00.000 --> 00:00:01.964 [MUSIC PLAYING] 00:00:16.612 --> 00:00:17.570 DAVID MALAN: All right. 00:00:17.570 --> 00:00:20.340 Welcome to Introduction to Game Development. 00:00:20.340 --> 00:00:22.820 My name is David Malan, and this is Colton Ogden. 00:00:22.820 --> 00:00:25.160 And this is a class that assumes only a class like CS50, 00:00:25.160 --> 00:00:28.160 which is the colleges and the extension schools introduction to computer 00:00:28.160 --> 00:00:30.050 science but more generally we just assume 00:00:30.050 --> 00:00:32.689 that you have prior programming experience in most any language 00:00:32.689 --> 00:00:34.100 and therefore have some comfort with some 00:00:34.100 --> 00:00:35.724 of the basic constructs of programming. 00:00:35.724 --> 00:00:38.875 But we assume no background in Lua or Lab 2D or any of the frameworks 00:00:38.875 --> 00:00:40.250 that we'll be using in the class. 00:00:40.250 --> 00:00:42.230 All of that lies ahead. 00:00:42.230 --> 00:00:46.087 So, if you're like me, you probably grew up with video games of some sort. 00:00:46.087 --> 00:00:48.920 And when you maybe started programming, the programming environments 00:00:48.920 --> 00:00:52.340 were perhaps very text based, black and white terminal window, and the like. 00:00:52.340 --> 00:00:55.580 And maybe you did something graphical with a language like scratch or Alice 00:00:55.580 --> 00:00:57.890 or beyond, or if you're in the world of the web, 00:00:57.890 --> 00:01:01.550 you've made more graphical applications of some sort, but still pretty static. 00:01:01.550 --> 00:01:04.950 The sort of content comes on the screen, then the content changes, and so forth. 00:01:04.950 --> 00:01:07.866 And it's a little less obvious if you're a little newer to programming 00:01:07.866 --> 00:01:10.340 how you go about creating some of those games 00:01:10.340 --> 00:01:12.410 from yesteryear with which you all grew up, 00:01:12.410 --> 00:01:15.350 where there's a lot more animation, there's a lot more asynchronicity, 00:01:15.350 --> 00:01:17.210 lots of things happening at the same time. 00:01:17.210 --> 00:01:19.700 A lot of events happening, and you all NOT-- 00:01:19.700 --> 00:01:21.740 not only want to capture this interactivity, 00:01:21.740 --> 00:01:23.600 but also want to respond to events that are 00:01:23.600 --> 00:01:25.308 happening, especially if you have players 00:01:25.308 --> 00:01:27.110 elsewhere next to you or online. 00:01:27.110 --> 00:01:29.210 And so the way the course will be structured 00:01:29.210 --> 00:01:32.309 is through a narrative of these various games, many of which 00:01:32.309 --> 00:01:33.600 you might have played yourself. 00:01:33.600 --> 00:01:35.450 But over the course of the semester, we dive 00:01:35.450 --> 00:01:37.370 into the context of each of these games and look 00:01:37.370 --> 00:01:39.980 at some of the underlying principles, the constructs via which 00:01:39.980 --> 00:01:42.604 they were built up, and really use them as a point of departure 00:01:42.604 --> 00:01:45.200 for talking about those various capabilities 00:01:45.200 --> 00:01:47.600 that you might integrate into your own games. 00:01:47.600 --> 00:01:49.970 And then punctuating the semester, ultimately, 00:01:49.970 --> 00:01:51.320 will be a number of milestones. 00:01:51.320 --> 00:01:54.560 Some in the form of smaller assignments that are meant to reinforce just some 00:01:54.560 --> 00:01:58.170 of the more recent material and sort of set you up for success when the course 00:01:58.170 --> 00:02:01.110 is deeper and more hands on projects. , Because indeed, 00:02:01.110 --> 00:02:04.401 the project is where you'll build or extend some of your own games. 00:02:04.401 --> 00:02:07.400 And then the class itself will culminate at the very end of the semester 00:02:07.400 --> 00:02:09.900 with your very own final project, an opportunity to propose, 00:02:09.900 --> 00:02:13.040 to design, and implement a game that somehow or other draws 00:02:13.040 --> 00:02:14.330 upon the course's lessons. 00:02:14.330 --> 00:02:16.880 So that when you walk out of here in just a few months' time, 00:02:16.880 --> 00:02:19.020 you've not only played your fair share of games, 00:02:19.020 --> 00:02:21.522 but have actually built several of your own. 00:02:21.522 --> 00:02:23.480 So without further ado, allow me to turn things 00:02:23.480 --> 00:02:27.510 over to Colton for a look and a stroll through yesteryear's Pong. 00:02:27.510 --> 00:02:29.050 COLTON OGDEN: Thanks, David. 00:02:29.050 --> 00:02:31.790 I'm very excited to begin teaching you guys this course 00:02:31.790 --> 00:02:34.910 because game development was actually what got me 00:02:34.910 --> 00:02:36.520 into programming in the first place. 00:02:36.520 --> 00:02:40.910 I remember back in 2006 or 2007 buying this book here, 00:02:40.910 --> 00:02:47.110 3D Game Programming All In One, which was a look through 3D game programming. 00:02:47.110 --> 00:02:51.110 And it was a monolithic text in the context of a game engine 00:02:51.110 --> 00:02:54.110 that was popular in the late 2000s called Torque. 00:02:54.110 --> 00:02:58.310 It's not as in vogue these days, but it at the time was pretty popular, 00:02:58.310 --> 00:03:00.200 and it used a language called TorqueScript. 00:03:00.200 --> 00:03:02.750 And I remember reading through this book and seeing all this code, 00:03:02.750 --> 00:03:05.010 and I had never seen like source code at all before, 00:03:05.010 --> 00:03:07.457 or had ever been introduced to programming. 00:03:07.457 --> 00:03:09.290 And, frankly, I found it quite intimidating, 00:03:09.290 --> 00:03:13.400 because I was looking at all the syntax that I didn't understand, 00:03:13.400 --> 00:03:15.650 and I didn't know anything about game development. 00:03:15.650 --> 00:03:19.857 I had always played games growing up and been fascinated by it, 00:03:19.857 --> 00:03:22.190 but as I started getting more comfortable with computers 00:03:22.190 --> 00:03:23.939 and I started to get more curious about it 00:03:23.939 --> 00:03:25.940 and realized that it was a major profession, 00:03:25.940 --> 00:03:27.770 I started to dive a little deeper. 00:03:27.770 --> 00:03:29.030 This was my first foray. 00:03:29.030 --> 00:03:32.060 And after spending a little bit of time away from it 00:03:32.060 --> 00:03:34.820 after looking through the source code for a TorqueScript, which 00:03:34.820 --> 00:03:38.240 was rather arcane, a lot of percent symbols and dollar signs 00:03:38.240 --> 00:03:41.720 are the weird things that I just hadn't gotten my mind around. 00:03:41.720 --> 00:03:45.830 I went back to it, started to really learn the basics of programming 00:03:45.830 --> 00:03:48.980 and other languages, like Python and c and c++, 00:03:48.980 --> 00:03:53.314 and I grew to really like programming and computer science a lot. 00:03:53.314 --> 00:03:55.980 And here's just an image of what Torque looked like at the time. 00:03:55.980 --> 00:03:59.330 It was really the sort of predecessor to Unity nowadays. 00:03:59.330 --> 00:04:02.330 Although, in my opinion, Unity does things a lot better. 00:04:02.330 --> 00:04:05.270 It was more accessible, it uses languages that are more in vogue 00:04:05.270 --> 00:04:08.270 and popular and used by other people already in other domains. 00:04:10.489 --> 00:04:13.030 And so we'll be covering Unity at the tail end of the course. 00:04:13.030 --> 00:04:15.890 So we'll be covering predominantly 2D game development. 00:04:15.890 --> 00:04:18.589 But the topics that we'll be covering today 00:04:18.589 --> 00:04:23.087 as we get started in the context of Pong are these bullet points here. 00:04:23.087 --> 00:04:25.670 Lua, which will be the language that we're using predominately 00:04:25.670 --> 00:04:28.010 throughout the course, which is a dynamic scripting language very 00:04:28.010 --> 00:04:29.750 similar to Python and JavaScript. 00:04:29.750 --> 00:04:33.290 We'll be covering LOVE 2D as our primary game framework, which 00:04:33.290 --> 00:04:39.590 is a runtime and a framework which exposes all of its methods 00:04:39.590 --> 00:04:41.630 for drawing, audio, input, etc. 00:04:41.630 --> 00:04:45.530 via Lua, so that it's very easy to write code very quickly, 00:04:45.530 --> 00:04:46.910 but get very good results. 00:04:46.910 --> 00:04:50.667 And the documentation for their framework is superb, in my opinion. 00:04:50.667 --> 00:04:53.000 Today we'll be talking about a few just basic principles 00:04:53.000 --> 00:04:54.960 as we get our feet wet with game development. 00:04:54.960 --> 00:04:57.830 Things like drawing shapes, drawing text, 00:04:57.830 --> 00:05:00.440 these are both very big aspects of Pong, which 00:05:00.440 --> 00:05:03.500 is just a very simple game based on shapes and text 00:05:03.500 --> 00:05:05.120 moving around the screen. 00:05:05.120 --> 00:05:09.350 We'll be talking about Delta Time and Velocity, which Delta Time is probably 00:05:09.350 --> 00:05:11.587 arguably one of the most important variables 00:05:11.587 --> 00:05:14.420 that we keep track of in any game framework or engine, which is just 00:05:14.420 --> 00:05:17.710 the amount of time that's elapsed since the last frame of execution 00:05:17.710 --> 00:05:22.454 in our game, measured in LOVE 2D in terms of seconds, fractions of seconds. 00:05:22.454 --> 00:05:24.370 We'll be talking about game state, because you 00:05:24.370 --> 00:05:25.660 can have a state in your game. 00:05:25.660 --> 00:05:29.359 You can be at the title screen, you can be playing, you can be in a menu. 00:05:29.359 --> 00:05:32.650 This will, obviously, be very important because you want different update logic 00:05:32.650 --> 00:05:35.525 and rendering logic depending on what state you're in. 00:05:35.525 --> 00:05:37.900 We'll be talking about basic object oriented programming, 00:05:37.900 --> 00:05:41.080 for those who might be unfamiliar coming from C. 00:05:41.080 --> 00:05:45.640 It's basically a way of encapsulating our data, any of our game objects, 00:05:45.640 --> 00:05:49.150 in such a way that the variables that are relevant to them 00:05:49.150 --> 00:05:52.370 are put together, along with functions that will operate on that data. 00:05:52.370 --> 00:05:55.330 So instead of having like 20 different variables for all 00:05:55.330 --> 00:05:58.210 these different objects that you have to keep track of in your code, 00:05:58.210 --> 00:06:01.000 each individual object can keep track of all its own information, 00:06:01.000 --> 00:06:04.240 like its position, or anything else that's relevant to it. 00:06:04.240 --> 00:06:07.150 We'll be talking about hit boxes today, predominantly, 00:06:07.150 --> 00:06:09.280 in the context of box collision, because we'll 00:06:09.280 --> 00:06:11.989 be talking about Pong, which is just paddles and a ball. 00:06:11.989 --> 00:06:13.030 Those are all rectangles. 00:06:13.030 --> 00:06:16.600 And they'll be colliding with what's called axis aligned bound-- 00:06:16.600 --> 00:06:21.820 axis aligned bounding boxes, which makes calculating whether two boxes collided 00:06:21.820 --> 00:06:25.750 very simple, as opposed to calculating rotated hit boxes, 00:06:25.750 --> 00:06:27.190 which is a bit more complicated. 00:06:27.190 --> 00:06:29.439 And then, lastly, we'll polish off with sound effects, 00:06:29.439 --> 00:06:32.620 because adding that polished layer, in my opinion, is important 00:06:32.620 --> 00:06:36.670 and it ties it all together and makes it feel like a more cohesive whole. 00:06:36.670 --> 00:06:40.294 So two important things that we'll need to do when we're following along 00:06:40.294 --> 00:06:43.210 with the examples, which I'll show you a link to the repo in a moment, 00:06:43.210 --> 00:06:44.590 is getting LOVE 2D installed. 00:06:44.590 --> 00:06:46.190 It's a very simple process. 00:06:46.190 --> 00:06:48.290 The first link here is just a download link. 00:06:48.290 --> 00:06:50.920 So it's available for all major operating systems. 00:06:50.920 --> 00:06:53.620 So Linux, Mac, and Windows. 00:06:53.620 --> 00:06:56.190 And then the Getting Started link down here below 00:06:56.190 --> 00:06:58.300 will give you some tips as to how to get started, 00:06:58.300 --> 00:07:02.080 actually running it on your machine on Mac, iAlias, 00:07:02.080 --> 00:07:07.240 the actual runtime executable within the app that it comes with. 00:07:07.240 --> 00:07:11.470 So in my bash profiles that I can easily just type love space 00:07:11.470 --> 00:07:15.167 dot in any directory that has a main dot Lua file, 00:07:15.167 --> 00:07:16.750 and I can run it anywhere very simply. 00:07:16.750 --> 00:07:20.050 And there are similar instructions located on the page for other operating 00:07:20.050 --> 00:07:20.770 systems. 00:07:20.770 --> 00:07:23.350 And this is the repo here, which has all of the source code 00:07:23.350 --> 00:07:24.550 that we'll be using today. 00:07:24.550 --> 00:07:28.780 And I've structured it in a series of 13 different subrepos 00:07:28.780 --> 00:07:32.560 so that you can follow along and we can build upon Pong starting from scratch, 00:07:32.560 --> 00:07:35.326 going all the way to a fully implemented game. 00:07:35.326 --> 00:07:37.450 So the first thing we'll talk about is what Lua is. 00:07:37.450 --> 00:07:40.160 We'll be using Lua for about 75% of the course. 00:07:40.160 --> 00:07:42.370 It's a very popular dynamic scripting language. 00:07:42.370 --> 00:07:45.040 Portuguese for moon, and it was invented in the early 90s 00:07:45.040 --> 00:07:48.250 as primarily a config language and a runtime language 00:07:48.250 --> 00:07:54.130 for compiled code bases to save time on adding code to those code bases 00:07:54.130 --> 00:07:55.720 and recompiling them. 00:07:55.720 --> 00:07:58.480 A lot faster and a lot easier, especially in the context 00:07:58.480 --> 00:08:01.960 of the 90s when computers were much slower, to expose 00:08:01.960 --> 00:08:07.270 the core functionality of your application to Lua so that you can just 00:08:07.270 --> 00:08:11.500 run it dynamically and then interact with your compiled code on the fly, 00:08:11.500 --> 00:08:13.960 rather than having to recompile and wait minutes, 00:08:13.960 --> 00:08:19.150 potentially hours, just to get some new behavior. 00:08:19.150 --> 00:08:22.690 It's a language that's focused around the concept of a table. 00:08:22.690 --> 00:08:26.060 Almost everything in Lua aside from basic variables, are tables. 00:08:26.060 --> 00:08:30.230 A table is essentially a dictionary in Python or an object in JavaScript. 00:08:30.230 --> 00:08:31.790 Very similar. 00:08:31.790 --> 00:08:33.890 Intent, for embedded use in larger applications, 00:08:33.890 --> 00:08:37.150 and the very nature of Lua intended to be 00:08:37.150 --> 00:08:39.460 used in the context of these large applications 00:08:39.460 --> 00:08:42.130 meant that it was perfect for interacting with game engines. 00:08:42.130 --> 00:08:44.800 Because game engines are a perfect example of code bases 00:08:44.800 --> 00:08:48.850 that are traditionally compiled code for speed purposes. 00:08:48.850 --> 00:08:52.240 But it can be very cumbersome to have to add minor functionality, 00:08:52.240 --> 00:08:56.680 and then recompile it and potentially have your whole studio take hours. 00:08:56.680 --> 00:09:00.910 So we'll be using Lua and a compiled game framework, LOVE 2D, 00:09:00.910 --> 00:09:03.880 to allow us to rapidly develop. 00:09:03.880 --> 00:09:06.700 It's similar to JavaScript and Python. 00:09:06.700 --> 00:09:09.010 A little bit more so to JavaScript. 00:09:09.010 --> 00:09:11.230 And it's very excellent because it was initially 00:09:11.230 --> 00:09:15.070 intended as a config language, and a-- just sort of a glue layer. 00:09:15.070 --> 00:09:19.870 It's very good for storing data and code together, almost one in the same. 00:09:19.870 --> 00:09:24.700 So LOVE 2D is a fast 2D game develop-- 00:09:24.700 --> 00:09:25.630 development framework. 00:09:25.630 --> 00:09:28.790 It's compiled in C++ and it runs very efficiently. 00:09:28.790 --> 00:09:33.312 Because it's so simple, despite the fact that we're running it in Lua, 00:09:33.312 --> 00:09:35.020 and as modules for basically anything you 00:09:35.020 --> 00:09:37.061 would need in the context of 2D game development. 00:09:37.061 --> 00:09:40.690 Only 2D game development officially, although some people I know 00:09:40.690 --> 00:09:44.840 are working on slight little 3D experiments, but nothing official yet. 00:09:44.840 --> 00:09:47.872 But it has graphics, keyboard input, math, basically, anything 00:09:47.872 --> 00:09:50.080 you could want in the context of 2D game development. 00:09:50.080 --> 00:09:51.050 It's completely free. 00:09:51.050 --> 00:09:51.950 It's portable. 00:09:51.950 --> 00:09:54.310 You can even run it on mobile and also the web. 00:09:54.310 --> 00:09:57.400 And it's excellent for prototyping, even if you don't necessarily 00:09:57.400 --> 00:10:01.420 want to publish a game in LOVE 2D, it's great and easy and fast just 00:10:01.420 --> 00:10:03.970 to whip something up in LOVE 2D, and then port 00:10:03.970 --> 00:10:05.920 that over to whatever framework or engine 00:10:05.920 --> 00:10:08.870 you might be using in the real world. 00:10:08.870 --> 00:10:12.222 So before we get into looking at some actual concrete code, 00:10:12.222 --> 00:10:14.680 I think the most fundamental thing we should take a look at 00:10:14.680 --> 00:10:16.430 is what a game loop is. 00:10:16.430 --> 00:10:21.420 So a game, fundamentally, is just an infinite loop, like a while true or a 00:10:21.420 --> 00:10:22.790 while one. 00:10:22.790 --> 00:10:27.200 Only in this case, every iteration of that loop we're doing a set of steps 00:10:27.200 --> 00:10:28.850 back to back over and over again. 00:10:28.850 --> 00:10:31.480 We're processing input so we're seeing, has the user pressed 00:10:31.480 --> 00:10:34.220 a key on the keyboard, have they touched their joystick, 00:10:34.220 --> 00:10:36.896 have they moved the mouse, clicked the mouse. 00:10:36.896 --> 00:10:39.020 If they have, we need to feed that into our update. 00:10:39.020 --> 00:10:42.061 We need to keep track of that, and then change anything in our game state 00:10:42.061 --> 00:10:43.520 that relies upon that input. 00:10:43.520 --> 00:10:46.640 So we should move our paddles, we should detect collision, 00:10:46.640 --> 00:10:49.460 we should register all of this, and then whatever has updated, 00:10:49.460 --> 00:10:51.410 we want to rerender that. 00:10:51.410 --> 00:10:52.670 We want to render it-- 00:10:52.670 --> 00:10:55.190 render where it's changed so that we have the-- 00:10:55.190 --> 00:10:58.477 we see on our screen, visually, that things have actually 00:10:58.477 --> 00:11:00.560 changed in our game world and we interact with it, 00:11:00.560 --> 00:11:03.260 and we get a sense that we're using something, 00:11:03.260 --> 00:11:07.430 interacting with something dynamic. 00:11:07.430 --> 00:11:12.140 And in the context of 2D games, the most fundamental way of looking at the world 00:11:12.140 --> 00:11:14.720 is via the 2D coordinate system, which is just simply 00:11:14.720 --> 00:11:18.949 as we learned in geometry in high school, x and y-axis. 00:11:18.949 --> 00:11:21.740 In this case, it's slightly different than what we typically learn. 00:11:21.740 --> 00:11:26.360 In high school, we tend to learn that the xy origins, sort of bottom left, 00:11:26.360 --> 00:11:30.650 y positive goes up, negative goes down, positive x goes right, 00:11:30.650 --> 00:11:31.830 and negative x goes left. 00:11:31.830 --> 00:11:34.280 But in this case, we're actually starting in the top left, 00:11:34.280 --> 00:11:38.740 and then it goes y positive down, y negative up, x positive right, 00:11:38.740 --> 00:11:40.640 x negative left. 00:11:40.640 --> 00:11:42.980 And everything that we want to draw in our game 00:11:42.980 --> 00:11:45.350 needs to have an x and y-coordinate to draw in order 00:11:45.350 --> 00:11:48.170 for it to be visually seen on the screen. 00:11:48.170 --> 00:11:51.230 So today's goal, we're going to start a fairly low level 00:11:51.230 --> 00:11:55.670 and work our way up through examples today and in future classes. 00:11:55.670 --> 00:11:58.160 Our first game is arguably one of the simplest, 00:11:58.160 --> 00:12:00.920 but also, one of the most famous games of all time, Pong, 00:12:00.920 --> 00:12:03.500 which was released in 1972. 00:12:03.500 --> 00:12:05.499 And the gist of Pong is you have a paddle 00:12:05.499 --> 00:12:08.540 on the left side of the screen, a paddle on the right side of the screen, 00:12:08.540 --> 00:12:11.630 whoever scores 10 points by getting the ball past their opponent's paddle 00:12:11.630 --> 00:12:14.510 onto the edge of the screen, wins. 00:12:14.510 --> 00:12:19.220 And so today in our lecture, the scope is we want to, first and foremost, 00:12:19.220 --> 00:12:22.640 draw shapes to the screen, because that's how we get our ball and-- ball 00:12:22.640 --> 00:12:23.630 and paddles rendering. 00:12:23.630 --> 00:12:25.490 And those are just simply rectangles. 00:12:25.490 --> 00:12:28.070 We want to control the 2D position of these paddles, 00:12:28.070 --> 00:12:32.630 because we want them to move up and down and want the ball to also move. 00:12:32.630 --> 00:12:36.050 We want to detect collision between the paddles and the ball, 00:12:36.050 --> 00:12:38.780 because that's how we get the ball to deflect off the paddles, 00:12:38.780 --> 00:12:40.790 and to deflect off the ceiling and the floor. 00:12:40.790 --> 00:12:44.250 And, also, how we detect whether it's gone beyond the edges of the screen, 00:12:44.250 --> 00:12:47.270 such that one player scores a point. 00:12:47.270 --> 00:12:51.710 And then we want to add sound effects for sort of a feedback 00:12:51.710 --> 00:12:54.660 and sort of put ourselves into the game a little bit more. 00:12:54.660 --> 00:12:57.380 And then scorekeeping, because ultimately the purpose of the game 00:12:57.380 --> 00:12:59.780 is to beat your opponent, so you want a way 00:12:59.780 --> 00:13:02.840 to see who has scored 10 points first. 00:13:02.840 --> 00:13:06.590 And so we're going to look through a set of examples now in the repo. 00:13:06.590 --> 00:13:11.240 If we look at Pong Zero, I've set this to be called, The Day Zero Update. 00:13:11.240 --> 00:13:16.460 It's a trend among many games to have the games release major content 00:13:16.460 --> 00:13:17.870 updates as the x update. 00:13:17.870 --> 00:13:24.470 So just to be cute, I think we'll call each individual example here, 00:13:24.470 --> 00:13:26.220 The Something Update. 00:13:26.220 --> 00:13:29.960 And so I'm going to go into the Pong Zero 00:13:29.960 --> 00:13:36.680 Repo of the directory, the GitHub repo. 00:13:36.680 --> 00:13:40.669 And if we're looking at Pong Zero here, we can see it 00:13:40.669 --> 00:13:41.960 says here, The Day Zero Update. 00:13:41.960 --> 00:13:44.990 I've commented everything fairly heavily so that we can-- 00:13:44.990 --> 00:13:48.290 if you're reading the code, you can sort of get a sense of what's going on. 00:13:48.290 --> 00:13:52.580 At line 23, we're going to start off by just declaring 00:13:52.580 --> 00:13:54.039 a window width and a window height. 00:13:54.039 --> 00:13:55.788 And these are just constant variables that 00:13:55.788 --> 00:13:58.230 will be accessible throughout the rest of our application. 00:13:58.230 --> 00:14:01.160 So I'm just setting 1280 by 720 as an arbitrary resolution. 00:14:01.160 --> 00:14:03.770 It doesn't matter too much. 00:14:03.770 --> 00:14:07.700 An important thing that we need to look at here is that line 29, 00:14:07.700 --> 00:14:12.470 we're using a function called love.load, and I'm actually 00:14:12.470 --> 00:14:14.030 going to go back to the slides here. 00:14:14.030 --> 00:14:15.988 We're going to look at a few functions, and I'm 00:14:15.988 --> 00:14:18.560 going to go over them and just sort of tell you 00:14:18.560 --> 00:14:21.240 what they do before we look at the code in too much detail. 00:14:21.240 --> 00:14:26.330 So love.load is just a function that-- given to us by LOVE, LOVE 2D, 00:14:26.330 --> 00:14:28.940 and we overwrite it. 00:14:28.940 --> 00:14:31.080 We give it behavior, we tell it what to do. 00:14:31.080 --> 00:14:34.190 And LOVE 2D is going to look at it in our main.lua file. 00:14:34.190 --> 00:14:37.460 If we're looking at Pong Zero, you'll see it just has a main.lua file. 00:14:37.460 --> 00:14:42.410 LOVE 2D expects just a main.lua file, and will run the main.lua file, 00:14:42.410 --> 00:14:45.830 and you can reference any other file within the directory from that main.lua 00:14:45.830 --> 00:14:46.360 file. 00:14:46.360 --> 00:14:49.540 It's our bootstrap, effectively. 00:14:49.540 --> 00:14:52.610 We're going to override love.load with whatever 00:14:52.610 --> 00:14:55.250 we want to execute at the very beginning of our application. 00:14:55.250 --> 00:14:57.400 It's just a startup function. 00:14:57.400 --> 00:15:01.590 We can also define all that behavior outside of the function above it, 00:15:01.590 --> 00:15:03.887 but it's good practice to find it within love.load 00:15:03.887 --> 00:15:05.720 so that someone reading your code will know, 00:15:05.720 --> 00:15:08.990 OK, this is where all the startup code takes place. 00:15:08.990 --> 00:15:12.570 Love.update(dt) is a very important function. 00:15:12.570 --> 00:15:14.720 This function takes in a variable called (dt). 00:15:14.720 --> 00:15:17.164 Love passes it in a function. 00:15:17.164 --> 00:15:19.330 You're going to overwrite it with your own behavior, 00:15:19.330 --> 00:15:23.530 and Love is going to execute this every frame, passing it in delta time, 00:15:23.530 --> 00:15:27.790 and you can use delta time (dt) in that function 00:15:27.790 --> 00:15:32.680 to change your application based upon how much time has passed. 00:15:32.680 --> 00:15:36.670 (dt) will always be a fraction of a second, potentially more, 00:15:36.670 --> 00:15:38.320 depending on how slow your computer is. 00:15:38.320 --> 00:15:41.410 But, typically, one-sixtieth of a second. 00:15:41.410 --> 00:15:43.930 And you can scale anything in your game by that amount 00:15:43.930 --> 00:15:47.560 to get even behavior across all frame rates. 00:15:47.560 --> 00:15:52.180 Love.draw is the other big function amongst-- 00:15:52.180 --> 00:15:53.350 between update and draw. 00:15:53.350 --> 00:15:55.720 Two of the two, arguably, most important functions. 00:15:55.720 --> 00:15:58.120 Love.draw is the function that we're going 00:15:58.120 --> 00:16:02.996 to define that has all of our drawing behavior, our rendering behavior in it. 00:16:02.996 --> 00:16:05.620 And that's where we can draw our paddles, we can draw our ball. 00:16:05.620 --> 00:16:07.411 And then update is where we can like change 00:16:07.411 --> 00:16:09.840 the paddles position and so forth. 00:16:09.840 --> 00:16:13.100 Two more important functions we'll take a look at in the first example. 00:16:13.100 --> 00:16:19.799 Love.graphics.printf is the LOVE 2D analog of printf and C. The difference 00:16:19.799 --> 00:16:22.090 being that this printf lets us actually draw physically 00:16:22.090 --> 00:16:24.220 onto the screen versus a console. 00:16:24.220 --> 00:16:28.180 We give it a text as a string, and an x and a y-coordinate and, optionally, 00:16:28.180 --> 00:16:32.890 a width and an align, and it'll will draw the text at xy, 00:16:32.890 --> 00:16:35.500 but it will also take into consideration the width, 00:16:35.500 --> 00:16:37.720 and it'll also take in consideration the align. 00:16:37.720 --> 00:16:41.840 The with is how much to align it, and the align is the mode of alignment. 00:16:41.840 --> 00:16:47.610 So if we say x is zero width, our window width, and then we say align center, 00:16:47.610 --> 00:16:51.250 it's going to go between zero and our window width and center align it. 00:16:51.250 --> 00:16:54.460 So that'll have the effect of center aligning our text. 00:16:54.460 --> 00:16:57.100 But we can just as easily say, right, and it will right 00:16:57.100 --> 00:16:59.200 align it between those two and have the effect 00:16:59.200 --> 00:17:01.033 of rendering the screen-- rendering the text 00:17:01.033 --> 00:17:03.220 along the right edge of the screen. 00:17:03.220 --> 00:17:06.760 And then lastly, love.window.setmode takes a width and a height 00:17:06.760 --> 00:17:08.260 and some optional parameters. 00:17:08.260 --> 00:17:11.730 Those parameters being things like V sync and full screen, 00:17:11.730 --> 00:17:16.550 and will actually set up our window and get it rendering onto the screen. 00:17:16.550 --> 00:17:21.400 And so if we go back to our source code here, it-- at line 29, 00:17:21.400 --> 00:17:23.599 we're overwriting love.load. 00:17:23.599 --> 00:17:27.670 We're passing in love.window.setmode, window width and window 00:17:27.670 --> 00:17:31.570 height, which recall we defined up above as 1280 by 720. 00:17:31.570 --> 00:17:33.220 We're passing in a table. 00:17:33.220 --> 00:17:36.280 This is the syntax for a table, these curly brackets. 00:17:36.280 --> 00:17:39.920 And the way that we define keys and values is just with an equal sign 00:17:39.920 --> 00:17:40.420 therein. 00:17:40.420 --> 00:17:44.202 So full screen gets false, resizeable gets false, V sync gets true. 00:17:44.202 --> 00:17:45.910 So it's going to not be full screen, it's 00:17:45.910 --> 00:17:48.230 going to be a not resizeable but it is going to be 00:17:48.230 --> 00:17:49.840 synced to our monitor's refresh rate. 00:17:49.840 --> 00:17:53.410 And that's where V sync is, short for vertical sync. 00:17:53.410 --> 00:17:56.740 And then on line 40, we're overwriting love.draw, 00:17:56.740 --> 00:18:00.700 and this has the love.graphics.printf function, they're in, 00:18:00.700 --> 00:18:03.250 and we're saying-- we're passing in the string, hello, Pong. 00:18:03.250 --> 00:18:06.360 We're starting it at x zero, we're setting it 00:18:06.360 --> 00:18:10.090 at y window height divided by 2, minus 6. 00:18:10.090 --> 00:18:13.220 Because the default font size in LOVE 2D is 12 pixels tall. 00:18:13.220 --> 00:18:15.220 So we're shifting it up by six so it's perfectly 00:18:15.220 --> 00:18:18.910 centered vertically in the screen. 00:18:18.910 --> 00:18:22.936 And then we're setting the alignment amount, the width, to window width 00:18:22.936 --> 00:18:25.810 so that it's going to align it within the entire width of our window. 00:18:25.810 --> 00:18:27.620 And now we're setting it to center alignment. 00:18:27.620 --> 00:18:30.078 So it's going to be center aligned within our entire window 00:18:30.078 --> 00:18:31.230 starting at x zero. 00:18:31.230 --> 00:18:36.280 And so if we go to Pong zero, and then we actually run it, 00:18:36.280 --> 00:18:38.080 it has the effect of doing this. 00:18:38.080 --> 00:18:41.500 We're just rendering in our default font, default size, 00:18:41.500 --> 00:18:44.230 hello, Pong, right in the middle of the screen. 00:18:44.230 --> 00:18:47.290 So not a terribly exciting example, but it 00:18:47.290 --> 00:18:51.400 is showcasing the most important functions of LOVE 2D, 00:18:51.400 --> 00:18:55.060 so that we can get started with slightly more interesting examples. 00:18:55.060 --> 00:18:59.270 So, our first content update, is the Low-Res Update. 00:18:59.270 --> 00:19:03.590 So we're developing Pong and Pong is an old game. 00:19:03.590 --> 00:19:06.280 It doesn't look like the example that we just looked 00:19:06.280 --> 00:19:09.410 at where the font is fairly high res. 00:19:09.410 --> 00:19:11.740 We want something that looks a little more retro. 00:19:11.740 --> 00:19:14.890 So what we want to do is get our resolution 00:19:14.890 --> 00:19:18.490 looking like it's from a game released in 1972. 00:19:18.490 --> 00:19:22.220 So what we're going to do is look at a few more important functions here. 00:19:22.220 --> 00:19:25.240 So Pong One has these functions. 00:19:25.240 --> 00:19:28.470 So love.graphics.setDefaultFilter. 00:19:28.470 --> 00:19:32.710 This function, the purpose of that, is every time 00:19:32.710 --> 00:19:36.130 we have a font or an image in our application, 00:19:36.130 --> 00:19:39.010 it's going to be applied a filter by default. 00:19:39.010 --> 00:19:41.186 So it's going to by default a bilinear filter. 00:19:41.186 --> 00:19:43.060 So what's going to happen, the effect of that 00:19:43.060 --> 00:19:48.400 is, basically, whenever we magnify or downscale a texture, 00:19:48.400 --> 00:19:51.130 it's going to think that-- it's going to assume 00:19:51.130 --> 00:19:55.960 that we want it to be slightly blurred so as to not look too pixilated. 00:19:55.960 --> 00:19:57.700 Which is good in certain contexts. 00:19:57.700 --> 00:20:01.362 For higher res 2D game development, that's good, but as we're going to see, 00:20:01.362 --> 00:20:03.820 that's not particularly good in the context of retro games. 00:20:03.820 --> 00:20:08.180 Retro games have a very 2D, crisp, pixilated aesthetic, 00:20:08.180 --> 00:20:09.890 and we want to preserve that. 00:20:09.890 --> 00:20:11.740 And so this lets us set a default filter. 00:20:11.740 --> 00:20:14.030 We'll see that in usage shortly. 00:20:14.030 --> 00:20:17.130 Another important-- very important function which is the input phase 00:20:17.130 --> 00:20:21.150 of our game loop that we saw earlier, love.keypressed(key) is what's going 00:20:21.150 --> 00:20:24.610 to allow us to start interacting with that aspect of our game. 00:20:24.610 --> 00:20:28.440 So love.keypressed(key) is a callback function that LOVE expects in main.lua. 00:20:28.440 --> 00:20:29.940 We're going to overwrite it. 00:20:29.940 --> 00:20:31.980 It gets passed in a key, and this function 00:20:31.980 --> 00:20:35.230 gets called every time by LOVE 2D whenever we press a key. 00:20:35.230 --> 00:20:37.810 It'll detect a key pressed, and it will call this function. 00:20:37.810 --> 00:20:39.810 Whatever we've defined in here, it will call it, 00:20:39.810 --> 00:20:42.810 and we can set it to take in certain keys 00:20:42.810 --> 00:20:46.030 and perform certain operations on that input and it will get a string. 00:20:46.030 --> 00:20:49.320 So if we say-- if we press the escape key, 00:20:49.320 --> 00:20:52.390 key is going to be equal to the string escape in that function, 00:20:52.390 --> 00:20:53.910 and we have access to that. 00:20:53.910 --> 00:20:56.700 And another important function, love.event.quit. 00:20:56.700 --> 00:20:59.760 This has just a very simple effect of quitting the application, 00:20:59.760 --> 00:21:03.980 though we can call it in the code as opposed to doing it ourselves. 00:21:03.980 --> 00:21:07.882 And so here's an example of what texture filtering looks like. 00:21:07.882 --> 00:21:09.840 Point filtering is the same as nearest neighbor 00:21:09.840 --> 00:21:11.881 filtering, which is what we're going to be using. 00:21:11.881 --> 00:21:15.400 Bilinear filtering is shown on the right, where it looks pretty blurry. 00:21:15.400 --> 00:21:18.890 That's what LOVE 2D applies by default to both fonts and to textures. 00:21:21.510 --> 00:21:23.620 And we'll see that in an example. 00:21:23.620 --> 00:21:25.810 I can actually run it in two different styles. 00:21:25.810 --> 00:21:28.890 So if you go to Pong One in the repo, and then we run it, 00:21:28.890 --> 00:21:32.919 we see here, hello, Pong is now blown up. 00:21:32.919 --> 00:21:34.710 And we'll look at some more code, actually, 00:21:34.710 --> 00:21:36.360 to see as to why it's blown up. 00:21:36.360 --> 00:21:42.870 But if we go back to our code, let me pull up Pong One. 00:21:46.670 --> 00:21:52.150 Go to main.lua, and then I'm going to explain this in just a second, 00:21:52.150 --> 00:21:55.730 but let me comment this out and we'll see the difference here. 00:21:55.730 --> 00:21:58.370 You can see it looks a lot blurrier. 00:21:58.370 --> 00:22:00.930 And that's the default texture filtering taking place. 00:22:00.930 --> 00:22:04.400 It applies, like I said, not only to textures but also to fonts. 00:22:04.400 --> 00:22:06.150 And that's not the aesthetic we want. 00:22:06.150 --> 00:22:11.730 So let's look at Pong One in detail, starting at the top. 00:22:11.730 --> 00:22:14.250 On line 28, we're acquiring a library. 00:22:14.250 --> 00:22:16.850 This is how you get a library in your LOVE 2D 00:22:16.850 --> 00:22:18.740 application, or your LOVE application. 00:22:18.740 --> 00:22:21.160 Just equals require and the name of the library. 00:22:21.160 --> 00:22:25.490 Push is what we're going to be using to take our 1280 by 720 window 00:22:25.490 --> 00:22:31.490 and turn it into a virtual resolution window at 432 by 243. 00:22:31.490 --> 00:22:36.500 We can start to think of our game in terms of a more low res feel, 00:22:36.500 --> 00:22:40.220 and think about it in 432 by 243 pixels, but still render it 00:22:40.220 --> 00:22:42.240 in a window that's arbitrarily sized. 00:22:42.240 --> 00:22:47.060 In this case, we're preserving the 1280 by 720 window that we saw before. 00:22:47.060 --> 00:22:51.770 If you go to our love.load function, we see this being used on line 47. 00:22:51.770 --> 00:22:54.410 Instead of love.window.setmode, we're now 00:22:54.410 --> 00:22:59.000 using push setup screen, the push libraries setup screen function, 00:22:59.000 --> 00:23:02.570 where it takes a virtual width, a virtual height, our regular window 00:23:02.570 --> 00:23:05.840 width and our window height, and then the same table as before. 00:23:05.840 --> 00:23:09.850 And this has the effect of setting up a window that's 00:23:09.850 --> 00:23:12.710 got our concrete dimensions of 1280 by 720, 00:23:12.710 --> 00:23:16.580 but a virtual resolution of 432 by 243. 00:23:16.580 --> 00:23:20.480 And so now, when it renders, as we'll see shortly, as-- 00:23:20.480 --> 00:23:23.425 well, as we already did see, actually, it's magnified. 00:23:23.425 --> 00:23:28.490 It has the effect of giving us a lower resolution. 00:23:28.490 --> 00:23:32.930 And in line--on line 58, if we look at the love.keypressed function, 00:23:32.930 --> 00:23:37.400 we've put in there, if the key equals the string escape, 00:23:37.400 --> 00:23:39.500 then love.event.quit. 00:23:39.500 --> 00:23:41.030 So now we have input handling. 00:23:41.030 --> 00:23:45.530 We've overridden love.keypressed(key), which LOVE 2D is going to look 00:23:45.530 --> 00:23:47.919 for in our application, and then call as needed. 00:23:47.919 --> 00:23:49.460 And then we're just looking in there. 00:23:49.460 --> 00:23:52.250 If the key is escape, then love.event.quit. 00:23:52.250 --> 00:23:56.180 And if I run the application, I can now press escape on my keyboard 00:23:56.180 --> 00:23:59.780 and just quit it and not have to command quit or click 00:23:59.780 --> 00:24:03.450 the X on a Windows application. 00:24:03.450 --> 00:24:08.140 And we've changed one more thing, also, in the love.draw function on line 70. 00:24:08.140 --> 00:24:10.820 We're using the push library now. 00:24:10.820 --> 00:24:14.270 We need to-- it functions sort of as a state machine in that we 00:24:14.270 --> 00:24:17.240 set it to start rendering at a virtual resolution with push 00:24:17.240 --> 00:24:21.176 apply start, and then push apply end, and then anything in between, 00:24:21.176 --> 00:24:23.300 this is very similar actually to how Open Go works. 00:24:23.300 --> 00:24:25.110 We won't go into too much detail. 00:24:25.110 --> 00:24:30.860 But this is very similar in spirit to how much of Open Go programming works. 00:24:30.860 --> 00:24:32.810 Push apply start, push apply end. 00:24:32.810 --> 00:24:37.110 Between that, whatever we call, is going to render at this virtual resolution. 00:24:37.110 --> 00:24:40.250 And so we are calling the same love.graphics.print(f) function, 00:24:40.250 --> 00:24:43.490 hello Pong Zero, virtual high divided by two minus six. 00:24:43.490 --> 00:24:44.810 Same parameters. 00:24:44.810 --> 00:24:48.680 And it has the effect of rendering everything that's still got 00:24:48.680 --> 00:24:53.840 the old aliasing going on the skin-- 00:24:53.840 --> 00:24:58.150 texture filtering going on as the effect of giving us our magnified text. 00:24:58.150 --> 00:25:02.910 So same text, same size, but now our window of rendering is much smaller. 00:25:02.910 --> 00:25:04.550 So-- 00:25:04.550 --> 00:25:08.660 Any questions so far on how any of this works? 00:25:08.660 --> 00:25:09.160 OK. 00:25:09.160 --> 00:25:10.580 Awesome. 00:25:10.580 --> 00:25:14.660 So we've gotten text right into the screen, but we're nowhere close to Pong 00:25:14.660 --> 00:25:16.910 yet, so the first big thing, I think, that's 00:25:16.910 --> 00:25:20.210 going to get us closer in that direction is what we're 00:25:20.210 --> 00:25:22.040 going to call the rectangle update. 00:25:22.040 --> 00:25:26.080 So some important functions that we should look at. 00:25:26.080 --> 00:25:27.800 Love.graphics.newFont. 00:25:27.800 --> 00:25:30.860 The default font, I believe, is Arial. 00:25:30.860 --> 00:25:33.819 We don't want Arial in our application, because we want something 00:25:33.819 --> 00:25:35.110 that looks a little more retro. 00:25:35.110 --> 00:25:38.570 We want something that looks more relevant. 00:25:38.570 --> 00:25:43.400 Love.graphics.newFont will basically take a path to a font file 00:25:43.400 --> 00:25:49.050 that we have in our folder, which if you're in the Pong 2 folder, 00:25:49.050 --> 00:25:53.420 you'll see a font.ttf file, and a size. 00:25:53.420 --> 00:25:55.760 Because every font object that we instantiate 00:25:55.760 --> 00:25:59.240 needs to have a size, because the font objects are immutable. 00:25:59.240 --> 00:26:03.560 Once constructed, they cannot be changed so they need to be allocated on a size 00:26:03.560 --> 00:26:05.560 by size basis. 00:26:05.560 --> 00:26:08.540 Love.graphics.setFont will take whatever font 00:26:08.540 --> 00:26:13.070 object we've acquired from this function call, and we can set it here 00:26:13.070 --> 00:26:17.630 and it'll set the active font in LOVE 2D to be that font. 00:26:17.630 --> 00:26:20.240 Love is a state machine in the same sense 00:26:20.240 --> 00:26:25.520 as before, in that it will have an active font at any one time, 00:26:25.520 --> 00:26:29.630 and whatever print functions you call, will use the currently active font. 00:26:29.630 --> 00:26:33.590 And that also applies to whatever color you might want to render to the screen, 00:26:33.590 --> 00:26:34.260 whatever. 00:26:34.260 --> 00:26:37.070 If you have a font and you want to maybe render it in red, 00:26:37.070 --> 00:26:42.080 you need a set LOVE 2D's active color to red as well. 00:26:42.080 --> 00:26:47.330 Love.graphics.clear is a function that takes an RGBA quadruple, 00:26:47.330 --> 00:26:49.956 and will flush the screen in that color. 00:26:49.956 --> 00:26:52.580 It just has a simple effect of wiping the screen in that color. 00:26:52.580 --> 00:26:55.512 Useful for drawing just flat color backgrounds. 00:26:55.512 --> 00:26:58.220 And then the last function, probably the most important function, 00:26:58.220 --> 00:27:00.322 is love.graphics.rectangle. 00:27:00.322 --> 00:27:02.030 And this is the first function that we'll 00:27:02.030 --> 00:27:05.540 see that actually ends up drawing something beyond text to the screen. 00:27:05.540 --> 00:27:09.620 It takes it a mode, which can be fill or line, an x and a y 00:27:09.620 --> 00:27:13.140 and a width and a height, and it'll draw a rectangle in that mode. 00:27:13.140 --> 00:27:16.610 So either filled, so a filled rectangle, or a line rectangle. 00:27:16.610 --> 00:27:19.550 It'll take-- it'll draw it at xy with the width and the height 00:27:19.550 --> 00:27:21.420 that we pass in. 00:27:21.420 --> 00:27:24.240 So let's go ahead and take a look at Pong 2 00:27:24.240 --> 00:27:27.800 where we can see this actually implemented. 00:27:27.800 --> 00:27:31.640 So Pong 2, we have our font.ttf in there that I've included. 00:27:31.640 --> 00:27:33.179 And then a main.lua. 00:27:33.179 --> 00:27:35.220 And by the way, I forgot to mention last example, 00:27:35.220 --> 00:27:39.330 push the library that we required is also just in the same directory. 00:27:39.330 --> 00:27:43.790 And you can just do require as long as the file is there within the directory, 00:27:43.790 --> 00:27:45.170 it will just load it. 00:27:45.170 --> 00:27:46.670 You don't have to specify .lua. 00:27:46.670 --> 00:27:52.580 It assumes when you require some string, that it follows with the .lua suffix. 00:27:52.580 --> 00:27:56.780 So we're here looking at main.lua in Pong 2, the rectangle update. 00:27:56.780 --> 00:27:59.900 So on line 28 to 34, it's all the same stuff. 00:27:59.900 --> 00:28:00.800 We're acquiring push. 00:28:00.800 --> 00:28:04.210 We have our width and height virtually and physically. 00:28:04.210 --> 00:28:08.120 In our love.load function, we are on line 43 00:28:08.120 --> 00:28:13.100 declaring small font to be a love.graphics.newFont, 00:28:13.100 --> 00:28:15.590 giving it the path font.ttf because it's right there 00:28:15.590 --> 00:28:17.720 in the same directory at size eight. 00:28:17.720 --> 00:28:21.260 And this is going to create a font object, small font, that we can then 00:28:21.260 --> 00:28:24.600 set as the active font as needed. 00:28:24.600 --> 00:28:29.930 So if we go down to line 78 in the same directory, 00:28:29.930 --> 00:28:32.432 we see we're calling love.graphics.clear, 00:28:32.432 --> 00:28:33.890 and so we're passing it in a color. 00:28:33.890 --> 00:28:36.890 I sampled some images of Pong on Google Images 00:28:36.890 --> 00:28:45.050 and saw a background gray that I liked, so 40, 45, 52, RGB, and then 255 just 00:28:45.050 --> 00:28:49.727 means completely opaque, so no transparency, the alpha component. 00:28:49.727 --> 00:28:51.560 And then we're doing the same print.function 00:28:51.560 --> 00:28:53.210 as we did before on line 81. 00:28:53.210 --> 00:28:56.990 Below that on line 89 down through 95, we're actually 00:28:56.990 --> 00:28:59.600 calling love.graphics.rectangle. 00:28:59.600 --> 00:29:02.210 And these are drawing the two paddles and then the ball. 00:29:02.210 --> 00:29:05.720 So, note, love.graphics.rectangle fill mode, 00:29:05.720 --> 00:29:09.320 because we want the paddles to be completely filled, as is the ball. 00:29:09.320 --> 00:29:14.450 We're giving it an xy of 1030 and a width height of 520. 00:29:14.450 --> 00:29:19.280 And on 992, we're-- and that'll have the effect of drawing it a little bit 00:29:19.280 --> 00:29:23.270 shifted from the top left corner, five pixels wide, 20 pixels tall. 00:29:23.270 --> 00:29:26.300 On line 92, we're doing the same thing, except we're 00:29:26.300 --> 00:29:28.260 going virtual width minus 10. 00:29:28.260 --> 00:29:32.040 So it's going to go to the right edge of our screen, virtual width minus 10. 00:29:32.040 --> 00:29:33.660 So 432 minus 10. 00:29:33.660 --> 00:29:36.260 So 422. 00:29:36.260 --> 00:29:38.370 And then virtual height minus 50. 00:29:38.370 --> 00:29:41.382 So it's going to be slightly-- 00:29:41.382 --> 00:29:43.840 it's going to be slightly up from the bottom of the screen. 00:29:43.840 --> 00:29:47.540 So we have our top left paddle and our bottom right paddle. 00:29:47.540 --> 00:29:48.920 And then the ball is dead center. 00:29:48.920 --> 00:29:52.070 So we're sitting-- doing another graphics-- rectangle call. 00:29:52.070 --> 00:29:54.080 Virtual width minus two divided by two. 00:29:54.080 --> 00:29:56.300 So right in the middle, minus two, because our ball 00:29:56.300 --> 00:29:58.550 is going to be two pixels wide by two pixels tall. 00:29:58.550 --> 00:30:00.466 And same thing with virtual height, minus two. 00:30:00.466 --> 00:30:03.080 Our virtual height divided by two, minus two. 00:30:03.080 --> 00:30:08.040 So I'm going to go ahead and CD into the Pong 2 directory and run it. 00:30:08.040 --> 00:30:11.790 And that has the effect of we have our new font here in the middle. 00:30:11.790 --> 00:30:16.040 So it looks nice and retro, much more so than the Arial font as before. 00:30:16.040 --> 00:30:19.149 We have a rectangle here, five pixels wide by 20 pixels tall. 00:30:19.149 --> 00:30:21.440 A ball in the middle, which is four pixels wide by four 00:30:21.440 --> 00:30:23.940 pixels tall, and then a paddle on the bottom right, 00:30:23.940 --> 00:30:27.260 which is the same dimensions as the paddle on the left. 00:30:27.260 --> 00:30:29.407 So it looks very similar to Pong. 00:30:29.407 --> 00:30:32.240 It's not interactive at all, but we sort of getting the feel of what 00:30:32.240 --> 00:30:33.781 we want our application to look like. 00:30:33.781 --> 00:30:35.930 We have it mostly sketched out. 00:30:35.930 --> 00:30:41.160 So any questions so far as to how this works? 00:30:41.160 --> 00:30:41.660 OK. 00:30:41.660 --> 00:30:43.440 Awesome. 00:30:43.440 --> 00:30:44.340 So Pong 3. 00:30:44.340 --> 00:30:48.587 So currently we have no interactivity with our application, 00:30:48.587 --> 00:30:50.670 and we want to be able to move the paddles around. 00:30:50.670 --> 00:30:53.552 We don't want to just be looking at an-- at an image the whole time. 00:30:53.552 --> 00:30:56.010 So the paddle update is going to solve this problem for us. 00:30:56.010 --> 00:30:59.735 We're going to actually get our first sort of-- beyond pressing escape 00:30:59.735 --> 00:31:01.860 to quit the application, we're going to get a sense 00:31:01.860 --> 00:31:04.200 of interacting with it dynamically. 00:31:04.200 --> 00:31:07.620 So the important function that we're going to look at in this example 00:31:07.620 --> 00:31:11.310 is love.keyboard.isDownsomekey. 00:31:11.310 --> 00:31:13.597 And this is true-- this is a Boolean function. 00:31:13.597 --> 00:31:16.680 It just returns true or false depending on whether the key that we pass in 00:31:16.680 --> 00:31:21.420 as a string is currently pressed down on this frame. 00:31:21.420 --> 00:31:23.980 So it just returns true or false. 00:31:23.980 --> 00:31:27.030 And so let's go ahead and take a look at the demo. 00:31:27.030 --> 00:31:31.110 We're going to go ahead and pull up Pong 3. 00:31:31.110 --> 00:31:32.670 The main.lua, they're in. 00:31:35.430 --> 00:31:39.640 Notice that line 37 if you're looking in Pong 3 we, have a new constant 00:31:39.640 --> 00:31:43.260 we've defined called paddle speed, which gets the value 200. 00:31:43.260 --> 00:31:46.279 And this is just an arbitrary value that I found was a good speed. 00:31:46.279 --> 00:31:48.320 But this is how fast our paddle is going to move. 00:31:48.320 --> 00:31:50.280 We're going to scale it by delta time, so we're 00:31:50.280 --> 00:31:53.670 going to multiply it by how many seconds have passed. 00:31:53.670 --> 00:31:56.100 Typically, a fraction of a second since the last frame. 00:31:56.100 --> 00:31:59.040 And this is going to, therefore, move the same distance 00:31:59.040 --> 00:32:01.050 over time depending on whether your computer is 00:32:01.050 --> 00:32:05.230 running at 10 frames per second or 60 frames per second. 00:32:05.230 --> 00:32:10.680 So if we go down here to line 63, I've also set up two new variables. 00:32:10.680 --> 00:32:12.630 Player one score and player two score. 00:32:12.630 --> 00:32:14.550 Those are both initialized at zero. 00:32:14.550 --> 00:32:19.920 We're going to add in this example, also, some rendering of the score. 00:32:19.920 --> 00:32:22.470 And notice here on line 49, I've also added 00:32:22.470 --> 00:32:26.130 a new font, which showcases how you need to separate fonts based on their size 00:32:26.130 --> 00:32:27.660 because they're immutable objects. 00:32:27.660 --> 00:32:30.090 Score font gets love.graphics.newFont. 00:32:30.090 --> 00:32:35.430 Same exact font file, but it's 32 pixels large because the font-- or the score 00:32:35.430 --> 00:32:38.947 when rendered in Pong is pretty large in the middle of the screen. 00:32:38.947 --> 00:32:41.280 And so we're creating-- we have two different fonts now. 00:32:41.280 --> 00:32:44.210 One for rendering our message, one for rendering our score. 00:32:44.210 --> 00:32:47.460 And it's just going to render these two variables, player one score and player 00:32:47.460 --> 00:32:48.630 two score. 00:32:48.630 --> 00:32:52.260 And then we've also initialized our y values 00:32:52.260 --> 00:32:55.290 for the rectangles, the paddles on the left and the right. 00:32:55.290 --> 00:32:58.410 We need to keep track of their y position, because paddles in Pong 00:32:58.410 --> 00:33:00.330 can only move up or down. 00:33:00.330 --> 00:33:02.787 So player 1Y gets the same value it did before when 00:33:02.787 --> 00:33:05.370 we initialized the rectangle, when we drew it onto the screen. 00:33:05.370 --> 00:33:07.740 It's going to start at y 30, so pretty high up. 00:33:07.740 --> 00:33:10.350 And player 2y is going to start pretty low, virtual height 00:33:10.350 --> 00:33:14.250 minus 50, which is 432 minus 50. 00:33:14.250 --> 00:33:17.760 And so in love.update, which is our first actual use of the update 00:33:17.760 --> 00:33:21.780 function on line 75 with the (dt) parameter that gets passed in. 00:33:21.780 --> 00:33:24.320 Note, remember that LOVE 2D will pass that in for us, 00:33:24.320 --> 00:33:27.570 but we need to give it-- we need to define the behavior inside of it. 00:33:27.570 --> 00:33:31.770 We're using love.keyboard.isDown, and we're passing in the string w 00:33:31.770 --> 00:33:33.590 and s for this first block. 00:33:33.590 --> 00:33:36.000 This first block here is player one's movement. 00:33:36.000 --> 00:33:40.680 So, traditionally, on computer WASD is to move-- 00:33:40.680 --> 00:33:43.950 and this example, we're going to allow ourselves to move both paddles, 00:33:43.950 --> 00:33:46.170 so we're going to use w and s for the left paddle, 00:33:46.170 --> 00:33:48.030 and up or down for the right paddle. 00:33:48.030 --> 00:33:51.050 So if love.keyboard.isDown w, which means-- 00:33:51.050 --> 00:33:54.660 or we've currently pressing the W key, player 1y 00:33:54.660 --> 00:34:00.040 is going to get itself, plus negative paddle speed times delta time. 00:34:00.040 --> 00:34:01.450 So it's going to move up. 00:34:01.450 --> 00:34:04.924 It's going to take negative paddle speed, multiply it by delta time, 00:34:04.924 --> 00:34:06.840 and add that onto our y value, which will have 00:34:06.840 --> 00:34:08.580 the effect of shifting our paddle up. 00:34:08.580 --> 00:34:10.469 And it's the opposite for-- 00:34:10.469 --> 00:34:13.480 on line 82 for-- if we're pressing the s key. 00:34:13.480 --> 00:34:17.870 We need to increase the y by positive paddle speed, because recall, 00:34:17.870 --> 00:34:19.560 y-axis movement is-- 00:34:19.560 --> 00:34:22.359 up is negative, down is positive. 00:34:22.359 --> 00:34:24.900 We're doing the exact same thing with the paddle on the right 00:34:24.900 --> 00:34:30.570 except we're using up and down as the strings into love.keyboard.isDown. 00:34:30.570 --> 00:34:36.000 And then down below here, we are rendering in addition 00:34:36.000 --> 00:34:38.940 to what we rendered before, also, the score now. 00:34:38.940 --> 00:34:44.185 So on line 125, note that we're calling love.graphics.setFont, scoreFont, 00:34:44.185 --> 00:34:47.310 because if we don't call this, it will use just whatever the last font was, 00:34:47.310 --> 00:34:49.830 which by default is the eight pixel font because we set that 00:34:49.830 --> 00:34:51.840 up top in our program. 00:34:51.840 --> 00:34:54.030 We want to set it to the score font, and then we 00:34:54.030 --> 00:34:55.770 want to call love.graphics.print. 00:34:55.770 --> 00:34:59.520 In this case, I'm just printing them in concrete places not, using printf. 00:34:59.520 --> 00:35:01.820 Virtual width divided by two minus 50. 00:35:01.820 --> 00:35:03.570 So no matter how we scale our window, it's 00:35:03.570 --> 00:35:08.820 always going to be 50 pixels to the left of the center of the window, 00:35:08.820 --> 00:35:12.040 and the 30 pixels to the right of the center of the window 00:35:12.040 --> 00:35:16.780 if we're rendering the player two score. 00:35:16.780 --> 00:35:23.150 And so if we go into Pong 3 and we run it, it looks the same as before. 00:35:23.150 --> 00:35:26.880 Note, that we do have a score now in the middle of the screen, zero and zero. 00:35:26.880 --> 00:35:30.720 But, more importantly, we can move our paddles up and down. 00:35:30.720 --> 00:35:34.140 But there's one problem, and that's I can move beyond the edge of the screen, 00:35:34.140 --> 00:35:37.320 which is not behavior that we want in our application. 00:35:37.320 --> 00:35:40.310 So we have some interactivity, it's moving along, 00:35:40.310 --> 00:35:42.570 but we still have a long way to go, unfortunately. 00:35:42.570 --> 00:35:43.890 Or fortunately. 00:35:43.890 --> 00:35:47.340 So let's go ahead and look at the ball update. 00:35:47.340 --> 00:35:52.710 So we have paddles, they can move, they can move beyond the edge of the screen, 00:35:52.710 --> 00:35:57.070 but we don't have a ball that-- it just sits in the middle of the screen. 00:35:57.070 --> 00:35:58.704 And that's not what we're looking for. 00:35:58.704 --> 00:36:01.620 We want to have a ball that we can actually bounce between the paddles 00:36:01.620 --> 00:36:04.314 so we can get an actual game going beyond just moving paddles. 00:36:04.314 --> 00:36:06.480 So a few important functions we're going to look at. 00:36:06.480 --> 00:36:09.250 We're going to get our first look here at random. 00:36:09.250 --> 00:36:13.710 So in games, random number generation is a very common thing 00:36:13.710 --> 00:36:17.370 so that we get unpredictability and variability between different instances 00:36:17.370 --> 00:36:18.982 of our game. 00:36:18.982 --> 00:36:20.940 An important function that just belongs to Lua. 00:36:20.940 --> 00:36:23.010 It's not a LOVE 2D thing, it's just a Lua thing. 00:36:23.010 --> 00:36:25.530 Math.random seed numb. 00:36:25.530 --> 00:36:28.620 So many of you have probably heard of like seed, 00:36:28.620 --> 00:36:30.990 like a random number generator, seed, and that just 00:36:30.990 --> 00:36:33.150 means a random number generator. 00:36:33.150 --> 00:36:35.310 Because it's pseudo random, it needs some sort 00:36:35.310 --> 00:36:38.850 of starting value to base all of its random numbers off of. 00:36:38.850 --> 00:36:42.330 It takes a starting number, it performs some mathematical operation 00:36:42.330 --> 00:36:45.810 on that number to derive new random values that we can then 00:36:45.810 --> 00:36:46.990 use in our game engine. 00:36:46.990 --> 00:36:49.396 But if we give it the same number every single time, 00:36:49.396 --> 00:36:52.270 it's just going to give us the same random numbers every single time, 00:36:52.270 --> 00:36:53.890 which means it's not going to be random at all. 00:36:53.890 --> 00:36:55.420 It's going to be very consistent. 00:36:55.420 --> 00:36:58.530 So we need a way to seed our random number generator, 00:36:58.530 --> 00:37:01.930 give it a different initial value or seed, 00:37:01.930 --> 00:37:06.300 and we're going to do that with the function math.randomseed somenumb. 00:37:06.300 --> 00:37:09.930 OS.time is an important function in the context of this, 00:37:09.930 --> 00:37:13.980 because a very common way of getting a different number every time 00:37:13.980 --> 00:37:17.550 you run your application is passing it in whatever the current time is 00:37:17.550 --> 00:37:20.940 in seconds, because usually it's a very large number that 00:37:20.940 --> 00:37:24.210 is going to be different every single time you run your game, no matter what. 00:37:24.210 --> 00:37:27.150 Because it's based upon, in the context of most engines, 00:37:27.150 --> 00:37:32.450 in the context of Lua, what's called Unix Epoch time, which is zero 00:37:32.450 --> 00:37:39.150 zero UTC January 1st, 1970, which is some huge number nine or 10 digits long 00:37:39.150 --> 00:37:41.995 that changes every single second. 00:37:41.995 --> 00:37:44.370 And then in order to actually take advantage of all this, 00:37:44.370 --> 00:37:46.119 we need a function to get a random number, 00:37:46.119 --> 00:37:49.427 and so we do that with math.random, which takes a min and a max, 00:37:49.427 --> 00:37:51.510 although you don't need to technically pass a min, 00:37:51.510 --> 00:37:56.670 it'll just implicitly deuse min as one if you don't pass it a min. 00:37:56.670 --> 00:37:59.590 And it'll return a value inclusively within that range. 00:37:59.590 --> 00:38:02.790 So if you say math.random one, 50, it'll give us 00:38:02.790 --> 00:38:04.890 a random inclusively between 1 and 50. 00:38:04.890 --> 00:38:08.110 And if we just say math.random 50, it'll do the same exact thing. 00:38:08.110 --> 00:38:12.915 It'll say-- it'll assume rmin is one and give us a value between one and 50. 00:38:12.915 --> 00:38:14.790 And then two important mathematical functions 00:38:14.790 --> 00:38:18.450 that are very basic but helpful in the context of games almost everywhere, 00:38:18.450 --> 00:38:21.750 it's just math.min, which returns the lesser of two values, 00:38:21.750 --> 00:38:24.200 and math.max which turns the greater of two values. 00:38:24.200 --> 00:38:29.260 And we'll see this in the context of clamping values to some range. 00:38:29.260 --> 00:38:31.600 So let's go ahead and take a look at a demo here. 00:38:31.600 --> 00:38:33.806 So I'm going to go ahead and open up Pong 4. 00:38:37.620 --> 00:38:39.710 And going to look at main.lua they're in. 00:38:42.570 --> 00:38:48.300 So here on line 47, we see we're calling the math.random seedfunction as before. 00:38:48.300 --> 00:38:52.380 And note that we're passing in OS.time, another function call, 00:38:52.380 --> 00:38:55.779 because OS.time is going to be different every time we run our application. 00:38:55.779 --> 00:38:57.570 So we're seeding our application every time 00:38:57.570 --> 00:39:02.197 we run it based on whatever the current second is relative to zero, zero, zero, 00:39:02.197 --> 00:39:04.537 zero, zero, zero, January 1st, 1970. 00:39:04.537 --> 00:39:06.870 Which is going to be different every single time we run. 00:39:06.870 --> 00:39:10.800 Assuming we don't run it within the same second. 00:39:10.800 --> 00:39:16.860 And then if we go down to line 71 and seven-- or, sorry, 67 and 68, 00:39:16.860 --> 00:39:18.420 we now have-- 00:39:18.420 --> 00:39:21.420 we're giving a starting value to our ball, 00:39:21.420 --> 00:39:23.800 because we want to actually start manipulating our ball. 00:39:23.800 --> 00:39:27.300 So we give it an X and a Y. So we're setting it right to the center again 00:39:27.300 --> 00:39:29.490 but, now, we're defining a variable for it 00:39:29.490 --> 00:39:33.030 instead of just rendering it statically with our love.graphics.rectangle 00:39:33.030 --> 00:39:35.286 function, because we want this to change over time. 00:39:35.286 --> 00:39:37.660 We want to start letting our ball move around the screen. 00:39:37.660 --> 00:39:40.830 So these x and y variables are going to start changing now, 00:39:40.830 --> 00:39:44.100 and they're going to change relative to its current velocity. 00:39:44.100 --> 00:39:48.840 And its velocity is going to be stored in ball dx and ball dy. 00:39:48.840 --> 00:39:53.380 dx and dy are common shorthands for delta x and delta y, 00:39:53.380 --> 00:39:55.500 which is how you represent velocity. 00:39:55.500 --> 00:39:59.100 So what we're going to do, effectively, is take whatever our delta x and delta 00:39:59.100 --> 00:40:01.672 y are and add them onto our ball frame by frame, 00:40:01.672 --> 00:40:03.630 and that's going to have the effect of updating 00:40:03.630 --> 00:40:05.520 our ball's position by some value. 00:40:05.520 --> 00:40:07.890 And separating the delta x and the delta y 00:40:07.890 --> 00:40:12.662 will allow us to have different angles, different trajectories for our ball. 00:40:12.662 --> 00:40:15.370 And then another thing that we're also doing in this application, 00:40:15.370 --> 00:40:18.270 we're starting with the concept of a game state. 00:40:18.270 --> 00:40:21.840 Because now we can have a starting state, and what we're going to have 00:40:21.840 --> 00:40:23.040 is a play state. 00:40:23.040 --> 00:40:25.320 And so all we're going to do here in this example 00:40:25.320 --> 00:40:28.607 and in this application is start state as a string. 00:40:28.607 --> 00:40:31.440 In future examples, we're going to use what's called a state machine 00:40:31.440 --> 00:40:34.260 and actually separate out different states into their own-- 00:40:34.260 --> 00:40:35.770 into their own modules. 00:40:35.770 --> 00:40:36.870 But in the context of this game, we're just 00:40:36.870 --> 00:40:38.828 going to use a simple string just to illustrate 00:40:38.828 --> 00:40:42.180 how it works, and we're going to say our first state, when we start the game, 00:40:42.180 --> 00:40:46.650 should be the start string in the start state. 00:40:46.650 --> 00:40:49.440 And so here on line 86, what we're going to do 00:40:49.440 --> 00:40:52.394 is solve a problem that we had in the last example, which was, 00:40:52.394 --> 00:40:55.560 the paddles could move beyond the edges of the screen, which is not behavior 00:40:55.560 --> 00:40:56.820 that we should permit. 00:40:56.820 --> 00:41:02.640 So we're going to call math.max on zero, and the same operation 00:41:02.640 --> 00:41:06.180 we were doing before, and that will have the effect of returning whichever 00:41:06.180 --> 00:41:08.260 of those two values is greater. 00:41:08.260 --> 00:41:10.920 So if the value is-- 00:41:10.920 --> 00:41:13.971 if we're adding negative paddle speed to our y value 00:41:13.971 --> 00:41:15.720 and it goes into the negative range, which 00:41:15.720 --> 00:41:18.722 means it's beyond the top edge of the screen, 00:41:18.722 --> 00:41:20.680 zero is going to be the greater of those values 00:41:20.680 --> 00:41:23.750 and so it will always be zero in that case. math.max returns 00:41:23.750 --> 00:41:25.000 the greater of the two values. 00:41:25.000 --> 00:41:27.416 So it'll have the effect of clamping it such that it never 00:41:27.416 --> 00:41:29.520 goes above the top edge. 00:41:29.520 --> 00:41:32.130 The inverse is true for line 96, where we 00:41:32.130 --> 00:41:35.860 call math.min on virtual height minus 20, 00:41:35.860 --> 00:41:39.290 and player 1.y plus paddle speed.delta-- times delta time. 00:41:39.290 --> 00:41:41.790 And this will have the same effect, it will return whichever 00:41:41.790 --> 00:41:43.560 of these two values is lesser. 00:41:43.560 --> 00:41:47.820 In which case, if we've gone above virtual height minus 20, which 00:41:47.820 --> 00:41:52.410 is down at the bottom of the screen shifted by the size of our paddle, 00:41:52.410 --> 00:41:55.000 it's going to set it to virtual height minus 20. 00:41:55.000 --> 00:41:57.390 So we never go below that point. 00:41:57.390 --> 00:42:01.470 And we're doing the same thing for player two, exact same logic. 00:42:01.470 --> 00:42:04.440 And then if we're in the play state, we're 00:42:04.440 --> 00:42:07.069 going to actually update our ball's position. 00:42:07.069 --> 00:42:10.360 So we're in the-- if we're in the start state, ball's not going to move at all. 00:42:10.360 --> 00:42:14.730 But if we're in the play state, we want ball x to equal ball x plus ball 00:42:14.730 --> 00:42:16.200 x times delta time. 00:42:16.200 --> 00:42:19.440 And note that there is no shorthand in Lua 00:42:19.440 --> 00:42:23.520 for adding the value to itself, which is why we're calling ball 00:42:23.520 --> 00:42:26.340 x equals ball x, plus ball x times delta time, 00:42:26.340 --> 00:42:30.300 instead of just saying ball x plus equals ball x times delta time. 00:42:30.300 --> 00:42:33.090 Just a language decision that they made. 00:42:33.090 --> 00:42:35.760 But if we're in the play state, this will 00:42:35.760 --> 00:42:39.720 have the effect of scaling whatever our current ball's velocity is and-- 00:42:39.720 --> 00:42:43.020 times delta time, so it stays frame rate independent. 00:42:43.020 --> 00:42:45.930 And then adding it to ball x and ball y, which will shift it. 00:42:45.930 --> 00:42:53.300 And we get this actually working down here in line 170-- 00:42:53.300 --> 00:42:54.690 174. 00:42:54.690 --> 00:42:59.370 We're now-- instead of just rendering flat numbers to the screen, 00:42:59.370 --> 00:43:01.740 we're actually using ball x and ball y to render. 00:43:01.740 --> 00:43:05.190 And if we're in the play state, those will get updated. 00:43:05.190 --> 00:43:13.740 But if we go back up to line 127, now, we're in the love.keypressed function, 00:43:13.740 --> 00:43:15.420 so we're starting on line 120. 00:43:15.420 --> 00:43:19.530 Before we just had the if key equals escape, then love.event.quit. 00:43:19.530 --> 00:43:22.620 But now on line 127, we're going to check 00:43:22.620 --> 00:43:25.800 to see if the key is equal to enter or return, 00:43:25.800 --> 00:43:29.697 and then we're going to use that as our way of just testing state changes. 00:43:29.697 --> 00:43:32.280 So we're going to say if the game state is equal to the start, 00:43:32.280 --> 00:43:35.670 once you press entered, the game state should be equal to play. 00:43:35.670 --> 00:43:37.960 Otherwise, set it back to start. 00:43:37.960 --> 00:43:41.350 And we set it back to start, we're going to re-initialize our x 00:43:41.350 --> 00:43:43.780 and y to be in the center, virtual width divided by two 00:43:43.780 --> 00:43:46.420 minus two, virtual height divided by two minus two, 00:43:46.420 --> 00:43:51.040 and we're going to give it an initial random starting velocity again. 00:43:51.040 --> 00:43:54.040 And note here, this math.random two equal-- 00:43:54.040 --> 00:43:57.150 is equal to one, and 100, or negative 100. 00:43:57.150 --> 00:43:59.990 It's just Lua's way of doing a ternary operation. 00:43:59.990 --> 00:44:03.400 So in C, you will often have like-- 00:44:03.400 --> 00:44:08.530 you would be something like math.random two equals one, 00:44:08.530 --> 00:44:13.030 and you would have a question mark, 100 colon, negative 100. 00:44:13.030 --> 00:44:14.830 It's the same exact thing, but Lua doesn't 00:44:14.830 --> 00:44:18.850 have that sort of shorthand for a ternary operation, so we do it with 00:44:18.850 --> 00:44:20.350 and and or. 00:44:20.350 --> 00:44:24.310 We use logical operations instead to do the same thing. 00:44:24.310 --> 00:44:28.660 And note here we're also showcasing that math.random can take 00:44:28.660 --> 00:44:30.760 either one argument or two arguments. 00:44:30.760 --> 00:44:32.757 In this case, we're saying math.random two, 00:44:32.757 --> 00:44:35.090 which means it will give us a value between one and two. 00:44:35.090 --> 00:44:36.370 So a 50-50. 00:44:36.370 --> 00:44:39.070 And then if we do negative 50-50, that means 00:44:39.070 --> 00:44:41.500 we'll get a value between negative 50 and 50. 00:44:41.500 --> 00:44:44.147 So a range of 100, effectively. 00:44:44.147 --> 00:44:46.980 And so what that has the effect of doing, if we run our application, 00:44:46.980 --> 00:44:48.100 we go into Pong 4. 00:44:50.970 --> 00:44:53.570 We're in the start state so now we're rendering-- 00:44:53.570 --> 00:44:57.230 if we're in the start state, it's set to render that message. 00:44:57.230 --> 00:45:01.370 If we press Enter, the ball gets a random veloc-- it's actually 00:45:01.370 --> 00:45:03.710 applying the velocity frame by frame. 00:45:03.710 --> 00:45:05.360 It's updating in the update method. 00:45:05.360 --> 00:45:09.410 If we press Enter again, it gets reset and we're back in the start state. 00:45:09.410 --> 00:45:11.550 So we do it again, it's getting a random value. 00:45:11.550 --> 00:45:12.830 Do it again, random value. 00:45:12.830 --> 00:45:13.860 Random value. 00:45:13.860 --> 00:45:17.030 So every time we're getting a different random ball value. 00:45:17.030 --> 00:45:24.230 But what happens if we try to actually run it, or try to interact with it? 00:45:24.230 --> 00:45:25.140 Nothing. 00:45:25.140 --> 00:45:26.207 Goes straight through. 00:45:26.207 --> 00:45:28.040 So we're missing a key piece, even though we 00:45:28.040 --> 00:45:32.210 have the core components of our game engine implemented, 00:45:32.210 --> 00:45:35.720 we don't have any concrete game play, nothing's interacting. 00:45:35.720 --> 00:45:38.550 And that's a major piece that we need to look at. 00:45:38.550 --> 00:45:40.040 And so the next-- 00:45:40.040 --> 00:45:42.170 before we actually start doing that, though, we're 00:45:42.170 --> 00:45:46.880 going to take a look at the class update, Pong 5. 00:45:46.880 --> 00:45:52.640 And so in order to get into more of a-- in order 00:45:52.640 --> 00:45:55.130 to scale our code more effectively, we need 00:45:55.130 --> 00:45:58.200 to start looking in terms of classes. 00:45:58.200 --> 00:46:01.826 And instead of having an x and a y for our ball, an x and a y for our paddle, 00:46:01.826 --> 00:46:04.700 a delta x, a delta y for our ball, all these different variables that 00:46:04.700 --> 00:46:07.310 are sort of all over the place starting to bloat our code, 00:46:07.310 --> 00:46:10.154 before we get too crazy with it, we should think about 00:46:10.154 --> 00:46:13.070 how can we put this data altogether so that we can just think in terms 00:46:13.070 --> 00:46:15.530 of our paddles or our ball object. 00:46:15.530 --> 00:46:17.780 And so we use what's called a class. 00:46:17.780 --> 00:46:21.110 If unfamiliar, a class is simply a way of taking all these variables that 00:46:21.110 --> 00:46:25.490 we've been using thus far, but putting them together in a container such that 00:46:25.490 --> 00:46:29.600 we can just say, paddle.x or paddle-- 00:46:29.600 --> 00:46:31.970 you know, in this case, car. 00:46:31.970 --> 00:46:37.530 If we have a function called drive car, now we can just say, car.drive instead. 00:46:37.530 --> 00:46:40.520 We don't have to have functions that are separate from our values, 00:46:40.520 --> 00:46:42.020 that-- we can put them all together. 00:46:42.020 --> 00:46:45.500 We can ask what's our car's current mileage instead 00:46:45.500 --> 00:46:48.120 of having all these different variables all over the place. 00:46:48.120 --> 00:46:51.890 So the classes are effectively blueprints. 00:46:51.890 --> 00:46:53.390 Use it-- you define a class. 00:46:53.390 --> 00:46:55.970 You say, OK, my car class is going to have 00:46:55.970 --> 00:46:58.400 a-- it's going to have a mileage variable, 00:46:58.400 --> 00:47:02.081 it's going to have a paint variable, it's going to have a make and a model, 00:47:02.081 --> 00:47:05.330 it's going to have all these things, and it's going to maintain its own state. 00:47:05.330 --> 00:47:07.080 It's going to maintain all of that for us. 00:47:09.890 --> 00:47:10.970 as seen here. 00:47:10.970 --> 00:47:14.090 And, typically, these are what are called fields. 00:47:14.090 --> 00:47:16.700 And then we'll have methods as well. 00:47:16.700 --> 00:47:20.730 Functions that, instead of being like completely separate from this data, 00:47:20.730 --> 00:47:23.960 a car now basically owns its own functions. 00:47:23.960 --> 00:47:28.337 It has its own method called drive, or turn, or honk, et cetera, 00:47:28.337 --> 00:47:30.170 and we don't need to have a function called, 00:47:30.170 --> 00:47:35.090 like, turn car, or honk car, et cetera. 00:47:35.090 --> 00:47:37.700 And then this class is effectively a blueprint. 00:47:37.700 --> 00:47:40.550 Well, we'll see shortly how to define a class, 00:47:40.550 --> 00:47:44.617 but in order to actually have like one paddle that has its own set of data, 00:47:44.617 --> 00:47:47.450 and another paddle that has its own set of data, we need to define-- 00:47:47.450 --> 00:47:51.170 we need to instantiate, create objects from this class. 00:47:51.170 --> 00:47:54.440 Basically, use this class as a blueprint, but take it to a factory 00:47:54.440 --> 00:47:57.470 and create concrete cars from the blueprint. 00:47:57.470 --> 00:47:58.685 And those are objects. 00:48:01.200 --> 00:48:03.195 And so as seen here, our paddles and ball 00:48:03.195 --> 00:48:05.880 are perfect simple use cases for doing this. 00:48:05.880 --> 00:48:08.090 So let's go ahead and take a look at Pong 5. 00:48:14.481 --> 00:48:17.230 So in Pong 5, immediately, if you look at the directory structure, 00:48:17.230 --> 00:48:21.190 you can see that we've added a ball.lua and a paddle.lua. 00:48:21.190 --> 00:48:25.510 And it's tradition in most languages that have object oriented programming, 00:48:25.510 --> 00:48:31.180 as it's called, to capitalize class names just so you can differentiate 00:48:31.180 --> 00:48:37.400 classes, for example, from concrete objects or variables or functions. 00:48:37.400 --> 00:48:43.420 So if you go to our main.lua, on line 35, 00:48:43.420 --> 00:48:46.700 we're requiring a library, called class, which 00:48:46.700 --> 00:48:50.750 is what's going to allow us to actually create these classes. 00:48:50.750 --> 00:48:52.400 Because classes are not native-- 00:48:52.400 --> 00:48:55.310 they are in a sense a native Lua feature, 00:48:55.310 --> 00:48:59.900 but Lua's way of doing object oriented programming is a little bit convoluted. 00:48:59.900 --> 00:49:04.190 Some folks have kindly put together a library that makes it a lot simpler, 00:49:04.190 --> 00:49:07.430 and a lot more closely related to other languages that do object oriented 00:49:07.430 --> 00:49:12.530 programming more predominantly, like Java or C#, or even Python, 00:49:12.530 --> 00:49:16.370 allow us to use the keyword class in a way that's very similar to those 00:49:16.370 --> 00:49:17.560 libraries. 00:49:17.560 --> 00:49:22.749 On line 39 and 43, we're acquiring our own code, paddle and ball, 00:49:22.749 --> 00:49:25.040 and we're going to take a look at those right now so we 00:49:25.040 --> 00:49:27.330 can see what a class looks like. 00:49:27.330 --> 00:49:30.314 So I'm going to go ahead and open up a-- 00:49:33.200 --> 00:49:36.740 the ball file, ball.lua. 00:49:36.740 --> 00:49:38.630 And we can see here all we need to do just 00:49:38.630 --> 00:49:43.220 to create a ball class is, using our class library, ball gets class, 00:49:43.220 --> 00:49:45.050 and then curly brackets like that. 00:49:45.050 --> 00:49:47.900 And so now we have a class object, a class table, 00:49:47.900 --> 00:49:50.780 effectively, because everything in Lua is a table. 00:49:50.780 --> 00:49:53.030 But we can think about it in terms of objects. 00:49:53.030 --> 00:49:56.060 We have a class object called ball, and then we 00:49:56.060 --> 00:49:59.520 can start to define functions that belong to this class. 00:49:59.520 --> 00:50:03.230 So we're going to define what's called a constructor, or an init function 00:50:03.230 --> 00:50:04.820 in this case, an initializer. 00:50:04.820 --> 00:50:09.200 And it's going to allow us to initialize our ball with whatever we want. 00:50:09.200 --> 00:50:11.930 In this case, we want to start our ball off with an x and a y 00:50:11.930 --> 00:50:13.610 and a width and a height. 00:50:13.610 --> 00:50:16.410 And notice within here we have a word called self. 00:50:16.410 --> 00:50:20.330 Self and this are common words in object oriented programming languages 00:50:20.330 --> 00:50:25.610 that mean whatever object we're creating with this class is going to be self. 00:50:25.610 --> 00:50:27.010 So we'll see that-- 00:50:27.010 --> 00:50:28.070 we'll see that shortly. 00:50:28.070 --> 00:50:29.250 Self.x gets x. 00:50:29.250 --> 00:50:31.940 So whatever concrete object we create using this call, 00:50:31.940 --> 00:50:36.320 this init call, set its x to this x, set its y to the y, set its width, 00:50:36.320 --> 00:50:37.000 set its height. 00:50:37.000 --> 00:50:38.240 That specific object. 00:50:38.240 --> 00:50:39.980 Self. 00:50:39.980 --> 00:50:42.620 And then we're doing the same thing for delta y and delta x, 00:50:42.620 --> 00:50:46.460 only that we are setting those two random values just as we did before. 00:50:46.460 --> 00:50:49.910 Self.dy, self.dx. 00:50:49.910 --> 00:50:54.890 That belongs to whatever specific object gets instantiated using this init call 00:50:54.890 --> 00:50:57.530 as we'll see in the code. 00:50:57.530 --> 00:51:01.070 We're defining just a reset function here just to make it easy. 00:51:01.070 --> 00:51:02.660 Before we had a-- 00:51:02.660 --> 00:51:06.150 several lines of code that set our ball to the middle of the screen 00:51:06.150 --> 00:51:07.580 and gave it a random velocity. 00:51:07.580 --> 00:51:09.830 We're doing that now, and this is a good way of sort 00:51:09.830 --> 00:51:11.810 of refactoring out groups of logic. 00:51:11.810 --> 00:51:15.260 We're creating a function called reset that just does that all in one function 00:51:15.260 --> 00:51:19.250 call, and we just call that within our main function, condensing our code. 00:51:19.250 --> 00:51:22.130 And then notice we have an update and a render function now. 00:51:22.130 --> 00:51:24.440 And we are going to call these from our own update 00:51:24.440 --> 00:51:27.560 and our own draw function such that every object 00:51:27.560 --> 00:51:31.940 that we want in our game, every entity, and we'll build upon this game 00:51:31.940 --> 00:51:33.680 by game in the future. 00:51:33.680 --> 00:51:39.170 We'll just call update and render on everything from our main.lua, 00:51:39.170 --> 00:51:43.160 and defer all of that to each individual class and objects 00:51:43.160 --> 00:51:46.670 so we don't have to have a main.lua that's like 800 lines of code. 00:51:46.670 --> 00:51:50.480 We just break out all of the updates that are pertinent to the ball here, 00:51:50.480 --> 00:51:53.720 and all the render code that's pertinent to the ball here, 00:51:53.720 --> 00:51:56.090 call each individual balls update and render, 00:51:56.090 --> 00:51:59.630 and save ourselves a lot of time in refactoring. 00:51:59.630 --> 00:52:01.655 We're doing the same thing if we look at paddle. 00:52:04.400 --> 00:52:06.410 Paddle's a class, as well. 00:52:06.410 --> 00:52:10.310 It gets the reason the class library. 00:52:10.310 --> 00:52:13.910 Same exact sort of thing here, xy with height, and a dy. 00:52:13.910 --> 00:52:17.960 In this case, we're just initialising that to zero so that we're not moving. 00:52:17.960 --> 00:52:20.640 And then we're calling the update function here. 00:52:20.640 --> 00:52:25.250 So if our dy is less than zero, we're using the math.max function as before, 00:52:25.250 --> 00:52:29.630 with the top edge of the screen, and then whatever our y plus our current dy 00:52:29.630 --> 00:52:31.820 is, so delta y. 00:52:31.820 --> 00:52:34.880 And then here, self.y gets math.min, virtual height 00:52:34.880 --> 00:52:39.220 minus self.height, self.y plus self.dy times delta time. 00:52:39.220 --> 00:52:42.470 So that's the clamping behavior that we saw before with the paddles, only now, 00:52:42.470 --> 00:52:45.660 we took it from main and we put it in our update function 00:52:45.660 --> 00:52:48.110 so each paddle calls its update, and we take some lines 00:52:48.110 --> 00:52:49.700 of code out of our main file. 00:52:49.700 --> 00:52:52.622 And then it has its own render function here, same as the paddle. 00:52:52.622 --> 00:52:55.580 The render function for the paddle and the render function for the ball 00:52:55.580 --> 00:52:57.380 are effectively the same. 00:52:57.380 --> 00:53:04.300 And so if we go to our main, we-- we're acquiring the paddle 00:53:04.300 --> 00:53:07.000 and we're acquiring ball so that we can use them. 00:53:07.000 --> 00:53:13.810 So if we go down to line 79, instead of initializing our ball dx or ball dy, 00:53:13.810 --> 00:53:18.520 ball x, ball y, paddle y, player 1y, player 2y, 00:53:18.520 --> 00:53:22.990 now, we have player one is simply paddle 10, 35, 20. 00:53:22.990 --> 00:53:25.180 And player two is a paddle, virtual width minus 10, 00:53:25.180 --> 00:53:28.090 virtual height minus 30, 520. 00:53:28.090 --> 00:53:30.990 Ball is a ball, virtual width divided by two minus two, 00:53:30.990 --> 00:53:33.385 virtual height minus two, minus two, [? five by ?] two 00:53:33.385 --> 00:53:35.170 minus two, four and four. 00:53:35.170 --> 00:53:39.070 So those paddles now have control over their own x and y, their own width 00:53:39.070 --> 00:53:45.010 and height, and the battle-- or the ball has its own control over the xy width 00:53:45.010 --> 00:53:45.960 and height. 00:53:45.960 --> 00:53:48.650 And the self applies to this object. 00:53:48.650 --> 00:53:53.300 This is whatever self was in our constructor that we saw before. 00:53:53.300 --> 00:53:55.630 So even now we can just call-- so we can simply 00:53:55.630 --> 00:54:01.550 say player 1.x player 1.width, player 1.y, and everything is contained. 00:54:01.550 --> 00:54:04.055 We don't need a million variables to keep track 00:54:04.055 --> 00:54:05.680 of all the things going on in our game. 00:54:05.680 --> 00:54:08.890 And this is going to be especially important as we scale, and we have-- 00:54:08.890 --> 00:54:11.500 maybe we have 100 things on the screen at one time. 00:54:11.500 --> 00:54:14.560 We don't want 100 times x variables where 00:54:14.560 --> 00:54:20.690 x is, however many properties that thing has that we need to keep track of. 00:54:20.690 --> 00:54:23.470 It's all the same logic except, now, we're 00:54:23.470 --> 00:54:28.420 calling player one update and player two update in our update function, 00:54:28.420 --> 00:54:30.670 instead of having all that logic therein, 00:54:30.670 --> 00:54:33.342 where they're moving and then keeping track of whether 00:54:33.342 --> 00:54:36.050 or not they're going past the top and bottom edges of the screen. 00:54:36.050 --> 00:54:39.370 And then if game state is play, we're now just calling ball update. 00:54:39.370 --> 00:54:43.210 And these are all getting passed in delta time. 00:54:43.210 --> 00:54:45.120 And then same thing here. 00:54:45.120 --> 00:54:47.890 Instead of having all that logic for restating the ball 00:54:47.890 --> 00:54:50.327 as one block of code, we took it out, we refactored it, 00:54:50.327 --> 00:54:52.660 we put it into our ball class, and now all we have to do 00:54:52.660 --> 00:54:56.470 is just one line of code, ball or reset. 00:54:56.470 --> 00:55:00.490 And then here down on line 169 in our draw function, 00:55:00.490 --> 00:55:04.060 we just have player one render, player two render, ball render. 00:55:04.060 --> 00:55:06.925 And later on as we scale and we make games 00:55:06.925 --> 00:55:09.550 that have a lot more things on the screen, a lot more entities, 00:55:09.550 --> 00:55:11.890 we can just do these renders in a loop. 00:55:11.890 --> 00:55:15.700 We can just say for each entity in our screen, just render it. 00:55:15.700 --> 00:55:18.340 For each entity in our screen, just update it. 00:55:18.340 --> 00:55:21.190 We can condense thousands-- hundreds of lines of code 00:55:21.190 --> 00:55:25.030 into just a few lines of code by deferring update logic 00:55:25.030 --> 00:55:27.820 and rendering logic to each individual entity, 00:55:27.820 --> 00:55:30.070 thanks to object oriented programming. 00:55:30.070 --> 00:55:33.370 And so that's how we're going to refactor using classes. 00:55:33.370 --> 00:55:37.930 So any questions on how any of that works so far? 00:55:40.640 --> 00:55:41.290 Cool. 00:55:41.290 --> 00:55:44.170 This is a good point, I think, to take a five minute break. 00:55:44.170 --> 00:55:49.676 And once we come back, we'll talk about how to look at frames per second. 00:55:49.676 --> 00:55:52.180 All right. 00:55:52.180 --> 00:55:56.800 So we're going to take a minute just to look at something kind of small, 00:55:56.800 --> 00:56:00.940 but often it's the case where in games if we 00:56:00.940 --> 00:56:06.400 want to make sure that we are performing like our applications performing well, 00:56:06.400 --> 00:56:08.940 we want to-- some way to monitor our frames per second. 00:56:08.940 --> 00:56:10.731 And so I figured I would just take a second 00:56:10.731 --> 00:56:15.160 to illustrate this quickly so that we can use this in the future. 00:56:15.160 --> 00:56:18.830 The two functions that are going to be important for us here-- 00:56:18.830 --> 00:56:21.959 well, the first of these is just a little small cosmetic addition 00:56:21.959 --> 00:56:22.750 to the application. 00:56:22.750 --> 00:56:25.720 It's just love.window.setTitle. 00:56:25.720 --> 00:56:31.530 Title, so far our application, I'm not entirely sure what the-- 00:56:31.530 --> 00:56:35.110 it says by default, I think it says-- 00:56:35.110 --> 00:56:36.650 what does it say-- untitled. 00:56:36.650 --> 00:56:37.150 Yeah. 00:56:37.150 --> 00:56:41.410 So that's not-- it's a layer of lack of polish, more or less. 00:56:41.410 --> 00:56:43.820 And it'd be nice just to solve that problem quickly. 00:56:43.820 --> 00:56:47.937 So we're going to call a function called love.window.setTitle, some string, 00:56:47.937 --> 00:56:49.520 which will solve that problem quickly. 00:56:49.520 --> 00:56:52.900 We can make it look as if we have that detail down. 00:56:52.900 --> 00:56:57.160 And then the thing that's actually going to let us determine whether or not 00:56:57.160 --> 00:56:59.500 we are running well or we're running very poorly 00:56:59.500 --> 00:57:04.180 is a function called love.timer.getframespersecond.getFPS, 00:57:04.180 --> 00:57:07.700 which is something that LOVE graciously gives us for free 00:57:07.700 --> 00:57:09.950 and allows us to very easily slap it wherever we want. 00:57:09.950 --> 00:57:11.866 We can print it to the console, or we can just 00:57:11.866 --> 00:57:13.420 draw it straight to our application. 00:57:13.420 --> 00:57:15.640 In this case, we're going to do the latter. 00:57:15.640 --> 00:57:19.800 So I'm going to go ahead and go into LOVE-- 00:57:19.800 --> 00:57:24.390 or Pong 6 in our main. 00:57:24.390 --> 00:57:27.830 If we go ahead and look at line-- 00:57:32.190 --> 00:57:34.460 where is it-- line 64. 00:57:34.460 --> 00:57:37.600 love.window.setTitlePong, just quick and easy. 00:57:37.600 --> 00:57:39.770 Now, our window header is set appropriately, 00:57:39.770 --> 00:57:49.810 and if we go down to line 198, here I've decided 00:57:49.810 --> 00:57:54.850 to sort of split out this in a separate function, 00:57:54.850 --> 00:57:57.640 called display FPS on line 198. 00:57:57.640 --> 00:58:02.320 And the function is defined on line 207, so a function display FPS, 00:58:02.320 --> 00:58:03.570 takes no parameters. 00:58:03.570 --> 00:58:07.160 Its only goal is to just draw our current FPS to the screen. 00:58:07.160 --> 00:58:10.000 So we're going to set our current font to a small font. 00:58:10.000 --> 00:58:13.990 We're going to set our color-- so this is what I alluded to before in that we 00:58:13.990 --> 00:58:19.090 can set LOVE's rendering color to some RGBA quadruple, 00:58:19.090 --> 00:58:24.580 and anything that we draw beyond that point will then be drawn at-- 00:58:24.580 --> 00:58:27.140 it'll be drawn into whatever that color is. 00:58:27.140 --> 00:58:32.485 So, in this case, we're giving it red of zero, 255 on the green, zero blue, 255 00:58:32.485 --> 00:58:35.110 fully opaque, which has the effect of setting our color to just 00:58:35.110 --> 00:58:36.640 completely green. 00:58:36.640 --> 00:58:40.360 And then love.graphics.print, our current FPS-- 00:58:40.360 --> 00:58:45.700 string, and then our current FPS here, which is love.timer.getFPS. 00:58:45.700 --> 00:58:49.590 But it's going to return that as a number, and by default, 00:58:49.590 --> 00:58:52.480 Lua does not allow you to concatenate strings and numbers, 00:58:52.480 --> 00:58:55.790 so we're going to concatenate here with this ..operator, 00:58:55.790 --> 00:58:58.180 which is the way of doing string concatenation in Lua. 00:58:58.180 --> 00:59:00.013 We're going to call the two string function. 00:59:00.013 --> 00:59:01.792 So we're going to take in love.timer.FPS, 00:59:01.792 --> 00:59:03.500 we're going to make it a string, and then 00:59:03.500 --> 00:59:05.110 we're going to concatenate it here. 00:59:05.110 --> 00:59:08.832 And then we're going to call love.graphics.print on that value, 00:59:08.832 --> 00:59:10.540 and then we're going to put it at 10, 10. 00:59:10.540 --> 00:59:14.360 So shift it just a little bit from the top left edge of the screen. 00:59:14.360 --> 00:59:18.310 So that's going to have the effect of the go to Pong 6, and we run it. 00:59:18.310 --> 00:59:20.587 We can see now it starts at zero and 52 because it 00:59:20.587 --> 00:59:22.420 has to gather a few frames of data before it 00:59:22.420 --> 00:59:25.090 has a number we can actually use. 00:59:25.090 --> 00:59:32.011 But we see there FPS at 60, and so our game runs, otherwise, just the same. 00:59:32.011 --> 00:59:32.760 Completely random. 00:59:32.760 --> 00:59:36.820 A little bit broken, but that's OK, we'll fix it up. 00:59:36.820 --> 00:59:39.850 But currently we have a problem, and that's 00:59:39.850 --> 00:59:43.210 that our ball is just going straight through our paddles. 00:59:43.210 --> 00:59:45.274 So how can we fix this problem? 00:59:49.070 --> 00:59:51.640 We need some way of detecting collision. 00:59:51.640 --> 00:59:59.350 So in 2D games, generally, there's a concept of aa bb collision detection. 00:59:59.350 --> 01:00:04.750 And what this is is axis aligned bounding box collision detection, which 01:00:04.750 --> 01:00:09.124 means that we have bounding boxes, just rectangles, quads, 01:00:09.124 --> 01:00:12.040 which have an x and a y and a width and a height which are nonrotated. 01:00:12.040 --> 01:00:14.470 So they're completely aligned with our axes. 01:00:14.470 --> 01:00:16.810 They're completely parallel perpendicular. 01:00:16.810 --> 01:00:22.660 So the only way that we can get this easy math, the aa bb collision 01:00:22.660 --> 01:00:25.982 detection working is if we have no rotation of our boxes. 01:00:25.982 --> 01:00:27.440 They have to be completely aligned. 01:00:27.440 --> 01:00:30.860 But if they are, we have a very simple algorithm, 01:00:30.860 --> 01:00:34.600 which is we're just making sure that no edges of our boxes 01:00:34.600 --> 01:00:39.290 are outside the opposite edges of our-- of the other rectangle. 01:00:39.290 --> 01:00:45.070 So if we have one rectangle-- and I'll illustrate this on the screen here. 01:00:45.070 --> 01:00:47.290 We have two rectangles. 01:00:47.290 --> 01:00:53.380 If this top edge is below this edge, we know 01:00:53.380 --> 01:00:56.530 no matter what, they're not going to inter-- they're not intersecting. 01:00:56.530 --> 01:00:58.720 There is no way it can because it's below here. 01:00:58.720 --> 01:01:02.080 So no matter where it is on the x and the y, if it's below here, 01:01:02.080 --> 01:01:04.120 it's not a collision. 01:01:04.120 --> 01:01:08.450 If this edge is on this side of this rectangle, 01:01:08.450 --> 01:01:11.950 we know, as well, there's no way those two boxes can overlap. 01:01:11.950 --> 01:01:15.340 And it applies to every edge as long as it is the opposite edge. 01:01:15.340 --> 01:01:19.450 So if this edge is below this one, if this edge is above this one, 01:01:19.450 --> 01:01:22.240 if this edge is on the right, and this edge is on the left, 01:01:22.240 --> 01:01:25.790 it means that no matter what, those boxes aren't colliding. 01:01:25.790 --> 01:01:28.600 So we can simply do four conditions. 01:01:28.600 --> 01:01:38.500 We can say, if rec1.x is not greater than rec 2.x, plus rec2.width, 01:01:38.500 --> 01:01:43.900 and rec1.x plus rec1.width is not less than rec2.x, 01:01:43.900 --> 01:01:49.870 so if the two edges are not beyond their opposite edges, same thing with the y, 01:01:49.870 --> 01:01:55.090 and the y plus rec1.height, we know that we have a collision. 01:01:55.090 --> 01:01:59.830 We know that because we haven't fulfilled any of those criteria. 01:01:59.830 --> 01:02:05.680 But we know that if that's not true, if the-- 01:02:05.680 --> 01:02:09.300 one of the edges is not beyond the opposite edge, then it's-- 01:02:09.300 --> 01:02:10.820 we do have a collision. 01:02:10.820 --> 01:02:12.380 So it is going to be true. 01:02:12.380 --> 01:02:14.660 So we'll see that here in our code. 01:02:14.660 --> 01:02:26.200 The go to Pongs 7, at line 113, we have a function 01:02:26.200 --> 01:02:29.300 that we're calling called ball collides. 01:02:29.300 --> 01:02:31.684 Our ball class has a function called collides. 01:02:31.684 --> 01:02:33.850 So let's go ahead and take a look at our ball class. 01:02:40.070 --> 01:02:42.740 And in this case, we've defined our function such 01:02:42.740 --> 01:02:45.020 that it takes in a paddle parameter, so it's 01:02:45.020 --> 01:02:47.210 going to compare against another rectangle that 01:02:47.210 --> 01:02:50.410 has an xy and a width and a height. 01:02:50.410 --> 01:02:57.470 And we're saying that if rx is greater than the paddle x, plus the paddle 01:02:57.470 --> 01:03:01.850 width, which means if rx is greater than the right edge. 01:03:01.850 --> 01:03:04.880 So if our top left is greater than the-- 01:03:04.880 --> 01:03:08.250 or just our left-- is greater than the right edge, 01:03:08.250 --> 01:03:10.190 we know that we can't collide. 01:03:10.190 --> 01:03:16.130 Same thing if it's greater than the other rectangles, 01:03:16.130 --> 01:03:20.250 self.x plus self.width. 01:03:20.250 --> 01:03:21.350 No, sorry. 01:03:21.350 --> 01:03:24.040 In that case, if the paddle's x is-- 01:03:24.040 --> 01:03:26.840 it basically the same operation but from the paddle's perspective. 01:03:26.840 --> 01:03:31.130 If the paddle is greater than the rectangle on the right side, 01:03:31.130 --> 01:03:33.525 if it's farther along the right side past the right edge, 01:03:33.525 --> 01:03:35.150 we know that there can be no collision. 01:03:35.150 --> 01:03:36.470 It's just impossible. 01:03:36.470 --> 01:03:38.270 Same thing with y. 01:03:38.270 --> 01:03:42.050 If the y-- self.y, so this ball is y-- 01:03:42.050 --> 01:03:45.060 is greater than the paddle's y, plus the paddle height. 01:03:45.060 --> 01:03:47.420 So if it's below the edge of the paddle, because we're 01:03:47.420 --> 01:03:51.980 taking the height into consideration, or if the paddle's y is greater 01:03:51.980 --> 01:03:57.140 than this ball's y plus self.height, then we 01:03:57.140 --> 01:04:00.440 know that that also can't be a collision. 01:04:00.440 --> 01:04:03.206 But if that's not true, then we need to return true. 01:04:06.170 --> 01:04:11.050 And so if we go back to our main-- no, that's the wrong main. 01:04:11.050 --> 01:04:16.840 We go back to main.lua here. 01:04:16.840 --> 01:04:19.590 We're calling ball.collides. 01:04:19.590 --> 01:04:22.030 So if we're in our game state, if we're in our-- sorry, 01:04:22.030 --> 01:04:25.810 if we're in our play state, if game state is equal to play, 01:04:25.810 --> 01:04:29.650 if the ball collides with player one, so player one is the left paddle. 01:04:29.650 --> 01:04:36.400 So if there's a collision detected, the ball.dx and dx is our x velocity. 01:04:36.400 --> 01:04:39.500 So it's whatever direction it's moving on the x-axis. 01:04:39.500 --> 01:04:44.230 So it's going to be moving to the left if it's gone-- 01:04:44.230 --> 01:04:46.067 if we detected a collision. 01:04:46.067 --> 01:04:48.400 And it doesn't matter whether it's moving left or right, 01:04:48.400 --> 01:04:52.842 but we needed-- what we need to do is set it to its negative value. 01:04:52.842 --> 01:04:54.550 Because if it's moving left and we said-- 01:04:54.550 --> 01:04:59.380 let's say it's moving left at its negative 20 pixels and we set to 20, 01:04:59.380 --> 01:05:01.840 the dx is now 20, it's going to start moving to the right. 01:05:01.840 --> 01:05:06.880 It's going to have the effect of inverting its x velocity 01:05:06.880 --> 01:05:09.170 and, therefore, reversing its direction. 01:05:09.170 --> 01:05:12.280 But what we're also doing here with times 1.03 01:05:12.280 --> 01:05:15.070 is we're multiplying a little bit just to speed up the game. 01:05:15.070 --> 01:05:18.460 Because we don't want the game to into perpetuity just have the same velocity. 01:05:18.460 --> 01:05:20.230 It's not going to ramp up the excitement. 01:05:20.230 --> 01:05:23.200 We want to keep things going, we want to get some momentum going, 01:05:23.200 --> 01:05:25.660 so what we're going to do is call ball.dx 01:05:25.660 --> 01:05:30.707 equals its negative value times a scaler that we've determined arbitrarily. 01:05:30.707 --> 01:05:32.665 In this case, I've decided it should be point-- 01:05:32.665 --> 01:05:37.060 1.03 so it'll increase it by 3% every time. 01:05:37.060 --> 01:05:41.515 And then in the event that we have a-- 01:05:41.515 --> 01:05:45.760 our ball-- because it's getting added, its x velocity is getting 01:05:45.760 --> 01:05:48.550 added each frame to its position, we want 01:05:48.550 --> 01:05:52.620 to make sure that it's not like inside of our paddle. 01:05:52.620 --> 01:05:55.600 Because it is possible that it could shift a certain number of pixels 01:05:55.600 --> 01:05:59.260 to the left, or to the right, because the same operation applies. 01:05:59.260 --> 01:06:01.750 Such that the two are sort of like on top of each other. 01:06:01.750 --> 01:06:04.161 We want to re-- we want to shift it, we want to reset it. 01:06:04.161 --> 01:06:06.910 So what we're going to do-- because it'll detect another collision 01:06:06.910 --> 01:06:08.040 immediately if that's the case. 01:06:08.040 --> 01:06:10.270 If it, on the next frame, it's within that paddle, 01:06:10.270 --> 01:06:13.860 it's going to say that it's still colliding with that paddle 01:06:13.860 --> 01:06:15.610 so it's going to shift its velocity again. 01:06:15.610 --> 01:06:18.430 And it's going to have the effect of it infinitely sort of bouncing 01:06:18.430 --> 01:06:19.804 back and forth within the paddle. 01:06:19.804 --> 01:06:21.160 We don't want that to happen. 01:06:21.160 --> 01:06:23.466 So if we detect a collision, we want to shift it. 01:06:23.466 --> 01:06:26.590 We want to make sure it's completely outside of the paddle's collision box. 01:06:26.590 --> 01:06:31.390 So we're saying ball.x gets player one.x, plus five. 01:06:31.390 --> 01:06:34.210 Plus five because that's the width of the paddle. 01:06:34.210 --> 01:06:38.401 So that has the effect of just once you detect a collision, negative set-- 01:06:38.401 --> 01:06:40.150 x velocity to negative, and then instantly 01:06:40.150 --> 01:06:44.830 shift it right on the right edge of the left paddle. 01:06:44.830 --> 01:06:47.170 And we're doing the same thing here. 01:06:47.170 --> 01:06:51.670 If ball collides a play or two, we're doing the-- we're negating or inverting 01:06:51.670 --> 01:06:54.340 its x velocity. 01:06:54.340 --> 01:06:57.520 And then-- this is the same exact operation, 01:06:57.520 --> 01:07:01.360 but since it's based on the-- the left top left corner, 01:07:01.360 --> 01:07:04.540 we can't minus it by five, that wouldn't make sense. 01:07:04.540 --> 01:07:07.840 We're going to minus it by four because that's the width of the ball. 01:07:07.840 --> 01:07:11.310 So if we minused it by five, we would have one pixel of space. 01:07:11.310 --> 01:07:15.010 We plussed it by five on this example, because we're 01:07:15.010 --> 01:07:16.667 coming in from the right side. 01:07:16.667 --> 01:07:19.750 We want to just make sure that it's right on the right edge of the paddle, 01:07:19.750 --> 01:07:22.240 so we're setting it to player one.x. 01:07:22.240 --> 01:07:25.960 And in this case, we're using the minus four 01:07:25.960 --> 01:07:28.210 because that's the width of the ball. 01:07:28.210 --> 01:07:30.995 So we want to shift it to the left, the width of the ball, 01:07:30.995 --> 01:07:34.120 and that will have the effect of the right paddle, if there is a collision, 01:07:34.120 --> 01:07:36.520 it'll just get shifted over, and the right-- the ball 01:07:36.520 --> 01:07:40.150 will be touching the paddle right on their two edges. 01:07:40.150 --> 01:07:45.700 Here on line 118, we're solving the problem we had before of what 01:07:45.700 --> 01:07:48.580 happens when the-- 01:07:48.580 --> 01:07:50.830 oh, sorry, that's actually not what I was thinking of. 01:07:50.830 --> 01:07:57.880 This is the-- if there's a collision, then we want the ball's y velocity 01:07:57.880 --> 01:07:59.350 to randomize every time. 01:07:59.350 --> 01:08:02.470 So this has the effect of when we're playing the game 01:08:02.470 --> 01:08:05.320 and we've detected a collision between the two paddles, 01:08:05.320 --> 01:08:08.320 we don't want the same angle back and forth every time 01:08:08.320 --> 01:08:11.710 because then the game will just infinitely take place the exact same-- 01:08:11.710 --> 01:08:14.291 the same angle will just keep happening over and over again. 01:08:14.291 --> 01:08:15.290 We don't that to happen. 01:08:15.290 --> 01:08:19.689 We want some variability in terms of how the ball bounces off the paddle. 01:08:19.689 --> 01:08:22.130 So what this does is, still within the condition, 01:08:22.130 --> 01:08:25.210 if the ball collides with player one, we're 01:08:25.210 --> 01:08:31.010 going to, say, if the y velocity of the ball is negative, 01:08:31.010 --> 01:08:32.740 then we want to keep it going negative. 01:08:32.740 --> 01:08:34.823 We still want the ball-- like if the ball's coming 01:08:34.823 --> 01:08:37.420 at a sort of an upward angle and it bounces off the paddle, 01:08:37.420 --> 01:08:39.290 we want the x velocity to shift. 01:08:39.290 --> 01:08:42.819 We want it to go to opposite direction, but we want the ball to keep going up. 01:08:42.819 --> 01:08:45.069 We don't want the ball to like bounce back down, which 01:08:45.069 --> 01:08:45.950 wouldn't make any sense. 01:08:45.950 --> 01:08:47.680 We don't want to negate the y velocity. 01:08:47.680 --> 01:08:49.638 So we're going to keep the y velocity negative, 01:08:49.638 --> 01:08:53.050 we're going to set it to a negative value between 10 and 150. 01:08:53.050 --> 01:08:54.050 And it's just arbitrary. 01:08:54.050 --> 01:08:55.700 You can set that to whatever you want. 01:08:55.700 --> 01:08:58.840 And then we're going to do the same thing if the y velocity is positive. 01:08:58.840 --> 01:09:02.229 We want the ball to-- 01:09:02.229 --> 01:09:07.510 we want the ball to go in the positive direction if it's already coming down. 01:09:07.510 --> 01:09:10.569 So we're doing the exact same thing here. 01:09:10.569 --> 01:09:13.403 It's the same logic in the player two instance. 01:09:13.403 --> 01:09:16.319 And then this was what I thought I was looking at before for a second, 01:09:16.319 --> 01:09:20.649 but this is how we fix the issue of the upper and lower boundary of the screen. 01:09:20.649 --> 01:09:21.149 Right. 01:09:21.149 --> 01:09:24.750 Because it's one thing to solve the fact that we have the paddles 01:09:24.750 --> 01:09:27.720 now deflecting the ball, but we don't want the ball 01:09:27.720 --> 01:09:30.270 to infinitely go above the top edge of the screen, 01:09:30.270 --> 01:09:32.080 or the bottom edge of the screen. 01:09:32.080 --> 01:09:34.842 So this is just a simple if condition. 01:09:34.842 --> 01:09:37.050 We're just saying if the ball's less than or equal to 01:09:37.050 --> 01:09:40.407 zero, which means if the ball's at the top edge of the screen, 01:09:40.407 --> 01:09:42.240 just set it to zero, so make sure it doesn't 01:09:42.240 --> 01:09:46.319 go above the edge of the screen, and then negate its wide velocity, 01:09:46.319 --> 01:09:48.490 so it's instantly going to start going downwards. 01:09:48.490 --> 01:09:50.273 Yes. 01:09:50.273 --> 01:09:53.880 AUDIENCE: This question is about Pong 7, line 113. 01:09:53.880 --> 01:09:57.270 Couldn't the shifting of the balls dx and y 01:09:57.270 --> 01:10:01.660 be done in the ball collides function, if there is a collision? 01:10:01.660 --> 01:10:06.330 COLTON OGDEN: The shifting of the ball's function if ball collide-- no, 01:10:06.330 --> 01:10:08.580 collides the ball-- the collides function 01:10:08.580 --> 01:10:11.470 is a-- it just returns true or false. 01:10:11.470 --> 01:10:14.520 So it would be-- 01:10:14.520 --> 01:10:19.530 I mean, I think you probably could refactor it out that way, 01:10:19.530 --> 01:10:21.540 but the purpose of collides isn't to have 01:10:21.540 --> 01:10:23.819 any sort of side effects like that. 01:10:23.819 --> 01:10:25.860 Its only purpose is just to return true or false. 01:10:25.860 --> 01:10:28.651 Because we can do any-- we could have any sort of behavior we want. 01:10:28.651 --> 01:10:31.560 In a collides function, we may not necessarily want to shift the ball 01:10:31.560 --> 01:10:33.601 or do anything, we might just want it return true 01:10:33.601 --> 01:10:35.280 and print something to the console. 01:10:35.280 --> 01:10:37.920 So, in terms of, I think, in an engineering perspective, 01:10:37.920 --> 01:10:40.847 it makes more sense just to have a simple true or false function, 01:10:40.847 --> 01:10:43.680 and then determine how you want that to actually influence your game 01:10:43.680 --> 01:10:48.310 state inside your main function, or inside some other function. 01:10:48.310 --> 01:10:48.810 OK. 01:10:51.420 --> 01:10:53.570 And so, Yeah, we went down here. 01:10:53.570 --> 01:10:57.660 The top edge of the screen, and then bottom edge of the screen. 01:10:57.660 --> 01:11:00.840 If the ball.y, it's same exact thing, just the bottom edge of the screen. 01:11:00.840 --> 01:11:04.770 If the ball.y is greater than or equal to virtual height minus four, 01:11:04.770 --> 01:11:06.840 and we're doing virtual high minus four, why? 01:11:06.840 --> 01:11:08.190 AUDIENCE: Could get stuck at the bottom 01:11:08.190 --> 01:11:09.450 COLTON OGDEN: Exactly. 01:11:09.450 --> 01:11:12.180 So we want to make sure that we write-- as soon as we-- 01:11:12.180 --> 01:11:15.310 the bottom edge of the ball touches the bottom of the screen, 01:11:15.310 --> 01:11:18.720 we want to detect a collision, then we want to say ball.y, the-- 01:11:18.720 --> 01:11:22.290 gets virtual height minus four in case it overshot the bottom edge 01:11:22.290 --> 01:11:25.600 based on how much time has elapsed and how much the velocity is, 01:11:25.600 --> 01:11:28.320 you want to instantly put it right up so that it's at the edge 01:11:28.320 --> 01:11:29.910 so it's a clean bounce. 01:11:29.910 --> 01:11:32.340 And then we want to negate the y velocity just the same 01:11:32.340 --> 01:11:35.500 as we did up above. 01:11:35.500 --> 01:11:46.160 And so if we run our program here, Pong 7, looks the same, 01:11:46.160 --> 01:11:50.200 but now the ball's bouncing. 01:11:50.200 --> 01:11:52.810 And note that it got a neg-- it got a random-- it 01:11:52.810 --> 01:11:54.601 looks like it's going below the bottom edge 01:11:54.601 --> 01:11:57.760 because the monitor is currently at 720 and that's the window resolution, 01:11:57.760 --> 01:11:59.740 but it is bouncing off the bottom edge as well. 01:11:59.740 --> 01:12:03.250 And the angle, if you'll note, is a little bit different every time, 01:12:03.250 --> 01:12:10.180 because we are giving it a random y velocity, a y-- 01:12:10.180 --> 01:12:10.680 yeah. 01:12:10.680 --> 01:12:14.770 And then that's influencing-- oh, I messed up. 01:12:14.770 --> 01:12:17.784 I wanted to illustrate the speed increase. 01:12:17.784 --> 01:12:19.450 It's going to take a little bit of time. 01:12:19.450 --> 01:12:25.180 But every time it detects a collision, it is going to be scaling its-- 01:12:25.180 --> 01:12:28.346 the x velocity by 1.03. 01:12:28.346 --> 01:12:30.220 So it's going to make it a little bit faster. 01:12:30.220 --> 01:12:31.840 Now, currently, the y angle's a bit steep, 01:12:31.840 --> 01:12:33.950 so it's going to take forever to illustrate that. 01:12:33.950 --> 01:12:37.190 But we'll see that in a later example. 01:12:37.190 --> 01:12:42.290 So we have the basics of our game. 01:12:42.290 --> 01:12:43.960 But how are we keeping score? 01:12:43.960 --> 01:12:47.518 What's the determining factor for how we keep score in Pong? 01:12:47.518 --> 01:12:48.579 Left or right. 01:12:48.579 --> 01:12:51.120 As long as it goes past the left or right edge of the screen. 01:12:51.120 --> 01:12:53.635 So what do we need to thereby do? 01:12:53.635 --> 01:12:55.054 AUDIENCE: [INAUDIBLE] 01:12:55.054 --> 01:12:57.000 COLTON OGDEN: We do need a counter, and we 01:12:57.000 --> 01:12:59.700 need to also monitor whether the ball has 01:12:59.700 --> 01:13:03.720 collided with the left or the right boundary of the screen. 01:13:03.720 --> 01:13:06.150 And then have that increment that counter. 01:13:06.150 --> 01:13:08.970 So we're going to go ahead and take a look at Pong 8 01:13:08.970 --> 01:13:13.330 to see how this is implemented. 01:13:13.330 --> 01:13:18.352 We have here on line 88 and 89 some counter variables, player one score, 01:13:18.352 --> 01:13:19.060 player two score. 01:13:19.060 --> 01:13:21.640 We've had those for a long time, but we haven't used them. 01:13:21.640 --> 01:13:23.440 We've only used them to draw to the screen. 01:13:23.440 --> 01:13:27.940 We're actually going to now increment them, and show them as scorekeeping 01:13:27.940 --> 01:13:31.390 variables in our code here. 01:13:31.390 --> 01:13:33.290 I thought I had implemented it in Pong 8, 01:13:33.290 --> 01:13:36.760 but I think I might have left out the actual incrementing of the score. 01:13:36.760 --> 01:13:39.220 But this is the logic that's pertinent to that example. 01:13:39.220 --> 01:13:43.030 So if ball.x is less than zero, which just 01:13:43.030 --> 01:13:48.430 means if we've gone past the left edge of the screen, 01:13:48.430 --> 01:13:49.840 ignore serving player for now. 01:13:49.840 --> 01:13:52.640 The important thing is now we are doing player two score, 01:13:52.640 --> 01:13:54.580 gets player twp score, plus one. 01:13:54.580 --> 01:13:56.140 Just a simple increment. 01:13:56.140 --> 01:13:57.880 And then we're resetting the ball. 01:13:57.880 --> 01:13:59.740 Same thing for here. 01:13:59.740 --> 01:14:01.990 If the ball.x is greater than virtual width, 01:14:01.990 --> 01:14:08.860 so pass the right edge of the screen, and actually it could be ver-- 01:14:08.860 --> 01:14:12.120 if ball x plus four is greater than virtual width, 01:14:12.120 --> 01:14:14.000 then and it will have the same effect. 01:14:14.000 --> 01:14:16.947 But, actually, no, because we want to make sure 01:14:16.947 --> 01:14:19.030 that we don't see the ball at all when they score. 01:14:19.030 --> 01:14:20.488 So, yeah, this is actually correct. 01:14:20.488 --> 01:14:23.920 If ball.x is greater than virtual width, then serving player gets two, 01:14:23.920 --> 01:14:25.895 player one score is player one score plus one. 01:14:25.895 --> 01:14:27.520 And then we're going to reset the ball. 01:14:27.520 --> 01:14:29.050 Serving player. 01:14:29.050 --> 01:14:33.370 So now what we need to talk about is the idea of serving. 01:14:33.370 --> 01:14:35.680 So when we start up the game-- 01:14:35.680 --> 01:14:37.328 so let's go ahead and take a look at-- 01:14:42.004 --> 01:14:44.920 we're going to go to Pong now so we're going to go straight to Pong 9, 01:14:44.920 --> 01:14:47.700 and then we need to take a look at what a state machine is. 01:14:47.700 --> 01:14:50.670 So currently in the game, we've talked about state a little bit. 01:14:50.670 --> 01:14:53.700 We've had the start state, which means the game is ready for us 01:14:53.700 --> 01:14:56.857 to just press Enter and then the ball will go off in a random direction. 01:14:56.857 --> 01:14:58.190 And then we have the play state. 01:14:58.190 --> 01:15:02.490 And the play state is set to our paddles interacting with the ball, 01:15:02.490 --> 01:15:06.000 and then keeping track of score, basically. 01:15:06.000 --> 01:15:08.010 A state machine is very important. 01:15:08.010 --> 01:15:11.400 It's a ubiquitous concept in game development. 01:15:11.400 --> 01:15:14.730 It just means, how can we monitor what state we're in 01:15:14.730 --> 01:15:19.050 and what transitions take place between those states to bring out new states. 01:15:19.050 --> 01:15:22.230 And each individual state has its own logic. 01:15:22.230 --> 01:15:26.220 And by breaking out the logic of these states separately, 01:15:26.220 --> 01:15:32.820 we can scale our code much bigger and not have monolithic code for-- 01:15:32.820 --> 01:15:36.270 this particular diagram is an example of what you might have as a state 01:15:36.270 --> 01:15:40.290 machine for a character like Mario where you have a ducking state, a release 01:15:40.290 --> 01:15:43.320 state which takes the down-- the in-- like the input of down. 01:15:43.320 --> 01:15:45.750 So if we're releasing down, it'll become standing. 01:15:45.750 --> 01:15:49.130 So ducking state, the transition is release the down key, 01:15:49.130 --> 01:15:50.270 he becomes standing. 01:15:50.270 --> 01:15:52.140 Standing key, press the down key. 01:15:52.140 --> 01:15:56.350 He becomes ducking, these are states and transitions. 01:15:56.350 --> 01:16:01.770 These individual states are the overall representation of his behavior 01:16:01.770 --> 01:16:03.080 at large, basically. 01:16:03.080 --> 01:16:06.240 And the same logic applies to our game. 01:16:06.240 --> 01:16:09.090 We have a play state, we have a serve state. 01:16:09.090 --> 01:16:11.010 We want to have maybe a game over state. 01:16:11.010 --> 01:16:16.300 If someone scores 10 points, then it should say, oh, the winner is x. 01:16:16.300 --> 01:16:19.290 And you can define any arbitrary number of states, it-- 01:16:19.290 --> 01:16:22.540 which depends upon your model, whatever game you want to develop. 01:16:22.540 --> 01:16:24.690 For example, like Super Mario has a title screen, 01:16:24.690 --> 01:16:27.864 maybe your game has like a high score state. 01:16:27.864 --> 01:16:30.030 You want to display all the high scores in your game 01:16:30.030 --> 01:16:35.470 and we'll actually show that in a lecture next week. 01:16:35.470 --> 01:16:37.050 But this is what a state machine is. 01:16:37.050 --> 01:16:40.860 It's just a-- it can be in any one particular state at one time, 01:16:40.860 --> 01:16:45.270 and the transitions are what allow you to go in between your states. 01:16:45.270 --> 01:16:49.150 And each state does have transitions in and out of other states. 01:16:49.150 --> 01:16:51.830 And we're going to use this in Pong 9. 01:16:51.830 --> 01:16:54.870 So beyond illustrating the score, we're going 01:16:54.870 --> 01:16:59.370 to start keeping track of more than just the start and the play state. 01:16:59.370 --> 01:17:02.560 We're actually going to start modeling the serve state. 01:17:02.560 --> 01:17:05.018 And so let me go ahead and illustrate what this looks like. 01:17:08.370 --> 01:17:12.630 So if we're here, I just pressed Enter. 01:17:12.630 --> 01:17:16.140 We started at the start state as normal, but I pressed Enter 01:17:16.140 --> 01:17:18.180 and now it says player one serve. 01:17:18.180 --> 01:17:19.970 So we're actually serving. 01:17:19.970 --> 01:17:22.530 And so if I press Enter again as it instructs me, 01:17:22.530 --> 01:17:25.290 player one is on the left, the ball should move to the right. 01:17:25.290 --> 01:17:27.640 Which it does. 01:17:27.640 --> 01:17:33.010 So I'm going to go ahead and lose on purpose as player two. 01:17:33.010 --> 01:17:34.590 And now it's player two's serve. 01:17:34.590 --> 01:17:38.370 So whichever character, whichever player loses should get to serve again. 01:17:38.370 --> 01:17:40.560 And so now if I press Enter, note when we 01:17:40.560 --> 01:17:42.960 were player one, the ball moved to the right. 01:17:42.960 --> 01:17:46.000 So for player two, the ball moves to the left. 01:17:46.000 --> 01:17:49.260 So we have now a little bit more interaction. 01:17:49.260 --> 01:17:51.150 We have different states. 01:17:51.150 --> 01:17:53.940 We start off the game, and then we serve, and we play. 01:17:53.940 --> 01:17:57.140 So when the ball's live, when we're actually doing this, 01:17:57.140 --> 01:17:58.140 we're in the play state. 01:17:58.140 --> 01:17:59.800 Now we're in the serve state. 01:17:59.800 --> 01:18:03.720 So what's the transition between the-- 01:18:03.720 --> 01:18:07.400 sort of the play state and the serve state? 01:18:07.400 --> 01:18:08.804 What's the transition there? 01:18:12.550 --> 01:18:13.950 We score a point. 01:18:13.950 --> 01:18:17.800 So if we are looking at our state diagram and we're in the play state, 01:18:17.800 --> 01:18:23.284 the transition to the serve state is x player scores a point. 01:18:23.284 --> 01:18:25.450 And then if we're in the serve state, the transition 01:18:25.450 --> 01:18:29.180 is someone presses enter. 01:18:29.180 --> 01:18:30.200 Enter key gets pressed. 01:18:30.200 --> 01:18:33.110 And so that's how we want to think about our games 01:18:33.110 --> 01:18:37.820 if we have a bunch of different sets of, sort of logic, 01:18:37.820 --> 01:18:42.500 that we can sort of take out of our game and think about conceptually, 01:18:42.500 --> 01:18:46.190 it allows us to break our game up into a bunch of different modes and states, 01:18:46.190 --> 01:18:49.370 and not really get overburdened by all these variables 01:18:49.370 --> 01:18:51.860 that maybe need to keep track of-- or what state are we in? 01:18:51.860 --> 01:18:53.090 Like what are all these variables doing? 01:18:53.090 --> 01:18:55.460 And we'll see how we can break this out in a more 01:18:55.460 --> 01:18:57.469 modular fashion in future weeks. 01:18:57.469 --> 01:18:59.510 Note that right now, currently all we're doing is 01:18:59.510 --> 01:19:04.010 we're setting a state variable to some string here, 01:19:04.010 --> 01:19:05.990 and just doing if conditions on it, which 01:19:05.990 --> 01:19:08.960 works fantastically for small examples. 01:19:08.960 --> 01:19:14.660 So if, like, for example, if we're in the update function and game state-- 01:19:14.660 --> 01:19:17.750 see, over here we're saying if the game state is set to serve, 01:19:17.750 --> 01:19:24.430 then we're initializing all of the variables. 01:19:24.430 --> 01:19:27.650 And if the game state is play, then we need 01:19:27.650 --> 01:19:31.200 to actually perform our logic here. 01:19:31.200 --> 01:19:34.040 So if the-- if we're in play, this is going to get called, 01:19:34.040 --> 01:19:36.980 each frame, and we're going to say, if ball collides with player one, 01:19:36.980 --> 01:19:37.730 do all this stuff. 01:19:37.730 --> 01:19:42.560 And then this allows us to sort of think of our game. 01:19:42.560 --> 01:19:46.160 It's almost like having separate update functions within our update function. 01:19:46.160 --> 01:19:49.220 And we'll actually see how we can take these out of one update 01:19:49.220 --> 01:19:53.310 function in future weeks with a actual state machine class 01:19:53.310 --> 01:19:56.090 and implement things a little bit more abstractly. 01:19:56.090 --> 01:20:01.260 But suffice to say, now, whenever we want to like, for example, 01:20:01.260 --> 01:20:04.280 make a transition, if someone scores here, 01:20:04.280 --> 01:20:06.760 like if we're going to the left side of the screen, 01:20:06.760 --> 01:20:09.320 it's ball x is less than zero. 01:20:09.320 --> 01:20:12.350 All we have to do is just set this state to serve 01:20:12.350 --> 01:20:16.040 and our update function is then going to update appropriately. 01:20:16.040 --> 01:20:20.150 So any questions on how sort of state machines or the state 01:20:20.150 --> 01:20:23.196 works here in the context of Pong? 01:20:23.196 --> 01:20:23.696 Yes. 01:20:23.696 --> 01:20:29.600 AUDIENCE: So the state machine is the relationship to the state 01:20:29.600 --> 01:20:34.520 or is the container of the states? 01:20:34.520 --> 01:20:37.550 COLTON OGDEN: The state machine is sort of a-- 01:20:37.550 --> 01:20:40.580 the overall conceptual look at what your different states 01:20:40.580 --> 01:20:42.680 are and their transitions, yes. 01:20:42.680 --> 01:20:45.560 And in future weeks we're not implementing a state machine 01:20:45.560 --> 01:20:47.150 object or a class here. 01:20:47.150 --> 01:20:50.120 But in future weeks, we will see the state machine class 01:20:50.120 --> 01:20:53.240 that manages transitions between different states in a more 01:20:53.240 --> 01:20:54.910 modular and clean fashion. 01:20:54.910 --> 01:20:57.140 All we're doing here is our state machine 01:20:57.140 --> 01:21:01.050 is just if statements and saying if the state is equal to this, then do this. 01:21:01.050 --> 01:21:03.716 And then change the state to some value. 01:21:03.716 --> 01:21:05.911 AUDIENCE: So the state machine is a concept? 01:21:05.911 --> 01:21:07.160 COLTON OGDEN: It is a concept. 01:21:07.160 --> 01:21:07.940 Yeah. 01:21:07.940 --> 01:21:13.880 But we will see an implementation of a state machine as an object next week. 01:21:13.880 --> 01:21:17.010 Any more questions? 01:21:17.010 --> 01:21:17.746 OK. 01:21:17.746 --> 01:21:19.730 Cool. 01:21:19.730 --> 01:21:22.850 So currently we have scoring. 01:21:22.850 --> 01:21:26.180 As we saw, the player one score and player two score are getting 01:21:26.180 --> 01:21:28.820 incremented now and, therefore, getting rendered to the screen 01:21:28.820 --> 01:21:31.940 whenever we go to the left or the right edge. 01:21:31.940 --> 01:21:33.840 So we're keeping track of score. 01:21:33.840 --> 01:21:38.183 But what do we need now in order for someone to win? 01:21:38.183 --> 01:21:39.937 AUDIENCE: [INAUDIBLE]. 01:21:39.937 --> 01:21:40.770 COLTON OGDEN: Sorry? 01:21:40.770 --> 01:21:42.021 AUDIENCE: [INAUDIBLE]. 01:21:42.021 --> 01:21:43.820 COLTON OGDEN: Yes it does. 01:21:43.820 --> 01:21:44.630 Exactly. 01:21:44.630 --> 01:21:46.827 So it's actually quite simple. 01:21:46.827 --> 01:21:49.160 All we need to really do is just an if statement, right? 01:21:49.160 --> 01:21:54.260 If someone's score is equal to some value, 10, then some player has won. 01:21:54.260 --> 01:22:00.290 So if we look at player-- if you look at Pong 10, we go to main, 01:22:00.290 --> 01:22:14.080 and go to here, line 174, and also line 160, 01:22:14.080 --> 01:22:19.114 we can see that all it literally is is in our logic 01:22:19.114 --> 01:22:21.780 from before, where we're just testing to see whether the ball is 01:22:21.780 --> 01:22:23.370 gone beyond the left or the right edge. 01:22:23.370 --> 01:22:26.120 Because this is effectively where you need to do your check anyway 01:22:26.120 --> 01:22:28.350 to see whether someone scored a point. 01:22:28.350 --> 01:22:31.140 So all we're doing is adding logic to that part of the program 01:22:31.140 --> 01:22:34.600 and saying after we increment their score. 01:22:34.600 --> 01:22:38.182 If it's equal to 10 we're setting a value called the winning player. 01:22:38.182 --> 01:22:39.390 We're going to set it to two. 01:22:39.390 --> 01:22:44.130 So if the ball.x is less than zero, then that means that player one got 01:22:44.130 --> 01:22:46.960 scored on because it went pass the left edge of the screen. 01:22:46.960 --> 01:22:50.640 Therefore, player two score should go up. 01:22:50.640 --> 01:22:54.150 And, therefore, the winning player should be, too, if the player two or-- 01:22:54.150 --> 01:22:56.430 two score is equal to 10. 01:22:56.430 --> 01:23:00.180 And in this case, see, we're here, we're setting a new state done, 01:23:00.180 --> 01:23:04.680 and then if that's not the case, or if their score is still less than 10, 01:23:04.680 --> 01:23:09.180 we should still-- we should set it back to serve, and then reset the ball. 01:23:09.180 --> 01:23:13.450 And so if we go to our update function, here-- 01:23:13.450 --> 01:23:18.090 actually, we're doing it in our update phase. 01:23:18.090 --> 01:23:22.320 So, currently, if it's the done state, the ball gets reset 01:23:22.320 --> 01:23:24.780 but no update is being applied to the ball in that case. 01:23:24.780 --> 01:23:26.950 We still have scores 10. 01:23:26.950 --> 01:23:30.090 It'll still render the score, so score whoever's got 10 01:23:30.090 --> 01:23:32.100 and it'll show the other player's score. 01:23:32.100 --> 01:23:35.400 And the actual logic that applies here, whenever 01:23:35.400 --> 01:23:39.510 we want to break out of that state, is in our love.keypressed key function. 01:23:39.510 --> 01:23:41.520 We see you're on line 227. 01:23:41.520 --> 01:23:46.140 If game state is equal to done, which we set it to before, 01:23:46.140 --> 01:23:48.237 and this will only execute if they've pressed 01:23:48.237 --> 01:23:50.070 Enter or return, so it's effectively waiting 01:23:50.070 --> 01:23:52.770 for them to press Enter or Return. 01:23:52.770 --> 01:23:54.840 You want to set game save back to serve. 01:23:54.840 --> 01:23:56.470 We want to reset the ball. 01:23:56.470 --> 01:23:58.540 We want to initialize those scores back to zero. 01:23:58.540 --> 01:24:02.430 So we're setting up a brand new game, effectively. 01:24:02.430 --> 01:24:06.650 If the winning player is one, then we'll give serving player to two 01:24:06.650 --> 01:24:09.570 so that they have the advantage on the next game, 01:24:09.570 --> 01:24:11.830 and then otherwise set it to one. 01:24:11.830 --> 01:24:17.160 And if we go down to our render function, so down to line 275, 01:24:17.160 --> 01:24:21.789 if we're in the done state, then we should render to the screen player 01:24:21.789 --> 01:24:23.580 and then winning player, because, remember, 01:24:23.580 --> 01:24:26.230 we set winning player to one or two, depending on whether-- 01:24:26.230 --> 01:24:29.370 depending on who won and who scored the tenth point. 01:24:29.370 --> 01:24:35.880 We'll say player one or two wins, and we'll just render that and then 01:24:35.880 --> 01:24:37.560 press Enter to restart after that. 01:24:37.560 --> 01:24:39.150 And that's the logic for that. 01:24:39.150 --> 01:24:41.700 And we can see this in playoffs. 01:24:41.700 --> 01:24:45.930 If it's too slow, we might not have to go through an entire run. 01:24:45.930 --> 01:24:47.930 But I sped up the-- 01:24:47.930 --> 01:24:50.520 whoops-- I want to actually get the ball back. 01:24:50.520 --> 01:24:53.360 I set up the speed so it's-- we're in the serve state, 01:24:53.360 --> 01:24:57.570 we're in the play state, it's up-- the ball bounced back. 01:24:57.570 --> 01:25:02.390 It's going to be a bit tedious, but suffice to say, it's a big payoff. 01:25:02.390 --> 01:25:02.890 Don't worry. 01:25:06.050 --> 01:25:08.870 Should have set the speed a little faster. 01:25:08.870 --> 01:25:09.550 Almost there. 01:25:12.140 --> 01:25:14.520 It's getting tense. 01:25:14.520 --> 01:25:15.750 And player one wins. 01:25:15.750 --> 01:25:19.590 So there we-- we're also setting the font to a larger size, and in the code 01:25:19.590 --> 01:25:21.960 I create a new font object that's basically 01:25:21.960 --> 01:25:26.430 between the small font and the score font, which is a large font. 01:25:26.430 --> 01:25:28.590 So 16 size font. 01:25:28.590 --> 01:25:31.890 And so player one wins, and that's really all it boils down to. 01:25:31.890 --> 01:25:35.910 Just keeping track of your counter and just making sure that when you do 01:25:35.910 --> 01:25:39.390 hit 10 in your logic for detecting the screen collisions, 01:25:39.390 --> 01:25:41.094 that you set the state to done. 01:25:41.094 --> 01:25:44.010 And if the state is done, then you just need to monitor keyboard input 01:25:44.010 --> 01:25:46.830 and see whenever someone presses enter. 01:25:46.830 --> 01:25:48.740 Someone press Enter in our love.keypressed, 01:25:48.740 --> 01:25:50.580 it does the-- it has the effect of setting 01:25:50.580 --> 01:25:53.742 player-- it's player two serve because player one won so that's only fair. 01:25:53.742 --> 01:25:56.700 We're going to press Enter to serve and then we begin a brand new game. 01:25:56.700 --> 01:25:57.449 And that's simple. 01:25:57.449 --> 01:26:03.120 So now we have an infinitely playable game with a bunch of simple states. 01:26:03.120 --> 01:26:05.670 We're missing a very important detail, though, in my opinion, 01:26:05.670 --> 01:26:06.390 and that's sound. 01:26:06.390 --> 01:26:08.430 Currently, our game is just very-- 01:26:08.430 --> 01:26:10.990 it's great, the gameplay all works. 01:26:10.990 --> 01:26:13.980 Everything is working fine, but just missing a little polish. 01:26:13.980 --> 01:26:14.970 And so what we're going to do is we're going 01:26:14.970 --> 01:26:17.429 to start adding audio to the game, which is, in my opinion, 01:26:17.429 --> 01:26:20.011 one of the more fun things to add because it also means you're 01:26:20.011 --> 01:26:21.390 close to the end of your project. 01:26:21.390 --> 01:26:25.150 Love.audio.newsource is a function we're going to look at here. 01:26:25.150 --> 01:26:29.930 All this is going to do is take a path and then optionally a type. 01:26:29.930 --> 01:26:31.980 And this path is going to be to a sound file, 01:26:31.980 --> 01:26:35.430 and it's going to create an audio object that you can play back 01:26:35.430 --> 01:26:37.590 at any point in your application. 01:26:37.590 --> 01:26:41.860 So what we're going to effectively do is just whenever a collision happens, 01:26:41.860 --> 01:26:46.020 depending on what type of collision it is, we'll just play a particular sound. 01:26:46.020 --> 01:26:49.300 And a program that I really like to use for all of this, 01:26:49.300 --> 01:26:51.660 and I-- what I encourage you guys to download 01:26:51.660 --> 01:26:54.570 if you want to start tinkering with your own sounds for your project, 01:26:54.570 --> 01:26:57.300 is a program called bfxr. 01:26:57.300 --> 01:26:58.885 It's free on Windows and Mac. 01:26:58.885 --> 01:27:00.510 I'm not sure if they have a Linux port. 01:27:00.510 --> 01:27:05.160 They might have a Linux port of a similar program called sfxr 01:27:05.160 --> 01:27:07.219 which is what this is based off of. 01:27:07.219 --> 01:27:10.260 But what this allows you to do is just generate a bunch of random sounds. 01:27:10.260 --> 01:27:13.770 And I can illustrate that shortly for you. 01:27:13.770 --> 01:27:16.740 If you would like to grab it, it's on bfxr.net. 01:27:16.740 --> 01:27:19.310 It's a super quick download and-- 01:27:19.310 --> 01:27:26.890 here, I'll actually-- I'll demonstrate it just so we can see how it plays out. 01:27:26.890 --> 01:27:28.950 So this is the interface. 01:27:28.950 --> 01:27:30.510 Make sure I have some audio. 01:27:30.510 --> 01:27:34.050 And then there is a lot of different presets here. 01:27:34.050 --> 01:27:37.170 So there's pickup slash coin, laser slash shoot. 01:27:37.170 --> 01:27:39.750 It's meant for sort of like small games like this, 01:27:39.750 --> 01:27:45.229 like implementing on the fly prototype audio type stuff. 01:27:45.229 --> 01:27:46.770 But you can see, it just implements-- 01:27:46.770 --> 01:27:49.140 I'll turn that down, it's a little loud. 01:27:49.140 --> 01:27:50.697 It-- we have power-ups, for example. 01:27:50.697 --> 01:27:53.780 So every time I click on this, it's going to give us a random power-up so. 01:27:53.780 --> 01:27:55.704 [COMPUTER SOUND EFFECTS] 01:28:00.520 --> 01:28:05.340 And then randomized, you get all sorts of weird nasty stuff. 01:28:05.340 --> 01:28:08.740 And then-- the stuff that we'll use is blip slash select. 01:28:08.740 --> 01:28:13.060 Most of the things in our in like interfaces and games like Pong you 01:28:13.060 --> 01:28:17.120 just want simple sounds like this. 01:28:17.120 --> 01:28:20.620 So I've already done the work of generating a few sounds 01:28:20.620 --> 01:28:23.770 that I thought fit pretty well. 01:28:23.770 --> 01:28:28.785 I'll go ahead and show you the code first in Pong 11. 01:28:28.785 --> 01:28:31.660 If we go to-- if you'll see-- if you look at the directory structure, 01:28:31.660 --> 01:28:33.220 you'll see we have a sounds folder. 01:28:33.220 --> 01:28:36.730 In the sounds folder, I've created three sounds, paddle hit, which 01:28:36.730 --> 01:28:39.160 is anytime the paddle hits the ball. 01:28:39.160 --> 01:28:41.740 Score, which is when any-- anytime the ball 01:28:41.740 --> 01:28:45.190 goes past the left or the right boundary of the screen. 01:28:45.190 --> 01:28:47.680 And then wall hit, so any time the ball touches 01:28:47.680 --> 01:28:49.600 the top or the bottom of the screen. 01:28:49.600 --> 01:28:52.580 And so the logic of this is extremely simple. 01:28:52.580 --> 01:28:54.531 All we need to do is whenever-- 01:28:54.531 --> 01:28:56.905 we already have it implemented, so all we need to do is-- 01:29:01.570 --> 01:29:07.002 oh, first thing, I should say, and this is a good illustration of a table. 01:29:07.002 --> 01:29:09.210 And we'll start to see this a lot more in the future. 01:29:09.210 --> 01:29:11.950 We didn't really use tables much in this lecture, 01:29:11.950 --> 01:29:16.390 but the table is Lua's like sort of be all, end all, data structure. 01:29:16.390 --> 01:29:19.810 It's the dictionary-- Python dictionary, JavaScript object. 01:29:19.810 --> 01:29:23.020 It's an array, it's everything that you need for anything 01:29:23.020 --> 01:29:24.830 beyond simple variables in Lua. 01:29:24.830 --> 01:29:28.360 It's what everything, even like classes in other libraries are made out of. 01:29:28.360 --> 01:29:32.140 In this case, we're just initializing a table here called sounds, 01:29:32.140 --> 01:29:35.410 and we're passing in three keys so it takes-- it can take in key value pairs, 01:29:35.410 --> 01:29:40.330 or you can just give it a list of values and it will create indices for them 01:29:40.330 --> 01:29:42.280 implicitly. 01:29:42.280 --> 01:29:46.460 Here, we're just passing in like you would do in Python or JavaScript. 01:29:46.460 --> 01:29:50.740 Paddle hit, and note that it does need these square brackets in order 01:29:50.740 --> 01:29:55.870 to initialize key value pairs in a table like-- in this format here. 01:29:55.870 --> 01:29:58.740 Paddle hit gets love.audio.newsource. 01:29:58.740 --> 01:30:00.490 And in this case, it just takes in a path, 01:30:00.490 --> 01:30:02.930 so sounds slash paddle head dot wave. 01:30:02.930 --> 01:30:05.530 And we're giving it the key word-- or the string static, 01:30:05.530 --> 01:30:08.690 which is the type of asset it is-- 01:30:08.690 --> 01:30:09.610 it's stored as. 01:30:09.610 --> 01:30:12.734 So you can have either static or stream audio assets. 01:30:12.734 --> 01:30:14.650 So if they're static, they're loaded in memory 01:30:14.650 --> 01:30:17.191 and they're kept in memory for the execution of your program. 01:30:17.191 --> 01:30:20.560 If they're stream, then they're loaded on the fly as needed 01:30:20.560 --> 01:30:21.670 by your game engine. 01:30:21.670 --> 01:30:24.100 And streamed audio assets can be helpful if you 01:30:24.100 --> 01:30:28.037 have a huge game with a ton of sounds and a long like large audio files. 01:30:28.037 --> 01:30:30.370 You don't want to keep all those in memory, necessarily, 01:30:30.370 --> 01:30:33.670 because that could take up many, many, many megs or gigs of audio. 01:30:33.670 --> 01:30:36.882 And if you're sort of loading assets on the fly, 01:30:36.882 --> 01:30:38.590 if you have dynamic loading in your game, 01:30:38.590 --> 01:30:41.320 then that's another thing you should take into consideration. 01:30:41.320 --> 01:30:43.810 In this case, these are very tiny sound files, 01:30:43.810 --> 01:30:46.409 because they're like-- like a fraction of a second long. 01:30:46.409 --> 01:30:49.450 So we're just setting them all to static so they get preserved in memory, 01:30:49.450 --> 01:30:52.160 and we're just loading all three of them into this table. 01:30:52.160 --> 01:30:56.530 And if we want to refer to these later on, all we need to do is sounds-- 01:30:56.530 --> 01:31:01.510 we can either reference sounds.paddlehit, like that, 01:31:01.510 --> 01:31:02.500 if we wanted to. 01:31:02.500 --> 01:31:08.050 Because by default, Lua just gives you a dot keyword, sort of the way JavaScript 01:31:08.050 --> 01:31:11.830 does its objects, with the same name as the key that you passed in, 01:31:11.830 --> 01:31:18.550 or you can do it the Pythonic way, which is, without the dot, sorry-- 01:31:18.550 --> 01:31:23.060 with just square brackets and now have the same of-- those two are equivalent. 01:31:23.060 --> 01:31:27.280 It won't work, though, if you decided to put a space in your key, 01:31:27.280 --> 01:31:30.640 it will, I believe, it will-- 01:31:30.640 --> 01:31:33.600 just won't work at all, but it may inject an underscore. 01:31:33.600 --> 01:31:35.870 I'll have to test it out and find out. 01:31:35.870 --> 01:31:40.330 But, generally, it's not best practice to use dot 01:31:40.330 --> 01:31:43.450 when you already have the keys lined up like this, anyway. 01:31:43.450 --> 01:31:45.970 And what you can do with strings that you 01:31:45.970 --> 01:31:49.600 can't do with dot using the dot notation, 01:31:49.600 --> 01:31:53.530 is dynamically generate a lookup of your table with you-- 01:31:53.530 --> 01:31:56.170 which you can do with strings, which you-- yeah, 01:31:56.170 --> 01:32:00.460 because you can't in a four loop do four something in table, 01:32:00.460 --> 01:32:02.620 and then table dot something, that just won't work. 01:32:02.620 --> 01:32:06.310 But you can do for everything in your table 01:32:06.310 --> 01:32:12.626 and then look up the key as that value, that iterated value in your table. 01:32:12.626 --> 01:32:14.921 We'll see examples of that in future lectures. 01:32:14.921 --> 01:32:16.670 But that's just something to keep in mind. 01:32:16.670 --> 01:32:19.210 So we have our table here, a sounds table. 01:32:19.210 --> 01:32:22.490 Oh, and-- and we have our sounds ready, they're loaded in memory. 01:32:22.490 --> 01:32:24.550 All we need to do now is wherever we have 01:32:24.550 --> 01:32:28.370 anything that, like any collision in our code, we just do this. 01:32:28.370 --> 01:32:30.970 It's as simple as the table. 01:32:30.970 --> 01:32:33.590 At the key that we want, colon, which is Lua's 01:32:33.590 --> 01:32:38.560 way of calling a function of a class or a table. 01:32:38.560 --> 01:32:43.840 Colon play, and the play function is part of the new source audio 01:32:43.840 --> 01:32:46.512 object in LOVE that we created in the table, 01:32:46.512 --> 01:32:48.970 and that will just have the effect of just playing it once. 01:32:48.970 --> 01:32:50.380 You can set it to looping. 01:32:50.380 --> 01:32:54.662 You can say the sounds paddle hit, set looping to true, 01:32:54.662 --> 01:32:56.870 and it will just infinitely play over and over again, 01:32:56.870 --> 01:32:58.210 which we wouldn't want for a sound like this. 01:32:58.210 --> 01:32:59.251 It would sound obnoxious. 01:32:59.251 --> 01:33:01.226 But if you have a music track, for example, 01:33:01.226 --> 01:33:03.100 in a level, or something like that, you would 01:33:03.100 --> 01:33:06.790 want set looping to be true so that when it finally ends, 01:33:06.790 --> 01:33:11.500 your user isn't just playing a game in silence. 01:33:11.500 --> 01:33:14.810 So we're doing it with paddle hit, we're doing it with wall hit as well. 01:33:14.810 --> 01:33:19.300 So I've named them appropriately so that it's easy to infer where 01:33:19.300 --> 01:33:21.730 and for what purpose the sound files are used. 01:33:21.730 --> 01:33:24.650 So whenever they're at the upper or lower boundaries, play the wall 01:33:24.650 --> 01:33:29.560 hit sound, and then whenever the ball reaches 01:33:29.560 --> 01:33:31.450 the left or the right edge of the screen, 01:33:31.450 --> 01:33:34.250 just play the score sound effect. 01:33:34.250 --> 01:33:39.490 And so if we play our game, and this is always one of my more favorite parts 01:33:39.490 --> 01:33:43.150 is playing the game with audio because it just makes 01:33:43.150 --> 01:33:45.640 such a difference, in my opinion. 01:33:45.640 --> 01:33:47.755 We get sound effects. 01:33:47.755 --> 01:33:52.510 It's a little thing, and it's very easy, but it adds-- it adds so much flavor. 01:33:52.510 --> 01:33:54.520 And then (explosion), and there we go. 01:33:54.520 --> 01:33:58.060 And then our game is practically implemented at this point. 01:33:58.060 --> 01:33:59.890 There's just one more example that I would 01:33:59.890 --> 01:34:02.681 like to show you guys, a small example, because all of the examples 01:34:02.681 --> 01:34:08.020 thus far have had the resize equal-- resizable equals false. 01:34:08.020 --> 01:34:11.870 Sort of key in the push setup screen initializer, 01:34:11.870 --> 01:34:15.700 and in case you want to have a game where you can resize your window, 01:34:15.700 --> 01:34:18.310 all we need to do is call a function called love.resize, 01:34:18.310 --> 01:34:20.370 which takes a width and a height. 01:34:20.370 --> 01:34:23.140 And what we're going to end up doing with that, 01:34:23.140 --> 01:34:25.960 specifically, for our use case, because we're using push, 01:34:25.960 --> 01:34:30.730 we're going to go to Pong 12. 01:34:30.730 --> 01:34:40.170 And then if we go to main.lua we see here on line 85, 01:34:40.170 --> 01:34:43.852 I've changed resizable to equal true now so that it will actually 01:34:43.852 --> 01:34:45.310 allow us to resize the application. 01:34:45.310 --> 01:34:47.684 If that's false, you won't even be able to click and drag 01:34:47.684 --> 01:34:50.500 the bottom corner of the screen, it just won't let you do it. 01:34:50.500 --> 01:34:56.400 And then all you have to do is call love.resizewidthheight, 01:34:56.400 --> 01:34:59.160 and then pass in push resize with height. 01:34:59.160 --> 01:35:03.810 Because push underneath the hood takes a texture and renders to it, 01:35:03.810 --> 01:35:05.700 and then upscaled it to fill your window, 01:35:05.700 --> 01:35:08.826 and so it needs to know what your current window dimensions are so 01:35:08.826 --> 01:35:10.950 that it can upscale it to fit the right dimensions. 01:35:10.950 --> 01:35:12.783 And push also adds things like letterboxing, 01:35:12.783 --> 01:35:16.440 which is convenient if you want to maintain the exact same aspect ratio. 01:35:16.440 --> 01:35:19.050 And in a game where maybe you have the UI that's 01:35:19.050 --> 01:35:21.480 driven by the size of your application, this function 01:35:21.480 --> 01:35:23.646 will be important because then you can resize your-- 01:35:23.646 --> 01:35:27.150 you can resize and reposition your UI elements appropriately. 01:35:27.150 --> 01:35:30.990 Because if your game is small, maybe you want certain parts of UI 01:35:30.990 --> 01:35:33.030 to be invisible, or in a different location 01:35:33.030 --> 01:35:36.210 altogether, just so that you don't take up a ton of screen space, 01:35:36.210 --> 01:35:39.190 and just to accommodate all possible users of your application. 01:35:39.190 --> 01:35:43.610 But that has the effect now of-- if we go into Pong 12 01:35:43.610 --> 01:35:48.510 and then run it, actually, might not even be able to use the but-- yeah, 01:35:48.510 --> 01:35:51.390 I can just do this now. 01:35:51.390 --> 01:35:56.100 I can resize it, and it'll maintain the virtual width and height 01:35:56.100 --> 01:36:01.240 that we set it to before, because that's like first and foremost 01:36:01.240 --> 01:36:03.420 what push will do and it'll letterbox no matter 01:36:03.420 --> 01:36:07.391 what size your application is to make sure that it maintains that aspect 01:36:07.391 --> 01:36:07.890 ratio. 01:36:07.890 --> 01:36:10.380 So if you're beyond that aspect ratio vertically or horizontally, 01:36:10.380 --> 01:36:12.270 you'll get the appropriate letterboxing for it. 01:36:12.270 --> 01:36:13.311 So it's super convenient. 01:36:13.311 --> 01:36:19.020 You don't have to worry about your users getting super distorted aspect ratios, 01:36:19.020 --> 01:36:22.380 because they are using some sort of unforeseen resolution. 01:36:22.380 --> 01:36:25.110 That will always maintain it even if it's super tiny 01:36:25.110 --> 01:36:27.080 because their monitor is super thin. 01:36:27.080 --> 01:36:29.030 It will always maintain the aspect ratio. 01:36:29.030 --> 01:36:33.460 But that's pretty much it for Pong, actually. 01:36:33.460 --> 01:36:37.414 We have a complete game, start to finish, and if you have any questions, 01:36:37.414 --> 01:36:38.580 I'd be happy to answer them. 01:36:41.550 --> 01:36:43.350 Any questions? 01:36:43.350 --> 01:36:43.850 Cool. 01:36:43.850 --> 01:36:44.100 All right. 01:36:44.100 --> 01:36:46.400 Well, I'm excited to teach the rest of this course to you guys. 01:36:46.400 --> 01:36:47.620 We've only scratched the surface. 01:36:47.620 --> 01:36:48.786 We have a lot more to cover. 01:36:48.786 --> 01:36:51.900 Next week, we'll actually be covering Flappy Bird 01:36:51.900 --> 01:36:54.710 so we'll get some nice colorful graphics, which 01:36:54.710 --> 01:36:58.620 is a stark difference to our black and white aesthetics today. 01:36:58.620 --> 01:37:00.000 But that's it for Pong. 01:37:00.000 --> 01:37:02.297 So thanks for coming.