字幕列表 影片播放 列印英文字幕 COLTON OGDEN: All right. Good evening, everybody. Welcome back to GD50. This is lecture six. And today we're going to be venturing out of the 8-bit world and back into a more modern era of gaming. We're talking about Angry Birds today. I have pretty fond memories of Angry Birds, actually. It was the first mobile game that I remember playing where I realized that mobile was actually a viable platform for gaming. I played it, I think, back in 2009, was when it was first released. And I really enjoyed it. It has a very simple formula. The goal, if you're unfamiliar, is you have birds that you control by a sling shot on the left side of the screen. And then these pigs that have stolen your eggs, you're trying to destroy them or kill them by knocking down their fortresses made out of various materials, wood, glass, metal. And you get a few different bird types. But the gist of it is, basically, just sling shot a bird into some structure, knock it down. And the whole entire underlying mechanism for how things work is via a physics engine, which we'll talk about today in lecture, called Box2D Probably the most ubiquitous 2D physics engine. But that's the overall structure of the game. It's literally just throwing things into structures, knocking them down. And then that tactile and fun game play makes for really great mobile gaming. And I enjoyed it a lot back in the day. This is a screenshot of the first level in the first Angry Birds, which most people I think have probably seen. This is a sample from one of their newer games. As you can see, it's taken a whole new layer here. There's stone and a bunch of intricately varying structures and new creatures and stuff like that. The game has changed a lot, but the formula has stayed the same. And today we'll explore the foundation of what makes this game work. And the topics today, a smaller than usual. But Box2D, we can consider as being a pretty large topic. We'll be talking about Box2D, which is the physics engine we'll be using today in lecture. And via Love2D, which has its own wrapper for Box2D. And we'll also talk a little bit about mouse input, which you haven't really done a lot. But it's very apt, especially in the context of mobile gaming. Because mouse input and touch input are synonymous. But first, let's get a lecture demo. If we have a volunteer come up on stage to showcase what I've put together here. So let me make sure I'm in the right directory here. So whenever you're ready, go ahead and press Enter. So this is a little demo I put together to demonstrate the concepts we'll talk about today in class. This is just the Start screen, but it already shows Box2D representing a bunch of these square shaped aliens, which we'll actually be what we're trying to target in the game. But notice that they all fell and had their own collision and their own physics that took place, and I didn't have to manually code rotation and stuff like this. This is all stuff that Box2D takes care of for us. And we'll see it used to great effect soon. If you go ahead and just click anywhere on the screen, we'll go to the main part of the game. So this is a very simple representation of what Angry Birds is. You start on the left side of the screen. You have a bird, in this case an alien, we used a free art pack that uses aliens instead of birds, but it's the same concept. You have an alien that you can click and drag. So if you click it and then drag around, you can see the trajectory that you'll have when you let go of the mouse. So we're simulating where it's going to go via these purple circles. And then the goal in Angry Birds is to throw the bird into, or the alien, into the fortress guarding the pigs, or in this case, square shaped aliens. So if you go ahead and just launch the bird by letting go of the mouse you'll see. Oh, there we go. And we knocked down the. OK. What happened was we shot the alien into the structure. It destroyed one of the wooden blocks guarding the other alien. And then, as soon as that happened, the top box, because these are all being simulated via Box2D's physics engine, that other box was detected as being unsupported. So it fell down, it hit the other alien. And we've coded it so that when a collision occurs between an obstacle and our alien of sufficient speed, it should kill the alien. Which is how it works in Angry Birds. So if you'll try it again. And this time we hit both of those at the same time. So it triggered both of those being deleted. But this guy is still alive. So after it stops moving, which is similar to how it behaves in the original game, it's going to let us try again to shoot. So if we try one more time. And then we launch and hit it, we kill it and then we get a victory. So that's the overall underlying foundation for what Angry Birds is. Obviously we're using a very simple representation. It doesn't have a lot of the frill that Angry Birds does. But we could easily build upon this and create a fully fleshed out game very similar to Angry Birds. So thanks, Steven. I appreciate the demonstration. So that's our goal today. We'll be talking about how to construct a very basic but functional simulation of what Angry Birds is at its core. Which is just flinging things into obstacles, destroying them, and ultimately destroying the things that they're protecting, the aliens, the pigs that are in the base game. So here's a shot of what the different sprite sheets we're going to be using are. There's a really great sprite sheet that I got off of Open Game Art. Kenny is the artist. He makes a lot of great art. If you notice, it's very similar looking to the art that we use in the Mario lecture. Actually, it's the same artist. So if you're ever looking for assets, he's got a ton of awesome assets on open game art. So we have a set of aliens, square and round shaped. I decided just arbitrarily we made the round shaped aliens the birds. So we shoot those into the structures that are protecting the square shaped aliens. The square shape aliens will be the bad guys in this case. And then we have another sprite sheet down here below on the left side. Which just, I used the tile here just to make a ground element. The rest of these you could easily include in the game if you wanted to, but we're only using the ground here. And then notice here, we have this large sprite sheet which has a bunch of different shapes and sizes of materials. The whole sprite sheet comes with metal and explosive and glass sheets as well. But just for simplicity we only use the wood here. But notice that we have entirely whole pieces and then we have pieces that are partially destroyed and then pieces that are hollow. You could easily model all of these in your game and use them. But we only decided to use just a couple of these, which was just the horizontal and the vertical ones that are completely whole. These, unfortunately, don't have quite the same systematic layout as the sprites that we used before. They're not laid out in a grid evenly spaced. So in this case, in util dot Lua, I ended up hard coding the different XY width and height quads for each of these, which is what you have to do in a situation where you're interacting not with tiles per se, but with more organic shaped objects. So it can take a little more time to end up constructing all of the quads for your objects when you have a sprite sheet like this. But, fortunately, you only have to do it once. Here's a few useful links before we get started in talking about what Box2D is and how to use it, and basically how it's called love dot physics, effectively, in love2D. The first two links are documentation for love 2D, what the functions and objects are that we'll be talking about. And a simple tutorial that talks about how to make a ball bounce in love 2D using Box2D. The third is a great resource that I used actually to learn most of what I know about Box2D, especially in the context of this lecture. So it talks about a lot of different concepts. It talks about all the things that we'll be talking about. And then it goes into a lot more detail about how you can go about constructing a lot of really cool, crazy things like tanks and pulleys and a whole bunch of different things that are worth looking into if you're looking into potentially making a physics based game. Obviously we won't be going into things of that complexity here. But you could easily do it with Box2D. It makes it very, very possible. So the very first thing that we should talk about when we want to construct a game, or simulation, or whatever we want to do, with Box2D is we need some sort of system that will actually perform the simulation for us. And the fundamental core of what a Box2D app or game is, the core is the world object. So there is a world that you can think of as sort of being your world, but what it effectively is is a machine that simulates all of the pieces that you've told it interact with each other in Box2D. So Box2D has a set of objects called fixtures and bodies. Those perform the physical interactions. And it's up to the world to update all of those and apply the relevant forces and physics calculations that resolve collisions and do all sorts of things. All of the things that Box2D does for us, the world takes care of this for us. So we don't have to manually go through and update every single object with its velocity that we've done before and check for collisions. The world does this for us, and resolves them based on how we tell it to resolve them. And the world also possesses, like an actual world would, gravity on the x and y-axis. In this case, we have gravity applied on the y-axis going down, so it's a positive value. We set it to 300 in this distro, but you can set it to whatever you want. Setting a lower gravity would have the effect of making it feel like we're on the moon or something. Making it feel like we're on a different planet. So that's what the world is. The world simulates everything. And we'll go through just a few terms here before we look at some source code. But there's a few terms that we need to understand before we can really understand what Box2D is doing. And this is the function that we use to create a new world in love2D. Very simple, love dot physics dot new world. And this love dot physics is just a name space that encapsulates all of the Box2D functions and objects that love2D has access to. So anything that you see in love dot physics is effectively a wrapper for Box2D. And to clarify about Box2D, Box2D is just a library that's written in C++ that you can plug-in pretty much anywhere you want to. Unity uses it and, actually, most 2D game engines that I've ever seen including Live GDX, for example, which is a very large Java 2D game framework uses Box2D. You can use it anywhere. In this case we're using love2D's own wrapper for it. So the people that created love 2D, they took Box2D and then they just put a bunch of Lua functions around them, around all the objects and functions to make it possible to use it in the same style that we use the rest of the framework. This is how you create a new world. This is the first step in getting your Box2D simulation working. So any questions so far as to how we can get that going? OK. So beyond the world object, which is the foundation, sort of sets up our stage, you can think of it as our stage, we need bodies to actually interact with each other. So a body is just an abstract container. It basically holds a position and a velocity. And you attach things to it via what are called fixtures that allow you to give the body a shape, and therefore a collision box, and therefore allow it to interact with other things. But a body is essentially all the disparate things in your scene interact with each other and move around. And so to create a new body we just do love dot physics dot new body. We pass in the world, so therefore when we do this, the world has a reference to this body now. And every time we call update on our world, which we'll see in the source code, it will know, OK, I have a reference to this body, perform all of the relevant checks on the collision for that body and all the fixtures that it contains. Update its position, updates velocity, and so forth. And not only a world, but it also gets an x and a y, which will place it in the world on instantiation. The last parameter here, type. There are three fundamental types of bodies which we'll see in love 2D, static, dynamic, and kinematic. And that basically influences how it'll interact with the other objects, the other bodies, in our scene. So we have the world, which encapsulates everything, all the bodies, all the fixtures. And then we have the bodies, which are the entities in our game world that have position and velocity, effectively. The last key ingredient here that will allow us to create interactions between the bodies that we have are fixtures. And fixtures, all a fixture is, is this abstract object that will allow you to attach a shape to a body. So bodies are shapeless by default. They don't have a shape. They are just a container that has position velocity, effectively. But they don't interact with anything else. And they don't know how to interact with anything else until you give them a fixture. And the fixture you will give the body and a shape. So for example, if you want the bird that we were looking at earlier, the alien, the round alien, we create a body for it in our world, which doesn't mean anything yet. But we say, I'm going to attach a fixture to that alien. I'm going to give it a circle shape. And it'll then know whenever it performs any calculations, that that alien should interact with things as if it were round. And therefore trigger collisions based on a circular hitbox, as opposed to a rectangular or polygonal hitbox, as we'll see. Fixtures, in addition to attaching shapes to bodies, which will, as said here, influence how they collide with other bodies, they have density, which we'll see. So that things with higher density obviously will fall faster, or not fall faster, but they will influence things as if they have more weight. They will push things farther when they collide with them. They also have friction and they have restitution. Restitution is bounciness. So if something, if we had our alien, no restitution when it hits the ground it'll just fall flat. But if we give it a higher restitution it will actually bounce when it hits the ground. And therefore interact with the world a little bit differently. So when we want to take a fixture and apply a shape to a body, we have a few different shapes that we can apply to it that are given to us by default in love 2D. So circle shape, rectangle shape, edge shape. These are just, effectively, how we define how our bodies interact with other bodies. How, for example, if it's something that's circular it should roll when it's moving along the ground. Or when it hits something the corner obviously of it won't hit something because it's rounded, as opposed to something that has a square hit box, it'll affect things in a slightly different way. And we can define arbitrarily shaped hit boxes, thanks to the polygon shape. If we want something to be shaped like a pentagon, for example, and have it roll around and behave like such, we can just define a polygon via a set of vertices. And then affix that to a body and it will behave as if it were pentagon shaped. And this is how you would instantiate just as we've seen with love dot physics dot new world and love dot physics dot new body, love dot physics dot new fixture takes in a body and a shape, and will apply that shape to the body. And the world after that will know exactly how to collide with things. And so the last thing, the last slide we'll look at here before we start looking at source code is what the different body types are. So I alluded to having three different body types before, static, dynamic, and kinematic. So a static body will exist in our world but not actually be affected by gravity or the collision of anything else. Things can hit it and bounce off of it and do their own thing, but the static body will never be influenced by something else. It exists as some sort of permanent structure, almost like the ground. You don't really affect the ground by moving into it and bouncing into it, unless you do it with enough force. But in our Box2D world, a static body cannot be influenced by anything else. A dynamic body is the opposite. It has the full simulation of Box2D. Gravity affects it, things collide into it, it will bounce off of them. It'll do what you would expect a normal body to do. If I throw a ball in this room and it hits the wall, it's a dynamic body. The walls are the static bodies in this case. And then a kinematic body is a hybrid between the two. It's something that can move and can rotate and do things, but it's not influenced by other objects colliding with it. So, for example, if I have a platform that's just spinning indefinitely, but it's not being affected by gravity and it doesn't move when I hit it, that's a kinematic body. It's still moving and it's semi-static and it influences other things, but it's not purely static. It does have a little bit of behavior that it can grant it. So let's go ahead and look at a few examples now and see how this actually looks in code. So I'm going to go into an example, if you're looking in the distro, there is an example called static. So we'll take a look here and see what a static body looks like in our scene. And for all of these examples leading up to Angry Birds, we're just going to render everything with shapes for simplicity. But this, as anti-climactic as it is, is a full Box2D world with just a single static body. And it's just this square here, colored white. The static body doesn't move, it doesn't do anything. Nothing can influence how it moves or behaves. But it exists in our world as a permanent fixture. And if we had dynamic bodies and we threw a dynamic body at it, for example, the dynamic body would bounce off, the static body would stay there permanently. So let's go ahead and take a look at what the source code looks like for that. So I'm here in main dot Lua of our static file. And just as we've seen before, we needed to find a world. So I have a world here on line 45. Love dot physics dot new world, no x-able gravity, but we are going to have 300 units of positive gravity on the y-axis, which is going from top to bottom. We need a body for our square that's in our game world, our static square. So we're going to go ahead and define a new body here. Love dot physics dot new body takes in the world, recall, because that's how our world's going to have a reference to that body, doesn't know about it unless we pass it into here, our new body constructor. And then I'm just going to put it right in the middle of the screen. So virtual width divided by 2 and virtual height divided by 2. The difference between Box2D bodies and things that we've drawn before or seen before is that everything is defined by its center point, as opposed to its top left. So I'm able to say virtual width divided by 2 and virtual height divided by 2 here. But I don't actually need to say virtual width divided by 2 minus whatever the half of that square is. By default, the center point is the XY of that object. And this last string here in the constructor for our new body, static, tells the constructor that this is going to be a static body specifically, not a dynamic body and not a kinematic body. So we have a body and it's static, but it doesn't have a shape, it doesn't really know how to interact with anything else in our world. So we're going to give it a-- we're going to create a new shape first. So love gives us a few functions in the form of new X shape. We have new rectangle shape, new circle shape, new edge shape, a few other ones. We're just going to create a new rectangle of width and height of 10. So that's what the 10 and 10 are here. Then we're going to create a new fixture. We're going to affix the box shape to our body with this function. And then once we do that, all we have to do is then render a polygon with fill and then we get the coordinates for our-- or the vertices for our polygon by saying, body get world points. So that's a function off of any Box2D body that will basically get where it is in the world and all of its vertices. And then you just pass in the shape that you want to get the points for. And that will end up just exploding here into a set of vertices that fill up this love dot graphics dot polygon. And the end result of that is we get a square. And we specifically use polygon instead of love dot graphics dot new rectangle because that's what the get world points function explodes out to. It doesn't explode out to the number of arguments that would satisfy love dot graphics dot rectangle. So not a very exciting example. But this is basically the foundation of a full Box2D application. So any questions as to how this works at all? AUDIENCE: Yeah. I'm wondering. How does it determine the center of a abnormal shape? Not like a polygon, square, rectangle, or circle. COLTON OGDEN: The center of an abnormal shape. I'm not entirely sure. The circle is just-- AUDIENCE: [INAUDIBLE] COLTON OGDEN: Yeah. I'm not entirely sure about a polygon though. I haven't looked into that into too much detail. I can explore that and see. That's something that I think Box2D is-- the actual library implements that and calculates that. Probably based upon calculating the area of-- and to repeat for the camera if I didn't repeat already, it's how does Box2D calculate the center point of something non-symmetrical, like a polygon? And best I can understand is that it would do an area calculation off of it and figure out where all the vertices tend towards, I guess. But, yeah, that's something that's implemented in the library. I'm not entirely sure. I can look into it and see and then I'll post in the Slack. AUDIENCE: It seems like [INAUDIBLE] you did have that, it would be difficult to place in a very precise spot. COLTON OGDEN: Yeah. If it were-- yeah. If we did have an odd shape to get it placed in a exact spot. Yeah, I'm not entirely sure. I'd have to explore that a little bit. Interesting idea though. But, yeah, that's essentially how we get a Box2D world up and running. We have a static body. It's not terribly interesting. But with one change-- so I have a separate example called dynamic in the source code distro. But all we need to do to really see the difference between a static and dynamic body is on line 48, just change static to dynamic, save it, and then rerun it. And then we'll immediately see that it's affected by gravity and it moves downwards as we've told the world that our gravity is set to positive 300. And so that behaves how we would expect it to. Now, there's nothing else in the scene so it's not particularly interesting. So I've created a another example called ground. So let's go ahead and look at that. So if we want more interesting behavior, what do we need to do in a nutshell? AUDIENCE: Have more shapes and make a ground. COLTON OGDEN: Some more shapes and make a ground. Exactly. That's a simple way we can start to get instantly a sense of how powerful Box2D is. Just introduce more shapes that interact with each other in different ways. So ground is an example that just introduces a ground into our scene, so that we can see the box fall down and actually collide with something else. And Love2D makes this nice and easy. Excuse me. They have a actual edge shape that will allow us to basically form where the ground is. Anything that collides with this-- it's effectively a line. But no matter how fast anything moves in are scene, the body will not move past that line. So it's a nice, easy way of getting a ground in our scene without having to implement a polygon that maybe has two vertices going left to right on the screen. So that's what we do here. We have a ground body, which is a static body, recall. Because the ground shouldn't move. We're going to change the box into a dynamic body, but the ground shouldn't move. The ground should be unaffected by anything. It's going to be static in our scene. And it's going to have an edge shape. So notice here, we take 0, 0 as the XY. And then a virtual width is zero. So with shapes, when we define them here, it's not going to draw the shape at 0, 0, at virtual width in 0. This is relative to wherever the body is located. So wherever our body is, this shape will be drawn with these coordinates, this X and Y and this width and height, relative to that. And specifically, relative to the center point of wherever we place a body. So if this ground is set to 0, 0 at virtual width and zero, where do we need to place the actual body for it to render the ground appropriately? AUDIENCE: The middle bottom. COLTON OGDEN: Yeah, so we place it towards the middle bottom. So we actually end up placing the body itself here at virtual height minus 30. And when we affix this edge shape to the body, we'll end up, even though it says 0, 0 virtual width zero, it's relative to wherever the body's XY are. So it's actually going to be 0 virtual height minus 30, virtual width zero will be where that edge exists. And then lastly, just as we did with our box, we need a ground fixture so that the ground body knows how it should interact with other things. So we're going to affix the edge shape, which is just a line, to our ground body. And then here we do love dot graphics-- just as we did with the love dot graphics dot polygon for our box, we're going to do love dot graphics dot line for our ground body. And we're going to do the exact same thing, get world points and get points passing the edge shape here. And I'm also setting a line width of 2 just so we can see it a little bit better. And I'm going to color it red. Again, I colored the box up here green. So go ahead take a look at what this looks like. So I'm going to go into ground. And so notice-- and I also added a little bit of restitution to the-- as I said before, restitution is a quality that a fixture can have which gives it bounciness. So rather than just falling flat down onto the ground, it bounces a little bit as well. And we can see the interaction, so I'll play it one more time. It starts in the middle. And then as soon as it hits the ground body that we created before, the edge shape, it bounces a couple of times. But it shows that the box is dynamic, but the ground is static. Nothing influences the position of the ground. It gets hard set and will stay there permanently. So that shows a nice, easy simple demonstration of an interaction between a static and a dynamic body. I'm going to go ahead and pull up another example here. So kinematic, recall, what was the difference between a kinematic and a static or dynamic body? AUDIENCE: Kinematic can move but not influence other shapes. COLTON OGDEN: Correct. So a kinematic body can move. You can ascribe it positional velocity or angular velocity, it's rotation. But when something collides with it, it's not going to be influenced by it. It's going to influence the body that it collides with. It's going to affect it in some way, but nothing colliding with the kinematic body is going to have an effect on its position or its velocity. It's something that exists and does something programmed and will just do that indefinitely, but it will interact with other dynamic bodies as we have programmed. And it will not interact with other kinematic bodies either. They will almost pretend that each other doesn't exist. If you overlap a static and a kinematic body or kinematic and a kinematic body, they render on top of each other, but they don't actually influence each other's position or anything like that. So I'm going to go ahead and run the kinematic example. So here we have a few things going on. We have the box body that we had before, the dynamic box that falls from the middle of the screen. We have the ground on the very bottom to catch it. But we have three kinematic boxes in the very center that are spinning that influence the green box when the green box collides with them. So as you can see, it tosses it around and then the green body falls back to the bottom. And in this example I've taken away its restitution. So as soon as it hits the ground, it just falls flat. Just like that. And so, as you can see, these blue bodies, they're moving. They have angular velocity indefinitely. Specifically 360 degrees per second. And they will stay in that exact position and rotate in that exact way forever. But as soon as they interact with a dynamic body, they actually cause a collision with that dynamic body. And the collision resolves and this green body gets tossed around because it's dynamic. It will basically do whatever it can to interact with the game world as long as it's interacting with other bodies and resolving its collision that way. And so that's the key example between what the three different bodies are fundamentally. And with these three body types you can construct pretty much any scene that you want to. And obviously the bodies and the fixtures can get insanely complex. I mean, we can look back here at this first example. These structures are a composition of many different types of dynamic bodies that have been given anchors and joints and all sorts of other things to make them look as if they're big constructions. But at the end, they're all just a bunch of little bodies that are welded together, fixtures that are welded together. And these fundamental building blocks are how you construct scenes like this. Put a bunch of these blocks together, weld them together with joints, in this case. We won't cover joints in the context of this lecture. But if you're wondering how all of these individual things can be collideable while still constructing these massive scenes and having physics applied to them, they're just jointed together using weld joints or other types of joints, pulley joints depending on what they are. In this case, there is two circle shapes here, circle fixtures, that are the wheels on this cart and then they're welded to the flat constructions here. Allowing those to be dynamic allows the wheels to roll, and therefore carry the other load with it. Same with this bridge. I forget the exact name of the joint, but it's a chain of fixtures together that are welded by a specific kind of joint. And by putting them together in this way, you get a bridge and all sorts of things that you could think of, including tanks, which is in the notes here. This third link, one of them talks about how to implement a tank by having treads going around circles and then a massive body. You could do anything with Box2D, it's an awesome library. In the context of Angry Birds, really we just scratched the surface for what is possible here. So I'm going to demo now. Actually, I enjoyed, I think, this demo, this bit of code, more than I enjoyed the Angry Birds implementation. And it's a program that I wrote called Ball Pit. Oh, by the way, before I get into that. So I'll leave that as a little teaser, I guess. Looking at kinematic, really quickly. We're going to look and see that I've created a table for the kinematic bodies, a table for the fixtures, and then one shape. Because you only need one shape actually, and you can apply one shape to as many bodies as you want to as long as they all have the same shape. I just create three kinematic bodies here. So spacing them out relative to the center with this math here. They get the string kinematic. And that's really the key difference. And then before finishing, I make sure that I set them to have an angular velocity. So this is how you spin something. If you want to set something to spin indefinitely, just set an angular velocity. In this case, 360 times degrees to radians. And that's just a formula written out as a constant, which is just the number-- I forget offhand what exactly the formula is. But it's up here. It's degrees to radians 0.01745329, et cetera. But just like pi, it's a number that you can use to multiply a number in degrees, and you'll get a number in radians. And there's the reverse up there as well. We have to do this because Box2D expects, for any types of rotation, it expects it in radians. I prefer thinking in degrees. So I passed in 360 times degrees to radians. And so we render down here, just as we did with the box body. We render the kinematic bodies, polygon fill, kinematic bodies at i get world points kinematic shape get points. Nothing terribly different. The only real key difference is that we've added kinematic as a string to the constructor for the body. And we've added some angular velocity. And recall, this will make it spin indefinitely, but it will never be influenced-- its angular velocity will never be influenced, its position will never be influenced by anything else in our scene. So now with that out of the way, I'm going to pull up Ball Pit. And maybe I'm just a little bit too excited about this. But I enjoyed this a lot. So what it is, is this is like a bigger demonstration of putting all these pieces together. We have a bunch of circle shapes. They're all interacting with each other. They all have physics. And then I have a larger shape here, this square, which has a higher density than everything else. And by pressing Spacebar I can just dive into-- throw it into the ball pit, and it'll cause an interaction with everything else. And a bug slash feature that I discovered about this is that if you press Spacebar over and over again, it never resets its velocity. So it just slams down into the ball pit. So it's just kind of fun, and I think there might actually be possibly a game idea in here. But, I mean, well, what are the pieces here? What's different about this? AUDIENCE: They're all dynamic shapes. COLTON OGDEN: Yeah. So they're all dynamic shapes. Except for the ground at the very bottom. And also, hidden from view are actually two more static shapes on the left and right. Because if we didn't have those, all of the balls would fall to the side out of view. But yeah, we have the static delimiters for our scene. But we have a bunch of dynamic bodies, the balls are all dynamic. And then the square is also dynamic. And then, like I said before, the only real difference between them is that obviously the square is a rectangle. But it also just has a higher density. And so by giving it a higher density it pushes everything else-- oh, what happened there? That was weird. It pushes-- I think it went to sleep because we didn't do anything for a while. But it's able to fall through everything else because it knows that it's heavier, and that it should push and apply a larger force to everything else that's around it. And so by using these fundamental building blocks of what Box2D is, you can construct a lot of really cool simulations and other fun programs and actually get interesting game ideas. I'm inclined to believe that Angry Birds started out as somebody messing around with the Box2D, or physics engine like this. And it was inspired by some other game that I should look into a little bit. But the creators of that game, presumably, found this physics engine and were like, oh, this is cool. I'm going to put a tower of blocks here and just throw something at it. And then they realized, oh, we can make a game out of this. And so I encourage you, if you're ever curious to just experiment with things like that. We could probably turn this into a game. I don't know. I like this a lot. It's a good segue from the abstract, for lack of a better word, examples that we used earlier, and merges it more into the realm of how can we make something fun with this? And so that's how we're going to start moving into the distro today. So the main topic of today's lecture is Box2D and how we use it to make a game. Another thing that we should consider is mouse input. We haven't really used it yet. And I believe I've mentioned it before, slightly offhand. But Love2D makes it super easy. It's just like we do with keyboard input. We just have a couple of callbacks that are in main dot Lua, mouse pressed and mouse released. The difference between these and key pressed and key released is that they also get an X and a Y. Because usually, when you click the mouse or release the mouse, you want to know where it happened because that's obviously pertinent to what you're doing when you're using a mouse. So these are fired by Love2D every time you click or release a mouse button. And they get the X the Y and the key, and you can do whatever you want with those. And just as we've done in prior lectures so that we can use mouse input in other modules besides main dot Lua, there's a function called love dot mouse dot key pressed and key released that I implemented in main dot Lua. You can check those out. They're very similar to how we did the input tables for the keyboard before. But they allow us to use this functionality inside of other functions, other modules, besides main dot Lua. So let's go ahead and start looking at-- this is where we're going to start looking at the distro for Angry Birds and how we can take all these pieces and form them into an actual game. So the first thing we'll do, we'll take a look at just a couple of things and then we'll take a short break and then we'll get more into the meat of it. But let me go ahead and clear out all of these. And then we're going to pull up-- so the distro is in Angry 50. And so main dot Lua is here. So not a whole lot is different in here. So we have two states in our game. So we had a start and a play state, as we saw before. The start state is like we've done before. The only difference is in this start state, well, for one, it's running a Box2D simulation. And two, we're actually using mouse input. So actually, let's look at main so you can see where I've added this, which is different than before. So we have love dot key pressed, as we've seen before. But we also have mouse pressed and mouse released. And then mouse was pressed and mouse was released. So those are the main differences in main dot Lua this time, as opposed to last prior lectures, which we only had keyboard input. And as you can see here, we have input tables for keys pressed and released on the mouse. And so we initialized those to empty on every update frame just like we've done with the keyboard. And then the input tables get updated in the callback functions as we've done before. And so that's basically all that's different about main dot Lua this time. And that's how we've tied together the new mouse functions that we've just looked at into our game. The states that exist in our game are play state and start state. So very simple. Very similar to last week where we looked at Zelda, we only basically had a start state and a play state. The start state, we can see here, just to tie together the last bit of our usage of the mouse. Love dot mouse dot was pressed 1. Does anybody know what this 1 is? AUDIENCE: Is that left click? COLTON OGDEN: It is left click. So Love2D assigns integer values to all of your mouse buttons. And 1 is traditionally the default value for left click. Some frameworks will use 0. But Lua, 1 index so start with 1 instead of 0. The thing about the start state here that's kind of cool and interesting. So I'm going to go ahead and play it again, the game. So we start off and right off the gate, just to make things interesting rather than just have a static screen that says Anger 50 click to start, we're actually running a Love2D, a Box2D simulation here. It's a world with a bunch of squares. And so what kind of bodies are all of these? AUDIENCE: [INAUDIBLE]. COLTON OGDEN: They're all dynamic bodies. And we've encapsulated them all, just as we did before in the ball pit example, with some invisible static bodies on the left, right, and bottom of the screen. Because if we didn't have those static bodies they would just fall all the way down. And the nice thing is we don't actually have to render anything. So if you want maybe an invisible barrier for something in your game, or you want to encapsulate something, something physical, you don't have to render anything. You can just have arbitrarily shaped and positioned static bodies. And that will act as a container. So that's all we're doing there. We have a container set for all of our little square alien guys. And by creating, I think, a hundred of them and just letting them drop, we have this interesting visual start to our game with very, very minimal effort. So we can take a look at this. So in our start state init, as I said before, we have a world. We obviously need a member of new world. Anytime we do any Box2D stuff you have to start by having a love dot physics dot new world, else you won't be able to run any simulations. Going to create a ground, walls, and then a bunch of aliens. So this here, we can see that we have a table that we're inserting aliens into. But we have a class called alien. So anybody want to ballpark what an alien class ultimately encapsulates or ultimately is? AUDIENCE: Probably the way it looks, like the skin. COLTON OGDEN: It is, yeah, that's definitely a part of it. So the way it looks, or it's skin. So it does have a reference to that. And then more functionally, it also possesses a body and a fixture. So rather than having a bunch of bodies and fixtures that are separated out and maybe just in tables at the surface level of whatever, our level, just wrap them in a class. And then we just can maintain a reference to each individual alien's body and fixture that way. So it's a little bit more encapsulated. It's a little bit more object oriented, little bit cleaner in my opinion. The alien class can take square or round as its-- well, it can take anything you want to as its type. So this will ultimately decide how it's rendered and what shape it gets. But if it's square, which is the default, so if you just create an alien with no type, it will get a love dot physics dot rectangle shape. So as we saw before, that's just a box. And then if not, we just default to circle shape. But you could program it to take in whatever shape you want, and then just give it that shape. And you don't really have to do much in terms of coding how it interacts with anything else in terms of collision, at least. Because, thankfully, Box2D will know, OK, it's a circle. It should spin around and interact with things like a circle. Or it's a rectangle so it should interact with things like it's a box. Just nice and convenient. And then we'll just create a fixture here. This set user data function here is important because we'll see in the context of how we actually resolve collisions in a customized way, we'll need user data to be able to differentiate what gets collided in our world. But at the moment you can just know that this basically allows us to pass in-- to set arbitrary data onto a fixture. So we can say, fixture set user data alien, the string alien. And what that means is that fixture has some customized metadata about it that says, this is an alien. It's whatever we want to do with it. We could give this a table as well. We could just say, the user data is a table and then has a bunch of information that we can then use at collision time to perform different work on it. But this set user data function is how we are able to resolve collisions between obstacles and aliens differently than, say players and aliens, or even the alien and the ground. Because when we do any Box2D collision, right, the world's taking care of the collisions for us. How do we tell the world, OK, when I hit the ground I want to play a sound, but not do anything. If I hit this box at this velocity, I want it to destroy it. If I hit the alien, I want the alien to disappear and I want to show a victory label. How do we do all these different things? We do that with what are called collision callbacks in the context of Box2D. And we'll see how that works. But suffice to say, user data will be very important coming forward. And then this launched false, actually we don't end up using this so this is irrelevant. But it has a render function here, which just takes in the bodies X and Y and will draw it at the angle that it's at. So that's an important thing. When we before, what we were doing is drawing things via shapes. So love dot graphics dot polygon, love dot graphics dot circle, love dot graphics dot line. But if we want to draw a sprite instead, we need to draw it at the right position, first of all, right? And then things also rotate in Box2D. So we need to draw it at the right angle. So what we do is we can actually query the body for its X. We can query the body for its Y. And we can also query the body for its angle. And then we can draw the texture and the quad that we want to using those XY and the angle, and that will have the effect of drawing a sprite in the world that mirrors what's going on in Box2D, rather than just a simple shape. So that's as simple as it is for drawing a sprite instead of a shape. You can see here, the 17.5 17.5. Does anybody know what that's for? AUDIENCE: Not sure. COLTON OGDEN: So 17.5 17.5 at the end is half of the width and half of the height of the aliens. So aliens are 35 by 35 in this game. We pass those in. This is the center of origin. So when we rotate something by center of origin, it basically describes where the rotation is going to take place. So if it's rotated about the top left and we rotate something, it's going to have the effect of the sprite going around in a circle in sort of an odd way. If we rotate the sprite based on the center of origin of the actual sprite itself, that will have the effect of rotating the sprite on its center. So you can set that origin wherever you want to and it'll perform a 360 degree rotation about that point. And we're setting that point to half-- to basically the middle of where we're drawing the sprite. So that will have the effect of when we give it this angle here, self dot body to get angle, the rotation will take place in place. It won't take place-- it won't be some sort of weird about the top left corner rotation, which is not what we want. So when you see center of origin being modified like that, you can assume that it's because we have an offset and we're trying to find the center of where we're drawing and rotate about that to do an in place rotation. But not necessarily, you could also draw something. You could also, maybe you want something some sort of magical ball of energy to rotate about a rod or something. And so you want it to rotate around a different center, or whatever you want arbitrarily. But typically, at least mostly that I've seen, this is useful for making sure that your rotations, your in place rotations, are accurately rendered. So any questions about how the alien class works? All right. So that's the alien class. That's the basically the fundamental building block of our game. The other part is the obstacle. We have obstacles and we have aliens. The obstacles and aliens are actually very similar. So what's the difference between-- I mean, ultimately, how are they similar? AUDIENCE: They're both dynamic [INAUDIBLE].. COLTON OGDEN: They're both dynamic bodies, yeah. Really, the only thing that's different about a obstacle and an alien is what we do with them in our scene and how they're rendered. But they function very similarly. They're just dynamic bodies that we give a shape to and we render them with that shape. In this case, the obstacle constructor, we've decided to design it such that it could take a shape horizontal or vertical, which is similar to the type that we saw before with the alien, where it could be square or circular. In this case, if we look back at our sprite sheet back here, we can see there's a ton of different shapes. But the only two shapes that we're going to use in the context of this game, just for this demonstration, are the horizontal, clean wooden shape here and the vertical one that's right here. And so in order to find those out I had to open up basically this sprite in my sprite editor, figure out where the XY width and height were, and then create a quad manually off of that. And then I edited the util dot Lua-- or not util dot Lua, the dependencies dot Lua here. Normally we just create gframes and then we use generate quads. And you can do that like with the aliens and with the tiles, the tiles being this sprite sheet that-- this one right here. These are 35 by 35. These are 35 by 35. These are not 35 by 35. These are a bunch of different shapes and sizes. So I went through and gframes wood is for manually created quads here. And then there's four because I also added the semi broken shapes as well. But we don't actually use those. But you could decide to turn these into these on collision, perhaps maybe if the velocity isn't strong enough to break it necessarily, but you want to have some sort of feedback that you collided with it. You can just set maybe the frame to 1 or 2 on collision for that object, instead of 2 and render it appropriately. But if you're dealing with a sprite sheet and that sprite sheet has odd distribution of its sprites, sometimes you have to figure out where the offsets are manually and do it that way. The ideal is that you don't have to and that you can do it programmatically. But it really depends on the game, what your domain is and what objects you're interacting with in the scene. Any questions on the why or how as to that? Cool. So back to the obstacle. So they're horizontal or vertical, and what that does is it sets the frame to 2 or 4. And the 2 or 4 being in gframes that would, the 4 quads that I had a hand, figure out the coordinates for. And really, it's not a whole lot different at that point. If it's a horizontal or a vertical shape, then we need to set its width and height appropriately. Because it's going to have a different width and a different height. If it's vertical, obviously the height is higher than the width. And if it's horizontal, the opposite is true. But they're both a rectangle shape. So you can pass in the width and the height after you calculate that, and give it the right shape, and then ascribe it a fixture. And then set user data in this case, we set user data to obstacle. So now that obstacle, the fixture specifically, knows that it's an obstacle, as opposed to being an alien, as opposed to being anything else, as opposed to being the ground. And so when we explore in a few minutes what the custom collision, world collision callbacks, that we can actually define all this interesting collision behavior for, this user data is going to be relevant. And then we render it just like we render the alien as before. So any questions on obstacles and aliens, how they differ, how they're the same? Cool. All right. Let's take a five minute now. When we get back, we're going to actually look at the play state. We're going to look at what makes a level. And we're going to actually look at how we can customize the world to resolve collisions in ways that are relevant to our game behavior. As in, how to make things break when we collide with them, how to make the victory screen pop up when we've destroyed the bird, so on and so forth. All right. Welcome back. So before we took a break, we were talking about the aliens and obstacles that are in our game world that interact with each other. They are the backbone of what makes our games slash Angry Birds work. You throw aliens into the obstacles, obstacles break, the bad pigs slash aliens die and then you score points. But we actually have to model these interactions and we have to tell our game, our world, what to do when these collisions happen in order for things more interesting than just things bouncing off each other to work. That's the default behavior. Box2D's goal, by default, is when two things overlap, assuming that they're dynamic or at least one of them is dynamic, is to push the dynamic bodies away until they no longer overlap via either position or rotation. But that's not the gist or the goal of our game. Because what we want to have happen is different things happen and certain things to disappear and to break and all sorts of other things to happen when different kinds of objects interact with different kinds of objects at differing speeds. So in order to do this, we need to define collision callbacks for our world. So a callback, recall, is a function that gets called back when something happens. It's just something that will get called at a specific time or later on. And we can define these callbacks for our world such that when two things collide with each other, it will execute this call back and then perform the corresponding logic that we've defined therein. And with every collision in Box2D, there are four callbacks that take place. There's begin contact, so when two things begin to overlap or begin to contact one another. End contact, so once that ends, once two objects are pushed away from each other. Presolved, which happens right before the collision actually gets solved in Box2D, meaning that the things get pushed away from each other. And then postsolved, meaning right after they get pushed away from each other. And the postsolved in particular is interesting because it gets the information about how the collision needed to resolve. So how much velocity or rotation needed to happen within that interaction. And we will not be using end contact, presolve, or postsolve. We will only be using begin contact because, really, that's all we need in order to model the behavior that we're looking for. Because anything that happens in our game we can just figure it out as soon as two objects touch each other. And these are things, if you're interested in a tutorial that goes over these in perhaps a little bit more detail, there's a link here in the slides. But I'll show you how to actually implement these callbacks yourself. You do this via a function called world set callbacks, in this case, F1, F2, F3, F4. And recall, because Lua is a dynamic language where functions are first class objects, you can pass in functions as arguments to other functions. And that's what we're doing here. So this is assuming that we have four functions we've defined called begin contact, end contact. presolve, or postsolve. Their actual names don't matter at all. These are just the de facto names for them. What matters is that you have the logic there and you pass in function objects that perform something. And you can pass in all empty functions and Box2D will still behave as normal. These are only for when you want more complicated behavior out of your game than just things bouncing off of each other and moving relative to one another. So does that make sense? So we'll see how this actually works. We're going to go ahead and open up level dot Lua. So level dot Lua is a container class that basically has our game level in it, including the world and all the entities. And we update it and are able to model, effectively, like a level from Angry Birds. That's really what it is. It has a world. So the level has its own world with 300 positive Y gravity, as we saw before. It has a table called destroyed bodies. And we'll see that in a second. And then here we have four functions. Starting on line 22, we have begin contact, which is a long function. And then we have end contact, presolve, and postsolve. Those are the four callback functions that I alluded to just a few seconds ago. They take in slightly different signatures. The first three take A, B, and collision. And then the last one takes in A, B, collision, and then a normal impulse and a tangent impulse, which are the forces that it needed to apply in order to push apart the two objects. Like I said, we won't be using these three functions. But there might be a situation where you need to use those functions. Maybe you want end contact because, in your game, two objects attached to one another, when they collide maybe they're magnetic or something, and then once they pull apart maybe you want a particle effect or something to show that they've separated or something. And then presolve and postsolve. Presolve, offhand I can't think of a use case, but postsolve could be useful for, depending on your game, whether you need to just figure out maybe the amount of force that they needed to separate. Maybe you multiply that by some amount and cause some sort of dramatic effect. Those are ultimately dependent on the domain of your game. The important function that we'll be using today is the first one, begin contact. And notice, that we've defined these four functions, even if these three are just empty. But we pass in, as I said before, the set callbacks function takes in those four functions. And notice another interesting thing, because of Lua's dynamic nature, line 11 you can see that we have level init, which is the constructor for our level class. Within the constructor we are defining more functions. You can define functions as many layers deep as you want to. And you can even return functions from functions, which are called higher order functions. Really do whatever you want to. In this case we're just defining the collision callbacks here inside of our init. But you could put the most anywhere you want to. You can have them outside of the class, you can have them wherever you want. You can have them be global functions in your main dot Lua, which I don't know how I feel about that. But you can do whatever you want to as long as the functions exist and you can reference their symbols, you can pass them into self dot world set callbacks. And now, whenever a collision happens in the world period, it's going to call all four of those functions at each stage of each collision. So you could see that potentially getting a little bit hairy if you were to scale high enough with all of your logic. If you had a million lines of code in each of these and its executing million lines of code per collision, you could run into trouble. But, fortunately, we're not going anywhere near that. The gist of begin contact, so it takes in an A and a B and a collision. We don't end up using the collision itself, we just use the A and the B because that's all we need for our game world. The A and the B are what? Do we know what the A and the B are? AUDIENCE: Probably the two objects [INAUDIBLE].. COLTON OGDEN: The two objects. Do you know whether it's a body or a fixture? AUDIENCE: It would the fixture. COLTON OGDEN: It would be the fixture. The fixtures collide with each other, not the bodies. So when you have a body, recall that the fixture attaches the shape to a body. The body is just a position and velocity container for a bunch of fixtures, the body is. Each individual fixture collides with other things, other fixtures in your game world. And so, recall that before we had fixture set user data because that's what we ended up needing to have data on when we do the collisions. We check to see via get user data what something is or what metadata we've given to that fixture. And then we can then separate different classes of objects this way. We can say, oh, this object was an alien, or this object was an obstacle, or this object was the ground. And then we can say, oh, did we collide with an alien? Was the collision between an alien and the ground? If it was, OK, let's play a bounce sound effect. And if it was between an alien and the other alien, the player and the other alien, and the velocity was fast enough, OK, the alien should die. Right? We can do arbitrary things. And so the way that I've programmed it here such that we can see what two things interacted with each other, and this is a very simple use of user data. All we we're doing in this code base is just assigning strings to fixtures, but you could assign tables to fixtures with arbitrary amounts of data and do all sorts of things. In this case, we're only using strings. So I create a table, an empty table, and then I just assign at table, at that string, true. And then I can just query that table. Do I have a key player and a key alien? Do I have a key obstacle and a key obstacle? This is how you can figure out what your two objects were. Because A could be a player and B can be an obstacle. A can be an obstacle and B could be the player. So you have to take both of that into consideration. So it allows us to do if types obstacle and types player, so a collision between the player and an obstacle, if it's fast enough, then we can destroy the obstacle. This is what we do here. So I take the absolute value of the velocity on the X and Y. So I do vel X vel Y gets the body's linear velocity. So linear velocity is just where it's moving in the world. And it returns two values because velocity has an X and Y component. And then we sum it here by taking its absolute value of both parts and adding it together. So if it's moving fast on the x-axis, but not fast on the y-axis, or if it's not moving fast on either, or if it's moving fast on both, we have a sense, in general, what's the average velocity of our object. If it's moving fast on any of the axes, we can assume that that's sufficient force to cause an object to get destroyed, right? So we do if the velocity is greater than 20, just an arbitrary value that I came up with that seemed appropriate, then we're going to do this. Table dot insert self dot destroyed bodies, which we saw earlier. And then the obstacles body. Now, why are we inserting that value here as opposed to, maybe just destroying it inside this function? AUDIENCE: Destroying the fixture. COLTON OGDEN: Destroying the body. The fixture, yeah, in this case. AUDIENCE: I mean the fixture, sorry. Because you're still referencing it later in the code. COLTON OGDEN: You're still referencing later in the code. Box2D maintains a reference to all of the bodies, all of the fixtures in your world, regardless of whether you've deleted them or not. But if you delete them while it's in the middle of a, like checking for collision, it'll try to do another collision with that destroyed body and you'll get a crash or a stack overflow error. I found I experienced both of those. You don't want to ever delete or destroy anything while inside a collision callback for your world. It will cause horrible things to happen. So what we do is we maintain a reference to everything that we're going to destroy by just inserting it. So if we do table dot insert the body of whatever we want into destroyed bodies, we can then loop over that after the world updates. And then just destroy them one by one outside of the update function. And the reason that we are passing in the body and not the fixture is when we destroy a body, it destroys all the fixtures associated with that body as well. So we're just destroying the top level container here. In this case, it doesn't matter too much whether we destroy a fixture or a body because it's a one to one relationship. But if you had, let's say, a body that has five fixtures on it. And if that entire thing collides with something else and you want to destroy that entire thing, you want to destroy the body, not an individual fixture. Because when you destroy the body, it destroys all the fixtures, not just the one fixture. So that's we're deleting the body, adding the body to destroyed bodies, and then later performing a delete off of that. The function is destroy here. So on line 157-- well, 155 to 159, this is where we actually iterate over everything that we wanted to flag, or that we've flagged as destroyed, and we destroy it. So if not body is destroyed, destroy it. And then once we destroy it, we're going to go down here and end up actually removing the obstacle and the alien class from our list of aliens and obstacles. Because that maintains a reference to what we're drawing and we want to also delete that. So not only do we want to delete the object from the world, we want to delete the objects that we've created that are a wrapper for the bodies and fixtures and also the drawing of our aliens so it no longer gets drawn to the scene, basically. So yeah, don't ever delete a body or a fixture inside your callbacks. Always flag them and delete them afterwards. Basically, don't delete in the middle of a world update function call, which we see here. Notice that this takes place, 152, were doing self world update. And then on 155 to 159, we've populated destroyed bodies via the collision callback that we defined up above. So in here, this is where we can actually destroy everything. This is outside of the update function, here, the world update function. We don't need to worry about stack overflow or a segfault, which we get by deleting something while it's in the middle of processing its collisions. So unfortunate bug. If you ever find yourself running into stack overflows or segfaults in your collision callbacks, make sure you're not deleting anything. But we can see here, it's very similar, the behavior we defined between obstacles and the player, between obstacles and aliens, and between the player and alien. Ultimately, it's check to see whether the average of its velocity is greater than a certain number, in this case, 20. And if it is, flag it as destroyed. So if the player hits the alien, destroy it. If a obstacle hits the alien, destroy it. And it's similar to how it works in Angry Birds. When you throw something at a structure in Angry Birds and a piece of debris falls off of it and hits the pig, usually kills the pig too. And if your bird hits the pig, that usually kills the pig. But if you're not moving fast enough or if a piece of debris isn't moving fast enough, it'll just nudge the pig, it won't actually kill the pig. So that's why we're taking all of this into consideration. We're not just doing a blind delete off of the body's in our code, we're actually making sure, is it also moving fast enough, i.e. does it have enough force? And if it does, then perform the code, then perform the deletion or flag it as being deleted. And so once again, that's why user data is important. Because that's how we're able to-- because notice in the callback we just get an A and a B. And those are always going to be fixtures. Fixtures, in order for it to know what kind of a fixture it is, whether it belongs to a player or an alien, we need to give it some information. So the set user data flags the fixtures as being of a specific type. And then we can fetch it here with get user data and then actually perform the relevant game logic. Any questions as to how this works? AUDIENCE: Are you checking for if two obstacles knock into each other? COLTON OGDEN: Am I checking with two obstacles collide with each other? I might not be. In that case-- you should. In that case, since we're not, they'll just bump into each other. But, yeah, if we wanted two wooden obstacles to destroy each other if they hit fast enough, you would just do the same thing here. If types obstacle, I guess. But in this case, because they're both the same key, you would have to do if types-- let's see how we're doing it again. So types obstacles is true, types obstacles is true. You would say if types obstacle and not types alien, not types player, not-- there's a cleaner way to do it. AUDIENCE: Like a series of [INAUDIBLE] statements. COLTON OGDEN: Yeah. AUDIENCE: [INAUDIBLE] statements. COLTON OGDEN: Yeah. That's true. Yeah, there's a lot of ways. And if I were to re-engineer this, I would also abstract out this code and make it a function. Because it's pretty much the same code between all three of these. But just to illustrate and just for simplicity because it's pretty similar interactions, didn't really put too much engineering forethought into it. Definitely if you expand upon it, I would recommend doing that. But that's the gist of making our world behave beyond just resolving collisions and pushing obstacles away from each other, which is the default behavior. So we set the callbacks. We're good. Now things, when we interact with each other, they'll behave differently, they'll trigger different behavior. We have this thing called a launch marker, an alien launch marker. Anybody know what that might be? AUDIENCE: Is that the little dots that show the trajectory? COLTON OGDEN: Yeah, so it's the dots that show the trajectory. It's one, the alien being rendered on the left side of the screen without any physics applied to it, that's click and dragable. And it also renders a trajectory. And when you release the mouse, it launches an actual Box2D alien traveling in the direction that that trajectory foretells. If we look at alien launch marker here, it basically maintains a reference to whether we're aiming or not. So it's got a couple of states. It's got a launch state and an aiming state. An alien that we'll have a reference to eventually, which will spawn and will give it an impulse. So an impulse is effectively setting its velocity immediately to some value, as opposed to something over time. We can apply force to an object, which would be like you driving your car up against something and then gradually accelerating, that's applying force. And we can also apply impulse by going full speed with our car and hitting an object, and that will have the effect of applying an impulse at a certain velocity. When we drag our alien and then we release it, we want to apply an impulse in the opposite direction of where we're dragging based on a certain amount. I scaled it by 10, but you can have it be arbitrary. And then the trajectory models where it's going. And the trajectory is calculated via these lines here. So from line 90 to 104. There is a formula for, in that Box2D set of tutorials, that actually shows you how to calculate an estimated trajectory given a starting impulse and a starting position. Which is this formula here. It's semi-complicated. The article goes into detail as to how it works, but it effectively calculates 1/60 of a second, assuming that we're running our simulation at 1/60 of a second, it will, over 90 iterations here, 1 to 90, calculate each individual step of that simulation. And then I only render every 5 here. So if i is mod 5 s 0, then I'll actually end up drawing a circle at trajectory X, trajectory Y. Trajectory X and Y being here. Shifted X, shifted Y being the starting location. And then we multiply i by 1/60 of a second, which will give us the scalar for this impulse here. And then with gravity we have to do this i squared plus i times 1/2 times the gravity on the y-axis times 1/60 squared. The article goes into a little bit more detail as to how it works. But that's it converted into source code. But it's effectively a gravity simulation and a velocity simulation over time. And by rendering it based on 90 iterations, which is one and a half seconds, at 1/60 of a second we can forecast where exactly we're going. And then when we apply this impulse, X and Y, the ball will actually travel, the alien will actually travel in that direction at that exact trajectory. That's the complicated part of the launch marker. The other part is that it has a couple of states, like I said before. So when we click and we're not launched, it should go into aiming mode. And so if we're aiming then we're going to set a rotation to-- actually, rotation is not relevant because this was before I ended up using the predictive trajectory method. The shift at X and Y though, those are relevant because that's the starting location for your trajectory. That's wherever your mouse is. And we clamp it so that it doesn't go past a certain limit on the left or the right, so that it stays within a box area. But this will be whenever you let go of the mouse, that will be where we spawn the Box2D alien and apply an impulse in a negative direction relative to where we move the mouse. So if we move the mouse to the left and down, it's going to negate that with an impulse going up and to the right, if that makes sense. And that's what's shown by the trajectory. And then, aside from that, it renders different things depending on whether or not-- what state we're in. So if we haven't launched, it will render just the alien. If we're in aiming mode then it should actually render and calculate the trajectory. Otherwise, it would just render the alien. And so once we release the mouse, so was released 1 and were aiming, launched is true, spawn an alien. So we create a new alien with self world, it's round. We started at shifted X and shifted Y. We set its linear velocity to the same values that we calculated before. So it's base X minus shifted X times 10. So the times 10 is a scalar amount. And then the base X is where we've moved it, effectively. Or, no. Base X is where it starts and shifted X is where we've moved it. And so by subtracting shifted from the base, we get the negative direction that we want to effect the impulse. And then the impulse is set here with linear velocity. And then we also set it to have a restitution of 0.4. Recall, restitution is bounciness. So our alien bounces a little bit when it hits the ground. And then anybody know what angular damping might be? Any guesses? Angular damping is when it rotates, basically, friction on its rotation. So that when it rotates on the ground, it doesn't roll indefinitely. If we don't set that, it'll just roll forever and ever and ever and ever. Which is not what we want. We want it to stop at a certain point because once it stops, we know, OK, now we can get the next alien ready to launch. And that's the gist behind the launch marker. How we render trajectory. For the math on that, a little bit more in detail, I would explore that URL. It goes into it into pretty good detail. I use that as a reference for creating this bit of code here. But yeah, effectively, is it's just rendering a bunch of circles with that trajectory and calculating it over 90 ticks, 90 frame iterations. Back to the level. Sorry, any questions overall as to how the launch marker works? AUDIENCE: No. COLTON OGDEN: Cool. All right. So then we have an alien's table, an obstacles table, edge shape for the ground. And then we just create an alien to destroy. Spawn a few obstacles here. So in this case, two vertical obstacles and a horizontal one. Positioned such that the horizontal one is over the vertical ones and they're spaced apart such that the aliens are in the middle of them. Then the ground here, we give the ground some friction, 0.5. And that's pretty much it for setting up our level. So if we wanted to, after this point, we have the foundation necessary to really spawn arbitrary levels with admittedly simple obstacles at this point. But we could set-- because all we're doing here is just simple insertions to our aliens and obstacles table, we could create pretty much any level just by, maybe in data, specifying level could be like a table and then aliens could be another table. And then maybe all it is just a like X equals some value, and then Y equals some value. And then obstacles is the same thing. And then all we do is just we iterate over this level definition and we just say, new alien for every table here. And then new obstacle for every table in here. And then now your levels are data driven. It's easy just to make levels. You don't have to code, really, much. And you can put this in a separate file. Be like, levels dot Lua, and then just load individual levels at a time. Level 1 equals-- levels would be the top level container. And then you would have 1 equals all of this, and then 2 equals another one, 3 equals another one. And then you're not really programming as much as you are just laying things out in data. Super nice and concise. That's a nice thing about a language like Lua, is that you can, and it's the same thing in JavaScript with JSON, you can just define things as data and then write a script to go over it and construct your actual relevant data structures that way-- in your code that way. When you have the foundation like we have now where you can think in terms of obstacles and aliens, you can construct levels like so. And obviously you could go a lot more complicated with this. All we're doing is having very simple almost boring static obstacles. They're not static in a technical sense because they're dynamic objects, but all they really do is just stand there and then fall over. But if you wanted a pulley system or maybe something that's shaped in a giant head or something, you can create arbitrarily complex objects that way using joints. And if you're curious, I recommend to look into the documentation for Love2D a little bit more. Especially their weld joints are what you would use to combine pieces in arbitrary shapes. But you could easily take this to the next level and start to create in that same level definition, arbitrarily shaped, welded together obstacles. But that would be, I think, a next step if you're looking to take this beyond just one level. I would say, think in terms of, how can I get my game world represented in a very simple data like way? Because not only does it make it easier for you to create content, it allows you to shift that burden to somebody else and allow you to give the task of creating levels less to a programmer and maybe more to somebody who has just a design background who isn't as comfortable writing code. And allow you to create the engine that constructs the game world based off of this data. Any questions as to how we've set things up here? OK. So we have the ground, we have a background. A background is just a simple class that renders a static image that you can scroll the image with left or right, but we don't end up using it much. It's relevant in Angry Birds because in Angry Birds they have a camera, and the camera pans left to right depending on how far away the fortress is from your sling shot. So that's in there if you want to experiment with it at all, and experiment with a moving camera. Maybe use timer dot tween to tween the camera or just to have it track the alien if you want larger worlds. But we don't end up using it in the distro as much. Our update function is simple. We update the launch marker, update the world, and then we process all the bodies that we flagged as being destroyed, which we've already seen. We reset destroyed bodies to an empty table because we've processed all of them. We actually remove from our level the obstacle objects so that they're no longer rendered, and we no longer try to reference the bodies that are destroyed. And then notice here too, when we destroy the obstacle, we're playing a sound effect inside this bit of code. So we can just do g sounds. I put five wooden sound effects in there just for variability. It'll pick a random one. And then using to string at the number, just create break one, two, three, four, five here. And then stop and play it. Same thing here. I have a sound called kill for when we destroy an alien. So when we flag an alien as destroyed and we remove it from the scene, we should also call that sound effect. And then if the alien stops moving in our scene, the player, we can get a reference to it here, self dot launch marker dot alien, as opposed to any aliens in our scene. When it stops moving, so we get the average of its velocity and if it's less than 1.5, so not perfectly still, because it's tedious to wait that long, but almost still, we destroy the alien and then we create a new launch marker. We destroy the alien so that the world doesn't keep a reference to it and then we just create a brand new marker, which has the effect of instantiating a new alien there when we relaunch. And then here, if there are no more aliens in our scene, if we've destroyed all of the aliens, in this case there's only ever one, but there could easily, with a little more work, be a few more aliens in the scene, if it's set to zero, then go back to the start. Which we saw before when we finally killed the alien. And then all we're doing here is deferring rendering to the individual objects we want to render, the launch marker, the alien, the obstacle. We render ground tiles. So recall, from our ground example before we were just using a line, an edge shape to represent the ground. But if we look at our game, it's a little hard to see because I'm in 720, but there's actually a ground tile here at the very bottom, a bunch of them. And even though we have all of those tiles, all we're doing to detect collision is just an edge shape. So what we're doing is just beyond having the edge shape in our scene, we draw that tile, which is frame 12 in our sprite sheet from negative virtual width to virtual times 2. So three screen widths total. And then we just do it in increments of 35 pixels because that's how wide the tile is. And that'll just create a bunch of the same tile at the very bottom of our screen. So just a graphical thing, not really necessarily functional. We have the edge shape already in our scene, but just to make the ground look a little larger than one pixel tall. And then if we haven't launched anything, we should display some instructions here. And then if we're at no aliens left, then we should display the victory screen. And the victory thing will last for just a little bit of time, because even though self dot aliens is zero, this bit of code doesn't register until after the velocity of our moving alien slows down sufficiently. So this will get called when we're just about to finally stop moving, and then we check to see, oh, OK, do we indeed have no aliens left? If not, time to go back to the start. We've completed a level. And then you obviously would just change this to go to be next level if you ended up implementing more levels in your game besides just one. Beyond that, that's pretty much all of the code that's in this example. It's fairly, I would say, unsophisticated relative to prior examples. But mainly, the burden is learning how to use the physics engine, the Box2D physics engine, which itself is quite a few functions. It's some of the longest documentation on Love2D's wiki, I think. But the principles are pretty simple, in my opinion. I think it's actually quite easy to get rolling with a lot of cool features. The vast majority of which I don't think we even really cover, at least in terms of what you can do with compound objects and joints and things like that, which really start to go into the world of more sophisticated and interesting physical simulations. Things like I alluded to before, pulleys and tanks and other things like that. Some features that we could look at potentially expanding upon if we wanted to make our game more interesting, is more shapes for our objects. So if we look at our sprite sheet here, we can see there are things like roofs and circles and things like that. So more interesting obstacles beyond just square rectangular shaped obstacles. Go back to-- like I said before, compound obstacles. So a bunch of things put together via joints. So pulleys, there's motors, weld joints, which you can use to-- you can affix a roof to a square and then have two pieces that are tied together. And you can make arbitrarily complex and shaped things and just be really interesting. And that big thing, that body with all these fixtures, will collide just as any other thing would, thanks to Box2D. As I alluded to before, instead of having levels to be hard coded into our level class, maybe define them in a Lua file called levels dot Lua. And then just have aliens be a table, obstacles be a table, and then whatever else, however more complicated you want to get with your game. You can add more things. But just have it be represented as simple data structures. So aliens that maybe have different shooting mechanics. So if you've played Angry Birds you're familiar with the fact that some birds will split off into multiple birds. Some birds will dive and go super fast and break through all the obstacles in their path. Some birds will explode, and then their explosion affects all of the obstacles around them. So there's a lot of different game play mechanics you can implement using different types of birds or aliens. And then different obstacle materials is another direction we could go. And it's supported out of the box with the sprite sheet that I provided because it has sheets for metal and glass and explosive material. So there's a lot of different interesting things you could do just by changing up what materials you're using in the game. And obviously those will have different densities and behave in different ways. So assignment five is a fairly simple assignment. So this is just as I alluded to before. The task here is to split the bird that you shoot by pressing Spacebar when it's in the bird-- the alien, when it's in the air, press Spacebar and have it split into three. So you have your one that you're shooting already. So it should just shoot off two more. One that's angled higher, one that's angled lower. And all of those should be interactable with your game world. And that's really it. So if you can do that then it'll show that you know how to effect the Box2D game world. Next time, next lecture, we'll be talking about-- the theme is going to be Pokemon, but we'll be talking about more generally RPGs and turn-based games of that nature. It won't maybe necessarily look quite as pretty as this. But we'll be striving for a similar over world that we can walk around and then engage in fairly simple turn-based battles and stuff like that. And also, we'll be talking about user interfaces and things like dialogue and stuff like that. But that was it for lecture six, Angry Birds. I'll see you next time. Thank you.
B1 中級 憤怒的小鳥編碼教程--CS50的遊戲開發入門篇 (Angry Birds Coding Tutorial - CS50's Intro to Game Development) 8 0 林宜悉 發佈於 2021 年 01 月 14 日 更多分享 分享 收藏 回報 影片單字