Advertisement
  1. Code
  2. Games
Code

Box2D for Flash and AS3: Building Structure

Difficulty:IntermediateLength:MediumLanguages:

In the first two parts of this series, we created two simple types of object, rendered using Flash's display list. In this part, we'll see how to extend this to let us create as many different types of object as we want, without our code getting all tangled up!

I recommend reading this series from the start, but if you want to start here, then grab this zip file -- it contains the source code from the end of the the second part. (The Download button above contains the source code from the end of this part.)


Step 1: New Balls Please

Let's take a quick look at where we left off:

Bunch of wheels and crates bouncing around. The wheels don't all have the same properties; each wheel has a random size, restitution, friction, and density. Ditto for the crates.

That's been fine for testing, but now let's make things a little less random. We'll start by replacing the wheels with tennis balls, which all have the same properties.

First, draw a tennis ball (or get a photo of one). Here's mine:

To make yours consistent with mine, make sure that it's called TennisBall.png, that you save it in \lib\, that it's 35x35px, and that it has a transparent background. (You can change any of these points, if you wish, but you'll have to figure out how to modify your code accordingly.)

Embed the PNG into your Main.as file, just like we've done before:

Now, create two arrays: one for your tennis ball Box2D objects, and one for the corresponding images:

(This is all much the same as we did in the second part of this series, so take another look at that if you need a refresher.)

Now we need a function to create a single tennis ball. Here's what we use to create a single wheel:

We can copy this and modify it to our needs. Since all tennis balls will have the same properties (size, restitution, friction, and density), we don't need the mRadius argument, and we don't need to use random numbers anywhere. It's hard to guess what values of restitution and so on will be correct, so just have a go - we can always correct them later.

Here's my createTennisBall() function:

A few things to note:

  • The b2CircleShape() constructor is expecting to receive the radius of the tennis ball, in meters, and I don't want to squash or enlarge the image I've made. I guess I could look up the radius of an actual tennis ball, enter this value in here, and alter the pxToM scale factor to make everything else fit, but instead I've just told the constructor to convert the pixel radius of my tennis ball image to meters.
  • Restituion is the bounciness of the object, on a scale of 0 to 1; tennis balls are pretty bouncy so I've set this quite high.
  • Friction is also between 0 and 1; tennis balls are quite rough so I set this fairly high, too.
  • Density is not limited to any particular range; tennis balls are mostly air, so I set this to the same value as my crates (which I'm also assuming are empty).
  • Unlike in createWheel(), there's no need to resize the image (as I mentioned above), so I've deleted those lines.
  • I've just commented the onClickWheelImage event listener out for now, as it's not relevant.

Try to compile the SWF. It should work - if it didn't, you missed something out - but since we aren't actually calling createTennisBall() from anywhere, nothing will be different.


Step 2: Creating Tennis Balls

Let's just remove the wheels straight away, and replace them with tennis balls. Change this code, in getStarted():

...to this:

Note that the first argument has been deleted, since we don't manually set the size any more, but all other arguments remain the same. Try it out:

Hmm... not quite right. Ah, of course - we didn't add any code to render the tennis balls. We'll do that next. Change onTick() from this:

...to this:

(It's really just a case of doing a find-and-replace, switching "wheel" to "tennisBall".) Try the SWF now:

Better, but we don't have any rotation (we didn't need it before, because the wheels were all rotationally symmetric). Add that now:

Ah. Remember we had this problem with the crates? Remember how we solved it? Have a go at doing the same here, on your own. My solution is below if you want to check:

Test it out:

Ace.


Step 3: Creating Bowling Balls

I'd like to add three more types of ball: a bowling ball, a basketball, and a rubber ball. As you can see from the above, it's a pain to do the same thing over and over again. We'll add the bowling ball next, and try to simplify the process so we don't have to keep making this irritating little changes.

Here's my bowling ball image. Again, feel free to use your own, but try to make it 60x60px with the name BowlingBall.png:

Embed the image and add the necessary arrays:

Write a createBowlingBall() function:

...and create the actual objects:

Phew. Okay, now for the rendering. Let's take another look at our rendering code so far:

Notice anything? Thanks to the change we made in the last step - to fix the rotation problem - the two highlighted sections are almost identical; just swap out the word "tennisBall" for "crate" in each case and they're a perfect match.

This suggests that we could combine them into a single loop, which could be used to render all objects. That way, we wouldn't need to create a new loop to render the bowling balls. Let's look at this next.


Step 4: Rendering in a Single Loop

To do this, we just need to combine the tennisBallArray and crateArray into a single array. It's easier if I show you:

See how that works? In lines 6-10, we create a couple of new arrays: one contains all the tennis ball and crate b2Body objects, and the other contains all the tennis ball and crate Sprites. Most importantly, they contain them in the same order - if the 17th element of generalImages[] is a tennis ball, then the 17th eleemnt of generalB2BodyArray[] is the same tennis ball's b2Body.

The for loop then does exactly the same as the two before it did; it's just way more general. Try it out:

The bowling ball images don't move, but look how easy it is to add them to the renderer:

That's all it takes! Check it out:

It's beginning to run a little slowly on my machine, so I'm going to reduce the number of objects from 60 to 30:

There's more room for them now, too:


Step 5: Creating Basketballs

Basketballs, next. My image is 75x75px and called Basketball.png:

As usual, start by embedding the image as a class:

Now we'll add the two arrays for the basketball b2Body objects and Sprites. Except... wait. Why do we need a separate array for these?

We don't, actually! Not any more. Let's combine all our b2Body objects into one array, and all our Sprites into another. Change this:

...to this:

Change this:

...to this:

In every create«Whatever» function, change the equivalent of this:

...to this:

...and this:

...to this:

We can make onClickWheelImage() applicable to all types of objects very easily now; just change this one line:

...like so:

Finally, in onTick(), we can delete the lines highlighted below:

Compile your SWF to make sure it works:

If it doesn't, it's probably due to a missed array somewhere; just check your error messages and try again.

That took a while, but it makes it much simpler to add new objects. We're gradually streamlining the whole process.

There's another simplification we can make to these arrays, though...


Step 6: Linking Images to b2Body Objects

Why do we have the generalImages[] array? It's simply so that we can link a b2Body to its associated image, and vice-versa. But there's a better way to do this.

Every b2Body has an internal "user data" object - this can be set to any object you like, and can be retrieved from anywhere. We can set this to point to the b2Body object's Sprite.

It's simple to do; in each create«Whatever»() function, alter the equivalent of this line:

...to the equivalent of this:

Then, in onTick(), instead of finding the image from an array, we can find it using the user data:

(If you wanted to, you could replace the user data with a sprite sheet and use blitting to render the objects here. It's quite simple to do so, with this structure.)

You could actually remove the generalImages[] array altogether now... apart from one thing: it's still used in onClickWheelImage() to get a reference from the image to the b2Body.

We could just comment onClickWheelImage() out, because we're not using it right now, but that would be lazy. And this raises a good point, anyway: we need to be able to find any image's associated b2Body. Unfortunately, we're using the Sprite class, and it doesn't have a built-in "user data" - but we could give it one.

Create a new class, called B2Sprite, extending Sprite, in your \src\ folder:

(I've used a capital B to distinguish it from the official Box2D classes.)

Create a b2Body property:

Because this extends Sprite, it'll act exactly the same as a Sprite, except with that extra property that we need.

Now, in every create«Whatever»() function, change the equivalent of this code:

...to this:

Also, wherever you have the equivalent of this line:

...add another line underneath it, to create a connection from the B2Sprite to the b2Body:

Then, you can alter onClickWheelImage(), like so:

Now that you've done all of that, you can uncomment out the lines that add event listeners for onClickWheelImage() (in the tennis ball and bowling ball creation functions), and try out your SWF:

Click a tennis ball or bowling ball to see the effect.

Rename the onClickWheelImage() function to onClickClickableImage(). If you like, you could try making the crates clickable, too!


Step 7: Creating Basketballs (Still)

Hey, weren't we adding basketballs? Let's get back to that.

So, we've removed the image array, which means we were about to write the createBasketball() function. But, wait... I can't help but wonder whether we need the generalB2BodyArray[].

It turns out that we don't. The b2world contains a list of all b2Body objects inside it, so the array is unnecessary. We'll just make a quick change - I promise - and then we can add the basketball.

This list is not an array. It works like this:

  1. You request world.GetBodyList(), and this gives you the first b2Body object in the list.
  2. With this body, you call .GetNext() to retrieve the next b2Body object in the list.
  3. If .GetNext() returns null, you've reached the end of the list.

Have a go at re-writing the onTick() rendering code to use this list instead of the generalB2BodyArray[]. Here's my solution if you want to check:

(I've taken the opportunity to add some additional checks and change the name of some variables; you don't have to do this, but it's probably easier if you do, to keep our code consistent.)

You can now remove all references to generalB2BodyArray[]. Make sure you test your SWF to check it still runs fine:

Creating that basketball is now pretty simple. First, write createBasketball():

...then, create a whole bunch of them:

Done! Try it:

He shoots, he scores.


Step 8: Making Object Construction Easier

Every time we write a new create«Whatever»() function, we copy and paste an old one. They're almost identical. And, as you've seen, that usually means we can improve our code structure to prevent us from having to copy and paste the same lines over and over again; it means there's a better way to do things, which will make it easier for us to create new objects in the future.

Look at this:

I've been using it as the template for all my b2Body creation functions. Here's how they all boil down:

For our round objects, some of these steps are identical, and most of them only differ by a couple of parameters or instance names. So, what if we could inherit all that identical code from some general createRoundObject() function?

We can't do exactly that, but we can do something similar. Create a new folder in \src\ called \builders\, and create a new class inside that called RoundObjectBuilder.as:

This is going to take a b2world and create a b2Body, so add appropriate code for that:

It's going to create a round object, so we'll give it a function to create such a shape:

(Where's the radius? You'll see...)

It's going to create a fixture def, so create a function for that, too:

(Where do we define the properties? Again, you'll see...)

It's going to create a body def, too:

It's going to create an actual body and fixture, and set the body's initial velocity:

Next, we add the image:

Do you see where we're going with this? You might have guessed that we'll eventually create an instance of RoundObjectBuilder and then call createShape(), createFixtureDef(), and so on, all in the right order, with the right parameters, then retrieve the b2Body object after everything's set up. That's correct, but there is a little more to it than that.


Step 9: Creating a Specific Builder

First, create protected variables to hold all the properties that are missing:

Now, create a new class, in your \builders\ folder, called TennisBallBuilder.as, and make sure it extends RoundObjectBuilder:

(Remember, super() will run the code in the constructor function of RoundObjectBuilder, because we extended it.)

Now, underneath that call to super(), set the restitution, friction, and density of the tennis ball:

Next, embed the tennis ball image, and use it to set the Image class which will be used to create the bitmap; also, set the radius:

So now we have a class that encapsulates all of the information and all the functions needed to create a tennis ball. We've just got to put it all together. Modify Main.createTennisBall() like so:

If you test your SWF, you'll see that this still works:

But so what? We haven't gained much here. In fact, we've ended up with more code than we started with, and it still requires copying and pasting.

Time for one more improvement. Create a new class, in \builders\, called RoundObjectDirector.as:

Here's how this will work:

  1. We'll tell the RoundObjectDirector to use a specific RoundObjectBuilder.
  2. We'll tell the RoundObjectDirector to create a new round object.
  3. The RoundObjectDirector will do so, using the RoundObjectBuilder that we specified, and will return said object.

Here's the code:

See how most of the code in createRoundObject() is taken from our Main.createNewTennisBall() function? All you have to do is replace "tennisBall" with "roundObject" in each case. (Well, okay, I've also changed all measurements to be in meters rather than pixels, since we're not dealing with any images here.)

Now we can modify Main.createNewTennisBall() like so:

(Note that I've converted the starting positions to pixels here, before passing them to the Director.)


Step 10: Creating Rubber Balls

Let's create a brand new type of object, and see how much faster it is. We'll use a super-bouncy rubber ball:

Mine is 15x15px and called RubberBall.png.

First step: create a new class, extending RoundObjectBuilder, to create the rubber ball:

Second step: create Main.createRubberBall():

Third step: create the actual objects:

That's it. Easy.


Step 11: Getting Even More General

I can't help myself. I have to make one more simplification.

Did you notice that Main.createRubberBall() and Main.createTennisBall() are almost identical? The only things that differ are the type of RoundObjectBuilder and the variable names.

Let's combine them into a single function: Main.createRoundObject():

I've also made roundObjectDirector a protected variable, belonging to Main, so that we don't have to keep creating a new one:

Delete the createTennisBall() and createRubberBall() functions.

Now we can alter the code that creates objects, from this:

...to this:

The SWF still works:

Excellent.


Conclusion

I'll admit; that took us a long time, and didn't give us a spectactuarly different SWF. I hope you agree that the cleaner code was worth it, though.

This new approach to creating object (called the Builder pattern) is going to make it very easy for us to create new types of object in the future, to add them to the world, and to make changes to all of them at once by modifying the base Builder.

If you're feeling a little left behind by the pace of this tutorial, don't worry; we ended up focusing on design patterns rather than Box2D in particular here, but in the next part of the series we'll get back to Box2D's built-in features. In particular, we'll be working our way towards a physics-powered, keyboard-controlled, 2D platform game!

Until then, I suggest you do the following:

  • Change your code for creating basketballs, bowling balls, and wheels so that they also use Builders.
  • Create a new round object (medicine ball? ping-pong ball?), with a new image and set of properties, using Builders.
  • If you're up for a challenge, have a go at creating a brand new set of builder for rectangular objects, like the crate.

Good luck! If you've got any questions, please ask them below.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.