字幕表 動画を再生する
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.