WEBVTT X-TIMESTAMP-MAP=LOCAL:00:00:00.000,MPEGTS:900000 00:00:00.000 --> 00:00:02.976 [MUSIC PLAYING] 00:00:16.097 --> 00:00:17.430 COLTON OGDEN: Hey, good evening. 00:00:17.430 --> 00:00:19.970 Welcome to GD50 lecture four. 00:00:19.970 --> 00:00:22.140 This is Super Mario Bros. 00:00:22.140 --> 00:00:23.990 As seen on the slides here, though, we're 00:00:23.990 --> 00:00:26.220 not using the actual Super Mario Bros. sprite sheet. 00:00:26.220 --> 00:00:27.260 This is sort of like a rip off. 00:00:27.260 --> 00:00:28.968 But I found a really awesome sprite sheet 00:00:28.968 --> 00:00:33.590 that has all the basic tiles that we need to get this thing working. 00:00:33.590 --> 00:00:36.782 There's a link in the distro as to where you can find it online. 00:00:36.782 --> 00:00:38.240 I had a lot of fun playing with it. 00:00:38.240 --> 00:00:40.040 So hopefully, maybe if you're curious, you 00:00:40.040 --> 00:00:43.100 can use some of the sprites in there to go off and do your own thing. 00:00:43.100 --> 00:00:48.740 But Super Mario Bros.-- the actual game which this lecture and assignment are 00:00:48.740 --> 00:00:51.822 based off of-- is the game shown here. 00:00:51.822 --> 00:00:53.280 I think everybody knows what it is. 00:00:53.280 --> 00:00:55.238 It's probably the most famous game of all time. 00:00:55.238 --> 00:00:57.260 But this game came out in 1985-- 00:00:57.260 --> 00:00:59.720 sort of revolutionized the gaming industry. 00:00:59.720 --> 00:01:03.890 It was the game that brought the gaming industry from a crash 00:01:03.890 --> 00:01:08.530 in the '70s thanks to a lot of poor game making policies 00:01:08.530 --> 00:01:11.630 and companies and low QA standards. 00:01:11.630 --> 00:01:15.200 It basically took the gaming crash of the late '70s, early '80s 00:01:15.200 --> 00:01:19.340 and brought games really back to the forefront of people's consciousness. 00:01:19.340 --> 00:01:22.700 This and games like Legend of Zelda and a lot of other NES titles 00:01:22.700 --> 00:01:25.280 made Nintendo basically the dominator of the video games 00:01:25.280 --> 00:01:27.346 industry in the '80s and '90s. 00:01:27.346 --> 00:01:29.470 And even today, with games like Breath of the Wild, 00:01:29.470 --> 00:01:31.670 they're still doing their thing. 00:01:31.670 --> 00:01:32.900 This is Super Mario Bros. 00:01:32.900 --> 00:01:34.670 It's a 2D platformer. 00:01:34.670 --> 00:01:38.270 And what this basically means is you control Mario, who's a plumber. 00:01:38.270 --> 00:01:41.570 He goes around, walks sort of looking at him from the side. 00:01:41.570 --> 00:01:42.800 He walks left to right. 00:01:42.800 --> 00:01:44.130 He can jump up and down. 00:01:44.130 --> 00:01:45.240 He's affected by gravity. 00:01:45.240 --> 00:01:46.280 He can hit blocks. 00:01:46.280 --> 00:01:47.420 He can jump on enemies. 00:01:47.420 --> 00:01:50.450 He can go down pipes, and there's a bunch of levels. 00:01:50.450 --> 00:01:53.350 It was, for its time, quite a complicated game, 00:01:53.350 --> 00:01:58.660 and it spawned numerous offshoots and rip offs and other good quality 00:01:58.660 --> 00:02:00.580 platformers. 00:02:00.580 --> 00:02:02.550 While we talk about Super Mario Bros. today, 00:02:02.550 --> 00:02:05.840 some of the topics we'll actually be talking about are tile maps-- 00:02:05.840 --> 00:02:10.130 so how we can take basically a series of numbers-- 00:02:10.130 --> 00:02:13.640 tile IDs-- and turn that into a game world. 00:02:13.640 --> 00:02:20.260 As you can see here, the game is broken up into blocks of 16 by 16 tiles. 00:02:20.260 --> 00:02:22.460 You can see the bricks and the question mark blocks, 00:02:22.460 --> 00:02:26.630 and the pipes are even all composed of simple tiles. 00:02:26.630 --> 00:02:28.610 And they map to IDs. 00:02:28.610 --> 00:02:34.100 And when you take a 2D table or array and you just iterate over all of it 00:02:34.100 --> 00:02:37.550 and render the appropriate tile at the appropriate x, y, 00:02:37.550 --> 00:02:40.190 you get the appearance of existing in some game world, 00:02:40.190 --> 00:02:43.769 even though it's just composed of a bunch of tiny little blocks. 00:02:43.769 --> 00:02:45.560 2D animation is something we'll talk about. 00:02:45.560 --> 00:02:47.393 So far, we haven't really done any animation 00:02:47.393 --> 00:02:50.790 at all in terms of at least characters. 00:02:50.790 --> 00:02:52.250 We'll do that with Mario. 00:02:52.250 --> 00:02:55.610 He'll have-- our version of Mario, an alien-- 00:02:55.610 --> 00:02:58.006 when he's moving, he'll have two frames of animation. 00:02:58.006 --> 00:03:00.380 The frames of animation-- that's sort of like a flip book 00:03:00.380 --> 00:03:03.005 if you've ever used one, where you can see individual pictures. 00:03:03.005 --> 00:03:05.180 And when you display them rapidly back to back, 00:03:05.180 --> 00:03:07.190 you get the appearance of animation. 00:03:07.190 --> 00:03:08.480 We'll be talking about that. 00:03:08.480 --> 00:03:11.300 Procedural level generation-- we'll be making all of our levels 00:03:11.300 --> 00:03:12.570 generate randomly. 00:03:12.570 --> 00:03:14.900 So every time we play the game from the beginning, 00:03:14.900 --> 00:03:16.566 everything will be completely different. 00:03:16.566 --> 00:03:19.370 We don't have to hard code a finite set of levels. 00:03:19.370 --> 00:03:23.180 Everything will be dynamic and also interesting, in my opinion. 00:03:23.180 --> 00:03:25.820 We'll talk about the basics of platformer physics 00:03:25.820 --> 00:03:27.740 and how we can apply that to our game world 00:03:27.740 --> 00:03:34.310 here, because we are just using a table of tiles, 00:03:34.310 --> 00:03:37.730 each with an x, y that's hard coded in the game world space. 00:03:37.730 --> 00:03:41.330 All we have to really do is take an x, y of Mario, for example, 00:03:41.330 --> 00:03:44.150 and then just divide that by the tile size. 00:03:44.150 --> 00:03:46.670 And then we get basically what tile that is 00:03:46.670 --> 00:03:48.960 in our array at that point in the world. 00:03:48.960 --> 00:03:53.750 And so it's really easy to do arbitrary collision detection based 00:03:53.750 --> 00:03:56.210 on what direction you're going and not have to iterate over 00:03:56.210 --> 00:03:57.860 every single tile in your world. 00:03:57.860 --> 00:04:01.430 Because it's just a simple mathematical operation to get the exact tile 00:04:01.430 --> 00:04:05.000 given two coordinates, since the world is in a fixed space. 00:04:05.000 --> 00:04:07.250 We'll have a little snail in our game that 00:04:07.250 --> 00:04:09.680 walks around and does a couple of random animations 00:04:09.680 --> 00:04:13.070 and will go after the player, sort of like a little basic intro to AI. 00:04:13.070 --> 00:04:16.250 And then lastly, we'll touch on things like power ups and game objects 00:04:16.250 --> 00:04:19.200 and how we might be able to influence Mario and pick those up 00:04:19.200 --> 00:04:21.079 and that sort of thing. 00:04:21.079 --> 00:04:22.370 So first though, a demo. 00:04:22.370 --> 00:04:24.560 So if anybody is willing to come up on stage 00:04:24.560 --> 00:04:29.660 to test out my implementation of Mario, that would be awesome. 00:04:29.660 --> 00:04:31.440 James? 00:04:31.440 --> 00:04:35.970 I'm going to go ahead into here, so we should be all ready to go. 00:04:35.970 --> 00:04:39.360 So as soon as you're ready, go ahead and hit Return there, 00:04:39.360 --> 00:04:42.510 and you should be up and running. 00:04:42.510 --> 00:04:45.380 So as a part of having random levels-- 00:04:45.380 --> 00:04:49.160 so currently, we have a green alien. 00:04:49.160 --> 00:04:53.270 The blocks have a random chance in this case to spawn a gem. 00:04:53.270 --> 00:04:56.469 And so once they do, you can pick the gem up. 00:04:56.469 --> 00:04:58.010 Either they have a gem or they don't. 00:04:58.010 --> 00:05:00.330 You can pick it up, and you get 100 points. 00:05:00.330 --> 00:05:02.180 As we can see, the world is sort of shifting 00:05:02.180 --> 00:05:07.537 based on where James's avatar is, so it tracks the character. 00:05:07.537 --> 00:05:08.870 We have some notion of a camera. 00:05:08.870 --> 00:05:11.430 You're getting unlucky with the blocks so far. 00:05:11.430 --> 00:05:15.810 So you can fall down through the spaces, so you probably want to avoid that. 00:05:15.810 --> 00:05:18.314 But if you want to demonstrate doing it-- 00:05:18.314 --> 00:05:20.730 so in that case, we collided with the two blocks below it. 00:05:20.730 --> 00:05:23.070 The one on the right had the gem. 00:05:23.070 --> 00:05:26.160 So go ahead and just fall down so we can demonstrate. 00:05:26.160 --> 00:05:28.350 So when we fall down, we detect whether the player 00:05:28.350 --> 00:05:31.312 has gone below the world limit, and then we start him back 00:05:31.312 --> 00:05:32.520 at the beginning of the game. 00:05:32.520 --> 00:05:35.220 You can press Enter, it should regenerate a brand new world. 00:05:35.220 --> 00:05:38.400 Notice how we have random holes in the ground. 00:05:38.400 --> 00:05:39.960 We have random tiles. 00:05:39.960 --> 00:05:41.697 We have random toppers for them. 00:05:41.697 --> 00:05:42.780 All the blocks are random. 00:05:42.780 --> 00:05:44.220 We have snails now. 00:05:44.220 --> 00:05:45.960 They're sort of chasing after James. 00:05:45.960 --> 00:05:47.469 He can jump on top of them. 00:05:47.469 --> 00:05:50.010 There's a lot of little moving pieces here, but a lot of them 00:05:50.010 --> 00:05:51.270 are actually pretty simple. 00:05:51.270 --> 00:05:54.610 And I'll show you very shortly. 00:05:54.610 --> 00:05:55.970 JAMES: Should I stop? 00:05:55.970 --> 00:05:56.490 COLTON OGDEN: Yeah, sure. 00:05:56.490 --> 00:05:57.390 That would be a great point. 00:05:57.390 --> 00:05:58.098 So thanks, James. 00:05:58.098 --> 00:05:59.610 Appreciate it. 00:05:59.610 --> 00:06:04.470 Currently, there is no notion of a level ending. 00:06:04.470 --> 00:06:06.300 That's part of the piece that actually will 00:06:06.300 --> 00:06:09.600 spawn an object that the player can interact with to just sort of retrigger 00:06:09.600 --> 00:06:11.490 a new level, basically. 00:06:11.490 --> 00:06:18.670 But the whole engine behind this basic platformer is there, and it all works. 00:06:18.670 --> 00:06:22.410 And so our goal is seen here. 00:06:22.410 --> 00:06:24.300 Our goal in this lecture is to demonstrate 00:06:24.300 --> 00:06:27.960 how we can get things like a character that moves around on a screen, 00:06:27.960 --> 00:06:32.520 and a camera that tracks their position, and tiles that are randomized. 00:06:32.520 --> 00:06:35.220 And maybe there are pillars in the ground, holes in the ground. 00:06:35.220 --> 00:06:36.640 All of this, again-- 00:06:36.640 --> 00:06:38.640 at least the tiles-- are stored as just numbers. 00:06:38.640 --> 00:06:42.960 So all we really need to do is perform a transformation on a series of numbers. 00:06:42.960 --> 00:06:48.150 Maybe 1 is equal to a tile being there, 0 is equal to empty space. 00:06:48.150 --> 00:06:51.360 And so just by looking at it, we'll see we go column by column. 00:06:51.360 --> 00:06:55.140 We can say, oh, maybe there's a chance to not spawn any tiles along the y 00:06:55.140 --> 00:06:58.890 column on this x of the world map. 00:06:58.890 --> 00:07:02.670 Or on this particular y, maybe instead of spawning the ground level, 00:07:02.670 --> 00:07:05.972 we spawn a couple above it and down so that we get a pillar 00:07:05.972 --> 00:07:06.930 and so on and so forth. 00:07:06.930 --> 00:07:10.080 And it's just this summation of these randomizations 00:07:10.080 --> 00:07:14.620 equals a nice little variety of game levels. 00:07:14.620 --> 00:07:18.300 So the first thing we should talk about really is what a tile map is. 00:07:18.300 --> 00:07:21.720 And what I've alluded to so far is you can really think of a tile map 00:07:21.720 --> 00:07:27.030 as being effectively a 2D array or a table of numbers. 00:07:27.030 --> 00:07:30.180 And it's a little more complicated than that depending on how complex 00:07:30.180 --> 00:07:35.610 your platformer is, because some numbers are equal to tiles that are solid 00:07:35.610 --> 00:07:36.670 or not. 00:07:36.670 --> 00:07:39.840 So you should be able to check whether a tile is collidable, 00:07:39.840 --> 00:07:42.930 meaning that the player or whatever entity you want to check for 00:07:42.930 --> 00:07:44.930 can actually collide with it or 00:07:44.930 --> 00:07:45.430 Not. 00:07:45.430 --> 00:07:48.400 So obviously, we don't want to trigger a collision on empty tiles. 00:07:48.400 --> 00:07:50.400 We want the player to move freely through those. 00:07:50.400 --> 00:07:53.280 But if they run up against a wall or if gravity is affecting them, 00:07:53.280 --> 00:07:56.970 and they hit tiles below them or above them, we want to detect a collision 00:07:56.970 --> 00:07:59.627 and then stop them based on which direction they're moving. 00:07:59.627 --> 00:08:02.460 And depending on how complicated you get with your platformer, maybe 00:08:02.460 --> 00:08:04.422 you have animated tiles, for instance. 00:08:04.422 --> 00:08:07.380 So if a tile's animated, it will display a different frame of animation 00:08:07.380 --> 00:08:09.330 based on what timer you're on. 00:08:09.330 --> 00:08:10.590 Really, the sky's the limit. 00:08:10.590 --> 00:08:12.630 In this case, we'll be fairly simple. 00:08:12.630 --> 00:08:17.520 Our tiles will mostly just be numbers with a couple of other traits, which 00:08:17.520 --> 00:08:19.920 we'll see later on. 00:08:19.920 --> 00:08:22.840 And this is just an example here of a very simple map-- just a colored 00:08:22.840 --> 00:08:23.340 background. 00:08:23.340 --> 00:08:28.320 We have our character, and then we can sort of visualize all of those tiles 00:08:28.320 --> 00:08:34.307 as being just for the sake of theory 0s or 1s. 00:08:34.307 --> 00:08:37.140 So tiles0-- so I'll actually get into a little bit of implementation 00:08:37.140 --> 00:08:41.789 here as to how we can get drawing some very simple tiles. 00:08:41.789 --> 00:08:46.050 So if you're looking at the distro, in tiles0 00:08:46.050 --> 00:08:47.990 is going to be where we start off here. 00:08:47.990 --> 00:08:53.200 And I'm going to go ahead and run tiles0 so we can see what that looks like. 00:08:53.200 --> 00:08:54.330 So this is just tiles0. 00:08:54.330 --> 00:08:58.560 It's a much simpler program than what we just saw, but all we're doing here 00:08:58.560 --> 00:09:01.560 is just a color in the background and then tiles. 00:09:05.567 --> 00:09:08.400 Off the gate, anybody have any ideas as to what the first step would 00:09:08.400 --> 00:09:10.520 be if we wanted to implement this? 00:09:14.504 --> 00:09:19.304 AUDIENCE: Just put the tiles in a loop, draw them, and then have a background? 00:09:19.304 --> 00:09:21.470 COLTON OGDEN: So put the tiles in a loop, draw them, 00:09:21.470 --> 00:09:22.580 and then have a background. 00:09:22.580 --> 00:09:23.079 Yes. 00:09:23.079 --> 00:09:29.600 So basically, if this is main.lua in our tiles0, 00:09:29.600 --> 00:09:33.590 first thing we're going to need is a tiles table to store our-- 00:09:33.590 --> 00:09:35.990 we're not going to be storing just flat numbers. 00:09:35.990 --> 00:09:39.350 We'll be storing little mini tables that have a number in them and ID, 00:09:39.350 --> 00:09:45.432 so we can say tile.ID if we have a 2D table. 00:09:45.432 --> 00:09:46.640 Here, we have an empty table. 00:09:46.640 --> 00:09:47.848 We're going to populate that. 00:09:50.780 --> 00:09:54.470 If we're going to draw our tiles, we are going to need a sprite of some kind. 00:09:54.470 --> 00:09:57.980 And what I did was I just chopped out a little segment here. 00:09:57.980 --> 00:10:00.230 So this is tiles.png. 00:10:00.230 --> 00:10:04.076 It's just literally one tile from the main sprite 00:10:04.076 --> 00:10:05.450 sheet that comes with the distro. 00:10:05.450 --> 00:10:12.710 And then on the right side is just transparent so that we can offset-- 00:10:12.710 --> 00:10:19.370 maybe tile ID 1 is equal to solid block, and then tile ID 2 is equal to empty. 00:10:19.370 --> 00:10:24.050 And so if we recall generate quads, we can split up a sprite sheet 00:10:24.050 --> 00:10:26.560 into however many quads we want to. 00:10:26.560 --> 00:10:29.300 Let's say this is 16 tiles tall-- 00:10:29.300 --> 00:10:33.080 each tile-- and then the whole thing are two tiles wide. 00:10:33.080 --> 00:10:36.835 So it needs to be split into two separate tiles. 00:10:36.835 --> 00:10:41.330 We'll just generate quads, and then we'll have, recall, a table. 00:10:41.330 --> 00:10:45.290 Each of the indexes of that table will be 00:10:45.290 --> 00:10:47.560 a quad that maps to one of these tiles. 00:10:47.560 --> 00:10:50.270 So number 1 will be this tile here, number 2 00:10:50.270 --> 00:10:53.120 will be the transparent bit over here, and then 00:10:53.120 --> 00:10:55.929 that's how effectively our IDs are going to map 00:10:55.929 --> 00:10:57.470 into what gets drawn onto the screen. 00:10:57.470 --> 00:11:02.130 The ID is the index into our quad table. 00:11:02.130 --> 00:11:08.890 So going back into tiles0, we have here just a map width and height. 00:11:08.890 --> 00:11:13.880 We're just going to say generate a map 20 by 20. 00:11:13.880 --> 00:11:15.860 RBG-- we're just going to make it random, 00:11:15.860 --> 00:11:19.280 so we're going to clear the screen with a random color. 00:11:19.280 --> 00:11:24.020 And then this is the quads = GenerateQuads. 00:11:24.020 --> 00:11:26.114 And notice that we're passing in tile size here. 00:11:26.114 --> 00:11:28.530 It's good practice just to make your tile size a constant. 00:11:28.530 --> 00:11:31.940 So our tile size in this entire lecture-- 00:11:31.940 --> 00:11:33.710 they're all going to be 16 by 16. 00:11:33.710 --> 00:11:36.950 And so since they're symmetrical, we just pass in tile size TILE_SIZE. 00:11:36.950 --> 00:11:40.130 And then here is where we actually end up spawning the map-- 00:11:40.130 --> 00:11:42.960 so nested for loop. 00:11:42.960 --> 00:11:47.290 y gets 1 to map height, x gets 1 to map width. 00:11:47.290 --> 00:11:50.920 Remember, we have to insert a blank table into the base table that's 00:11:50.920 --> 00:11:52.700 going to act as our current row. 00:11:52.700 --> 00:11:56.570 And then in that row, we're going to add a small at tiles y, 00:11:56.570 --> 00:12:01.280 because y is going to be up here-- our current row and ID. 00:12:01.280 --> 00:12:07.790 And so what we're doing here is if y is less than 5-- 00:12:07.790 --> 00:12:11.150 meaning we'll just set an arbitrary point for the ground, basically. 00:12:11.150 --> 00:12:15.810 If it's less than 5 tiles from the top, then just make it the sky. 00:12:15.810 --> 00:12:19.340 And so sky-- up here on line 24, 25-- we just 00:12:19.340 --> 00:12:20.900 set two tile IDs, as I said before. 00:12:20.900 --> 00:12:23.772 Sky is 2, so it's going to be on the right side of the sheet. 00:12:23.772 --> 00:12:24.730 And then ground is one. 00:12:24.730 --> 00:12:28.230 It's going to be the very first quad generated in the sheet. 00:12:28.230 --> 00:12:33.200 So if y is less than 5, that ID should be equal to sky else 00:12:33.200 --> 00:12:35.420 it should be equal to ground. 00:12:35.420 --> 00:12:38.850 And so down here is where that comes into play. 00:12:38.850 --> 00:12:41.690 We're going to clear the screen with our random color. 00:12:41.690 --> 00:12:44.690 We're going to iterate over the loop, as James said. 00:12:44.690 --> 00:12:47.990 We're going to get the tile at tiles y x, 00:12:47.990 --> 00:12:53.630 and then we're just going to draw the sheet and the quads at that tiles ID. 00:12:53.630 --> 00:12:58.850 And then recall, since tables are 1 indexed but coordinates are 0 indexed, 00:12:58.850 --> 00:13:01.640 we take the x and the y, subtract 1 from them, 00:13:01.640 --> 00:13:03.680 and then we just multiply them by tile size. 00:13:03.680 --> 00:13:07.280 And that has the effect of drawing each of those tiles 00:13:07.280 --> 00:13:10.190 at their respective point in the world and making 00:13:10.190 --> 00:13:14.060 it seem as if we have this world-- this bunch of bricks 00:13:14.060 --> 00:13:17.010 with a random background every time. 00:13:17.010 --> 00:13:20.550 Which isn't all that interesting, but just a little bit more variety. 00:13:20.550 --> 00:13:22.910 And so that's the very basic gist behind it. 00:13:22.910 --> 00:13:26.118 I mean, it's essentially almost the same thing as what we did in match three, 00:13:26.118 --> 00:13:29.210 where we just mapped the individual tiles that 00:13:29.210 --> 00:13:38.090 were in the grid to indexes in the tile sheet based on the color and variety. 00:13:38.090 --> 00:13:41.370 Only this time, they're always going to be in the exact same place, 00:13:41.370 --> 00:13:43.760 so we don't have to worry about whether their x and y are 00:13:43.760 --> 00:13:45.440 different from their grid y and grid x. 00:13:45.440 --> 00:13:48.710 We're not maintaining a reference to those. 00:13:48.710 --> 00:13:49.999 And so that's static tiles. 00:13:49.999 --> 00:13:52.040 Does anybody have any questions about how we just 00:13:52.040 --> 00:13:53.769 draw static tiles to the screen? 00:13:53.769 --> 00:13:54.560 Pretty basic stuff. 00:13:58.440 --> 00:14:02.330 The whole name behind side scrolling game 00:14:02.330 --> 00:14:06.742 is that the tiles scroll based on what we're doing in the game. 00:14:06.742 --> 00:14:08.450 It can be an auto scroller, in which case 00:14:08.450 --> 00:14:10.802 maybe you're an airplane that's sort of going 00:14:10.802 --> 00:14:12.760 through a level that's scrolling automatically, 00:14:12.760 --> 00:14:14.080 and you're shooting things. 00:14:14.080 --> 00:14:16.121 And you're not really in control of where you go. 00:14:16.121 --> 00:14:18.549 Or it can be like Mario, where you control an avatar. 00:14:18.549 --> 00:14:20.340 And you can walk around and jump and stuff, 00:14:20.340 --> 00:14:22.560 and the camera will always be fixed on you. 00:14:22.560 --> 00:14:27.180 And so the scrolling is just relative to where your character's x and y are. 00:14:27.180 --> 00:14:30.480 So I'm going to show you guys an example of how we can 00:14:30.480 --> 00:14:32.357 get scrolling implemented in our game. 00:14:32.357 --> 00:14:35.190 And to do that, the function that we're really going to be looking-- 00:14:35.190 --> 00:14:37.290 at a new function-- 00:14:37.290 --> 00:14:41.250 is love.graphics.translate(x, y). 00:14:41.250 --> 00:14:45.240 And so what that does is effectively just translates 00:14:45.240 --> 00:14:48.840 Love2D's coordinate system so that whenever we draw something, 00:14:48.840 --> 00:14:52.190 it gets automatically shifted by x, y. 00:14:52.190 --> 00:14:58.770 And so that has the effect of the everything being sort of skewed 00:14:58.770 --> 00:15:00.630 based on the x, y that we pass it. 00:15:00.630 --> 00:15:05.100 And so if we maintain a reference to where the character is, 00:15:05.100 --> 00:15:09.480 we can just shift where everything gets drawn on the screen. 00:15:09.480 --> 00:15:13.020 And that will have the effect of it being a camera, but it's not. 00:15:13.020 --> 00:15:15.390 All we're doing is just shifting the coordinate system 00:15:15.390 --> 00:15:16.720 based on some offset-- 00:15:16.720 --> 00:15:19.914 x being in this case where the players is effectively. 00:15:19.914 --> 00:15:22.080 AUDIENCE: So it changes the whole coordinate system? 00:15:22.080 --> 00:15:22.996 COLTON OGDEN: It does. 00:15:22.996 --> 00:15:27.210 It shifts everything in the coordinate system that you draw by the x and y. 00:15:27.210 --> 00:15:31.170 And so that will basically affect what's getting rendered 00:15:31.170 --> 00:15:32.820 into the active window at that time. 00:15:35.710 --> 00:15:38.580 So I'm going to go ahead and pull up tiles1 here 00:15:38.580 --> 00:15:40.696 so we can see how this works. 00:15:40.696 --> 00:15:44.290 Let me go ahead and first run the program. 00:15:44.290 --> 00:15:47.970 So if we're going into tiles1 in the distro, 00:15:47.970 --> 00:15:49.980 currently it looks almost identical. 00:15:49.980 --> 00:15:53.610 But I can move it if I just press left or right. 00:15:53.610 --> 00:15:57.810 And so we can see here, this is where the 2D array of tiles 00:15:57.810 --> 00:15:59.140 gets cut off here. 00:15:59.140 --> 00:16:02.550 And then it also cuts off, because we're only generating a 20 by 20 level. 00:16:02.550 --> 00:16:05.560 It also gets cut off at the very right side as well. 00:16:05.560 --> 00:16:08.310 And these are details you would normally hide from the user 00:16:08.310 --> 00:16:13.740 by just clamping the x between 0 and the right side 00:16:13.740 --> 00:16:17.064 of the map minus VIRTUAL_WIDTH. 00:16:17.064 --> 00:16:19.730 And that will have the effect of whenever you get to this point, 00:16:19.730 --> 00:16:22.599 it won't let you go right anymore, and same thing for the left side. 00:16:22.599 --> 00:16:24.390 Well, all we're doing right now-- we're not 00:16:24.390 --> 00:16:26.170 doing it based on the character at all. 00:16:26.170 --> 00:16:29.590 We're just using keyboard input. 00:16:29.590 --> 00:16:32.100 So let's go ahead into tiles1. 00:16:35.280 --> 00:16:41.520 And so the important thing that we're going to look at is-- 00:16:41.520 --> 00:16:45.090 as I just alluded to, we're calling love.graphics.translate 00:16:45.090 --> 00:16:47.460 on some value called cameraScroll. 00:16:53.010 --> 00:16:56.930 It has to be a negative value, because if we're moving to the right 00:16:56.930 --> 00:16:59.246 up here or to the left-- 00:16:59.246 --> 00:17:02.370 if we're moving to the left, camera scroll basically is going to decrement, 00:17:02.370 --> 00:17:03.453 so it's going to get less. 00:17:03.453 --> 00:17:05.339 So we can say the camera scroll when we're 00:17:05.339 --> 00:17:08.470 going left is going to be 0 or less if we're starting at 0, 00:17:08.470 --> 00:17:10.170 or it's going to decrement. 00:17:10.170 --> 00:17:14.760 If we press right, camera roll should increase. 00:17:14.760 --> 00:17:19.480 If we want the appearance of moving to the right or moving to the left, 00:17:19.480 --> 00:17:22.829 you actually have to translate by the opposite direction. 00:17:22.829 --> 00:17:34.840 Because if we look at this, and if we call love.graphics.translate positive, 00:17:34.840 --> 00:17:36.810 all of this is going to get moved to the right. 00:17:36.810 --> 00:17:39.330 So it's going to have the appearance of us moving left. 00:17:39.330 --> 00:17:41.690 And if we translate it to the left by a negative amount, 00:17:41.690 --> 00:17:44.370 it's going to have the appearance of us moving right. 00:17:44.370 --> 00:17:48.570 So if our scroll is positive and we want to move to the right, 00:17:48.570 --> 00:17:52.330 we actually have to translate by a negative amount. 00:17:52.330 --> 00:18:00.660 And so that's why I'm calling negative math.floor(cameraScroll). 00:18:00.660 --> 00:18:04.710 Does anybody know why we're calling math.floor on cameraScroll 00:18:04.710 --> 00:18:09.980 instead of just calling negative camera scroll? 00:18:09.980 --> 00:18:13.950 Does anybody remember what math.floor does? 00:18:13.950 --> 00:18:17.560 So math.floor will return the-- 00:18:17.560 --> 00:18:21.450 it'll basically truncate the number down to the lowest integer. 00:18:21.450 --> 00:18:24.330 It will basically take off the floating point value. 00:18:24.330 --> 00:18:26.400 Because we're rendering to a virtual resolution 00:18:26.400 --> 00:18:32.640 with push, if we basically offset the translation by a fractional amount, 00:18:32.640 --> 00:18:34.020 you'll get artifacting. 00:18:34.020 --> 00:18:37.470 Because it's taking your window and just condensing 00:18:37.470 --> 00:18:42.280 your image onto a virtual canvas, you'll get weird blur and stuff like that. 00:18:42.280 --> 00:18:48.330 So whenever you draw something and you have a fractional number for something, 00:18:48.330 --> 00:18:51.840 and you're drawing it to a virtual canvas that's been magnified 00:18:51.840 --> 00:18:55.410 or it's being condensed, just make sure to math.floor it 00:18:55.410 --> 00:18:58.710 so you don't get any weird blur artifacting. 00:18:58.710 --> 00:19:02.056 If you take this out and experiment around, or even if in the distro 00:19:02.056 --> 00:19:04.680 you take it out of the player's position, you'll see the player 00:19:04.680 --> 00:19:07.560 will get weird blurry artifacting and stuff like that. 00:19:07.560 --> 00:19:10.912 So that's why that's there, in case you're curious. 00:19:10.912 --> 00:19:12.870 And so all we're doing here-- we're just saying 00:19:12.870 --> 00:19:16.580 if it's equal to left, scroll the camera left, scroll it right, 00:19:16.580 --> 00:19:22.910 or basically decrement our camera scroll and then increment or camera scroll. 00:19:22.910 --> 00:19:25.970 And then just use the negative version of that here. 00:19:25.970 --> 00:19:30.344 You could also just assign camera scroll equal to positive 00:19:30.344 --> 00:19:32.510 when you move left and negative when you move right, 00:19:32.510 --> 00:19:35.690 and then you could give it the regular camera scroll here. 00:19:35.690 --> 00:19:39.020 But it's sort of mentally flipped in terms of this part. 00:19:39.020 --> 00:19:44.480 So I just made the decision to decrement it here when we're pressing left, 00:19:44.480 --> 00:19:51.050 because we're going less on the x and then more on the x when we press right. 00:19:51.050 --> 00:19:52.010 Does this make sense? 00:19:52.010 --> 00:19:52.665 Anybody have question? 00:19:52.665 --> 00:19:53.831 AUDIENCE: I have a question. 00:19:53.831 --> 00:19:56.222 Is there a corresponding function in JavaScript 00:19:56.222 --> 00:20:01.672 and in other languages like this where you can shift a whole coordinate? 00:20:01.672 --> 00:20:04.130 COLTON OGDEN: Is there an equivalent function in JavaScript 00:20:04.130 --> 00:20:06.640 where you can shift the whole coordinate system? 00:20:06.640 --> 00:20:09.020 Not in base JavaScript, probably. 00:20:09.020 --> 00:20:10.760 I'm not too familiar with CSS. 00:20:10.760 --> 00:20:14.040 There might be a CSS function that does it. 00:20:14.040 --> 00:20:18.120 In a lot of 2D game engines, yes, I would say. 00:20:18.120 --> 00:20:22.310 And a lot of actual 2D game engines will have a camera object, which 00:20:22.310 --> 00:20:24.110 sort of encapsulates this behavior. 00:20:24.110 --> 00:20:26.480 Love2D doesn't have a camera, so this is sort 00:20:26.480 --> 00:20:30.200 of why we're doing this-- is because it's kind of a lower level game 00:20:30.200 --> 00:20:30.952 framework, Love2D. 00:20:30.952 --> 00:20:32.660 It doesn't really give you as many things 00:20:32.660 --> 00:20:36.800 right out the gate, which makes it great for teaching these concepts. 00:20:36.800 --> 00:20:41.180 But a more robust solution like Unity or Phaser 00:20:41.180 --> 00:20:45.350 or a lot of other game frameworks is that they'll just have a camera object. 00:20:45.350 --> 00:20:49.270 And you just basically give that your x, and then you just move that. 00:20:49.270 --> 00:20:51.170 You basically tell that to track the player-- 00:20:51.170 --> 00:20:55.985 like camera.trackPlayer or trackEntityPlayer-- 00:20:55.985 --> 00:20:57.360 and that'll have the same effect. 00:20:57.360 --> 00:20:58.693 It's a little bit more abstract. 00:20:58.693 --> 00:21:00.800 It's a higher level than what we're doing, 00:21:00.800 --> 00:21:04.427 but it's the same exact principle underlying. 00:21:04.427 --> 00:21:06.260 So any other questions as to how this works? 00:21:09.780 --> 00:21:11.090 All right, cool. 00:21:11.090 --> 00:21:13.610 So that's all we're effectively doing. 00:21:13.610 --> 00:21:17.180 We're just getting a camera scroll, decrementing it and incrementing it. 00:21:17.180 --> 00:21:19.970 And then just every frame, we're translating everything 00:21:19.970 --> 00:21:21.350 before we draw everything. 00:21:21.350 --> 00:21:25.010 You have to do the translation before you draw, because everything that you 00:21:25.010 --> 00:21:30.260 draw after the translation gets affected by the new coordinate system change. 00:21:30.260 --> 00:21:33.290 So that's scrolling. 00:21:33.290 --> 00:21:37.220 Let's get to actually talking about drawing a person-- an avatar-- 00:21:37.220 --> 00:21:46.280 more than just a set of tiles, since that's what the game revolves around. 00:21:46.280 --> 00:21:53.270 If we look at character0, this would be our first example here. 00:21:53.270 --> 00:21:57.350 This is just going to be a very simple example-- 00:21:57.350 --> 00:21:57.960 charactr0. 00:22:00.800 --> 00:22:02.990 You guys probably know how this works already. 00:22:02.990 --> 00:22:07.120 All we're doing is just drawing a sprite to the screen-- 00:22:07.120 --> 00:22:09.680 so just love.graphics.draw. 00:22:09.680 --> 00:22:13.070 We're getting quads from a tile sheet. 00:22:13.070 --> 00:22:15.470 I believe it's in the slides. 00:22:15.470 --> 00:22:18.260 The actual sheet is here. 00:22:18.260 --> 00:22:21.680 So we have this little guy-- 00:22:21.680 --> 00:22:22.970 several frames of animation. 00:22:22.970 --> 00:22:26.080 It's 16 wide, 20 tall, and we just take a quad. 00:22:26.080 --> 00:22:27.420 We split it up into quads first. 00:22:27.420 --> 00:22:32.240 So we know that it's 16 wide by 20 tall, so we just generate quads 00:22:32.240 --> 00:22:35.690 on this image by 16 and 20. 00:22:35.690 --> 00:22:38.000 And then in this example, all we're doing 00:22:38.000 --> 00:22:42.796 is taking the first frame, which is quads1, and just drawing that. 00:22:42.796 --> 00:22:45.170 As you can see here, we have a bunch of different things. 00:22:45.170 --> 00:22:47.000 We have like a crouching state, and we'll 00:22:47.000 --> 00:22:50.100 get to more about animations in a little bit. 00:22:50.100 --> 00:22:53.160 But here, we have him climbing up a ladder. 00:22:53.160 --> 00:22:55.290 But you can see all these different frames. 00:22:55.290 --> 00:22:57.800 We'll end up showing how you can play them back to back 00:22:57.800 --> 00:22:59.430 and get different animations. 00:22:59.430 --> 00:23:01.970 But for the sake of this basic example, all we're doing 00:23:01.970 --> 00:23:05.360 is just rendering the very first frame. 00:23:05.360 --> 00:23:08.200 And we can see that-- 00:23:08.200 --> 00:23:11.240 let me make sure I'm in the right file, which I am-- 00:23:11.240 --> 00:23:17.790 we are getting the character sheet here on line 43 and 44. 00:23:17.790 --> 00:23:21.820 And then we have to give him an x, So characterX, characterY. 00:23:21.820 --> 00:23:24.360 In this case, we're just setting him above tile 7, 00:23:24.360 --> 00:23:29.840 so we do 7 minus 1 times TILE_SIZE because tiles are 1 indexed 00:23:29.840 --> 00:23:31.909 but coordinates are 0 indexed. 00:23:31.909 --> 00:23:33.950 And then we just subtract the height so that he's 00:23:33.950 --> 00:23:38.450 right above the tile instead of right at the tile. 00:23:38.450 --> 00:23:44.120 And then down here, we do a love.graphics.draw 00:23:44.120 --> 00:23:46.460 on, as I said before, just characterQuads 1. 00:23:46.460 --> 00:23:49.660 Just a very basic hard coded example. 00:23:49.660 --> 00:23:51.410 Any questions at all as to how this works? 00:23:58.270 --> 00:24:00.514 So now let's say we want him to move. 00:24:00.514 --> 00:24:01.180 What do we need? 00:24:01.180 --> 00:24:03.304 What's the next step if we just wanted him to move? 00:24:08.984 --> 00:24:11.970 AUDIENCE: Give him an x and y? 00:24:11.970 --> 00:24:13.890 COLTON OGDEN: Yes, give him an x and y. 00:24:13.890 --> 00:24:17.350 So yes, so he does have an x and y already. 00:24:17.350 --> 00:24:19.750 So if you look at-- am I in the right one? 00:24:19.750 --> 00:24:23.179 So if you go to character0, this is character0 still. 00:24:23.179 --> 00:24:26.220 We have given him an x and y already, but there needs to be another step. 00:24:26.220 --> 00:24:27.511 What's the other step involved? 00:24:32.930 --> 00:24:36.620 So if we wanted to move, we need to check for keyboard input. 00:24:36.620 --> 00:24:39.320 And then we need to take his x-- 00:24:39.320 --> 00:24:42.140 we're just going to move him on the x-axis for now. 00:24:42.140 --> 00:24:47.390 We basically need to take his characterX variable up here, 00:24:47.390 --> 00:24:49.040 and we need to modify that. 00:24:49.040 --> 00:24:55.260 We can basically do the same thing that we did down here in love.update. 00:24:55.260 --> 00:24:58.040 Previously, it was on the coordinate system-- 00:24:58.040 --> 00:24:59.130 love.graphics.translate. 00:24:59.130 --> 00:25:01.220 We modified the camera scroll. 00:25:01.220 --> 00:25:05.900 We set that equal to scroll speed times delta time. 00:25:05.900 --> 00:25:07.130 We subtracted or added it. 00:25:07.130 --> 00:25:09.755 In this case, what we're doing is we have a new constant called 00:25:09.755 --> 00:25:13.250 CHARACTER_MOVE_SPEED, and we're just doing that exact same operation 00:25:13.250 --> 00:25:18.020 but on characterX instead of cameraScroll. 00:25:18.020 --> 00:25:24.900 So the end result of that is that we have the character here. 00:25:24.900 --> 00:25:28.287 And then we can move him left or right, and he go off screen. 00:25:28.287 --> 00:25:29.870 Now, there's a couple of things wrong. 00:25:29.870 --> 00:25:30.500 What's wrong? 00:25:30.500 --> 00:25:33.632 What are some of the things that are wrong with the scene right now? 00:25:33.632 --> 00:25:35.450 AUDIENCE: The camera should move with him. 00:25:35.450 --> 00:25:36.320 COLTON OGDEN: Camera should move with them. 00:25:36.320 --> 00:25:37.700 AUDIENCE: No animation. 00:25:37.700 --> 00:25:39.440 COLTON OGDEN: Does not have animation. 00:25:39.440 --> 00:25:41.690 Those are probably the two real things that are wrong. 00:25:41.690 --> 00:25:45.712 So camera does not track him, which is an important thing. 00:25:45.712 --> 00:25:48.670 Obviously, we want to be able to maintain a reference to our character, 00:25:48.670 --> 00:25:50.150 unless we're at the left edge of the screen. 00:25:50.150 --> 00:25:52.880 If we're at the left edge of the screen, this is actually OK. 00:25:52.880 --> 00:25:56.690 And that's part of the distro-- is we clamp the x so 00:25:56.690 --> 00:25:59.690 that it doesn't go past the left edge. 00:25:59.690 --> 00:26:05.210 But if we're beyond the middle and not to the right edge of the screen, 00:26:05.210 --> 00:26:08.910 it should be moving along with him and vice versa. 00:26:08.910 --> 00:26:11.030 And then he needs to animate, so his sprite 00:26:11.030 --> 00:26:15.050 needs to change every certain number of seconds whether he's moving. 00:26:15.050 --> 00:26:17.180 And it has to be only when he's moving, right? 00:26:17.180 --> 00:26:20.240 If he's standing still, you can have an idle animation. 00:26:20.240 --> 00:26:23.460 Some characters will tap their foot and do stuff like that. 00:26:23.460 --> 00:26:26.870 But let's say for the sake of this example we want him just 00:26:26.870 --> 00:26:28.790 to stand still when he's idle. 00:26:28.790 --> 00:26:31.940 And we want him to have an actual animation when he's moving. 00:26:31.940 --> 00:26:34.640 We need to take care of these two pieces-- 00:26:34.640 --> 00:26:38.310 three pieces if you count the idle animation part. 00:26:38.310 --> 00:26:43.310 So let's go into character2 and take care of the first part, which 00:26:43.310 --> 00:26:45.890 is tracking him. 00:26:45.890 --> 00:26:47.504 So let me go into character2. 00:26:47.504 --> 00:26:49.670 Let's run it first so we can see what it looks like. 00:26:54.610 --> 00:26:58.680 So now the camera is basically affixed to the player. 00:26:58.680 --> 00:27:01.670 In this example, we don't take care of the left edge issue. 00:27:01.670 --> 00:27:03.910 In the distro, that's fixed. 00:27:03.910 --> 00:27:09.210 But we have the basic side scrolling mechanic-- take a character, 00:27:09.210 --> 00:27:11.610 follow him. 00:27:11.610 --> 00:27:13.980 How do you think that we're accomplishing this? 00:27:17.520 --> 00:27:18.020 Yes. 00:27:18.020 --> 00:27:22.250 AUDIENCE: Translate the drawing against the characterX? 00:27:22.250 --> 00:27:24.700 COLTON OGDEN: Yes, exactly. 00:27:24.700 --> 00:27:28.190 And it can't be exactly the characterX though, 00:27:28.190 --> 00:27:31.760 because if it is, then the character is going to be on the left edge, right? 00:27:31.760 --> 00:27:35.290 So we need to offset our x that we translate by. 00:27:35.290 --> 00:27:40.820 We need to basically translate by his x minus half the screen space 00:27:40.820 --> 00:27:42.590 plus half the character width. 00:27:42.590 --> 00:27:45.340 And that will have the effect of translating it but always keeping 00:27:45.340 --> 00:27:48.530 that offset half a screen width away from the player, if that makes sense. 00:27:48.530 --> 00:27:52.450 And so what we're doing is in character-- this is character2, right-- 00:27:52.450 --> 00:27:58.040 character2, we're still doing the same thing we did. 00:27:58.040 --> 00:28:01.070 Actually, that's the wrong file. 00:28:01.070 --> 00:28:05.510 We are modifying characterX here. 00:28:05.510 --> 00:28:07.970 So same thing we did before-- 00:28:07.970 --> 00:28:10.920 multiply the move speed by delta time and either add or subtract it 00:28:10.920 --> 00:28:12.253 if we're pressing left or right. 00:28:12.253 --> 00:28:16.994 But also, here-- reintroducing camera scroll. 00:28:16.994 --> 00:28:20.160 And we're setting it to, like I said, characterX minus VIRTUAL_WIDTH divided 00:28:20.160 --> 00:28:23.450 by 2, half the screen, and then positive offset 00:28:23.450 --> 00:28:27.860 of his width divided by 2 so that he's perfectly right in the center. 00:28:27.860 --> 00:28:30.110 Because remember, characters' coordinates 00:28:30.110 --> 00:28:34.100 are set by their left, not their center. 00:28:34.100 --> 00:28:36.740 And then we just do what we did before. 00:28:36.740 --> 00:28:40.010 We translate the scene based on cameraScroll, 00:28:40.010 --> 00:28:44.720 and we render him at characterX characterY using math.floor 00:28:44.720 --> 00:28:48.430 to prevent him from being at a fractional point in our world space 00:28:48.430 --> 00:28:50.690 and then it being blurry and artifacted. 00:28:50.690 --> 00:28:55.487 And that's sort of it in terms of how we can get tracking over character. 00:28:55.487 --> 00:28:57.320 And if you wanted to track along the y-axis, 00:28:57.320 --> 00:28:58.760 you could do the exact same thing. 00:28:58.760 --> 00:29:02.690 Maintain a cameraScroll x and a cameraScroll y-- 00:29:02.690 --> 00:29:04.940 so keep them separated. 00:29:04.940 --> 00:29:09.260 And then you would just translate here. 00:29:09.260 --> 00:29:11.750 So we're passing in 0, because we don't want 00:29:11.750 --> 00:29:13.430 to track along the y-axis necessarily. 00:29:13.430 --> 00:29:18.140 But all you would need to do is pass in your y cameraScroll. 00:29:18.140 --> 00:29:20.579 And then you could do it based on characterY 00:29:20.579 --> 00:29:22.370 and whether or not they're above the ground 00:29:22.370 --> 00:29:25.310 or past a certain point in the sky. 00:29:25.310 --> 00:29:29.020 So any questions at all as to how the camera tracking is working here? 00:29:31.790 --> 00:29:33.830 All right. 00:29:33.830 --> 00:29:38.332 So we took care of one issue, which was the lack of tracking. 00:29:38.332 --> 00:29:40.790 But there was one other issue, which was he's not animated. 00:29:40.790 --> 00:29:47.330 All he's doing is just moving sort of like M.C. Hammer-- 00:29:47.330 --> 00:29:48.451 or is it M.C. Usher? 00:29:48.451 --> 00:29:48.950 M.C. Hammer? 00:29:48.950 --> 00:29:50.330 I forget. 00:29:50.330 --> 00:29:51.140 He's doing that. 00:29:51.140 --> 00:29:52.220 He's not doing anything. 00:29:52.220 --> 00:29:55.640 We need to actually animate him so that he looks like he has some life to him 00:29:55.640 --> 00:29:57.680 and that you can also differentiate importantly 00:29:57.680 --> 00:29:59.390 between two separate states. 00:29:59.390 --> 00:30:02.630 He can be idle, he's not moving, and he can be moving. 00:30:02.630 --> 00:30:05.450 So we should have some sort of visual feedback 00:30:05.450 --> 00:30:08.960 as to what's currently going on. 00:30:08.960 --> 00:30:14.030 So anybody know how we can go about implementing 00:30:14.030 --> 00:30:16.360 an animation for our character? 00:30:16.360 --> 00:30:18.703 What are the pieces that we'll need? 00:30:18.703 --> 00:30:20.667 AUDIENCE: I guess if he's moving right, then 00:30:20.667 --> 00:30:25.570 call a function and a render that looks through some images? 00:30:25.570 --> 00:30:26.320 COLTON OGDEN: Yes. 00:30:26.320 --> 00:30:29.680 So if he's moving right, then have a function 00:30:29.680 --> 00:30:31.750 that sort of loops through some images. 00:30:31.750 --> 00:30:34.520 That is effectively what we will be doing. 00:30:34.520 --> 00:30:37.680 We have a class called Animation, which I've introduced here. 00:30:37.680 --> 00:30:40.750 And all it basically does is keep track of-- you 00:30:40.750 --> 00:30:44.140 pass it in a table, which has the frames of the sheet 00:30:44.140 --> 00:30:45.760 that you want to animate over. 00:30:45.760 --> 00:30:47.160 So we can just pass in-- 00:30:47.160 --> 00:30:48.660 let's go ahead and take a look here. 00:30:50.700 --> 00:30:53.470 And I referenced the slide earlier, but all of these 00:30:53.470 --> 00:30:57.160 are 1, 2, 3, 4, 5, 6, 7, 8, 9, 10-- however many there are, 00:30:57.160 --> 00:31:01.296 you just pass into the animation. 00:31:01.296 --> 00:31:02.420 Let's say he's on a ladder. 00:31:02.420 --> 00:31:05.920 So let's say this is 1, 2, 3, 4, 5, 6, and 7. 00:31:05.920 --> 00:31:08.560 You say, the frames are going to be 6 and 7, 00:31:08.560 --> 00:31:12.070 so those will just loop left to right, starting back 00:31:12.070 --> 00:31:16.240 at the beginning when it's finished And then you give it an interval. 00:31:16.240 --> 00:31:19.520 So say I want the animation to happen this fast in terms of seconds, 00:31:19.520 --> 00:31:23.000 so I want it that maybe happened every 0.2 seconds. 00:31:23.000 --> 00:31:25.934 And so that will have the effect of every 0.2 seconds, 00:31:25.934 --> 00:31:27.100 it'll keep track of a timer. 00:31:27.100 --> 00:31:30.790 So have we gone over 0.2 seconds? 00:31:30.790 --> 00:31:33.700 Start at 0 and then add delta time to it every time. 00:31:33.700 --> 00:31:37.750 If we have, increment what our current frame of animation is. 00:31:37.750 --> 00:31:42.280 So our current frame is this one, and then 0.2 seconds elapses, 00:31:42.280 --> 00:31:43.480 it's going to be this one. 00:31:43.480 --> 00:31:47.000 And then 0.2 second elapses, and we need to loop back to the beginning. 00:31:47.000 --> 00:31:49.150 So we'll end up using modulus to take care of that 00:31:49.150 --> 00:31:51.070 as we can see in the Animation class. 00:31:54.520 --> 00:31:55.930 Basically, that's all done here. 00:31:55.930 --> 00:32:00.340 So if we have more than one frame of animation, recall it gets a def here. 00:32:00.340 --> 00:32:04.112 So we get frames, we get an interval, get a timer that's initialized to 0, 00:32:04.112 --> 00:32:05.320 and then get a current frame. 00:32:05.320 --> 00:32:09.070 We'll say the current frame is 1. 00:32:09.070 --> 00:32:12.322 And then as long as we have more than one frame, 00:32:12.322 --> 00:32:14.530 there's no point in looping over or trying to animate 00:32:14.530 --> 00:32:16.930 any animation that only has one frame. 00:32:16.930 --> 00:32:20.140 And we can, of course, have animations that only have one frame. 00:32:20.140 --> 00:32:23.500 Idle is only one frame of animation, as we saw here. 00:32:23.500 --> 00:32:24.959 That's only one frame. 00:32:24.959 --> 00:32:27.000 We don't need to do any sort of logic to say, oh, 00:32:27.000 --> 00:32:29.560 what's the next frame, because there's only one frame. 00:32:29.560 --> 00:32:37.330 But if we were to look at character3, we can see two frames there. 00:32:37.330 --> 00:32:39.760 And then that's just one, frame he's idle. 00:32:39.760 --> 00:32:42.860 And when we move left, he moves in that direction. 00:32:42.860 --> 00:32:45.822 Anybody recall how we can get him-- because obviously, 00:32:45.822 --> 00:32:47.530 we saw the spreadsheet just a second ago, 00:32:47.530 --> 00:32:50.290 and there was only one direction that the sprites were facing-- 00:32:50.290 --> 00:32:54.070 how we can get him to look that way, even though there's 00:32:54.070 --> 00:32:57.323 no sprites for him to look that way? 00:32:57.323 --> 00:32:59.170 AUDIENCE: Flip it on the axis. 00:32:59.170 --> 00:33:03.190 COLTON OGDEN: Flip it, so love.graphics.draw. 00:33:03.190 --> 00:33:07.900 Recall you can pass in a negative scale factor on whatever axis you want, 00:33:07.900 --> 00:33:10.912 and that'll have the result of flipping it along that axis. 00:33:10.912 --> 00:33:11.870 That's all we're doing. 00:33:11.870 --> 00:33:14.161 So this is the default frame, so we're just drawing it. 00:33:14.161 --> 00:33:17.300 And then we have to keep a reference to whatever direction he's facing. 00:33:17.300 --> 00:33:19.390 And if his direction is equal to right, we'll 00:33:19.390 --> 00:33:23.300 just draw that frame and then loop and process the animation. 00:33:23.300 --> 00:33:29.380 If he's facing left, draw it, but also perform a negative 1 transformation 00:33:29.380 --> 00:33:30.820 on the x-axis. 00:33:30.820 --> 00:33:33.670 And just like that, we have that working. 00:33:33.670 --> 00:33:34.600 So all we're doing-- 00:33:34.600 --> 00:33:36.070 just keep a timer. 00:33:36.070 --> 00:33:41.050 And then when the timer goes over our interval, just increment the frame. 00:33:41.050 --> 00:33:44.140 And then use modulus to loop back over it-- 00:33:44.140 --> 00:33:45.480 back to starting at 1. 00:33:45.480 --> 00:33:48.691 And that's all done here on this line 28. 00:33:48.691 --> 00:33:50.440 And so you can look in there a little more 00:33:50.440 --> 00:33:53.320 if you want to get a handle on how the math works, 00:33:53.320 --> 00:33:58.300 but it is just a simple sequence of iterating over a collection of frames 00:33:58.300 --> 00:33:59.640 based on a timer. 00:33:59.640 --> 00:34:02.830 And that has the effect-- just like a flip book, as I said earlier-- 00:34:02.830 --> 00:34:06.160 of our character having an animation and having some life. 00:34:06.160 --> 00:34:10.060 So any questions as to how this animation class works? 00:34:10.060 --> 00:34:12.437 AUDIENCE: The render is in the Animate class? 00:34:12.437 --> 00:34:13.270 COLTON OGDEN: So no. 00:34:13.270 --> 00:34:14.894 The render is not in the animate class. 00:34:14.894 --> 00:34:16.780 So the render is-- 00:34:16.780 --> 00:34:20.060 I realize I didn't show any actual main here. 00:34:20.060 --> 00:34:22.830 We have two animations here, which was just the idle one, 00:34:22.830 --> 00:34:24.550 so we're just passing in one frame. 00:34:24.550 --> 00:34:26.794 We're going to give it an interval of 1. 00:34:26.794 --> 00:34:29.710 It's not going to really matter, but just for the sake of consistency, 00:34:29.710 --> 00:34:31.750 we're giving it an interval of 1-- arbitrary. 00:34:31.750 --> 00:34:35.090 And maybe we want to change his animation later. 00:34:35.090 --> 00:34:39.100 So by having an interval here, we won't forget to add one later. 00:34:39.100 --> 00:34:41.409 Moving animation-- recall 10 and 11. 00:34:41.409 --> 00:34:43.130 So it's toward the end of the sheet-- 00:34:43.130 --> 00:34:46.090 the two walking frames. 00:34:46.090 --> 00:34:48.520 Interval here is 0.2 seconds. 00:34:48.520 --> 00:34:51.969 We need a current animation to render him, 00:34:51.969 --> 00:34:55.909 and then we keep a reference to whatever direction he's looking at. 00:34:55.909 --> 00:34:58.570 So if he's looking to the right, we're going 00:34:58.570 --> 00:35:02.282 to reference this in the love.graphics.draw at the bottom. 00:35:02.282 --> 00:35:04.990 And that's what we're going to use to perform the sprite flipping 00:35:04.990 --> 00:35:07.560 along the x-axis. 00:35:07.560 --> 00:35:09.500 Maintain a reference to that. 00:35:09.500 --> 00:35:14.500 And then down here, the part that we actually reference the animation 00:35:14.500 --> 00:35:18.670 is on line 150 if you're looking at character1. 00:35:18.670 --> 00:35:20.870 Or is it character2? 00:35:20.870 --> 00:35:22.410 Sorry, character3. 00:35:22.410 --> 00:35:26.130 If you're looking at line 150 in character3, 00:35:26.130 --> 00:35:29.307 we're using currentAnimation:getCurrentFrame(). 00:35:29.307 --> 00:35:32.640 So the class will actually just tell you whatever the current frame of animation 00:35:32.640 --> 00:35:37.410 is, because it keeps a reference to what frame it is based on the timer 00:35:37.410 --> 00:35:39.287 and how much has elapsed. 00:35:39.287 --> 00:35:43.760 AUDIENCE: So the class is generating a different frame real time 00:35:43.760 --> 00:35:45.019 and plugging it in there. 00:35:45.019 --> 00:35:45.810 COLTON OGDEN: Yeah. 00:35:45.810 --> 00:35:49.420 It's maintaining a reference to whatever the current frame is, 00:35:49.420 --> 00:35:51.990 and it's in the table of frames that it got when you gave it 00:35:51.990 --> 00:35:56.000 the definition up at the top here-- 00:35:56.000 --> 00:35:59.880 lines 51 to 58 where we create the two animations. 00:35:59.880 --> 00:36:03.870 Basically, it maintains a reference to which index in this frame table 00:36:03.870 --> 00:36:04.930 we're at. 00:36:04.930 --> 00:36:10.180 So if 0.2 seconds has elapsed, we start at 1, and then we go to 2. 00:36:10.180 --> 00:36:11.520 And then we'll go back to 1. 00:36:11.520 --> 00:36:15.970 And so it'll just basically return frames, index. 00:36:15.970 --> 00:36:20.720 And frames index 1 is 10, frames index 2 is 11. 00:36:20.720 --> 00:36:25.480 And so the function is getCurrentFrame. 00:36:25.480 --> 00:36:29.730 So characterQuads, currentAnimation, getCurrentFrame. 00:36:29.730 --> 00:36:37.410 And then here, because we're performing an origin transformation-- 00:36:37.410 --> 00:36:43.260 so that's another thing to consider when you're flipping sprites. 00:36:43.260 --> 00:36:45.480 When you flip a sprite, it actually flips along 00:36:45.480 --> 00:36:47.490 whatever its default origin is. 00:36:47.490 --> 00:36:53.130 And the default origin of any sprite is its top left corner here. 00:36:53.130 --> 00:36:56.880 So if you flip something along its x-axis, 00:36:56.880 --> 00:37:00.630 it'll appear here instead of just flipping in place. 00:37:00.630 --> 00:37:03.870 So you actually have to set the origin to its center 00:37:03.870 --> 00:37:07.090 when you do any sort of in place flipping of a sprite. 00:37:07.090 --> 00:37:10.290 So you'll notice in the code when you're looking 00:37:10.290 --> 00:37:16.980 at it that we have plus CHARACTER_WIDTH divided by 2 and plus CHARACTER_HEIGHT 00:37:16.980 --> 00:37:21.000 divided by 2 on these two here. 00:37:21.000 --> 00:37:24.240 So we shift where it gets drawn, and then we shift its origin 00:37:24.240 --> 00:37:26.610 offsets which are here on line 160. 00:37:26.610 --> 00:37:28.920 So if you look at love.graphics.draw, you'll 00:37:28.920 --> 00:37:31.770 see it has a lot of optional arguments. 00:37:31.770 --> 00:37:36.960 And these two at the bottom are the origin offset arguments. 00:37:36.960 --> 00:37:39.840 And so these only really come into play when 00:37:39.840 --> 00:37:44.970 you do some kind of flipping of a sprite on an axis 00:37:44.970 --> 00:37:50.560 and you want graphical consistency not to have it flip one way or the other. 00:37:50.560 --> 00:37:53.381 Sometimes that's the effect you're looking for, but in this case, 00:37:53.381 --> 00:37:53.880 it's not. 00:37:53.880 --> 00:37:56.360 We want him to literally stay in the exact same place. 00:37:56.360 --> 00:37:58.334 So to flip a sprite in the exact same place, 00:37:58.334 --> 00:38:00.750 you need a set its origin to its center, not its top left. 00:38:00.750 --> 00:38:04.090 Does that makes sense? 00:38:04.090 --> 00:38:05.130 OK. 00:38:05.130 --> 00:38:08.940 And also here, 0 is the rotation here. 00:38:08.940 --> 00:38:11.140 So it's sort of required if you're going to add 00:38:11.140 --> 00:38:13.870 this many arguments to the function. 00:38:13.870 --> 00:38:18.540 But we're testing if direction is equal to left, we want to flip by negative 1 00:38:18.540 --> 00:38:22.920 on the x, else just give it 1. 00:38:22.920 --> 00:38:26.880 So 1 just means default transformation, so no flipping. 00:38:26.880 --> 00:38:30.360 And then we don't flip on the y at all, so that will always be 1. 00:38:30.360 --> 00:38:34.950 And so that's in a nutshell how you can get your character to animate and also 00:38:34.950 --> 00:38:37.560 stay in place when you animate it. 00:38:37.560 --> 00:38:43.810 So any questions as to how animations or the origin offsets or any of that work? 00:38:47.660 --> 00:38:48.230 OK. 00:38:48.230 --> 00:38:50.500 So we did talk about animations. 00:38:50.500 --> 00:38:53.450 The last thing we'll talk about for the character is jumping. 00:38:53.450 --> 00:38:58.240 So if you recall from Flappy Bird, how can we get our character to jump? 00:38:58.240 --> 00:39:02.300 What are some of the pieces we need? 00:39:02.300 --> 00:39:07.050 AUDIENCE: Key press, and then the y goes up. 00:39:07.050 --> 00:39:08.490 And then we have to have gravity. 00:39:08.490 --> 00:39:09.360 COLTON OGDEN: Yep. 00:39:09.360 --> 00:39:14.140 So key press is one thing we need, so check for space 00:39:14.140 --> 00:39:16.020 is going to be the default key. 00:39:16.020 --> 00:39:19.240 y goes up, and then check for gravity. 00:39:19.240 --> 00:39:22.860 So not only do we need y, but we also need delta y. 00:39:22.860 --> 00:39:29.340 We need velocity, because gravity is a transformation on velocity, not 00:39:29.340 --> 00:39:31.780 strictly on position. 00:39:31.780 --> 00:39:39.540 So if we go back to character4, this is sort 00:39:39.540 --> 00:39:41.970 of a hackish way of implementing gravity, 00:39:41.970 --> 00:39:44.700 because we haven't actually incorporated tile collisions. 00:39:44.700 --> 00:39:47.830 And I'll defer most of the implementation for that 00:39:47.830 --> 00:39:51.060 as to the distro, and I'll go over with you guys. 00:39:51.060 --> 00:39:53.700 But right now, we have the exact same thing we had before, 00:39:53.700 --> 00:39:55.460 where we have tile scrolling. 00:39:55.460 --> 00:39:58.500 But if I press space bar, I go up, and then he comes down. 00:39:58.500 --> 00:40:01.300 And notice that he has an animation as well. 00:40:01.300 --> 00:40:02.410 He has a different frame. 00:40:02.410 --> 00:40:05.469 So if he's jumping, he's got a little jump frame. 00:40:05.469 --> 00:40:07.260 So that means now we have three animations. 00:40:07.260 --> 00:40:09.834 We have an idle animation, we have a moving animation, 00:40:09.834 --> 00:40:11.250 and then we have a jump animation. 00:40:11.250 --> 00:40:14.250 So effectively, we have three states as well-- 00:40:14.250 --> 00:40:18.090 idle state, moving state, and jumping state. 00:40:18.090 --> 00:40:20.220 Four states, actually. 00:40:20.220 --> 00:40:23.490 And also, I noticed a slight bug here where if you're still in the air, 00:40:23.490 --> 00:40:25.307 his frame doesn't change. 00:40:25.307 --> 00:40:27.390 So it actually probably should stay to that frame, 00:40:27.390 --> 00:40:28.830 even if he's standing still. 00:40:28.830 --> 00:40:30.700 But I guess it doesn't matter too much. 00:40:30.700 --> 00:40:34.050 We also interpret it as a feature. 00:40:34.050 --> 00:40:40.520 But he's got a couple of states when he's in the air. 00:40:40.520 --> 00:40:42.480 There should be two states here. 00:40:42.480 --> 00:40:44.730 One is jumping state, and one is falling state. 00:40:44.730 --> 00:40:48.540 And do we know why the two being different is an important thing? 00:40:52.780 --> 00:40:56.070 So if we think about Super Mario Bros. 00:40:56.070 --> 00:41:00.970 and we think about the differences between jumping and falling, 00:41:00.970 --> 00:41:04.260 what are some of the things that change based on whether Mario is jumping 00:41:04.260 --> 00:41:07.080 or whether he's falling? 00:41:07.080 --> 00:41:10.520 How does he interact differently with the environment, I should say? 00:41:15.050 --> 00:41:21.480 So if unfamiliar, Mario-- when he jumps, he can actually hit blocks. 00:41:21.480 --> 00:41:23.570 So if he's below a block and he hits a block that 00:41:23.570 --> 00:41:27.120 has some sort of behavior in it, it will trigger whatever is in that block, 00:41:27.120 --> 00:41:29.960 whether it's a coin or whether it's to destroy the block. 00:41:29.960 --> 00:41:34.790 And if he's falling, recall if he lands on top of an enemy like a goomba, 00:41:34.790 --> 00:41:36.450 he'll destroy the enemy. 00:41:36.450 --> 00:41:39.290 And so we need to distinguish between these two states. 00:41:39.290 --> 00:41:43.310 Because when he's jumping, he's not able to-- 00:41:43.310 --> 00:41:46.730 when he's actually going up, he can't attack the enemy. 00:41:46.730 --> 00:41:49.580 And likewise, when he's falling down, he can't destroy the block. 00:41:49.580 --> 00:41:52.550 So even though he's jumping up in the air 00:41:52.550 --> 00:41:55.460 and the gravity is applying a transformation 00:41:55.460 --> 00:42:00.590 and it all looks like one state, there's actually two important changes 00:42:00.590 --> 00:42:02.250 in his state that are relevant. 00:42:02.250 --> 00:42:04.740 And that's something that we'll need to pay attention to, 00:42:04.740 --> 00:42:05.698 and it's in the distro. 00:42:05.698 --> 00:42:09.920 He has a falling state and a jumping state. 00:42:09.920 --> 00:42:13.160 Even though they share the same animation, 00:42:13.160 --> 00:42:15.690 they have different behavior. 00:42:15.690 --> 00:42:24.180 So let's go ahead and look at the character4 distro here. 00:42:24.180 --> 00:42:28.130 So what I've done here is I've added a delta y for the character. 00:42:28.130 --> 00:42:31.340 So just like in Flappy Bird when we press space 00:42:31.340 --> 00:42:34.710 and we made our delta y go up to negative 50-- 00:42:34.710 --> 00:42:37.370 so instantly shot up pretty high, because that was getting 00:42:37.370 --> 00:42:38.870 applied every frame. 00:42:38.870 --> 00:42:41.150 Same thing here. 00:42:41.150 --> 00:42:45.560 Once we press space, we're going to change delta y to negative 50 00:42:45.560 --> 00:42:53.100 if we go down to right here. 00:42:53.100 --> 00:42:56.990 So if the key is equal to space, I have it in the love.keyPressed function. 00:42:56.990 --> 00:42:59.810 Since we're doing all this in main.lua just for illustration, 00:42:59.810 --> 00:43:02.890 things are a little simple. 00:43:02.890 --> 00:43:05.720 If key is equal to space and his delta y is 00:43:05.720 --> 00:43:07.670 equal to 0, what would happen if we didn't 00:43:07.670 --> 00:43:09.775 check to see if delta y was equal to 0? 00:43:09.775 --> 00:43:11.475 AUDIENCE: We double jump in the air. 00:43:11.475 --> 00:43:12.225 COLTON OGDEN: Yep. 00:43:12.225 --> 00:43:15.470 We'd be able to jump infinitely, so we have to do a check for that. 00:43:15.470 --> 00:43:17.330 We set his dy to JUMP_VELOCITY. 00:43:17.330 --> 00:43:26.660 JUMP_VELOCITY is a constant up top on line 29, which is negative 200. 00:43:26.660 --> 00:43:29.060 And then gravity is equal to 7. 00:43:29.060 --> 00:43:34.220 And so what we do is we set it to negative 200-- his delta y-- 00:43:34.220 --> 00:43:35.600 as soon as he jumps. 00:43:35.600 --> 00:43:40.850 And then every frame down in update, we basically 00:43:40.850 --> 00:43:43.580 increment his delta y by gravity. 00:43:43.580 --> 00:43:50.634 And then we increment his y by delta y times delta time. 00:43:50.634 --> 00:43:52.800 And so it'll have the effect of when he's in the air 00:43:52.800 --> 00:43:57.950 and he's got a negative velocity, it'll actually 00:43:57.950 --> 00:44:00.530 start becoming positive and positive until it is positive, 00:44:00.530 --> 00:44:03.350 and then he falls back to the ground. 00:44:03.350 --> 00:44:05.810 And then the hack that I was referring to earlier-- 00:44:05.810 --> 00:44:10.490 since we don't have collision detection implemented in this example yet-- 00:44:10.490 --> 00:44:13.310 is we're just basically checking to see whether he has 00:44:13.310 --> 00:44:17.180 gone below what we set the map's floor. 00:44:17.180 --> 00:44:22.280 And if he has, then set his position, first of all, to be above that tile 00:44:22.280 --> 00:44:23.780 here on line 133. 00:44:23.780 --> 00:44:25.722 And then set his delta y equal to 0. 00:44:25.722 --> 00:44:27.680 And that will allow us then to hit space again, 00:44:27.680 --> 00:44:29.462 because his delta y will be equal to 0. 00:44:29.462 --> 00:44:32.696 AUDIENCE: So I didn't see [INAUDIBLE] on it. 00:44:32.696 --> 00:44:34.550 Looks like there's always gravity, then. 00:44:34.550 --> 00:44:35.810 COLTON OGDEN: There is always gravity, something 00:44:35.810 --> 00:44:37.570 I realized shortly before lecture. 00:44:37.570 --> 00:44:44.540 But all you would really have to do is, I think, if character dy-- 00:44:44.540 --> 00:44:45.230 Yeah. 00:44:45.230 --> 00:44:48.271 You could easily take that out of there-- just an if statement around it. 00:44:48.271 --> 00:44:50.272 AUDIENCE: It's just a waste of resources, right? 00:44:50.272 --> 00:44:51.104 COLTON OGDEN: It is. 00:44:51.104 --> 00:44:53.300 I mean, it's not expensive, because all you're doing 00:44:53.300 --> 00:44:57.075 is incrementing a variable by a certain amount. 00:44:57.075 --> 00:44:59.700 If anything, if you're introducing an if condition every frame, 00:44:59.700 --> 00:45:01.910 which is probably the same if not actually more. 00:45:01.910 --> 00:45:06.030 I think a branch is more CPU than just an assignment. 00:45:06.030 --> 00:45:09.004 I'm not entirely sure about that. 00:45:09.004 --> 00:45:09.940 AUDIENCE: Interesting. 00:45:09.940 --> 00:45:10.731 COLTON OGDEN: Yeah. 00:45:10.731 --> 00:45:13.860 In this case, it doesn't really have any side effects. 00:45:13.860 --> 00:45:16.410 But it's a good thing to notice. 00:45:16.410 --> 00:45:20.265 But now notice that we can just sort of walk along the floor here, 00:45:20.265 --> 00:45:21.890 because there's no collision detection. 00:45:21.890 --> 00:45:27.320 We'll talk about how we implement a collision detection soon. 00:45:27.320 --> 00:45:31.430 So one thing that we'll start talking on-- and we'll take a break fairly 00:45:31.430 --> 00:45:32.660 soon-- 00:45:32.660 --> 00:45:34.250 is procedural level generation. 00:45:34.250 --> 00:45:36.350 So I am a big fan of procedural level generation, 00:45:36.350 --> 00:45:39.470 and platformer levels are actually fairly easy-- 00:45:39.470 --> 00:45:40.790 at least in a simple sense-- 00:45:40.790 --> 00:45:43.170 to procedurally generate. 00:45:43.170 --> 00:45:47.060 And so like with match three, all we basically 00:45:47.060 --> 00:45:48.890 did was just loop through our grid and just 00:45:48.890 --> 00:45:52.580 say, oh, get a random color and a random variety. 00:45:52.580 --> 00:45:56.306 And then with the assignment, it was a little bit more complicated, 00:45:56.306 --> 00:45:59.180 where you actually had to check to see whether you were on level one. 00:45:59.180 --> 00:46:01.040 And then if you weren't, then your variety 00:46:01.040 --> 00:46:05.422 should be maybe a certain amount depending on how far along you've 00:46:05.422 --> 00:46:06.380 progressed in the game. 00:46:09.710 --> 00:46:13.970 With a platformer level, we have to think 00:46:13.970 --> 00:46:21.800 about how we can take that grid of tile IDs and think about it mathematically. 00:46:21.800 --> 00:46:26.870 How can we get the results of a level, but make it different every time-- 00:46:26.870 --> 00:46:30.110 introduce some variation, right? 00:46:30.110 --> 00:46:33.710 And so the solution that I found that makes the most sense 00:46:33.710 --> 00:46:37.500 is going column by column. 00:46:37.500 --> 00:46:41.880 So here, we just have a bunch of-- this is just 00:46:41.880 --> 00:46:45.680 a very simple perfect screenshot to illustrate a very simple way 00:46:45.680 --> 00:46:47.030 of generating the level. 00:46:47.030 --> 00:46:53.180 But recall, if we just think about these tiles here-- these empty spaces-- 00:46:53.180 --> 00:46:59.720 being a 0 and these being a 1, it's sort of almost like binary in this case. 00:46:59.720 --> 00:47:05.630 We could just fill the entire thing with 0 first, just assume empty space. 00:47:05.630 --> 00:47:10.040 And then we could just column by column go down and just have a chance 00:47:10.040 --> 00:47:10.730 every column. 00:47:10.730 --> 00:47:12.740 OK, do I want to generate a ground here? 00:47:12.740 --> 00:47:15.830 If I do, start at the ground level and then just 00:47:15.830 --> 00:47:18.470 generate earth tiles all the way down. 00:47:18.470 --> 00:47:22.850 And then go to the next x position, do the same thing, do the same thing. 00:47:22.850 --> 00:47:26.420 And then maybe every column of the world that you're generating, 00:47:26.420 --> 00:47:29.540 you also have a chance to generate a pillar like this. 00:47:29.540 --> 00:47:35.300 So if generate pillar is true, then I want to spawn-- 00:47:35.300 --> 00:47:38.720 instead of starting the ground here, I want to start it here. 00:47:38.720 --> 00:47:41.020 And then maybe you have a flag that says, 00:47:41.020 --> 00:47:43.140 OK, not only do I want to generate pillars, 00:47:43.140 --> 00:47:45.590 I also want to generate chasms-- 00:47:45.590 --> 00:47:47.874 just empty space, obstacles for the player. 00:47:47.874 --> 00:47:50.290 Because if he falls down-- it goes below the world space-- 00:47:50.290 --> 00:47:51.870 it should be game over. 00:47:51.870 --> 00:47:55.400 So in that case, you just say if generate chasm-- 00:47:55.400 --> 00:47:59.270 make math.random 10 or whatever it is-- 00:47:59.270 --> 00:48:01.160 then just go to the next x. 00:48:01.160 --> 00:48:02.340 Don't even do anything. 00:48:02.340 --> 00:48:04.670 And that will have the result of generating a chasm. 00:48:04.670 --> 00:48:07.250 And so little piece by piece-- 00:48:07.250 --> 00:48:10.010 doing small things like that has the net effect 00:48:10.010 --> 00:48:14.810 of generating a lot of visually interesting, dynamic, 00:48:14.810 --> 00:48:15.691 and random levels. 00:48:15.691 --> 00:48:16.940 You never know what to expect. 00:48:16.940 --> 00:48:19.290 And this is a very basic example. 00:48:19.290 --> 00:48:21.780 You could go infinitely far with it. 00:48:21.780 --> 00:48:25.520 However many ideas you have in terms of how 00:48:25.520 --> 00:48:28.610 to create obstacles and interesting levels and scenery for the player-- 00:48:28.610 --> 00:48:30.758 you could absolutely implement that. 00:48:30.758 --> 00:48:35.159 AUDIENCE: How do you handle if there's a platform to jump on? 00:48:35.159 --> 00:48:37.239 You have to have that consistency. 00:48:37.239 --> 00:48:38.030 COLTON OGDEN: Yeah. 00:48:38.030 --> 00:48:41.750 So if it's a platform, it depends on how you want to implement platforms. 00:48:45.090 --> 00:48:47.870 And actually, I did a seminar on Super Mario Bros., 00:48:47.870 --> 00:48:50.840 and we did platforms as tiles. 00:48:50.840 --> 00:48:53.980 In this case, we'll have blocks that are actually 00:48:53.980 --> 00:48:55.940 what we've denoted as game objects-- which 00:48:55.940 --> 00:48:58.010 are a little bit different than tiles. 00:48:58.010 --> 00:49:00.950 Because they can have arbitrary sizes, and they don't necessarily 00:49:00.950 --> 00:49:02.990 have to be affixed to the world grid. 00:49:02.990 --> 00:49:08.960 But if you were to treat a platform that was, let's say, 00:49:08.960 --> 00:49:14.630 two tiles wide as tiles, all you would do 00:49:14.630 --> 00:49:19.250 is just basically have a flag up here that's like, generate platform 00:49:19.250 --> 00:49:20.987 equals true or whatever-- 00:49:20.987 --> 00:49:22.570 AUDIENCE: And then turn it off after-- 00:49:22.570 --> 00:49:24.170 COLTON OGDEN: Turn it off after however many iterations. 00:49:24.170 --> 00:49:25.610 You also need the size of it. 00:49:25.610 --> 00:49:29.834 You'll need a flag that's like platform width equals however many, 00:49:29.834 --> 00:49:31.250 and so you'll just keep a counter. 00:49:31.250 --> 00:49:35.960 It's like current platform tile equals 1, 2, 3. 00:49:35.960 --> 00:49:39.260 And if it's equal to width, then you don't generate it any more. 00:49:39.260 --> 00:49:43.560 And that has the effect of potentially colliding with pillars 00:49:43.560 --> 00:49:44.810 if you don't account for that. 00:49:44.810 --> 00:49:49.760 So you can also in your logic say, if I'm generating a platform right now, 00:49:49.760 --> 00:49:50.930 don't generate a pillar. 00:49:50.930 --> 00:49:52.971 But you could generate a chasm, because the chasm 00:49:52.971 --> 00:49:56.330 doesn't interfere with your platform. 00:50:01.160 --> 00:50:04.170 If you don't have platforms as tiles-- if they're different objects-- 00:50:04.170 --> 00:50:07.370 then you don't have to do it during the actual world generation phase. 00:50:07.370 --> 00:50:08.660 You can just test. 00:50:08.660 --> 00:50:12.020 You can just create a game object that's a platform. 00:50:12.020 --> 00:50:14.417 Depending on how complicated your algorithm is, 00:50:14.417 --> 00:50:17.250 maybe make sure that it's not next to a pillar when you generate it. 00:50:17.250 --> 00:50:20.900 And you could just do that by getting the tile here and then looking 00:50:20.900 --> 00:50:22.640 at the next four tiles-- 00:50:22.640 --> 00:50:23.810 something like that. 00:50:23.810 --> 00:50:29.690 We don't do platforms in this example, but it's something 00:50:29.690 --> 00:50:31.910 that you could pretty easily do with tiles. 00:50:31.910 --> 00:50:36.470 And slightly more difficult but also still fairly easy to 00:50:36.470 --> 00:50:40.460 do with game objects, which is included in the distro 00:50:40.460 --> 00:50:42.680 and which we'll touch on in a little bit. 00:50:45.802 --> 00:50:49.450 Let's see, we're at level-- 00:50:49.450 --> 00:50:51.950 oh, another couple of things that I wanted to show before we 00:50:51.950 --> 00:50:55.730 actually start getting into the code for how to generate levels. 00:50:55.730 --> 00:51:00.275 This is the sprite sheet for this whole project, which is a really cool sprite 00:51:00.275 --> 00:51:02.460 sheet that I found online. 00:51:02.460 --> 00:51:04.944 It's in the spirit of platformers like Mario, 00:51:04.944 --> 00:51:07.110 and it's got a nice little mockup here on the right. 00:51:07.110 --> 00:51:08.930 So I encourage you to take a look at that 00:51:08.930 --> 00:51:12.530 and just maybe get some inspiration and see all the different cool stuff. 00:51:12.530 --> 00:51:14.896 Tinker around with it if you want to. 00:51:14.896 --> 00:51:17.770 But as you can see here, there's a couple of pretty prominent things. 00:51:17.770 --> 00:51:19.580 We have a ton of tiles. 00:51:19.580 --> 00:51:20.830 These are all tiles here-- 00:51:20.830 --> 00:51:22.720 different tiles and variations. 00:51:22.720 --> 00:51:25.240 And then we have a ton of these toppers here. 00:51:25.240 --> 00:51:32.500 And so what really helps this whole demonstration 00:51:32.500 --> 00:51:35.140 of generating these levels is the fact that we have 00:51:35.140 --> 00:51:37.750 so much visual content to work with. 00:51:37.750 --> 00:51:40.930 And so here, again, are the tiles. 00:51:40.930 --> 00:51:42.130 Here are the toppers. 00:51:42.130 --> 00:51:46.360 And then when you take the two together and then you also 00:51:46.360 --> 00:51:49.840 have these random backgrounds-- 00:51:49.840 --> 00:51:52.300 these are toppers here, the top of the tiles here. 00:51:52.300 --> 00:51:57.340 It's incredibly easy to just have a sheer abundance of visual variety 00:51:57.340 --> 00:52:00.240 and interesting things in your game levels without even-- 00:52:00.240 --> 00:52:02.230 and the algorithms here are very simple. 00:52:02.230 --> 00:52:06.160 All we're doing is just checking to generate pillars and columns. 00:52:06.160 --> 00:52:07.420 I know. 00:52:07.420 --> 00:52:13.060 I thought it was really cool and helps illustrate the importance and power 00:52:13.060 --> 00:52:15.934 of this whole procedural approach to creating the levels for this. 00:52:15.934 --> 00:52:18.850 And there's actually not that many games that take advantage, I think, 00:52:18.850 --> 00:52:21.720 of procedural level generation in the platformer genre. 00:52:21.720 --> 00:52:24.184 Plenty of games like Minecraft and Terraria-- 00:52:24.184 --> 00:52:26.600 Terraria is a great platformer that is an example of that. 00:52:26.600 --> 00:52:29.342 But I don't think I've seen a really good Super Mario Bros. 00:52:29.342 --> 00:52:30.800 game that does something like that. 00:52:33.620 --> 00:52:34.120 Let's see. 00:52:34.120 --> 00:52:34.580 What time is it? 00:52:34.580 --> 00:52:35.230 6:23. 00:52:35.230 --> 00:52:37.210 Let's take a five minute. 00:52:37.210 --> 00:52:39.100 And then as soon as we get back from that, 00:52:39.100 --> 00:52:43.150 we'll start going into how we actually can implement the procedural level 00:52:43.150 --> 00:52:45.440 generation in more detail. 00:52:45.440 --> 00:52:46.920 All right, welcome back. 00:52:46.920 --> 00:52:47.890 This is lecture four. 00:52:47.890 --> 00:52:49.681 And before we took a break, we were talking 00:52:49.681 --> 00:52:54.040 about procedural level generation in the context of platformer levels. 00:52:54.040 --> 00:53:00.940 So recall, here are just a few examples that I took pretty quickly of my code. 00:53:00.940 --> 00:53:04.450 And you can see they have different backgrounds, different tiles. 00:53:04.450 --> 00:53:07.420 Sometimes we have chasms, sometimes we have pillars. 00:53:07.420 --> 00:53:12.610 We'll be talking about a few ways to do the tile version of that, 00:53:12.610 --> 00:53:14.120 because there's two levels here. 00:53:14.120 --> 00:53:18.620 In the distro, we'll see there are also things like bushes, for example. 00:53:18.620 --> 00:53:21.460 We can see in the top middle there the purple-- 00:53:21.460 --> 00:53:24.280 well, I guess those little purple cacti. 00:53:24.280 --> 00:53:28.960 And the one right below that, there is a pillar with a yellow fern on it. 00:53:28.960 --> 00:53:31.780 Those are separate objects from the tiles-- 00:53:31.780 --> 00:53:32.860 game objects. 00:53:32.860 --> 00:53:38.170 But the actual tiles themselves we'll dig in here a little bit as to how 00:53:38.170 --> 00:53:39.710 to get those generated. 00:53:39.710 --> 00:53:44.615 So the first thing we want to look at, level0, is just some flat levels-- so 00:53:44.615 --> 00:53:46.240 just basically what we've already done. 00:53:46.240 --> 00:53:50.980 So I'm going to go ahead and go into level0. 00:53:54.230 --> 00:53:59.680 And then if we see here, we have a simple flat level, 00:53:59.680 --> 00:54:01.040 just like we did before. 00:54:01.040 --> 00:54:02.690 Now the tiles are different. 00:54:02.690 --> 00:54:07.360 And if I press R, they're randomly generating every time. 00:54:07.360 --> 00:54:12.782 So you can get a sense of just how visually diverse this generation looks. 00:54:12.782 --> 00:54:14.740 Oh, I think that might have been a bug earlier. 00:54:14.740 --> 00:54:16.090 I'm not sure. 00:54:16.090 --> 00:54:17.440 Haven't seen that yet. 00:54:17.440 --> 00:54:19.480 But we can see here, I'm pressing R. 00:54:19.480 --> 00:54:26.020 All I'm doing is taking the array of tiles that we have, 00:54:26.020 --> 00:54:30.430 and I'm assigning it a tile set and a topper set 00:54:30.430 --> 00:54:35.830 in the case of the scope of this generation. 00:54:35.830 --> 00:54:39.460 So recall that the topper is just the top layer sprite, 00:54:39.460 --> 00:54:46.180 and the tile set is the tiles underneath. 00:54:46.180 --> 00:54:51.100 Anybody want to just suggest how I'm rendering the topper versus the tiles 00:54:51.100 --> 00:54:53.856 and what's going on there? 00:54:53.856 --> 00:54:58.100 AUDIENCE: You're just pulling it from part of the sheet, right? 00:54:58.100 --> 00:54:58.850 COLTON OGDEN: Yes. 00:54:58.850 --> 00:55:01.282 Yeah, in a nutshell, I'm just pulling the toppers 00:55:01.282 --> 00:55:02.740 from a different part of the sheet. 00:55:02.740 --> 00:55:05.510 Any idea how I'm storing information-- what's being stored here 00:55:05.510 --> 00:55:08.195 to get it to render like this? 00:55:08.195 --> 00:55:12.155 AUDIENCE: Maybe you just need to store the position of the topper 00:55:12.155 --> 00:55:15.620 and know that everything else is below that. 00:55:15.620 --> 00:55:16.460 COLTON OGDEN: Yes. 00:55:16.460 --> 00:55:18.410 So you could store the position of the topper 00:55:18.410 --> 00:55:21.200 and know that everything else is below that. 00:55:21.200 --> 00:55:24.360 That would work for a flat level. 00:55:24.360 --> 00:55:27.502 I don't think that would be reliable for a level that has pillars on it, 00:55:27.502 --> 00:55:29.960 because the pillars are a higher elevation than the ground. 00:55:29.960 --> 00:55:32.001 And then there's also chasms and stuff like that. 00:55:35.090 --> 00:55:37.115 So what's going on here actually is we're 00:55:37.115 --> 00:55:41.850 storing a flag in the tile that says whether or not it has a topper on it. 00:55:41.850 --> 00:55:45.320 And if it has a topper, then we render not only the tile, 00:55:45.320 --> 00:55:49.580 but as soon as we render the tile, we also render the topper. 00:55:49.580 --> 00:55:54.650 And I won't go too deep into the code here. 00:55:54.650 --> 00:55:58.640 But what we're doing to get all these different tile sets and topper sets 00:55:58.640 --> 00:56:04.570 too is we have to take all of these tile sets-- these collections of tiles-- 00:56:04.570 --> 00:56:06.050 and divide them up, right? 00:56:06.050 --> 00:56:09.390 We have to know that if we want to render the entire level in tile 00:56:09.390 --> 00:56:15.450 set one, then we should basically take this into its own sheet-- 00:56:15.450 --> 00:56:19.190 its own table-- this into its own table, this into its own table, going 00:56:19.190 --> 00:56:21.560 left to right actually. 00:56:21.560 --> 00:56:26.370 And we have basically four way nested loop. 00:56:26.370 --> 00:56:31.460 So we go every set on the x by every set on the y. 00:56:31.460 --> 00:56:36.380 And then within each of those, we want to look for every tile along the x 00:56:36.380 --> 00:56:40.070 and every tile along the y therein and split up 00:56:40.070 --> 00:56:45.020 the tile sets so that we can index into the individual quads. 00:56:45.020 --> 00:56:49.430 So in the actual code, I won't go too deeply into it here. 00:56:49.430 --> 00:56:53.330 But I'll show you where it is if you're curious to look into how we do that. 00:56:53.330 --> 00:56:58.790 It's in Mario in Source, util.lua, which is recall where we before 00:56:58.790 --> 00:57:02.930 stored our generateQuads function, which does a simple split on a tile 00:57:02.930 --> 00:57:08.420 sheet along its x and y based on whatever width and height you pass in. 00:57:08.420 --> 00:57:14.210 We have in here also a generateTileSets function, which takes in the quads 00:57:14.210 --> 00:57:16.790 from a generateQuads table. 00:57:16.790 --> 00:57:22.520 So we first generate quads on all of this or all of this. 00:57:22.520 --> 00:57:26.840 So we have every single frame of this divided by 16, which is-- 00:57:26.840 --> 00:57:28.130 I don't know how many that is. 00:57:28.130 --> 00:57:33.990 6 by 5 times 10 by 5, 10 by 4-- 00:57:33.990 --> 00:57:38.510 that many quads, so thousands of quads, I think, if not hundreds. 00:57:38.510 --> 00:57:41.930 This I'm pretty sure is thousands of quads. 00:57:41.930 --> 00:57:45.890 And then we take that and then divide it using 00:57:45.890 --> 00:57:49.790 the number of sets along the x-axis, sets on the y-axis, 00:57:49.790 --> 00:57:53.480 and then the size of each tile set along the x and size along the y. 00:57:53.480 --> 00:57:59.801 We basically divide it using a four way nested loop here. 00:57:59.801 --> 00:58:01.250 We basically just divide it up. 00:58:01.250 --> 00:58:03.980 And then instead of doing a generateQuads 00:58:03.980 --> 00:58:09.950 along the entirety of the picture, we just 00:58:09.950 --> 00:58:12.830 basically do a 2D slice of that quad table we get back 00:58:12.830 --> 00:58:14.930 from the first generateQuads call. 00:58:14.930 --> 00:58:18.229 So I encourage you to look in here and experiment with that. 00:58:18.229 --> 00:58:21.020 You don't need to necessarily know how it works for the assignment. 00:58:21.020 --> 00:58:25.400 But that's how we can basically take a giant sheet like this 00:58:25.400 --> 00:58:28.370 and easily integrate it into our code. 00:58:28.370 --> 00:58:32.090 We can just swap in and out whatever active tile 00:58:32.090 --> 00:58:34.520 sheet we want to work with, assuming that everything 00:58:34.520 --> 00:58:38.510 is cleanly laid out like this, which is on the part of you or your artist. 00:58:38.510 --> 00:58:44.000 You want to make sure that everything is conducive to programmatic organization. 00:58:44.000 --> 00:58:46.830 Had things been scattered around in a very awkward way-- 00:58:46.830 --> 00:58:50.300 maybe things were zig zagged or there were weird spaces or something like 00:58:50.300 --> 00:58:50.900 that-- 00:58:50.900 --> 00:58:53.630 we wouldn't be able to do something as clean as what we did here 00:58:53.630 --> 00:58:57.620 in util.lua with just 63 minus 20 lines of code 00:58:57.620 --> 00:59:01.164 by getting each individual tile set. 00:59:01.164 --> 00:59:03.080 So that's an important consideration if you're 00:59:03.080 --> 00:59:06.260 looking at creating assets for your project 00:59:06.260 --> 00:59:11.601 and you want to do some programmatic hot swapping of your tile sets. 00:59:11.601 --> 00:59:13.100 Let's make sure we're in the right-- 00:59:13.100 --> 00:59:15.980 we're not in the right example here, so we're 00:59:15.980 --> 00:59:23.210 going to go into level0 into main. 00:59:23.210 --> 00:59:27.380 And we have constants now for all of our tile sets and what the height is 00:59:27.380 --> 00:59:28.865 and how many they are wide by tall. 00:59:32.330 --> 00:59:33.380 We do it here. 00:59:33.380 --> 00:59:36.530 We get our regular quads from our tiles and toppers, 00:59:36.530 --> 00:59:39.730 so these are just literally every single tile within that big tile 00:59:39.730 --> 00:59:40.810 sheet put in one table. 00:59:40.810 --> 00:59:42.560 And then we just divided up into tile sets 00:59:42.560 --> 00:59:45.860 and topper sets here with generateTileSets function. 00:59:45.860 --> 00:59:50.160 And then we get a random tile set and a random topper set here-- 00:59:50.160 --> 00:59:54.260 math.random, number of tile sets, number of topper sets. 00:59:54.260 --> 01:00:00.480 And then at the very bottom also, we have a generateLevel function-- 01:00:00.480 --> 01:00:03.846 223-- which is going to be built upon in the next two examples. 01:00:03.846 --> 01:00:05.720 Level0 is just a flat level, so it's actually 01:00:05.720 --> 01:00:11.960 exactly what we saw before, which was just if y is less than 7, 01:00:11.960 --> 01:00:14.770 ID should be equal to sky or ground. 01:00:14.770 --> 01:00:17.067 And then this part is actually what I was 01:00:17.067 --> 01:00:19.150 alluding to before with the topper, because recall 01:00:19.150 --> 01:00:23.690 we need to store a flag in a tile to render a topper or not. 01:00:23.690 --> 01:00:28.540 And it should be whatever the top tile is in the level on the ground. 01:00:28.540 --> 01:00:31.960 In this very simple flat thing, we can always 01:00:31.960 --> 01:00:34.060 assume it'll be the same y level. 01:00:34.060 --> 01:00:36.580 In this case, if it's equal to 7, then topper 01:00:36.580 --> 01:00:38.500 should be true, otherwise, false. 01:00:38.500 --> 01:00:43.600 So every tile along y 7 is going to have topper equals true. 01:00:43.600 --> 01:00:46.420 And this comes into play up here. 01:00:46.420 --> 01:00:55.090 If we do love.graphics.draw(tilesheet), we have not only just tile.ID as we did 01:00:55.090 --> 01:01:01.150 before, but we have tile sets indexed into tileset now. 01:01:01.150 --> 01:01:03.760 So remember, tileset got a random value between 1 01:01:03.760 --> 01:01:08.930 and however many tile sets we had that we spliced out of our massive tile 01:01:08.930 --> 01:01:09.700 sheet. 01:01:09.700 --> 01:01:14.650 Now, we just index into that, and then we index into tile.ID. 01:01:14.650 --> 01:01:21.070 And tile.ID will then be whatever our ID is but relative to that sheet, not 01:01:21.070 --> 01:01:24.320 the whole entire sprite at once. 01:01:24.320 --> 01:01:25.570 And the same thing for topper. 01:01:25.570 --> 01:01:27.700 We have a topper set, we index into topper 01:01:27.700 --> 01:01:31.510 sets here at the topper set that we got, and then that's 01:01:31.510 --> 01:01:38.045 where we'll have the collection of tiles that form that particular set. 01:01:38.045 --> 01:01:39.670 And so the two are completely separate. 01:01:39.670 --> 01:01:43.150 They can be one random color tile with one random topper, but it's consistent. 01:01:43.150 --> 01:01:43.660 It's global. 01:01:43.660 --> 01:01:47.870 We have one topper set and one tile set that are active at any one time. 01:01:47.870 --> 01:01:50.560 And if we press R, which I did up here, then we 01:01:50.560 --> 01:01:52.972 just reset them to random on line 139 and 140. 01:01:52.972 --> 01:01:55.930 Tile set gets a new random number, topper set gets a new random number. 01:01:55.930 --> 01:01:58.220 It has the effect of-- 01:01:58.220 --> 01:02:01.000 we can just walk around and then generate random sets. 01:02:04.090 --> 01:02:04.960 So pretty simple. 01:02:04.960 --> 01:02:07.270 And recall again, topper is-- 01:02:07.270 --> 01:02:12.110 because the tile that we're standing on is y seven, topper equals true. 01:02:12.110 --> 01:02:16.624 So in that case, that particular top layer is always going to have a topper. 01:02:16.624 --> 01:02:18.790 And it gives us a nice little bit of visual variety, 01:02:18.790 --> 01:02:22.690 because it actually makes quite a bit of a difference having 01:02:22.690 --> 01:02:23.944 a topper versus no topper. 01:02:23.944 --> 01:02:26.110 And you can also just not have a topper and consider 01:02:26.110 --> 01:02:33.250 that a permutation of the toppers times tiles, like procedural algorithm. 01:02:33.250 --> 01:02:34.150 That's flat levels. 01:02:34.150 --> 01:02:36.740 Does anybody have any questions as to how this works 01:02:36.740 --> 01:02:38.230 or anything that we're doing here? 01:02:41.300 --> 01:02:42.780 OK. 01:02:42.780 --> 01:02:46.140 So things are a little flat, a little boring. 01:02:46.140 --> 01:02:50.160 The next step will be actually introducing one of the things 01:02:50.160 --> 01:02:55.110 that we can see here in our little collection of sample levels, 01:02:55.110 --> 01:02:58.290 like this pillar right here in the very middle. 01:02:58.290 --> 01:03:02.670 Does anybody recall how we go about spawning a pillar as opposed 01:03:02.670 --> 01:03:06.428 to just flat land? 01:03:06.428 --> 01:03:10.027 AUDIENCE: For that column, just put some more dirt down or more tiles down. 01:03:10.027 --> 01:03:11.430 COLTON OGDEN: Yep. 01:03:11.430 --> 01:03:15.880 So for that column, just put more tiles down instead of just the ground level. 01:03:15.880 --> 01:03:17.530 That's exactly what we're going to do. 01:03:17.530 --> 01:03:21.840 So I'm going to go ahead and open up level1 and main, 01:03:21.840 --> 01:03:25.770 and I'll run the example here as well just so you can see it looks like. 01:03:30.100 --> 01:03:32.590 So here we have quite a few. 01:03:32.590 --> 01:03:34.340 And notice we haven't implement collision, 01:03:34.340 --> 01:03:35.970 so we're still walking through them. 01:03:35.970 --> 01:03:38.120 But they're just random. 01:03:38.120 --> 01:03:40.530 Their random amount is up to taste, really. 01:03:40.530 --> 01:03:43.920 Right here it's pretty common, so it might be worth 01:03:43.920 --> 01:03:46.500 lowering the amount a little bit. 01:03:46.500 --> 01:03:51.120 If you wanted to, you could also maybe have a flag that says, spawn pillar, 01:03:51.120 --> 01:03:52.800 and maybe you want a pillar width. 01:03:52.800 --> 01:03:56.430 You could have anywhere between one and three tiles. 01:03:56.430 --> 01:04:02.070 And if its width is greater than 1, then just loop over a few times 01:04:02.070 --> 01:04:05.970 and just draw that same height a few times as opposed to just one time, 01:04:05.970 --> 01:04:08.982 and then set the flag back to false. 01:04:08.982 --> 01:04:10.440 A lot of things you can do with it. 01:04:10.440 --> 01:04:12.120 And also, they're a little tall here. 01:04:12.120 --> 01:04:15.214 For the main distro, I ended up making them a little shorter. 01:04:15.214 --> 01:04:17.130 But we'll see how we do this in the code here. 01:04:17.130 --> 01:04:23.800 It's going to mostly be down in our generateLevel function. 01:04:23.800 --> 01:04:26.340 So what we're doing here-- go ahead and hide that-- 01:04:26.340 --> 01:04:28.970 is we have basically this code here-- 01:04:28.970 --> 01:04:31.500 line 227 to 236. 01:04:31.500 --> 01:04:38.820 So all we're doing here is just filling our entire thing with just sky. 01:04:38.820 --> 01:04:41.340 We're just setting the entire thing to empty. 01:04:41.340 --> 01:04:45.520 And now we have a fully populated 2D array. 01:04:45.520 --> 01:04:47.520 All we need to do in order to change a tile-- we 01:04:47.520 --> 01:04:52.530 don't have to worry about insertions or adding too many tiles to our array. 01:04:52.530 --> 01:04:56.670 All we can do now is just directly change whatever tile exists there. 01:04:56.670 --> 01:05:01.380 So all we need to do is starting on line 239, 01:05:01.380 --> 01:05:05.400 we're going to start doing the column by column iteration over our entire level 01:05:05.400 --> 01:05:09.150 and deciding whether we should generate pillars or not. 01:05:09.150 --> 01:05:11.960 And we're always going to generate ground. 01:05:11.960 --> 01:05:14.960 So here's the flag spawnPillar. 01:05:14.960 --> 01:05:18.690 And if it's equal to 1, this is going to basically be assigned to spawnPillar. 01:05:18.690 --> 01:05:20.190 So math.random(5)==1. 01:05:20.190 --> 01:05:24.300 We have a 1 in 5 chance of spawning a pillar. 01:05:24.300 --> 01:05:28.755 If we just want a pillar, then pillar gets equal to 4 from 4 to 6-- 01:05:28.755 --> 01:05:34.470 so y gets 4 to 6 effectively-- 01:05:34.470 --> 01:05:37.800 tiles at pillar x, ID ground. 01:05:37.800 --> 01:05:41.310 And then here's where we set the topper, recall, 01:05:41.310 --> 01:05:47.820 because now pillars can be the top most tile on the surface. 01:05:47.820 --> 01:05:49.320 But they're above the ground level. 01:05:49.320 --> 01:05:53.540 So we just basically say, when we're generating a pillar, 01:05:53.540 --> 01:05:57.300 if pillar is equal to 4-- which is the very first tile that we start at-- 01:05:57.300 --> 01:05:59.190 then set topper equal to true here. 01:05:59.190 --> 01:06:01.140 Otherwise, set it to false. 01:06:01.140 --> 01:06:06.601 So that's how we can get pillars to also have toppers and then in this case, 01:06:06.601 --> 01:06:08.100 we're not generating any chasms yet. 01:06:08.100 --> 01:06:09.183 So all we're going to do-- 01:06:11.850 --> 01:06:15.030 once we've generated a pillar on that particular column, 01:06:15.030 --> 01:06:18.060 we'll just say ground gets 7 until the map height-- 01:06:18.060 --> 01:06:21.130 so towards the very bottom of the screen. 01:06:21.130 --> 01:06:22.720 And then we'll just set it to ground. 01:06:22.720 --> 01:06:25.890 And then topper-- in this case, we're going to make sure 01:06:25.890 --> 01:06:27.690 that we're not spawning a pillar. 01:06:27.690 --> 01:06:29.850 Because if we don't check this, then it'll 01:06:29.850 --> 01:06:35.310 also spawn a topper where the pillar meets the ground, 01:06:35.310 --> 01:06:36.930 and it'll look a little bit silly. 01:06:36.930 --> 01:06:39.500 And then we also want to check that ground is equal to 7. 01:06:39.500 --> 01:06:44.560 And so all together, that has the effect of this behavior. 01:06:44.560 --> 01:06:47.400 And so if we didn't check for that spawnPillar, 01:06:47.400 --> 01:06:49.560 we'd have a topper right below our feet here too, 01:06:49.560 --> 01:06:52.320 which looks graphically strange. 01:06:52.320 --> 01:06:54.240 And also, you can see-- 01:06:54.240 --> 01:06:59.169 emergently, we're getting double width pillars. 01:06:59.169 --> 01:07:00.960 And that's just kind of a natural byproduct 01:07:00.960 --> 01:07:02.420 of a lot of these randomizations. 01:07:02.420 --> 01:07:05.490 A lot of these procedural algorithms-- 01:07:05.490 --> 01:07:08.040 they'll generate outcomes that you might not necessarily 01:07:08.040 --> 01:07:10.500 have anticipated, which is kind of a cool thing. 01:07:10.500 --> 01:07:15.180 You didn't necessarily program it to have pillars that were two tiles wide, 01:07:15.180 --> 01:07:18.000 but just the nature of randomization-- that's just what you get. 01:07:18.000 --> 01:07:20.791 And that's another exciting thing about procedural level generation 01:07:20.791 --> 01:07:25.260 is that it can surprise even the person that wrote the algorithm. 01:07:25.260 --> 01:07:30.300 It's really cool, and it saves you work having to create levels. 01:07:30.300 --> 01:07:32.070 So that was pillared levels. 01:07:32.070 --> 01:07:37.109 Chasm levels-- who can tell me how we can do chasm levels? 01:07:37.109 --> 01:07:40.350 AUDIENCE: You just skip a column. 01:07:40.350 --> 01:07:42.590 COLTON OGDEN: Yep. 01:07:42.590 --> 01:07:43.550 you skip a column. 01:07:43.550 --> 01:07:45.841 So at the very beginning, all we can just basically say 01:07:45.841 --> 01:07:48.020 is, do I want to generate a chasm here? 01:07:48.020 --> 01:07:50.270 If I do, just skip. 01:07:50.270 --> 01:07:53.360 Go to the next iteration of the loop. 01:07:53.360 --> 01:07:56.060 And so we'll take a look at that. 01:07:56.060 --> 01:08:00.299 As simple as it is, because Lua doesn't have the notion of continue-- 01:08:00.299 --> 01:08:02.090 this will be a refresher, because I believe 01:08:02.090 --> 01:08:04.850 this was in one of the assignments-- 01:08:04.850 --> 01:08:08.310 it has a goto statement. 01:08:08.310 --> 01:08:13.450 So basically, same code as before, starting column by column. 01:08:13.450 --> 01:08:16.279 x equals one until map width. 01:08:16.279 --> 01:08:17.890 We have a 1 in 7 chance-- 01:08:17.890 --> 01:08:20.149 just arbitrary. 01:08:20.149 --> 01:08:22.250 And this should ideally-- 01:08:22.250 --> 01:08:28.529 if you're engineering an entire large game or application, 01:08:28.529 --> 01:08:31.839 this would be called SPAWN_CHASM_CHANCE probably, 01:08:31.839 --> 01:08:33.380 and just set that to seven somewhere. 01:08:33.380 --> 01:08:35.180 But we're just setting it to 7 here-- 01:08:35.180 --> 01:08:39.560 just a static magic number, but magic numbers are generally bad. 01:08:39.560 --> 01:08:46.460 Goto continue-- and so continue is here at the very bottom of the loop 01:08:46.460 --> 01:08:50.536 here, which is this for x = 1, mapWidth. 01:08:50.536 --> 01:08:53.660 So it will have the effect of skipping straight to x equals 2 if this at 1, 01:08:53.660 --> 01:08:55.670 for example. 01:08:55.670 --> 01:08:58.630 A lot of languages just simply have continue. 01:08:58.630 --> 01:09:05.090 Lua does not have continue, so this is a community established tradition 01:09:05.090 --> 01:09:07.729 for implementing continue-like behavior in Lua. 01:09:07.729 --> 01:09:14.000 You create a label via double colon with a name and then a double colon, 01:09:14.000 --> 01:09:16.200 and then you just goto it. 01:09:16.200 --> 01:09:18.859 And so that's as simple as it is for generating chasms. 01:09:18.859 --> 01:09:26.800 And so if we go to level2 and run that, we get chasms. 01:09:26.800 --> 01:09:30.359 And so now we've got a little bit of interesting visual variety. 01:09:30.359 --> 01:09:33.680 It's not spawning a ton of chasms in this example. 01:09:33.680 --> 01:09:35.590 It spawned one so far. 01:09:35.590 --> 01:09:36.470 There's another one. 01:09:36.470 --> 01:09:38.762 And then sometimes just emergently, you can get two. 01:09:38.762 --> 01:09:39.470 See, there we go. 01:09:39.470 --> 01:09:41.540 We get some interesting obstacles as a result. 01:09:41.540 --> 01:09:43.939 It almost looks as if someone intentionally did that-- 01:09:43.939 --> 01:09:45.199 almost. 01:09:45.199 --> 01:09:47.990 I would probably, like I said, shrink the pillar size a little bit. 01:09:47.990 --> 01:09:48.781 It's a little tall. 01:09:52.310 --> 01:09:52.910 That's that. 01:09:52.910 --> 01:09:56.360 That's basic procedural-- in the context of platformers, 01:09:56.360 --> 01:09:58.970 that's the mental model for how we can start 01:09:58.970 --> 01:10:01.059 thinking about generating obstacles. 01:10:01.059 --> 01:10:03.350 And there's a lot of different directions you could go. 01:10:03.350 --> 01:10:09.320 Let's say maybe you wanted to generate pyramids. 01:10:09.320 --> 01:10:10.910 I mean, it's a common thing in Mario. 01:10:10.910 --> 01:10:14.480 There will be steps, [INAUDIBLE] set for it. 01:10:14.480 --> 01:10:18.457 The same implementation would basically happen here. 01:10:18.457 --> 01:10:20.540 It would be a little bit different, because you're 01:10:20.540 --> 01:10:22.081 doing it on a column by column basis. 01:10:22.081 --> 01:10:24.770 But you'd effectively just maintain a reference 01:10:24.770 --> 01:10:27.620 to something like step height, and then you 01:10:27.620 --> 01:10:30.216 would say generate stairs is true here. 01:10:30.216 --> 01:10:32.090 And then you would just set step height to 1. 01:10:32.090 --> 01:10:34.360 So then you add a tile here. 01:10:34.360 --> 01:10:37.800 You would go from ground level up until step height, generate a tile, 01:10:37.800 --> 01:10:40.520 go the next one, and then increment step height to 2. 01:10:40.520 --> 01:10:44.630 And then do from ground until step height-- 01:10:44.630 --> 01:10:45.620 tiles go up. 01:10:45.620 --> 01:10:51.080 So 1 and then 2 and 3 until you've gone to stairs width, in which case 01:10:51.080 --> 01:10:52.400 you stop generating stairs. 01:10:52.400 --> 01:10:56.955 That's this principle behind how you could 01:10:56.955 --> 01:10:58.580 do something a little more complicated. 01:10:58.580 --> 01:11:00.980 Or pyramids-- same exact thing, pyramid width. 01:11:00.980 --> 01:11:03.890 And then you just go until pyramid width equals-- 01:11:03.890 --> 01:11:07.430 or we're at pyramid width divided by 2, make it go up. 01:11:07.430 --> 01:11:09.540 And if we're higher than that, make it go down. 01:11:09.540 --> 01:11:12.760 And then you have the effect of the pyramid approach. 01:11:12.760 --> 01:11:13.516 Yeah. 01:11:13.516 --> 01:11:15.265 AUDIENCE: Where are you putting the column 01:11:15.265 --> 01:11:18.576 generation if it's a [INAUDIBLE]. 01:11:18.576 --> 01:11:20.480 It's not in the play state. 01:11:20.480 --> 01:11:22.550 COLTON OGDEN: In this case, it's all in main.lua. 01:11:22.550 --> 01:11:25.520 But in the distro, it's going to be in levelmaker.lua. 01:11:25.520 --> 01:11:28.400 So we've broken out all of this functionality 01:11:28.400 --> 01:11:31.730 into just how we did Breakout. 01:11:31.730 --> 01:11:34.100 We had the same sort of thing-- level maker-- 01:11:34.100 --> 01:11:35.620 and it just has levelMaker.generate. 01:11:35.620 --> 01:11:37.370 And then you give it a width and a height, 01:11:37.370 --> 01:11:39.621 and it will generate an entire level for you. 01:11:39.621 --> 01:11:42.507 AUDIENCE: An entire level, but it has to continuously-- 01:11:42.507 --> 01:11:44.912 oh, you generate it all at once? 01:11:44.912 --> 01:11:48.244 It doesn't generate as you walk? 01:11:48.244 --> 01:11:51.410 COLTON OGDEN: The question is, does it generate continuously or all at once? 01:11:51.410 --> 01:11:53.010 It just generates all at once. 01:11:53.010 --> 01:11:56.090 So you could implement a-- 01:11:56.090 --> 01:12:01.820 if you wanted to do an infinite runner, the way you would do that is you 01:12:01.820 --> 01:12:04.450 would break up your level into chunks. 01:12:04.450 --> 01:12:07.860 And with infinite runners, usually you can only move in one direction. 01:12:07.860 --> 01:12:10.850 So as you go right, your levels that you've generated before-- they get 01:12:10.850 --> 01:12:14.960 discarded, so you avoid memory overconsumption. 01:12:14.960 --> 01:12:17.270 What you would do is you would just generate a chunk-- 01:12:17.270 --> 01:12:19.940 maybe a 100 by 20 level. 01:12:19.940 --> 01:12:22.850 And then you would go through that, through that. 01:12:22.850 --> 01:12:28.040 And then when you get to level end minus maybe like five tiles or 10 tiles, 01:12:28.040 --> 01:12:32.840 you would generate another one, append it, put it to the right, 01:12:32.840 --> 01:12:36.740 and then you would just go from the left to the right. 01:12:36.740 --> 01:12:40.960 And you probably would need some sort of semi-fancy code 01:12:40.960 --> 01:12:44.350 to splice them together once you've generated them. 01:12:44.350 --> 01:12:47.627 Alternatively, you could just always pad your-- 01:12:47.627 --> 01:12:49.210 no, you probably wouldn't want to pad. 01:12:49.210 --> 01:12:56.560 I would probably just splice them end to end and then get rid of x equals 1 100 01:12:56.560 --> 01:13:01.810 or however many on the left once it's gone past the left edge of the screen. 01:13:01.810 --> 01:13:05.050 In this case, to summarize, it's all static. 01:13:05.050 --> 01:13:06.780 But you could very easily-- 01:13:06.780 --> 01:13:11.090 not easily, but you could very well make it an infinite runner. 01:13:11.090 --> 01:13:11.590 Yeah. 01:13:11.590 --> 01:13:16.572 AUDIENCE: So we're rendering the entire level, but we just can't see it all? 01:13:16.572 --> 01:13:19.280 COLTON OGDEN: The question is, are we rendering the entire level, 01:13:19.280 --> 01:13:20.488 but we just can't see it all? 01:13:20.488 --> 01:13:21.410 The answer is yes. 01:13:21.410 --> 01:13:24.493 Currently, in this implementation, we're just rendering the entire level-- 01:13:24.493 --> 01:13:26.720 so tile by tile is getting drawn to the screen. 01:13:26.720 --> 01:13:30.410 For small examples like this, it's not a concern. 01:13:30.410 --> 01:13:33.860 But for a large level-- like if we did a Terraria level, for example. 01:13:33.860 --> 01:13:38.480 Terraria's thousands and thousands of tiles wide by probably 01:13:38.480 --> 01:13:40.340 1,000 or more tiles tall-- 01:13:40.340 --> 01:13:44.630 you want to render only a chunk, only what you can visibly see. 01:13:44.630 --> 01:13:49.730 And for that, you could use your camera offset and then just render 01:13:49.730 --> 01:13:53.949 from one tile to the left and above that to one tile below the bottom edge 01:13:53.949 --> 01:13:55.490 of the camera and to the right of it. 01:13:55.490 --> 01:13:57.650 Just render that subset of tiles. 01:13:57.650 --> 01:14:01.250 So you just need a for loop to iterate over a small section. 01:14:01.250 --> 01:14:04.550 AUDIENCE: So you can kind of make an array 01:14:04.550 --> 01:14:07.034 of what the map's going to look like and then 01:14:07.034 --> 01:14:11.184 just render only slices of the array that you can see. 01:14:11.184 --> 01:14:12.180 Is that right? 01:14:12.180 --> 01:14:18.654 If you put a multi-dimensional array and then you just go through it 01:14:18.654 --> 01:14:20.542 and render as you go-- is that the thought? 01:14:20.542 --> 01:14:22.250 COLTON OGDEN: Question was, you just have 01:14:22.250 --> 01:14:24.400 a multi-dimensional array of tiles for your level, 01:14:24.400 --> 01:14:25.983 and then you just render it as you go. 01:14:25.983 --> 01:14:27.050 The answer is yes. 01:14:27.050 --> 01:14:29.600 You would have your overall tiles-- 01:14:29.600 --> 01:14:35.030 your big 2D array of 100 by 20 or however many thousands of tiles. 01:14:35.030 --> 01:14:38.295 And then based on wherever your camera is rendering, 01:14:38.295 --> 01:14:41.150 it's just a for loop within that just of a nested amount. 01:14:41.150 --> 01:14:46.970 So maybe your player is at x 30 plus 6 tiles. 01:14:46.970 --> 01:14:51.350 So you would just render from 30 tiles to maybe 45 tiles 01:14:51.350 --> 01:14:55.040 on x and maybe 10 to 20 on the y-- 01:14:55.040 --> 01:14:56.120 just that chunk. 01:14:56.120 --> 01:14:58.100 And it's just relative to where your camera is. 01:14:58.100 --> 01:15:00.230 You're always rendering just a small little-- 01:15:00.230 --> 01:15:02.630 basically, it is effectively a camera at that point. 01:15:02.630 --> 01:15:05.630 It's rendering a chunk of the tiles to the screen. 01:15:05.630 --> 01:15:07.359 AUDIENCE: But in this code, it's not. 01:15:07.359 --> 01:15:08.650 COLTON OGDEN: In this code, no. 01:15:08.650 --> 01:15:10.300 The levels here are-- 01:15:10.300 --> 01:15:13.494 it's sufficiently complicated to introduce. 01:15:13.494 --> 01:15:15.410 I mean, it's not too complicated to introduce. 01:15:15.410 --> 01:15:16.520 It's pretty easy. 01:15:16.520 --> 01:15:20.540 But the consumption-- the processing here-- 01:15:20.540 --> 01:15:23.030 is very light, because the levels are fairly small. 01:15:23.030 --> 01:15:25.430 And even if we did have really large levels, 01:15:25.430 --> 01:15:29.550 it's sufficiently small to not have to worry about it. 01:15:29.550 --> 01:15:33.770 But if we did get to a point where your levels were 1,000 tiles or more, 01:15:33.770 --> 01:15:35.656 and then maybe those tiles have additional, 01:15:35.656 --> 01:15:38.030 you just want to squeeze all the performance possible out 01:15:38.030 --> 01:15:40.350 of your application. 01:15:40.350 --> 01:15:43.050 You could look into just rendering a subset. 01:15:43.050 --> 01:15:46.340 It's fairly simple to introduce but just not something 01:15:46.340 --> 01:15:50.420 that we included in this assignment. 01:15:50.420 --> 01:15:54.687 Any other questions as to how this sort of thing works? 01:15:58.115 --> 01:15:58.615 OK. 01:16:01.120 --> 01:16:04.960 So far, we've talked about procedural level generation. 01:16:04.960 --> 01:16:09.250 We've talked about animation and rendering and all that stuff. 01:16:09.250 --> 01:16:12.970 We haven't really talked about how to do tile collision. 01:16:12.970 --> 01:16:15.550 And we won't go into a terrible amount of detail, 01:16:15.550 --> 01:16:17.170 because the code is a little lengthy. 01:16:17.170 --> 01:16:19.961 It'll be part of your assignment to read over it and understand it, 01:16:19.961 --> 01:16:24.940 but it's in the TileMap class that we have. 01:16:24.940 --> 01:16:29.290 Basically, the whole gist is that because we're 01:16:29.290 --> 01:16:34.990 on a 2D tile array that's fixed, it'll always be at 0, 0, at least 01:16:34.990 --> 01:16:37.580 in the model that we've currently implemented. 01:16:37.580 --> 01:16:42.730 We can just convert coordinates to tiles and then 01:16:42.730 --> 01:16:48.310 just check to see whether or not the tiles at whatever that is 01:16:48.310 --> 01:16:51.220 are solid or not. 01:16:51.220 --> 01:16:57.830 Let's say we wanted to look at the top of our character in this case. 01:16:57.830 --> 01:17:00.970 So if we have our character here. 01:17:00.970 --> 01:17:04.240 For the sake of illustration, I put him between two tiles above him 01:17:04.240 --> 01:17:07.750 just to show why we need to do this the way that we are doing it. 01:17:07.750 --> 01:17:10.600 But you take the point here-- 01:17:10.600 --> 01:17:14.440 his very top left, so player.x and then player.y, 01:17:14.440 --> 01:17:17.080 which is effectively their version of 0, 0. 01:17:17.080 --> 01:17:20.610 And then player.x plus player.width minus-- 01:17:20.610 --> 01:17:27.210 we do a minus one for a lot of collisions 01:17:27.210 --> 01:17:29.740 so that he can walk between blocks and stuff like that. 01:17:29.740 --> 01:17:33.940 Because if you don't basically give him slightly less than the amount-- 01:17:33.940 --> 01:17:37.080 because he's 16 pixels wide, and the tiles are 16 pixels wide-- 01:17:37.080 --> 01:17:39.730 if he's between two blocks and he wanted to fall down, 01:17:39.730 --> 01:17:42.580 he just won't fall down, because it's still detecting a collision. 01:17:45.340 --> 01:17:47.140 Because if he's on the hole here-- 01:17:47.140 --> 01:17:50.500 let's say this is the hole, and these are the tiles here. 01:17:50.500 --> 01:17:52.230 The x plus the width-- 01:17:52.230 --> 01:17:55.120 it'll trigger a collision on this tile and this tile still. 01:17:55.120 --> 01:17:58.480 So basically, you need to minimize his collision box by one pixel 01:17:58.480 --> 01:18:03.970 to fit through 16 pixel gaps essentially is what it boils down to. 01:18:03.970 --> 01:18:07.315 But the gist behind collision-- 01:18:07.315 --> 01:18:09.190 in this case, this would only apply when he's 01:18:09.190 --> 01:18:11.440 jumping, because this is the only time at which he can really 01:18:11.440 --> 01:18:13.240 collide with tiles that are above him. 01:18:13.240 --> 01:18:17.190 You would test for whatever block falls on this pixel 01:18:17.190 --> 01:18:19.490 and whatever block falls on this pixel. 01:18:19.490 --> 01:18:23.380 And if either of them are solid, you trigger collision. 01:18:23.380 --> 01:18:25.610 And if not, then there's no collision at all. 01:18:25.610 --> 01:18:29.210 So if he's right here, for example-- 01:18:29.210 --> 01:18:31.960 right directly beneath a tile-- it's only going to check one tile. 01:18:31.960 --> 01:18:35.440 This point and this point are both going to fall on this tile. 01:18:35.440 --> 01:18:39.220 But the reason that we want to check for both points here and here 01:18:39.220 --> 01:18:42.850 is in the event that he is beneath two separate tiles, 01:18:42.850 --> 01:18:45.354 because now this point's going to check this tile, 01:18:45.354 --> 01:18:47.020 and this one's going to check this tile. 01:18:47.020 --> 01:18:50.950 We can't just check this tile, because if we only check this tile 01:18:50.950 --> 01:18:53.860 and there was no tile here but there was a tile here, 01:18:53.860 --> 01:18:57.070 him jumping would still not trigger a collision. 01:18:57.070 --> 01:18:59.950 It would think that it was only looking here and not here. 01:18:59.950 --> 01:19:05.200 So for every collision on every side we do of him effectively, 01:19:05.200 --> 01:19:09.520 we need to check both corners of that edge effectively. 01:19:09.520 --> 01:19:14.610 So when he's jumping, we turn this point-- 01:19:14.610 --> 01:19:19.690 this x, y-- into a tile by just dividing it by tile size. 01:19:19.690 --> 01:19:23.890 So we can say, player.x divided by tile size plus one. 01:19:23.890 --> 01:19:27.520 That's going to equal whatever tile this is on the x. 01:19:27.520 --> 01:19:29.200 And then same thing for the y-- 01:19:29.200 --> 01:19:32.410 we just divide the y by tile size, and then we add 1 to it. 01:19:32.410 --> 01:19:34.900 And that will allow us to get the exact tile. 01:19:34.900 --> 01:19:39.340 If we use those x, y that we get from that operation, 01:19:39.340 --> 01:19:44.560 we get the exact tile at that y, x index in our tile's 2D array effectively. 01:19:44.560 --> 01:19:45.820 So we do that for jump. 01:19:45.820 --> 01:19:50.099 We check both corners of the top of his head. 01:19:50.099 --> 01:19:52.390 We do the same thing for the bottom, only at that time, 01:19:52.390 --> 01:19:56.260 we're checking x, and then y plus height, 01:19:56.260 --> 01:19:58.810 and then x plus width, y plus height. 01:19:58.810 --> 01:20:01.862 And then if we're doing the left edge, what are we checking? 01:20:05.208 --> 01:20:07.125 AUDIENCE: The bottom left and top left? 01:20:07.125 --> 01:20:08.000 COLTON OGDEN: We are. 01:20:08.000 --> 01:20:13.220 So that will be x0, y, and then x0, y plus height. 01:20:13.220 --> 01:20:15.590 And then if it's the right edge, same thing. 01:20:15.590 --> 01:20:23.420 We check x plus width y, and then we check x plus width y plus height. 01:20:23.420 --> 01:20:30.320 And so that's the gist behind collision detection in the distro here. 01:20:30.320 --> 01:20:35.405 And you can see it in Mario if we go to TileMap. 01:20:39.590 --> 01:20:42.680 Point to tile-- this is effectively where it happens. 01:20:49.610 --> 01:20:52.390 On line 32, we're basically returning-- 01:20:52.390 --> 01:20:53.660 this bit of code here-- 01:20:53.660 --> 01:20:56.030 28 to 30-- is a check. 01:20:56.030 --> 01:20:59.120 Because we can jump over the map edge, we 01:20:59.120 --> 01:21:02.669 won't be able to check at tile y divided by TILE_SIZE 01:21:02.669 --> 01:21:05.460 plus 1, x divided by TILE_SIZE plus one, because those will be nil. 01:21:05.460 --> 01:21:07.209 Those won't exist, because he'll literally 01:21:07.209 --> 01:21:08.510 be outside the map boundaries. 01:21:08.510 --> 01:21:12.540 Same thing if he goes below it or he goes beyond the left or right edge. 01:21:12.540 --> 01:21:14.030 So that's all this code is here. 01:21:14.030 --> 01:21:17.780 It just makes sure that if we do go beyond the map boundaries, 01:21:17.780 --> 01:21:18.920 we return nil. 01:21:18.920 --> 01:21:23.540 So that way, we can check nil rather than getting a tile index error. 01:21:23.540 --> 01:21:26.370 And then on line 32 is the operation that I just mentioned, 01:21:26.370 --> 01:21:28.550 which was we take the y-- 01:21:28.550 --> 01:21:33.020 so this x and y that we pass in are going to be the player's actual x, y. 01:21:33.020 --> 01:21:38.360 When we pass those in, we're just going to get the tile at self.tiles, 01:21:38.360 --> 01:21:43.130 and then effectively y divided by TILE_SIZE taken down to an integer, 01:21:43.130 --> 01:21:43.890 and then add 1. 01:21:43.890 --> 01:21:49.070 Because recall, tables are 1 indexed, but the coordinates are 0 indexed. 01:21:49.070 --> 01:21:53.210 So this will result in a 0 indexed outcome, so we want to add 1 to it. 01:21:53.210 --> 01:21:57.000 Same thing for here-- math.floor(x) divided by TILE_SIZE plus 1. 01:21:57.000 --> 01:21:59.870 So effectively, points to tiles. 01:21:59.870 --> 01:22:01.660 And then we'll just get a tile from that. 01:22:01.660 --> 01:22:05.130 And the tile-- we can just check, hey, is that tile solid or not? 01:22:05.130 --> 01:22:07.830 If it is, trigger collision. 01:22:07.830 --> 01:22:12.980 So that's the gist behind being able to do it in a platformer 01:22:12.980 --> 01:22:14.459 where everything is fixed. 01:22:14.459 --> 01:22:16.250 That's sort of like a shortcut we can take. 01:22:16.250 --> 01:22:19.730 Because now, what's the nice thing about this? 01:22:19.730 --> 01:22:24.980 What jumps out as being a super nice thing about this algorithm, 01:22:24.980 --> 01:22:35.720 imagining that we have, let's say, 10,000 tiles in our game world. 01:22:35.720 --> 01:22:38.390 So if you look and see, all we're doing is 01:22:38.390 --> 01:22:40.700 we're just doing a simple mathematical operation 01:22:40.700 --> 01:22:44.150 on what his x and y is, right? 01:22:44.150 --> 01:22:46.520 What's the alternative to this? 01:22:46.520 --> 01:22:50.430 If we were doing this via AABB, for example, 01:22:50.430 --> 01:22:54.518 we'd have to iterate over every single tile, right? 01:22:54.518 --> 01:22:56.474 AUDIENCE: Can you summarize? 01:22:56.474 --> 01:23:00.386 To avoid iterating over everything on the screen, 01:23:00.386 --> 01:23:03.820 you just check the column that he's in and the column tile? 01:23:03.820 --> 01:23:04.880 COLTON OGDEN: Yep. 01:23:04.880 --> 01:23:09.720 So the gist is he's got an x and a y. 01:23:09.720 --> 01:23:13.940 The x and the y are going to be in world coordinates, so his x could be 67 01:23:13.940 --> 01:23:16.040 and his y could be 38 or something like that. 01:23:16.040 --> 01:23:17.450 They don't map evenly to tiles. 01:23:17.450 --> 01:23:20.660 But if we divide those by whatever the tile size is in our world-- 01:23:20.660 --> 01:23:25.310 16-- that's going to be the exact tile. 01:23:25.310 --> 01:23:30.500 We also have to add 1 to it, because the tables in lua are 1 indexed. 01:23:30.500 --> 01:23:34.040 But we can index our self.tiles at the x, y 01:23:34.040 --> 01:23:36.945 that we get from that-- the dividing by 16. 01:23:36.945 --> 01:23:39.320 And that will be the exact tile that he's colliding with. 01:23:43.340 --> 01:23:45.710 We don't have to basically have a collection of tiles 01:23:45.710 --> 01:23:48.710 that we iterate over and check whether they 01:23:48.710 --> 01:23:51.200 collide with the player using AABB collision detection 01:23:51.200 --> 01:23:52.220 like we've done before. 01:23:52.220 --> 01:23:54.620 Because recall, in Breakout, we had the bricks, right? 01:23:54.620 --> 01:23:56.990 They all had their own x, y, but they weren't on a grid. 01:23:56.990 --> 01:23:58.040 They weren't fixed. 01:23:58.040 --> 01:24:01.130 So we had to actually take them and do an AABB. 01:24:01.130 --> 01:24:03.740 We had to iterate over them and perform AABB on them, 01:24:03.740 --> 01:24:08.450 because there's no deterministic way to just index at them really quickly. 01:24:08.450 --> 01:24:11.300 It's the same thing with arrays versus linked lists. 01:24:11.300 --> 01:24:16.610 Because arrays-- you can calculate how far some value is given an index. 01:24:16.610 --> 01:24:18.450 You have instant access to it. 01:24:18.450 --> 01:24:22.070 It's an order of one operation as opposed to a linked list. 01:24:22.070 --> 01:24:24.230 If you want to try and get to a particular value, 01:24:24.230 --> 01:24:26.854 you have to iterate through the entire thing until you find it. 01:24:26.854 --> 01:24:31.790 AUDIENCE: Can you just look for the column that you might be landing on? 01:24:31.790 --> 01:24:36.650 COLTON OGDEN: You're getting the exact tile at whatever your x divided by 16-- 01:24:36.650 --> 01:24:38.150 or whatever your tile size is-- 01:24:38.150 --> 01:24:40.370 and your y divided by 16 is. 01:24:40.370 --> 01:24:43.010 And you're doing it, recall, for two different points 01:24:43.010 --> 01:24:44.777 depending on what you're looking for. 01:24:44.777 --> 01:24:47.360 If you're looking for the tiles that are above your character, 01:24:47.360 --> 01:24:49.280 you're going to be doing it for this point. 01:24:49.280 --> 01:24:53.030 So whatever this value is-- his base x, y-- 01:24:53.030 --> 01:24:57.490 whatever that is divided by 16 and then whatever that is divided by 16. 01:24:57.490 --> 01:25:00.310 And then that'll get you whatever tiles are directly above him. 01:25:00.310 --> 01:25:03.160 It will intersect with whatever tile intersects with this point 01:25:03.160 --> 01:25:05.272 and whatever tile intersects with this point. 01:25:05.272 --> 01:25:07.730 Same thing with here and here if we're looking on the left, 01:25:07.730 --> 01:25:09.080 here, here if we're looking on the bottom, 01:25:09.080 --> 01:25:11.610 and here and here if we're looking on the right side. 01:25:11.610 --> 01:25:13.640 And we check for collision after he's already 01:25:13.640 --> 01:25:18.340 moved so that these points will be intersected with potential blocks. 01:25:18.340 --> 01:25:20.840 And that's how we can check whether it's a collision or not. 01:25:20.840 --> 01:25:26.844 We do this when he moves and is in some sort of movement state. 01:25:26.844 --> 01:25:29.130 AUDIENCE: So you're still doing collision detection 01:25:29.130 --> 01:25:31.430 with his actual coordinates, but you're just 01:25:31.430 --> 01:25:34.620 narrowing what you're character width-- 01:25:34.620 --> 01:25:36.000 COLTON OGDEN: Yep. 01:25:36.000 --> 01:25:39.360 we're turning it from iterating over every single tile 01:25:39.360 --> 01:25:42.540 to an instant operation, because we can just mathematically get 01:25:42.540 --> 01:25:45.210 the exact tiles that he's at without having 01:25:45.210 --> 01:25:47.130 to worry about where he is in the map. 01:25:47.130 --> 01:25:50.450 It's just instant access. 01:25:50.450 --> 01:25:53.820 And this only works because we know the tiles are always 01:25:53.820 --> 01:25:55.290 fixed in the exact same locations. 01:25:55.290 --> 01:25:56.623 They're always starting at 0, 0. 01:25:56.623 --> 01:25:58.262 They're always going to be TILE_SIZE. 01:25:58.262 --> 01:25:59.970 Things get a little more complicated when 01:25:59.970 --> 01:26:04.920 we introduce game objects, which have their own independent x, y. 01:26:04.920 --> 01:26:06.824 And for those, you do have to iterate over. 01:26:06.824 --> 01:26:09.990 You have basically a collection of game objects or a collection of entities. 01:26:09.990 --> 01:26:12.270 Let's say we have snails in the game world. 01:26:12.270 --> 01:26:16.770 The snails aren't going to be at some fixed location every time. 01:26:16.770 --> 01:26:19.170 They can move continuously. 01:26:19.170 --> 01:26:21.960 So for those, we have to actually keep them all in a container 01:26:21.960 --> 01:26:26.940 and then loop through them and say, has my player collided with any of these? 01:26:26.940 --> 01:26:29.520 If he has, then trigger a collision with that snail-- 01:26:29.520 --> 01:26:34.532 kill it or kill the player if he's in a walking state or a jump state. 01:26:34.532 --> 01:26:36.240 And if he's in a falling state, then they 01:26:36.240 --> 01:26:39.480 should die, because he's colliding with them from the top. 01:26:39.480 --> 01:26:43.500 And you narrow down what collision you check for, as you can see at the bottom 01:26:43.500 --> 01:26:44.850 here. 01:26:44.850 --> 01:26:47.267 Tile collision-- when you're looking above your character, 01:26:47.267 --> 01:26:49.683 you're only testing that when you're in the jumping state, 01:26:49.683 --> 01:26:51.390 because it's the only time you need to. 01:26:51.390 --> 01:26:55.380 So that's the only point at which you'll collide with tiles that are above you. 01:26:55.380 --> 01:26:58.980 When you're in the falling state is when you'll check for tiles below you. 01:26:58.980 --> 01:27:01.390 And then you can interact with tiles to your side 01:27:01.390 --> 01:27:04.020 when you're in either the jumping, falling, or moving state, 01:27:04.020 --> 01:27:08.285 so you should check for left and right tiles in all three of those states. 01:27:08.285 --> 01:27:12.835 AUDIENCE: Shouldn't you always test for beneath you in case you get a chasm? 01:27:12.835 --> 01:27:13.960 COLTON OGDEN: In case what? 01:27:13.960 --> 01:27:15.430 AUDIENCE: In case you get a chasm. 01:27:15.430 --> 01:27:17.138 COLTON OGDEN: In case you get chasm, yes. 01:27:21.210 --> 01:27:21.930 You're correct. 01:27:21.930 --> 01:27:24.660 This should actually be tested only when in the player 01:27:24.660 --> 01:27:26.820 falling state and player walking state, yes. 01:27:26.820 --> 01:27:30.690 So the question was, shouldn't you be testing for tiles beneath you 01:27:30.690 --> 01:27:32.300 when you're walking? 01:27:32.300 --> 01:27:35.274 And yes-- not just falling, but walking as well. 01:27:35.274 --> 01:27:37.440 This one only jumping, this one falling and walking, 01:27:37.440 --> 01:27:39.690 and this one for jumping, falling, and moving. 01:27:44.490 --> 01:27:45.660 Does that make sense-- 01:27:45.660 --> 01:27:49.890 how we can take the x, y and sort of turn that into a tile 01:27:49.890 --> 01:27:52.410 by just dividing it by 16? 01:27:52.410 --> 01:27:56.970 And do note the plus 1 as well, because our tiles in our self.tiles 01:27:56.970 --> 01:27:58.530 are 1 indexed. 01:27:58.530 --> 01:28:01.440 And so when we divide x, y by tile size, we're 01:28:01.440 --> 01:28:05.160 going to get a 0 indexed coordinate. 01:28:05.160 --> 01:28:10.930 If our x is at 14, we're within the first tile. 01:28:10.930 --> 01:28:13.170 But if we divide that by 16, we're going to get zero. 01:28:13.170 --> 01:28:16.086 So we need to add 1 to that so that we get the first tile in the array 01:28:16.086 --> 01:28:20.280 still, which will be whatever that tile is. 01:28:20.280 --> 01:28:23.100 So that's how the collision works. 01:28:23.100 --> 01:28:27.390 It's all implemented in TileMap here. 01:28:27.390 --> 01:28:36.170 And basically every state that the player is in, which is in StatesEntity, 01:28:36.170 --> 01:28:38.870 and then player falling, idle, jump, and walking-- 01:28:38.870 --> 01:28:43.590 these are all states that perform this check. 01:28:43.590 --> 01:28:45.500 They basically do all the logic that's here 01:28:45.500 --> 01:28:47.720 at the bottom, which is testing in the player 01:28:47.720 --> 01:28:51.620 jumping state, falling state, and moving state for left or right collision. 01:28:51.620 --> 01:28:55.192 And then in the falling state, we check for collision below us. 01:28:55.192 --> 01:28:57.650 And then in jumping state, we check for collision above us. 01:28:57.650 --> 01:29:01.670 That's all done within the states themselves. 01:29:01.670 --> 01:29:05.089 But the actual transformation from pixels to tiles-- 01:29:05.089 --> 01:29:07.130 that's just a function that we call from TileMap. 01:29:07.130 --> 01:29:09.859 It's just a utility function. 01:29:09.859 --> 01:29:11.650 AUDIENCE: What's the function called again? 01:29:11.650 --> 01:29:13.460 COLTON OGDEN: It's called pointToTile. 01:29:13.460 --> 01:29:18.170 So if you're in TileMap on line 27-- 01:29:18.170 --> 01:29:19.430 pointToTile(x, y). 01:29:19.430 --> 01:29:22.430 And the first little bit here is just the bit 01:29:22.430 --> 01:29:25.910 that lets you basically go outside the map bounds 01:29:25.910 --> 01:29:27.960 without getting a tile index error. 01:29:27.960 --> 01:29:33.110 So if it's just outside the tile limits, less than 0, or greater than width, 01:29:33.110 --> 01:29:34.100 just return nil. 01:29:34.100 --> 01:29:35.870 And so you can do a check on nil to check 01:29:35.870 --> 01:29:40.345 to see whether TileMap pointToTile is equal to nil 01:29:40.345 --> 01:29:41.720 or not when you do the collision. 01:29:41.720 --> 01:29:46.570 And if it is, then just don't do anything probably. 01:29:46.570 --> 01:29:51.530 But assuming that you're within the tile boundaries, on line 32 01:29:51.530 --> 01:29:54.691 is where you do that transformation-- the math.floor, recall, 01:29:54.691 --> 01:29:56.690 because we want to get integer values for these. 01:29:56.690 --> 01:29:58.856 We don't want to get fractional numbers, because you 01:29:58.856 --> 01:30:03.220 can't index these tiles as fractional numbers, although I'm not sure. 01:30:03.220 --> 01:30:06.650 I think you might be able to in Lua generally-- index a tile 01:30:06.650 --> 01:30:07.740 by a fractional number. 01:30:07.740 --> 01:30:10.290 But in this case, we just want integers. 01:30:10.290 --> 01:30:14.440 So we call math.floor on y divided by TILE_SIZE plus 1, 01:30:14.440 --> 01:30:16.730 y divided by TILE_SIZE then add 1 to that, 01:30:16.730 --> 01:30:18.484 and then we do the same thing for the x. 01:30:18.484 --> 01:30:20.122 So that's the operation. 01:30:20.122 --> 01:30:23.330 And then wherever we want to check for whatever tiles we want to collide for, 01:30:23.330 --> 01:30:28.070 we just call pointToTile on those x and y coordinates. 01:30:28.070 --> 01:30:33.480 That's the backbone behind all the tile-based collision in the game 01:30:33.480 --> 01:30:34.400 effectively. 01:30:37.630 --> 01:30:41.490 Any questions as to how this works? 01:30:41.490 --> 01:30:41.990 Yes. 01:30:41.990 --> 01:30:45.742 AUDIENCE: So you're only detecting the collision of corners 01:30:45.742 --> 01:30:47.620 and not the edge itself? 01:30:47.620 --> 01:30:51.230 COLTON OGDEN: Correct, because you don't really need to check for the edge 01:30:51.230 --> 01:30:53.830 if you're taking into consideration the top and bottom corner, 01:30:53.830 --> 01:30:57.610 unless your entity is sufficiently tall that they need 01:30:57.610 --> 01:31:00.340 to check for more than three tiles. 01:31:00.340 --> 01:31:03.730 In this case, our entity is not more than two tiles tall, 01:31:03.730 --> 01:31:08.556 so we only need to check for his top left, bottom left. 01:31:08.556 --> 01:31:10.930 If we're doing a left collision, top right, bottom right. 01:31:10.930 --> 01:31:16.330 If we're doing a right collision, his top left, top right for top and bottom 01:31:16.330 --> 01:31:17.710 left, bottom right for bottom. 01:31:17.710 --> 01:31:20.980 If you had an entity that was eight tiles tall, 01:31:20.980 --> 01:31:26.330 you need to check every single tile along his right side, which 01:31:26.330 --> 01:31:30.550 just means you need to iterate over his entire height divided by tile size. 01:31:30.550 --> 01:31:34.450 And then just offset the y that you're checking for each of those tiles. 01:31:34.450 --> 01:31:36.090 Does that makes sense? 01:31:36.090 --> 01:31:38.860 OK, cool. 01:31:38.860 --> 01:31:39.490 All right. 01:31:39.490 --> 01:31:41.890 I alluded to this briefly by mentioning state. 01:31:45.340 --> 01:31:48.340 I don't know if I alluded so much to the fact that we're using entities. 01:31:48.340 --> 01:31:53.260 But in this distro, we're introduced to the concept of entities. 01:31:53.260 --> 01:31:57.960 An entity can be almost anything you want it to be. 01:31:57.960 --> 01:31:59.980 In this distro, we're considering entities 01:31:59.980 --> 01:32:03.250 to basically be anything that's living or sentient moving around-- 01:32:03.250 --> 01:32:04.900 in this case, the player or snails. 01:32:04.900 --> 01:32:10.132 Those are entities, and then they just are subsets of entity. 01:32:10.132 --> 01:32:11.590 An entity is a very abstract thing. 01:32:11.590 --> 01:32:15.850 You'll see it in a lot of game engines and a lot of discussions 01:32:15.850 --> 01:32:19.300 about how to organize your game and how to engineer it. 01:32:19.300 --> 01:32:24.460 Unity is probably the most prominent adopter 01:32:24.460 --> 01:32:27.460 of what's called the entity component system, whereby 01:32:27.460 --> 01:32:29.350 you have everything in your game. 01:32:29.350 --> 01:32:31.510 Every single thing in your game is an entity, 01:32:31.510 --> 01:32:34.480 and then every entity is comprised of components. 01:32:34.480 --> 01:32:37.960 And these components ultimately drive your behavior. 01:32:37.960 --> 01:32:41.290 It's sort of like if you're familiar with composition over inheritance. 01:32:41.290 --> 01:32:43.900 If you've heard of this as a software engineering thing, 01:32:43.900 --> 01:32:45.460 that's effectively the same paradigm. 01:32:45.460 --> 01:32:50.290 Rather than inherit a bunch of different things to be your-- 01:32:50.290 --> 01:32:52.620 let's say you have a base monster class. 01:32:52.620 --> 01:32:55.240 And then you have a goblin that's a subset of monster, 01:32:55.240 --> 01:32:56.710 so it inherits from monster. 01:32:56.710 --> 01:32:59.500 And then you have a goblin warlord who inherits from goblin, 01:32:59.500 --> 01:33:02.950 and then you have an ancient goblin warlord that inherits from that. 01:33:02.950 --> 01:33:06.270 Rather than have this nested tree of inheritance, 01:33:06.270 --> 01:33:11.380 you adopt composition, which means you take a base container, 01:33:11.380 --> 01:33:14.110 and then you fill it with different components that represent 01:33:14.110 --> 01:33:16.510 what the behavior of your object is. 01:33:16.510 --> 01:33:19.810 So if you have an entity-- 01:33:19.810 --> 01:33:21.820 let's say you give it a monster component. 01:33:21.820 --> 01:33:25.256 And then maybe you also give it an ancient component, 01:33:25.256 --> 01:33:26.380 so it's an ancient monster. 01:33:26.380 --> 01:33:28.210 And maybe you give it a goblin component, 01:33:28.210 --> 01:33:32.022 so then it's an ancient monster goblin. 01:33:32.022 --> 01:33:33.730 And then you give it a warlord component, 01:33:33.730 --> 01:33:35.150 so it's an ancient goblin monster warlord. 01:33:35.150 --> 01:33:36.940 So it has all the pieces that make it what 01:33:36.940 --> 01:33:40.930 it is without you having to create this crazy chain of inheritance. 01:33:40.930 --> 01:33:44.920 That's effectively what the model of an entity component system 01:33:44.920 --> 01:33:48.820 is versus standard inheritance-- using that to drive 01:33:48.820 --> 01:33:51.050 the model of your problem. 01:33:51.050 --> 01:33:53.890 In this case, we're not going into crazy entity components. 01:33:53.890 --> 01:33:56.020 But I wanted to bring it up, because Unity, 01:33:56.020 --> 01:34:01.000 which we'll be covering in a few weeks, is entirely component-based. 01:34:01.000 --> 01:34:03.550 Everything you write in Unity is a component. 01:34:03.550 --> 01:34:08.140 And entities, whether they're in an entity component system or not, 01:34:08.140 --> 01:34:10.570 form the backbone of most large games. 01:34:10.570 --> 01:34:15.440 Most games that have some complexity to them 01:34:15.440 --> 01:34:19.750 model most of the pieces within them as entities that have behaviors 01:34:19.750 --> 01:34:21.260 and do things. 01:34:21.260 --> 01:34:28.360 And so in this case, entities are snails and our player. 01:34:28.360 --> 01:34:31.900 And then separate from the tiles-- 01:34:31.900 --> 01:34:33.220 when we do collision for that-- 01:34:33.220 --> 01:34:36.130 we want to also check collision on every entity with the player. 01:34:36.130 --> 01:34:39.577 So we make sure that the player's collided with the snail in this case, 01:34:39.577 --> 01:34:41.910 because that's the only other entities that they can be. 01:34:41.910 --> 01:34:47.100 But you can have an arbitrary number of enemies if you want to. 01:34:47.100 --> 01:34:49.970 If you collide with an entity-- so just a for loop. 01:34:49.970 --> 01:34:54.940 So for entity in pairs of entities, check collision. 01:34:54.940 --> 01:34:58.720 If you're in the jump state, then die. 01:34:58.720 --> 01:35:02.410 If you're in the fall state, kill it, et cetera. 01:35:02.410 --> 01:35:06.609 When you're doing most of your entity to entity interaction stuff, 01:35:06.609 --> 01:35:08.150 that's generally how you'll model it. 01:35:08.150 --> 01:35:11.497 You'll just iterate over everything and then just collide everything. 01:35:11.497 --> 01:35:13.330 Depending on what collides with what, you'll 01:35:13.330 --> 01:35:16.970 just collide everything with everything else and process interactions that way. 01:35:16.970 --> 01:35:18.400 That's effectively how we do it. 01:35:18.400 --> 01:35:20.496 We have in the-- 01:35:20.496 --> 01:35:22.930 I believe it's in GameLevel. 01:35:22.930 --> 01:35:28.350 This maintains a reference to a table full of entities, 01:35:28.350 --> 01:35:29.830 a table full of objects. 01:35:29.830 --> 01:35:32.770 Objects can be-- we'll talk about that in a second-- 01:35:32.770 --> 01:35:35.800 gems, and blocks, and bushes, and stuff like that, and then a tile map. 01:35:40.820 --> 01:35:43.400 For every entity, we just update it. 01:35:43.400 --> 01:35:45.580 And then for every object, we update it. 01:35:45.580 --> 01:35:49.020 And then for every object in objects, we render it as well. 01:35:49.020 --> 01:35:50.660 And then we render every entity. 01:35:50.660 --> 01:35:55.100 This is just sort of basic how you would take a game world, 01:35:55.100 --> 01:35:57.320 populate it, and then process and update it. 01:35:57.320 --> 01:36:02.270 Just containers, tables that maintain a bunch of references to everything, 01:36:02.270 --> 01:36:03.830 and then just update them. 01:36:03.830 --> 01:36:06.050 The actual interaction takes place in the-- 01:36:06.050 --> 01:36:09.590 because they're dependent on what state we're in. 01:36:09.590 --> 01:36:16.610 If you look at all the different states for the player in the states slash 01:36:16.610 --> 01:36:23.040 entity folder, you'll see, for example, on line 62 of the player falling state, 01:36:23.040 --> 01:36:28.390 we're iterating over every object in the level.objects. 01:36:28.390 --> 01:36:31.400 And notice the player has a reference to its level 01:36:31.400 --> 01:36:33.780 so that it can access everything within it. 01:36:33.780 --> 01:36:36.170 And then within that level, all the objects are stored. 01:36:36.170 --> 01:36:38.820 So all it needs to do is just say, if the object collides 01:36:38.820 --> 01:36:43.400 the player and the object is solid, then set our dy to zero, 01:36:43.400 --> 01:36:44.822 et cetera, et cetera. 01:36:44.822 --> 01:36:47.030 All this code's actually pretty easy to read through, 01:36:47.030 --> 01:36:49.010 so I would encourage you to take a look at it 01:36:49.010 --> 01:36:51.800 and just understand how all the collision and stuff is 01:36:51.800 --> 01:36:57.740 working between the player, the objects, the blocks, and things like that-- 01:36:57.740 --> 01:37:01.900 things like blocks are solid, things like bushes are not solid. 01:37:01.900 --> 01:37:05.210 But that's the gist. 01:37:05.210 --> 01:37:07.670 Have a collection of objects or entities. 01:37:07.670 --> 01:37:10.940 And then depending on what state you're in, collide with some. 01:37:10.940 --> 01:37:14.540 And then depending on the state, maybe that kills you, 01:37:14.540 --> 01:37:17.130 maybe that kills the enemy, maybe nothing happens, 01:37:17.130 --> 01:37:18.350 maybe you become invincible. 01:37:18.350 --> 01:37:21.200 Maybe you collide with a power-up game object, 01:37:21.200 --> 01:37:26.570 and that power-up triggers your self.player.invincible is true. 01:37:26.570 --> 01:37:29.430 And then if self.player.invincible is true, 01:37:29.430 --> 01:37:32.060 then maybe you render him with a rainbow animation. 01:37:32.060 --> 01:37:35.960 And then in any of the functions where he would collide and die with an enemy, 01:37:35.960 --> 01:37:38.160 he no longer dies, he just kills them. 01:37:38.160 --> 01:37:44.690 So that's sort of the gist behind how you would interact with objects 01:37:44.690 --> 01:37:46.520 and how to process it. 01:37:46.520 --> 01:37:50.160 Game objects are different. 01:37:50.160 --> 01:37:54.470 Like I said earlier, these are examples of some of the objects here we can see. 01:37:54.470 --> 01:37:58.100 The gems on the bottom left there are all in the distro. 01:37:58.100 --> 01:38:00.740 If you hit a block-- and if we have a few minutes, 01:38:00.740 --> 01:38:02.690 I'll show you really quickly how that works-- 01:38:02.690 --> 01:38:06.590 if you hit a block, you'll have a chance to spawn a gem. 01:38:06.590 --> 01:38:11.000 If you collect the gen-- which means if you collide with that game object-- 01:38:11.000 --> 01:38:13.406 increment your score 100. 01:38:13.406 --> 01:38:16.280 These are all other objects that I didn't have any time to implement. 01:38:16.280 --> 01:38:21.366 But just off the gate, just as a mental exercise, 01:38:21.366 --> 01:38:23.240 how do you think we could implement a ladder? 01:38:26.570 --> 01:38:28.760 Yeah. 01:38:28.760 --> 01:38:30.700 AUDIENCE: You would just have a climb state. 01:38:30.700 --> 01:38:34.136 And if the player is touching a ladder and presses a certain key, 01:38:34.136 --> 01:38:37.010 they would enter the climb state, and that would cause them to go up. 01:38:37.010 --> 01:38:37.926 COLTON OGDEN: Correct. 01:38:37.926 --> 01:38:41.990 So what Tony said was if they go onto a ladder, 01:38:41.990 --> 01:38:43.820 they should go into a climb state. 01:38:43.820 --> 01:38:45.861 And depending on whether they're in a climb state 01:38:45.861 --> 01:38:49.540 or not, if they press a button, they should go up or down. 01:38:49.540 --> 01:38:50.870 And then you would check. 01:38:50.870 --> 01:38:54.140 If they're at the top of the ladder, get off the climb state, 01:38:54.140 --> 01:38:56.060 go into a walk state. 01:38:56.060 --> 01:38:59.375 Or if they're at the bottom of the ladder, go into a walk state. 01:38:59.375 --> 01:39:02.000 And that's just another game object that you just collide with, 01:39:02.000 --> 01:39:03.841 and then it's a new state for you. 01:39:03.841 --> 01:39:04.340 Yes. 01:39:04.340 --> 01:39:06.288 AUDIENCE: You may actually want it in a fall state, 01:39:06.288 --> 01:39:08.885 because that way you could have a ladder that doesn't actually go anywhere, 01:39:08.885 --> 01:39:09.926 it just gives you height. 01:39:09.926 --> 01:39:13.481 But you could use that to jump over wide gaps. 01:39:13.481 --> 01:39:15.230 COLTON OGDEN: What Tony said was you could 01:39:15.230 --> 01:39:17.980 have the ability to jump off a ladder. 01:39:17.980 --> 01:39:19.540 Is that what you Said Yeah. 01:39:19.540 --> 01:39:21.920 The ability to jump off a ladder so that you can then use it as an obstacle. 01:39:21.920 --> 01:39:22.878 That's absolutely true. 01:39:22.878 --> 01:39:26.950 Actually, in the mock up that we saw up here, it's super hard to see. 01:39:26.950 --> 01:39:28.925 I'll see if I can maybe zoom in on it here. 01:39:35.920 --> 01:39:41.740 Mario, Mario, graphics, and then it's called full sheet. 01:39:41.740 --> 01:39:46.470 The whole entire sheet that I used for this lecture is called fullsheet.png. 01:39:46.470 --> 01:39:49.560 I don't know what that is. 01:39:49.560 --> 01:39:56.886 So if you zoom in really high here, we can see effectively 01:39:56.886 --> 01:39:58.010 what you were alluding to-- 01:39:58.010 --> 01:40:01.181 right here, this little rope thing. 01:40:01.181 --> 01:40:03.180 I'm guessing for the sake of this mock up that's 01:40:03.180 --> 01:40:04.680 what they were trying to illustrate. 01:40:04.680 --> 01:40:07.970 But you have a game object that lets you go into a climb state. 01:40:07.970 --> 01:40:11.090 Whether it's a ladder or whether it's a rope, 01:40:11.090 --> 01:40:15.636 just add a new state for the player. 01:40:15.636 --> 01:40:18.260 If they're in that climb state, then we have this new animation 01:40:18.260 --> 01:40:21.830 which we saw in the sheet earlier, which was their back or their front. 01:40:21.830 --> 01:40:24.830 And then they just climb up it and just update it 01:40:24.830 --> 01:40:27.881 if they're moving up or down the ladder. 01:40:27.881 --> 01:40:29.880 And then just give them the ability to jump off. 01:40:29.880 --> 01:40:32.255 And then when you get to the top or bottom, just get off. 01:40:33.950 --> 01:40:37.580 And you could think a lot of the same thing with a lot of these obstacles, 01:40:37.580 --> 01:40:39.410 like the spikes here. 01:40:39.410 --> 01:40:43.410 If you're jumping and you hit it, you should probably die. 01:40:43.410 --> 01:40:50.070 And so you would check for if the object.ID maybe is equal to spikes 01:40:50.070 --> 01:40:55.230 or whether object.lethal equals true. 01:40:55.230 --> 01:40:56.850 Same thing with this one. 01:40:56.850 --> 01:41:01.410 And then some obstacles are completely cosmetic, like this mushroom here. 01:41:01.410 --> 01:41:04.770 In the case of the distro, bushes and mushrooms and cacti 01:41:04.770 --> 01:41:07.230 and all those sorts of things are just completely cosmetic, 01:41:07.230 --> 01:41:08.310 so you can walk through them. 01:41:08.310 --> 01:41:11.130 They don't trigger collision, but they're rendered as game objects. 01:41:11.130 --> 01:41:12.546 They're not part of the tile grid. 01:41:17.180 --> 01:41:19.560 They don't get processed in the same way as tiles. 01:41:19.560 --> 01:41:20.910 They're not stored in the y, x. 01:41:24.930 --> 01:41:27.760 So that's effectively how we can start thinking about objects 01:41:27.760 --> 01:41:29.010 and how to give them behavior. 01:41:29.010 --> 01:41:32.400 Part of the assignment is going to be adding a flag. 01:41:32.400 --> 01:41:34.300 So this flag is in the sprite sheet. 01:41:34.300 --> 01:41:36.150 So what you'll do-- 01:41:36.150 --> 01:41:38.760 and I'll touch on this at the end of the lecture here. 01:41:38.760 --> 01:41:41.910 We're getting close to it. 01:41:41.910 --> 01:41:44.490 These keys here actually at the bottom right-- 01:41:44.490 --> 01:41:47.150 so part of the assignment will be to-- 01:41:47.150 --> 01:41:48.430 it's actually right here. 01:41:48.430 --> 01:41:50.040 So I'll go over it really quickly. 01:41:50.040 --> 01:41:52.320 Ensure the player always starts above solid land. 01:41:52.320 --> 01:41:56.830 So in this case, when James came up here and ran, you ran the first example. 01:41:56.830 --> 01:41:58.770 The very first time that we spawned the game, 01:41:58.770 --> 01:42:06.180 it generated a chasm right where the player spawned at x1. 01:42:06.180 --> 01:42:08.480 And so he just falls to his death if that happens. 01:42:11.332 --> 01:42:13.290 Just right off the gate, anybody have any ideas 01:42:13.290 --> 01:42:16.920 as to what we could do to check to see if we're at solid land, 01:42:16.920 --> 01:42:21.321 assuming that the player's default start is at x1? 01:42:21.321 --> 01:42:24.243 AUDIENCE: At that tile, check if it's solid. 01:42:24.243 --> 01:42:27.824 If not, then just move it there over x until it's true. 01:42:27.824 --> 01:42:28.615 COLTON OGDEN: Yeah. 01:42:28.615 --> 01:42:31.590 What we probably want to do is look all the way down the column, 01:42:31.590 --> 01:42:36.400 because we start towards the top. 01:42:36.400 --> 01:42:39.610 If we find that there's no tiles down there-- it's just pure chasm-- 01:42:39.610 --> 01:42:42.660 we probably want to shift the player. 01:42:42.660 --> 01:42:45.720 And then random keys and locks-- 01:42:45.720 --> 01:42:50.760 let me open up LevelMaker so we can see what you'll be interacting with, 01:42:50.760 --> 01:42:53.940 because most of what you'll be doing actually is in LevelMaker. 01:42:58.080 --> 01:43:01.830 It does a lot of what we did before with just math.random, 01:43:01.830 --> 01:43:05.670 and then it will insert into objects. 01:43:05.670 --> 01:43:10.290 So objects is a table here. 01:43:10.290 --> 01:43:14.410 It will insert a game object depending on some logic. 01:43:14.410 --> 01:43:17.627 So in this case, if we're generating a pillar, 01:43:17.627 --> 01:43:19.710 we have a chance to generate a bush on the pillar. 01:43:19.710 --> 01:43:23.940 So if math.random(8) is 1, in this case, we're already generating a pillar. 01:43:23.940 --> 01:43:26.935 So we have an additional chance that's on top of the chance 01:43:26.935 --> 01:43:27.960 to generated a pillar-- 01:43:27.960 --> 01:43:33.180 so basically, I think it's a 1 in 64 chance on that particular iteration 01:43:33.180 --> 01:43:36.170 to generate a pillar with a bush. 01:43:39.010 --> 01:43:42.330 You just add a new game object to objects. 01:43:42.330 --> 01:43:44.710 In this case, this is the constructor for a game object. 01:43:44.710 --> 01:43:48.420 You give it an x, y, width, height, and then a frame. 01:43:48.420 --> 01:43:52.230 And then the frame is relative to whatever quad table matches the texture 01:43:52.230 --> 01:43:52.770 string here. 01:43:52.770 --> 01:43:56.185 So bushes is the texture, and so whatever quad in bushes 01:43:56.185 --> 01:43:57.060 you want to give it-- 01:43:57.060 --> 01:44:02.340 in this case, we just gave it a random frame from that. 01:44:02.340 --> 01:44:05.600 And then a lot of the same logic applies to other parts. 01:44:05.600 --> 01:44:08.550 This is another part where we generate bushes just on flat land. 01:44:08.550 --> 01:44:10.240 We have a chance to generate a block-- 01:44:10.240 --> 01:44:13.330 1 in 10 chance this is a jump block. 01:44:13.330 --> 01:44:16.350 So here we have texture, x, y, width, height, frame. 01:44:16.350 --> 01:44:18.570 Notice that we have collidable is true, and this 01:44:18.570 --> 01:44:22.980 is how we can test to see whether a tile is collidable or not. 01:44:22.980 --> 01:44:26.140 Hit is false, meaning that we haven't hit it yet. 01:44:26.140 --> 01:44:33.870 And if we have hit it, then we do this code basically-- onCollide gets called. 01:44:37.170 --> 01:44:40.865 You can see where this gets called in the collision code for-- 01:44:43.800 --> 01:44:45.165 if we look in Player. 01:44:52.800 --> 01:44:55.200 Player has check left collisions, check right collisions, 01:44:55.200 --> 01:44:59.280 and check object collisions. 01:44:59.280 --> 01:45:02.145 It doesn't have check up and down collisions. 01:45:02.145 --> 01:45:04.020 There is a corner case for both of those such 01:45:04.020 --> 01:45:05.519 that the logic had to be duplicated. 01:45:05.519 --> 01:45:06.960 I forget exactly why. 01:45:06.960 --> 01:45:10.550 But you basically get a list of objects that you check for. 01:45:15.840 --> 01:45:17.856 Oh, the reason why is because when you get 01:45:17.856 --> 01:45:19.980 the collided objects when you're in the jump state, 01:45:19.980 --> 01:45:22.840 you trigger the onCollide function. 01:45:22.840 --> 01:45:26.560 So let's go to PlayerJumpState. 01:45:26.560 --> 01:45:34.434 If we're in the jump state, this is where we would basically 01:45:34.434 --> 01:45:37.350 check to see if we've gotten any objects that collide with the player. 01:45:37.350 --> 01:45:41.690 If it's solid, call its onCollide function, object.onCollide, and then 01:45:41.690 --> 01:45:44.720 we just pass in the object itself, basically. 01:45:44.720 --> 01:45:48.100 And so if you go back to LevelMaker, that's 01:45:48.100 --> 01:45:50.290 where we write the onCollide function. 01:45:50.290 --> 01:45:55.000 We write the onCollide function within the game object here. 01:45:55.000 --> 01:45:56.750 So we just give it an onCollide, remember, 01:45:56.750 --> 01:45:58.940 because functions are first class citizens. 01:45:58.940 --> 01:46:03.000 We can just say onCollide gets function obj, 01:46:03.000 --> 01:46:06.440 where obj is going to be this object. 01:46:06.440 --> 01:46:12.130 If it wasn't hit already, one in five chance to spawn a gem-- 01:46:12.130 --> 01:46:14.260 so going to create a gem. 01:46:14.260 --> 01:46:16.320 It's got all the same stuff in it. 01:46:16.320 --> 01:46:20.710 In this case, it has its own function called onConsume. 01:46:20.710 --> 01:46:24.289 onConsume takes a player and an object. 01:46:24.289 --> 01:46:26.080 And then this is all arbitrary, by the way. 01:46:26.080 --> 01:46:27.980 You can create whatever functions you want. 01:46:27.980 --> 01:46:31.570 These are callback functions, effectively. 01:46:31.570 --> 01:46:35.050 We're just going to play the pickup sound and then add 100 to our score. 01:46:35.050 --> 01:46:38.830 And then here, in the event that we did get a gem, 01:46:38.830 --> 01:46:41.920 we tween it over the course of 0.1 seconds. 01:46:41.920 --> 01:46:46.040 We tween its y to be from below the block to up above, 01:46:46.040 --> 01:46:49.070 so it has an upwards animation, effectively. 01:46:49.070 --> 01:46:50.820 And then we have another sound that plays. 01:46:50.820 --> 01:46:53.111 But that's effectively how we're spawning game objects. 01:46:53.111 --> 01:46:56.787 Game objects have textures, x, y, width, height, and then 01:46:56.787 --> 01:46:58.870 you can give them callback functions that you then 01:46:58.870 --> 01:47:01.360 execute wherever it's relevant to you. 01:47:01.360 --> 01:47:05.650 In this case, you'll only really need to worry about onCollide, 01:47:05.650 --> 01:47:12.310 because the assignment is create random keys and locks. 01:47:12.310 --> 01:47:15.380 They have to be the same color, but you can choose them at random. 01:47:15.380 --> 01:47:18.190 If the player collides with the key, then he 01:47:18.190 --> 01:47:21.820 should probably get some flag that's like key obtained is true 01:47:21.820 --> 01:47:23.140 or something like that. 01:47:23.140 --> 01:47:27.900 And then you go to the block that spawns in the level, 01:47:27.900 --> 01:47:31.150 so you should spawn a block with that same color. 01:47:31.150 --> 01:47:37.300 And then on collide, you should unlock it, so get rid of the block 01:47:37.300 --> 01:47:40.900 and then spawn a new game object-- 01:47:40.900 --> 01:47:42.250 the flag. 01:47:42.250 --> 01:47:44.770 And then that flag will have its own on collide. 01:47:44.770 --> 01:47:47.830 And when you collide with the flag, restart the level. 01:47:47.830 --> 01:47:51.040 And that's effectively the gist behind the problem set. 01:47:51.040 --> 01:47:52.720 So it probably shouldn't take-- 01:47:52.720 --> 01:47:57.570 I would say probably maybe 40 or 50 lines of code probably should do it. 01:47:57.570 --> 01:47:59.530 AUDIENCE: That game object-- 01:47:59.530 --> 01:48:01.762 was that a class? 01:48:01.762 --> 01:48:02.470 It's not a table. 01:48:02.470 --> 01:48:03.460 What is that? 01:48:03.460 --> 01:48:05.200 COLTON OGDEN: It is a class. 01:48:05.200 --> 01:48:06.460 There's a GameObject class. 01:48:06.460 --> 01:48:11.290 A game object is basically-- and I realize I didn't touch on it too much. 01:48:11.290 --> 01:48:18.760 In the context of this distro, you could almost think of it as an entity. 01:48:18.760 --> 01:48:21.640 In this case, what I've done is I've differentiated 01:48:21.640 --> 01:48:24.040 between living things and non-living things 01:48:24.040 --> 01:48:28.960 as being entities versus game objects, which is a semi-arbitrary distinction. 01:48:28.960 --> 01:48:31.870 But for a small project like this, it makes sense. 01:48:31.870 --> 01:48:37.390 For a large project, I would probably create everything as an entity 01:48:37.390 --> 01:48:39.820 and then give different kinds of entities 01:48:39.820 --> 01:48:41.654 their own behavior and their own components, 01:48:41.654 --> 01:48:43.986 sort of like how you do with an entity component system. 01:48:43.986 --> 01:48:46.305 AUDIENCE: Are there two ways to create a class in Lua, 01:48:46.305 --> 01:48:50.530 one with the curly brackets and one with regular parentheses? 01:48:50.530 --> 01:48:53.510 COLTON OGDEN: There is, actually. 01:48:53.510 --> 01:48:56.650 So the question was, is there more than one way to create a class in Lua 01:48:56.650 --> 01:48:59.690 whether it's parentheses or curly brackets? 01:48:59.690 --> 01:49:00.190 Yes. 01:49:00.190 --> 01:49:02.315 I don't think I've ever actually talked about this. 01:49:05.430 --> 01:49:08.290 Let's go back to LevelMaker. 01:49:08.290 --> 01:49:12.550 If you instantiate a class and that class 01:49:12.550 --> 01:49:22.930 takes in just a table as its only argument, you can just pass in this. 01:49:22.930 --> 01:49:25.790 This effectively is that argument table. 01:49:25.790 --> 01:49:27.400 You don't need parentheses. 01:49:27.400 --> 01:49:30.141 It's effectively doing this-- 01:49:30.141 --> 01:49:32.140 same thing, only you don't need the parentheses. 01:49:32.140 --> 01:49:35.220 AUDIENCE: And then that's a table that's being passed in? 01:49:35.220 --> 01:49:37.820 COLTON OGDEN: Correct. 01:49:37.820 --> 01:49:40.840 It's just an alternative form of instantiation 01:49:40.840 --> 01:49:44.394 on things that only take a table as their argument for when 01:49:44.394 --> 01:49:45.310 they get instantiated. 01:49:45.310 --> 01:49:47.680 AUDIENCE: And you can only have one table in that case? 01:49:47.680 --> 01:49:49.935 COLTON OGDEN: Correct, yes. 01:49:49.935 --> 01:49:53.572 AUDIENCE: Wouldn't it be easier to create a new class which 01:49:53.572 --> 01:49:59.392 would have its own set of game objects so you 01:49:59.392 --> 01:50:04.260 would create a gem which would be helper dot gem, which 01:50:04.260 --> 01:50:06.754 would in turn create a game object? 01:50:06.754 --> 01:50:08.628 COLTON OGDEN: Can you say that one more time? 01:50:08.628 --> 01:50:10.500 AUDIENCE: It's kind of hard to explain. 01:50:10.500 --> 01:50:15.420 Wouldn't it be easier to create another class which 01:50:15.420 --> 01:50:19.028 would have your gem and everything, and your gem in that class 01:50:19.028 --> 01:50:20.504 would be a game object? 01:50:20.504 --> 01:50:22.472 But in this class, when you wanted a gem, 01:50:22.472 --> 01:50:27.400 you would say local gem equals helper class dot gem. 01:50:27.400 --> 01:50:30.750 COLTON OGDEN: The question was, wouldn't it 01:50:30.750 --> 01:50:37.120 be easier to create a helper class that would allow you to instantiate gems? 01:50:37.120 --> 01:50:37.620 Possibly. 01:50:40.260 --> 01:50:45.690 I think if you were going to design this a little bit more robustly, 01:50:45.690 --> 01:50:47.490 and if this were going to be a larger game, 01:50:47.490 --> 01:50:51.539 then you would just create a subclass for blocks, gems, et cetera. 01:50:51.539 --> 01:50:54.330 To shrink the number of files that we had in the distro and to sort 01:50:54.330 --> 01:50:59.040 of consolidate everything together and put all of the level code together 01:50:59.040 --> 01:51:04.440 in one spot, I decided to just create GameObject as an abstract class that 01:51:04.440 --> 01:51:07.950 you could then just create your own behavior for within the actual 01:51:07.950 --> 01:51:11.340 constructor-- which is this bit here, which is just the table-- 01:51:11.340 --> 01:51:14.742 and then allow you to override the onCollide and onConsume functions. 01:51:14.742 --> 01:51:16.950 You can actually give it whatever functions you want. 01:51:16.950 --> 01:51:22.540 You could give this some arbitrary named function and then test for it later. 01:51:22.540 --> 01:51:26.490 This is almost like an obscure way of inheritance. 01:51:26.490 --> 01:51:31.980 But I think if I were to engineer this with the goal of making it a really 01:51:31.980 --> 01:51:34.260 large game, I would just subclass. 01:51:34.260 --> 01:51:38.850 I would just create a class for gem, a class for block, a class for bush, 01:51:38.850 --> 01:51:40.830 et cetera, et cetera. 01:51:40.830 --> 01:51:43.170 It wasn't strictly necessary for this example, 01:51:43.170 --> 01:51:45.090 so we ended up keeping everything a little bit 01:51:45.090 --> 01:51:52.470 more abstract in a sense-- a little bit more general purpose. 01:51:52.470 --> 01:51:57.790 But yeah, you could definitely create classes for those. 01:51:57.790 --> 01:52:00.060 And if you were in an entity component system, 01:52:00.060 --> 01:52:03.405 you could have a consumable component. 01:52:03.405 --> 01:52:05.280 And then that consumable component would then 01:52:05.280 --> 01:52:09.510 allow you to give it some sort of behavior that affects the player when 01:52:09.510 --> 01:52:11.221 the player consumes that object. 01:52:11.221 --> 01:52:13.470 In this case, a gem is a consumable, so you would just 01:52:13.470 --> 01:52:16.290 give it a consumable component with a texture of the gem 01:52:16.290 --> 01:52:18.510 and then give it a callback function that 01:52:18.510 --> 01:52:20.025 just increments the player's score. 01:52:20.025 --> 01:52:24.060 You could probably put that in 10 or 15 lines of code. 01:52:24.060 --> 01:52:25.330 It would be pretty easy. 01:52:25.330 --> 01:52:29.680 And then blocks would be a spawner component, 01:52:29.680 --> 01:52:31.170 so they have a chance to spawn. 01:52:31.170 --> 01:52:36.560 And then you would pass in that spawner component a gem maybe, 01:52:36.560 --> 01:52:40.560 so it would have a chance to spawn the gem that you passed into the spawner 01:52:40.560 --> 01:52:46.030 component-- and then also a solid component to say, oh, this is solid. 01:52:46.030 --> 01:52:48.102 So if I hit it, I should trigger a collision 01:52:48.102 --> 01:52:49.560 and not be able to walk through it. 01:52:49.560 --> 01:52:53.760 So you just layer on these components. 01:52:53.760 --> 01:52:58.650 I would encourage you to think about this way of composing 01:52:58.650 --> 01:53:02.340 your objects a little bit, particularly as we get towards Unity, which 01:53:02.340 --> 01:53:07.560 makes a lot of use of this concept. 01:53:07.560 --> 01:53:10.290 In short, yes. 01:53:10.290 --> 01:53:12.326 I think that's pretty much everything. 01:53:12.326 --> 01:53:13.200 Let me just go ahead. 01:53:13.200 --> 01:53:18.090 We're running out of time here, but like I said, one more time-- 01:53:18.090 --> 01:53:21.504 make sure the player starts above solid land, random color key and lock, 01:53:21.504 --> 01:53:24.420 and then make sure that when you get the key, you can unlock the lock, 01:53:24.420 --> 01:53:25.920 and that spawns the goal. 01:53:25.920 --> 01:53:30.540 So this is all something you can just add to the LevelMaker class, 01:53:30.540 --> 01:53:35.620 and it will all work with your game level that way. 01:53:35.620 --> 01:53:39.210 And then you touch the goal flag, then respawn the level. 01:53:39.210 --> 01:53:41.520 So today, we talked about Super Mario Bros. 01:53:41.520 --> 01:53:44.850 The other big Nintendo game of that era-- 01:53:44.850 --> 01:53:47.700 arguably one of the greatest of all time-- is Zelda. 01:53:47.700 --> 01:53:51.840 So we'll be talking about a very simple Legend of Zelda game, 01:53:51.840 --> 01:53:54.810 where we just have a random dungeon that we can go through, 01:53:54.810 --> 01:53:57.480 a top down perspective, fight simple monsters, 01:53:57.480 --> 01:54:00.780 open chests, blow up walls-- that sort of thing. 01:54:00.780 --> 01:54:02.880 We'll talk about triggers and events. 01:54:02.880 --> 01:54:06.530 And then we'll talk about hurt boxes, inventory, 01:54:06.530 --> 01:54:09.950 a very simple GUI for opening up a menu, and then world states 01:54:09.950 --> 01:54:13.710 so that we can see which doors have been blasted open so that they 01:54:13.710 --> 01:54:15.840 render appropriately and whatnot. 01:54:15.840 --> 01:54:16.970 And that's it for Mario. 01:54:16.970 --> 01:54:17.970 Thanks a lot for coming. 01:54:17.970 --> 01:54:20.120 I'll see you guys next time.