Chapter    13

Pinball Game

I’d like to put the Box2D physics engine to good use, so in this chapter you’ll be making an actual pinball game. Pinball tables are all about using our physical world and turning that into a fun experience. With a physics engine, however, you’re not just limited to real-world physics.

You can create some elements of the pinball table, such as bumpers and balls, by simply choosing the right balance of friction, restitution, and density. Other elements need joints to work—a revolute joint for the flippers and a prismatic joint for the plunger. And of course, you need lots of static shapes that define the collision polygons of the table.

Because it would be impractical to define collision polygons in source code, at least for the level of detail necessary to build a believable pinball table, I’ll introduce another useful tool: PhysicsEditor. With that, you can create collision polygons by simply drawing the vertice—or, even faster, let PhysicsEditor trace the shape’s outlines with a single mouse click.

At the end of this chapter, you’ll have a fully playable pinball game, as depicted in Figure 13-1.

9781430244165_Fig13-01.jpg

Figure 13-1 .  The pinball table built in this chapter

Shapes: Convex and Counterclockwise

Let’s start with the requirements of collision polygons. The first thing you need to be aware of when defining collision polygons for Box2D and Chipmunk is that these engines expect the collision polygons to have the following properties:

  • Vertices defined in a counterclockwise fashion
  • Polygons as convex shapes

A convex shape is a shape where you can draw a straight line between any two points without the line ever leaving the shape. In concave shapes, you can draw a straight line between two points such that the line is not entirely contained within the shape. Figure 13-2 illustrates the difference between convex and concave shapes.

9781430244165_Fig13-02.jpg

Figure 13-2 .  Convex and concave shapes

Defining the vertices of a convex shape in a counterclockwise fashion can be illustrated by drawing a convex shape in your mind. You place one vertex anywhere and then go left to place another. Then go down and to the right, and you will have drawn a rectangle in a counterclockwise fashion. Or place another vertex and then go right, up, and then left, and you will have drawn a counterclockwise shape. It doesn’t matter where you start with the first vertex, but it’s very important to follow the counterclockwise orientation of vertices.

Fortunately, if you’re working with PhysicsEditor, you don’t have to care about polygon vertex order (orientation) or whether the polygon is convex or concave. PhysicsEditor automatically takes care of that for you transparently. PhysicsEditor splits concave shapes into one or more convex shapes. The physics objects loader shipped with PhysicsEditor then assigns all the shapes to a single Box2D body. It’s still good practice to try to avoid shapes being split in order to have as few collision shapes per body as possible to get the best performance.

Tip  How do you know if you made a mistake and accidentally created a clockwise-oriented or concave collision shape? Well, every physics engine reacts differently. Some will tell you up front by throwing an error. But in the case of Box2D, if a moving body hits a collision shape that is not well formed, the moving body will simply stop moving when it gets close to that shape. If you ever see that effect happening in your Box2D game, check the nearby collision polygons.

Working with PhysicsEditor

Armed with the knowledge about properly defining collision polygons, it’s time for you to check out the PhysicsEditor tool, which you can download from www.physicseditor.de. After opening the downloaded PhysicsEditor disk image and dragging PhysicsEditor.app to your application’s folder, you’re ready to run PhysicsEditor (see Figure 13-3). In the PhysicsEditor disk image, you’ll also find a folder named Loaders that contains the loader code (shape cache) for Box2D and Chipmunk plist files created by PhysicsEditor. You’ll be using the GB2ShapeCache class found in the Loaders/generic-box2d-plist folder in the example projects of this chapter to load the shapes created by PhysicsEditor.

9781430244165_Fig13-03.jpg

Figure 13-3 .  The PhysicsEditor application

If you’ve previously downloaded the PhysicsEditor disk image, you should download the latest version again because the GB2ShapeCache class may have been updated—for example, to be compatible with Box2D v2.2.

After adding the GB2ShapeCache files to your project, you’ll notice that the project won’t compile without errors anymore. The problem is that GB2ShapeCachewasn’t written with ARC in mind. But that’s easy to fix: simply go through the Edit image Refactor image Convert to Objective-C ARC . . . process again and allow Xcode to make the necessary changes to the GB2ShapeCache files.

You should now drag and drop the PNG files located in the PhysicsBox2DPinball01 project’s Assets/pinball folder onto the leftmost pane in PhysicsEditor, labeled Shapes.

Note  You’ll be using only the HD resolution images to create physics shapes with PhysicsEditor. You don’t have to create separate HD and SD resolution physics shapes. The physics simulation world is independent from the graphical representation of the objects and thus independent of the screen resolution.

The first thing you should modify in PhysicsEditor is the settings for the exporter. PhysicsEditor can export to several game engines, supports both Box2D and Chipmunk physics engines, and even allows you to create your own custom export format. To write files compatible with cocos2d, you must set the Exporter setting on the rightmost pane to Box2D generic (PLIST). Setting the exporter first is important because it enables or disables some features of the PhysicsEditor GUI, depending on the targeted physics engine’s capabilities.

Next you should set the PTM-Ratio setting to 240. This value’s unit is in pixels per meter, meaning 240 pixels will equal 1 meter in the Box2D physics simulation world. The dimensions of the Box2D physics world matter because Box2D is optimized to work best with objects of 1 to 10 meters in size. You can easily run a simulation with larger or smaller objects, but Box2D loses precision and can show odd behavior when you have very large (tens or even hundreds of meters) or very small objects (small or tiny fractions of a meter).

Because you’re using high-resolution images in PhysicsEditor, the PTM-Ratio of 240 will create a pinball table that is 4 meters high (960 Retina-resolution pixels divided by 240) and 2.6 meters wide (640 Retina-resolution pixels divided by 240). The actual pixels to meter ratio in cocos2d will be half the PTM-Ratio setting of PhysicsEditor—in this case, 120 pixels per meter. This is because the cocos2d coordinate system is in points, which means both the standard-resolution display and Retina displays have the same dimension: 320x480 points. A point equals 1 pixel on standard displays and 2 pixels on Retina displays. The size of the table in the Box2D physics world is unaffected by the actual screen resolution. If you work only with standard-resolution images and have Retina support disabled in your app, the PTM-Ratio setting in PhysicsEditor will equal that in Cocos2D.

Defining the Plunger Shape

Let’s start with the plunger, the spring that kicks the ball into play. Select the plunger image on the leftmost pane and click the Add polygon button on the center view’s toolbar. This creates a new triangle shape on the center working area and highlights it. Because you need a rectangular shape, double-click one of the sides to add a fourth vertex. If you add too many vertices, you can right-click or Option-click a vertex and choose Delete point to remove it.

Move the four vertices to the four corners of the plunger by clicking and dragging them. Create a rectangular shape that encompasses the entire plunger, including the spring. Doing so will avoid problems when the ball by accident falls below the plunger’s head.

You may have noticed the little bluish circle with the + inside. This is the anchor point of the shape, which coincides with the anchor point of the shape’s sprite. Later, when you position the shape in cocos2d, the shape’s anchor point will be centered on the coordinates you provide.

Move the anchor point to the bottom center of the plunger image to make it easier to position the plunger. You can drag the blue circle, but in many cases you’ll want to place it very accurately. In those cases, you can modify the anchor point’s absolute pixel position or the relative position in the Parameters pane under Image Parameters.

Figure 13-4 shows the plunger shape being edited.

9781430244165_Fig13-04.jpg

Figure 13-4 .  Manually defining a shape in PhysicsEditor

Instead of creating the shape from a polygon, you could have also used the Add Rectangle button. (But then I wouldn’t have been able to tell you how to add or delete vertices and how to drag them.)

You can and should also set the collision bits for the plunger under the Fixture parameters section in the Parameters pane. The collision bits allow you to define which shapes collide and which don’t. You’ll use the collision bit settings to prevent the plunger from colliding with any shapes but the ball.

To make working with collision bits easier, you can change the names of collision bits. The collision bit names aren’t exported; they serve only to remind you what each bit’s used for. By default, the bits are called bit_0 to bit_15. You’ll want to change the names of the first five bits to Ball, Bumper, Flipper, Plunger, and Wall, respectively.

Box2D shapes collide with each other only when the category bit (the check box column labeled Cat.) and the mask bit (the check box column labeled Mask) of both shapes are set. Typically you’ll want to assign each shape to just one particular category. In the case of the plunger, make sure that only the category bit for the Plunger category is set. In other words, you’re assigning the plunger’s shape to be in the Plunger collision bit category. With the Mask check box, you then set which other categories this shape is allowed to collide with with. In the case of the plunger, you should set only the Mask check box for the Ball category, allowing the plunger to collide with the ball. Figure 13-5 shows the correct settings for the Plunger collision category and mask flags.

9781430244165_Fig13-05.jpg

Figure 13-5 .  Collision parameters for the plunger

Note  So far, the plunger would collide with the ball, but the ball wouldn’t collide with the plunger. Keep in mind that defining collisions is a two-way process, and in this case you still have to put the ball in the Ball category and check the ball’s Mask bit that corresponds with the Plunger category to have both ball and plunger collide with each other.

You can also set the mask bit for the same category, allowing multiple objects of the same category to collide with each other. In the case of the ball shape, it would make sense to set the Mask bit of the Ball category to allow ball-to-ball collisions. This will be useful if you want to extend the pinball game to support a multiple balls on the table at the same time.

Below the Cat. and Mask columns are the buttons All, None, and Inv., which allow you to check all, uncheck all, or invert the check boxes’ checked status. They’re helpful to avoid clicking possibly dozens of check boxes one after another.

Defining the Table Shapes

The pinball table consists of three separate shapes named table-bottom, table-left, and table-top. The table background image is split up to make it easier to edit its shapes and to make it easier to create different pinball layouts without having to replace the entire image.

Select the table-top image in the Shapes pane to start editing the shape for the topmost part of the pinball table. As you can see in Figure 13-6, it’s a concave shape. With the manual method used to define the plunger, it would be difficult and error-prone to create all the vertices of this round shape manually, let alone ensure that the resulting shape is convex. PhysicsEditor makes that a lot easier for you: it traces the shape’s outline and creates a suitable shape with a single mouse click!

9781430244165_Fig13-06.jpg

Figure 13-6 .  The Shape Tracer creates shapes automatically

Click the magic wand icon in the toolbar called the Shape Tracer to open the Shape Tracer dialog shown in Figure 13-6.

The Shape Tracer shows the shape’s image and an overlay of the shape it’s going to create when you click the OK button. Below the image are a slider and buttons to its left and right that control the zoom level of the image. The image zoom settings don’t affect how the shape is created.

The most important setting you’ll want to adjust in the Shape Tracer is the Tolerance setting. Tolerance changes how accurately the image is traced to create a shape, which directly influences the number of vertices used for the shape, and the number of vertices of a shape in turn influence the performance of the physics simulation. Generally, you should always strive to achieve adequate collision responses with the least amount of vertices. The more important accurate collisions are for your game, the more vertices you should allow for some objects. At the same time, if you add many objects using the same shape, using a shape with fewer vertices will result in better performance.

By experimenting with the Tolerance setting, I found that a good compromise in this case is a Tolerance value of 4,0. This creates a shape with 18 vertices, and it’s the highest Tolerance value that still traces the image’s shape quite accurately. The default Tolerance setting of 1,0 would have created a shape with 31 vertices, so I was able to save 13 vertices. But you’ll notice if you cycle through the Tolerance values that even a Tolerance value of only 1,5 will already cut down the number of vertices to 20.

Tip  You can use the Frame Mode setting in the Shape Tracer to create a shape for an animation (sequence of images). To create an animation in PhysicsEditor, you’ll have to add more image files to a shape under Image Parameters in the Parameters pane, in the default PhysicsEditor window. By clicking the + button next to the Filename setting, you can add additional images to a single shape; then you can have the shape trace create a shape that’s either the intersection or the union of each animation frame’s shape.

When you’re satisfied with the traced shape, click the OK button to close the Shape Tracer dialog. This creates the new shape. You still have to perform a quick manual tweak for the pinball table’s collisions to work smoothly. Because the screen area defines the collision on the sides of the pinball table, you should drag the lower left-hand and lower right-hand corner vertices as well as the upper left-hand and upper right-hand corner vertices slightly outside the screen area and downward and upward, respectively, so that they’re clearly outside the black frame border drawn around the table-top image. See Figure 13-7 for a visual hint.

By extruding these two vertices, the shape will form a soft transition to the sides of the screen borders. Without extruding them, the ball might get reflected from the tips of these vertices because of the slight inaccuracies always present in physics simulations.

Now move the anchor point (the blue circle with the +) to the top left-hand corner, preferably by using the anchor point settings in the Parameters pane under Image Parameters. Set the Relative values to 0,0 and 1,0, respectively, to move the anchor point to the top left-hand corner. This anchor point position will later let you align the image exactly with the screen border by simply positioning it to 0,480 in point coordinates.

Note  If you don’t see the anchor point circle, and there are no anchor point settings under Image Parameters, you don’t have the Exporter setting in the Parameters pane set to the Box2D generic (PLIST) format.

The last step is to adjust the collision bits of the table-top shape. Check the category (Cat.) check box of the Wall row and then set the Mask check box in the Ball row. This will enable collisions of the pinball table with the ball. Accordingly, you’ll have to set the same collision bits for the table-left and table-bottom shapes because they’re all part of the Wall category and should collide with the Ball category.

9781430244165_Fig13-07.jpg

Figure 13-7 .  Finalizing the table’s top shape

Use the Shape Tracer to create the shape for the table-left image in the same way. Under Image Parameters, move the anchor point to Pixel coordinates 0,0 and 50,0. Don’t forget to set the collision bits just like earlier for table-top.

Now you’ll trace the shape for the table-bottom image. This requires a few additional steps because the table-bottom image actually consists of four individual, disjointed elements that need to have individual shapes. Version 1.0.4 of PhysicsEditor traces only contiguous shapes, so you need to open the Shape Tracer a total of four times. Each time you open the Shape Tracer, click the part of the image you want to trace and click OK to create the shape. A Tolerance setting of 4,0 works well for all four shapes. You should end up with four shapes covering all the four elements of the table-bottom image. Don’t forget to set the collision bits exactly like you did for table-top, as shown in Figure 13-7.

Defining the Flippers

Select the flipper-left image and open the Shape Tracer. Notice that the resulting shape initially suggested by the Shape Tracer doesn’t make much sense—the shape is a lot larger than the image would suggest.

That’s because the image has glow and shadow effects that create an aura around the shape itself and that’s nearly invisible on a light background. To enable the Shape Tracer to create a better shape, adjust the Alpha threshold setting. The default value is 0, which means that all pixels that are not entirely transparent will be considered when the shape is traced. In this case, you want to consider only the fully opaque pixels for the shape. If you set the Alpha threshold to 254, as in Figure 13-8, you get a much better result.

9781430244165_Fig13-08.jpg

Figure 13-8 .  Tracing the shape of a flipper with an alpha threshold to ignore the image shadow

Tip  If for any reason the Shape Tracer result isn’t what you want, and neither the Tolerance nor the Alpha threshold setting lets you fix the shape, you can still manually edit the shape after closing the Shape Tracer. Just click any vertex and drag it. You can also double-click a vertex to remove it or double-click a line segment between two vertices to add a new vertex.

Repeat the same process for the flipper-right image. And don’t forget to set the collision bits for both flippers. You’ll want to check the Flipper category and the Mask check box in the Ball row. Figure 13-8 shows the correct collision bit settings.

You should set the anchor point of the left flipper to Pixel coordinates 27,78—and 97,79 for the right flipper.

Defining the Bumper and Ball

The ball and bumper images are both circles, so you can use the Add Circle command from the toolbar to create a circle shape. The circle shape has only one “vertex” that acts as a handle to resize the circle shape.

Starting with the bumper, change the circle shape’s size and position so that it overlaps the solid part of the bumper while ignoring the bumper’s shadow. Set the bumper’s anchor point to Pixel coordinates 33, 42 so that the anchor point is centered on the bumper. Set the collision bits category check box next to Bumper, and set the Mask check box of the Ball row.

To simulate the bumper’s bounce effect, modify the Fixture parameter in the Parameters pane labeled Restitution. Restitution is the amount of elasticity with which an object repels other colliding objects. A restitution of 0 means that the collision is not elastic at all and will stop the incoming object. A restitution of 1 simulates a perfect elastic collision, allowing the colliding object to continue at the same speed after colliding.

Because in this case you want to simulate an additional repelling force coming from the bumper, you can use a value of 1,5 or higher to have the colliding object move away from the collision at a higher speed than it had when it impacted.

Lastly, create the circle shape for the ball image. Size and position the circle shape so that it overlays the entire ball image. Set the anchor point Relative values to 0,5 and 0,5.

As for the ball’s collision bits, set the category check box next to Ball and check all the Mask check boxes for the other categories: Ball, Bumper, Flipper, Plunger, and Wall. You can also simply click the All button at the bottom of the Mask column. Because you don’t use the other bits, it doesn’t matter if they are checked, too.

Some of the ball’s Fixture Parameters affect how the ball behaves on the table. I set the Density to 8,0, Restitution to 0,3 and Friction to 0,7. These settings give the ball just a little bit of bounce. Feel free to tweak these settings and observe how the ball’s behavior changes.

Save and Publish

Lastly you should save the current PhysicsEditor .pes file. You can later open the .pes file to continue editing shapes. You shouldn’t add the .pes file to your Xcode project; it’s used only by PhysicsEditor.

To use the shapes in cocos2d, you use the Publish button. This creates a .plist file that the GB2ShapeCache class distributed with PhysicsEditor can read. For the PhysicsBox2DPinball01 project, I published the shapes as pinball-shapes.plist in the Resources folder of the project. Add this file to the Resources group in Xcode.

Programming the Pinball Game

Now you can move on to the implementation phase and actually program the pinball game. You’ll learn how to lay out the table before moving on to the interactive elements like the ball, plunger, bumpers, and flippers. But before we get to that, I’d like to introduce you to the essential BodySprite class, which synchronizes a cocos2d sprite with a Box2D body not unlike the PhysicsSprite class provided by cocos2d’s Box2D project template. The main difference is that BodySprite uses the GB2ShapeCache class to create its b2Body object and will also destroy its body when a BodySprite object is released from memory.

Caution  Keep in mind that the pinball project uses Box2D, which is written in C++. This requires you to use the .mm file extension instead of .m for all class implementation files so that the compiler correctly switches to compiling C++ code instead of C code. Whenever you create a new Objective-C class, you have to rename the implementation file so that it uses the .mm file extension. Otherwise, you’ll see a lot of compile errors seemingly caused by the Box2D source code.

Forcing Portrait Orientation

The pinball table is designed to be played in portrait orientation, but cocos2d sets up its project templates to use only landscape orientation. You can easily fix that by opening AppDelegate.m and locating the method shouldAutorotateToInterfaceOrientation. This method should return YES for all supported orientations, otherwise it should return NO. You can use the UIInterfaceOrientationIsPortrait macro to test whether a given interface orientation is a portrait mode. If so, it will return YES.

-(BOOL) shouldAutorotateToInterfaceOrientation: ←
    (UIInterfaceOrientation)interfaceOrientation
{
    return UIInterfaceOrientationIsPortrait(interfaceOrientation);
}

Kobold2D users can also use the Supported Device Orientation buttons on the Summary tab of the application target’s properties to change which orientations are supported and which aren’t.

The BodySprite Class

The idea behind the BodySprite class is that you want to use a self-contained object for all your dynamic classes. So far, you simply added the body to the PhysicSprite’s physicsBody property, and it’s easy to forget to add the sprite itself as userdata to the body. The BodySprite class is designed to make the PhysicsSprite class more self-contained.

BodySprite is derived from PhysicsSprite and therefore contains a Box2D physicsBody property and instance variable. With all classes for the pinball game elements being derived from the BodySprite class, you have a common class to work with, which you can then further probe for its type. For example, you can use the isKindOfClass method to determine at runtime from any BodySprite* pointer which class you’re working with. The isKindOfClass method is supported by all classes that derive from NSObject.

In addition, the BodySprite class header file includes commonly used headers such as Box2d.h and Helper.h (see Listing 13-1). The Helper class contains helpful functions you’ve used before, such as toPixels and toMeters, as well as locationFromTouch and screenCenter. The Constants.h file contains the PTM_RATIO because it will be needed by multiple classes.

Listing 13-1.  The BodySprite Header File

#import "cocos2d.h"
#import "Constants.h"
#import "Helper.h"
#import "PhysicsSprite.h"
#import "GB2ShapeCache.h"
 
@interface BodySprite : CCSprite
{
}
 
/**
 * Creates a new shape
 * @param shapeName: Name of the shape and sprite
 * @param inWorld: Pointer to the world object to add the sprite to
 * @return BodySprite object
 */
-(id) initWithShape:(NSString*)shapeName inWorld:(b2World*)world;
 
/**
 * Changes the body's shape
 * Removes the fixtures of the body replacing them
 * with the new ones
 * @param shapeName name of the shape to set
 */
-(void) setBodyShape:(NSString*)shapeName;
 
@end

You use the initializer method initWithShape to initialize both the body and the sprite by using the supplied shape name as defined in PhysicsEditor. It assumes that both the name of the image and the name of the shape are identical.

Caution  By default TexturePacker retains the file extension of images so that your shape might be named “plunger,” but the sprite frame name might be “plunger.png.” To fix that, you have to check the Trim sprite names check box in the TexturePacker Output pane. The pinball.tps file used in this chapter has this check box already set; it removes the .png extension from the sprite frame names. That way, both the TexturePacker sprite frames and PhysicsEditor shape names will be identical.

For the texture atlas, you use TexturePacker and add the pinball folder as a smart folder reference, meaning that TexturePacker will keep the texture atlas contents up to date with all changes to the images in the pinball folder. Refer to Chapter 6 for instructions on how to set up a smart folder reference.

You also need to set an additional option you haven’t used yet: trim sprite names. This feature removes the .png suffix from the sprite names. The advantage is that you can use the same names for the physics shapes and the sprites. The only caveat is that you’ll have to refer to sprite frame names without the .png extension, which admittedly out of habit is sometimes easy to forget.

Listing 13-2 shows the BodySprite class implementation file.

Listing 13-2.  The BodySprite Class Implementation

#import "BodySprite.h"
 
@implementation BodySprite
 
@synthesize body;
 
-(id) initWithShape:(NSString*)shapeName inWorld:(b2World*)world
{
    // init the sprite itself with the given shape name
    self = [super initWithSpriteFrameName:shapeName];
    if (self)
    {
        // create the body
        b2BodyDef bodyDef;
        physicsBody = world- > CreateBody(&bodyDef);
        physicsBody- > SetUserData((__bridge void*)self);
    
        // set the shape
        [self setBodyShape:shapeName];
    }
    return self;
}
 
-(void) setBodyShape:(NSString*)shapeName
{
    // remove any existing fixtures from the body
    b2Fixture* fixture;
    while ((fixture = physicsBody- > GetFixtureList()))
    {
        physicsBody- > DestroyFixture(fixture);
    }
 
    // attach a new shape from the shape cache
    if (shapeName)
    {
        GB2ShapeCache* shapeCache = [GB2ShapeCache sharedShapeCache];
        [shapeCache addFixturesToBody:physicsBody forShapeName:shapeName];
 
        // Assign the shape’s anchorPoint (the blue + in a circle in PhysicsEditor)
        // as the BodySprite’s anchorPoint. Otherwise image and shape would be offset.
        self.anchorPoint = [shapeCache anchorPointForShape:shapeName];
    }
}

-(void) dealloc
{
    // remove the body from the world
    physicsBody- > GetWorld()- > DestroyBody(physicsBody);
}

@end

The BodySprite implementation initializes the sprite with the initWithSpriteFrameName method. Then it calls the world’s CreateBody method and sets self as the user data pointer of the body, allowing you to later access the BodySprite class in the Box2D collision callback methods. Because you’re using ARC, you need to __bridge cast self to void* to tell the compiler that self is passed into the C++ method SetUserData as a weak reference. In other words, you’re telling ARC that Box2D won’t release the memory of the self object, and that you accept that if self is released from memory by ARC, the body’s userdata will be an invalid pointer. Because BodySprite releases the Box2D body when it’s released, this won’t cause any issues because the body is released from memory before the self object.

Tip  The CCNode class also has a userData property, which you can use in the same way as b2Body’s userData field, including the requirement to use (__bridge void*) casts. But CCNode also has a userObject property, which is of type id and has the same purpose as userData. If you need to store an arbitrary Objective-C class object in a CCNode object, use the userObject property to avoid having to perform a __bridge cast.

The PhysicsEditor GB2ShapeCache class is used to add the fixtures to the body using the provided shapeName. The PhysicsBox2DPinball01 project already includes the necessary files. To make use of the GB2ShapeCache class in a new projects, just remember to add the GB2ShapeCache.h and GB2ShapeCache.mm files from the PhysicEditor.dmg disk image folder /Loaders/generic-box2d-plist to your Xcode project and then re-run the Convert to Objective-C ARC process described earlier.

The key point to take away here is that the BodySprite is a CCSprite that manages the allocation, deallocation, and configuration of the Box2D body for you. It also ensures that you can access the BodySprite class and thus the cocos2d sprite wherever you normally have access only to the Box2D body object. Thus, the BodySprite class becomes the glue that brings the physics body together with the cocos2d sprite.

If a sprite derived from BodySprite goes out of scope—for example, if you remove it as a child from its cocos2d parent node—then BodySprite will take care of destroying the Box2D body for you. This is the most important distinction from cocos2d’s PhysicSprite class, which only gets you halfway there.

In addition, you can change the sprite’s body shape by calling setBodyShape. This removes any existing fixtures from the body and then adds the fixtures associated with the given shapeName from the GB2ShapeCache. This will change only the collision shape of the body and preserve the body’s current state of motion, like its velocity, position, and rotation. Keep in mind that calling this method is time-consuming and should be done only as needed. It’s certainly not a good idea to change a body’s shape every frame.

All classes inheriting from BodySprite now have to concern themselves only with setting the correct shape name and any code that’s unique to the class.

Creating the Pinball Table

The pinball’s table is made up of three individual images and associated shapes, named table-top, table-left, and table-bottom. You’ll create the table using the TablePart class, which inherits from BodySprite. The TablePart header only adds the static initializer method tablePartInWorld, as seen in Listing 13-3.

Listing 13-3.  TablePart Class Interface

#import "BodySprite.h"
 
@interface TablePart : BodySprite
{
}
 
+(id) tablePartInWorld:(b2World*)world position:(CGPoint)pos name:(NSString*)name;
@end

The TablePart implementation in Listing 13-4 is also rather unimpressive because its only purpose is to initialize the BodySprite and then set body’s position to the desired location. This will also update the sprite’s position automatically the next time the update method of the PinballTableLayer class is executed (refer to Listing 13-3).

The TablePart class also sets the body type to b2_staticBody, which makes each TablePart a nonmoving object. This has two advantages, one being that Box2D doesn’t need to perform certain calculations on static objects, and the other simply that objects colliding with a static body willn’t affect a static body’s position or rotation at all.

Listing 13-4.  TablePart Class Implementation

#import "TablePart.h"
#import "Helper.h"
 
@implementation TablePart
-(id) initWithWorld:(b2World*)world position:(CGPoint)pos name:(NSString*)name
{
    if ((self = [super initWithShape:name inWorld:world]))
    {
        // set the body position
        physicsBody- > SetTransform([Helper toMeters:pos], 0.0f);
 
        // make the body static
        physicsBody- > SetType(b2_staticBody);
    }
    return self;
}
 
+(id) tablePartInWorld:(b2World*)world position:(CGPoint)pos name:(NSString*)name
{
    return [[self alloc] initWithWorld:world position:pos name:name];
}
@end

To create the three required TablePart instances (and later other pinball elements), I’ve created the TableSetup class, which creates the various BodySprite instances that will make up the bodies of the pinball table. TableSetup inherits from CCSpriteBatchNode to improve the rendering performance of the pinball table’s elements.

This is the header file of the TableSetup class:

#import "cocos2d.h"
#import "Box2D.h"
 
@interface TableSetup : CCSpriteBatchNode
{
}
 
+(id) setupTableWithWorld:(b2World*)world;
@end

And because that’s so unspectacular, let’s turn our attention to the implementation of the TableSetup class of the project, shown in Listing 13-5.

Listing 13-5.  TableSetup Class Implementation

#import "TableSetup.h"
#import "Constants.h"
#import "TablePart.h"
 
@implementation TableSetup
-(id) initTableWithWorld:(b2World*)world
{
    if ((self = [super initWithFile:@"pinball.pvr.ccz" capacity:5]))
    {
        // add the table blocks
        [self addChild:[TablePart tablePartInWorld:world
                       position:ccp(0, 480)
                                      name:@"table-top"]];
        [self addChild:[TablePart tablePartInWorld:world
                       position:ccp(0, 0)
                                      name:@"table-bottom"]];
        [self addChild:[TablePart tablePartInWorld:world
                       position:ccp(0, 263)
                                      name:@"table-left"]];
    }
 
    return self;
}
 
+(id) setupTableWithWorld:(b2World*)world
{
    return [[self alloc] initTableWithWorld:world];
}
@end

As of now, the job of the TableSetup class is to create the three TablePart classes that make up the static background elements of the pinball table. It also sets the correct position of each TablePart instance, which is influenced by their shape’s anchorPoint. For example, the table-top image has its shape anchorPoint set to the upper left-hand corner of the image so that positioning it at (0, 480) aligns the image and shape correctly at the top border of the screen. Because TablePart inherits from BodySprite, which inherits from CCSprite, it’s legal and quite convenient to add these classes directly to the TableSetup class, which inherits from CCSpriteBatchNode.

You’ll later extend the TableSetup class to add the other pinball table elements—plunger, ball, bumpers, and flippers.

Tip  Did you notice the use of the ccp method? It’s exactly the same as the CGPointMake method, but some cocos2d developers prefer to use ccp over CGPointMake simply because it’s shorter to type. You’ll find the entire ccp line of helpful math functions defined in CGPointExtension.h. They tend to come in handy particularly when you’re developing physics games. Box2D also brings its own math functions that are defined in b2Math.h. The reason is that Box2D’s vector classes like b2Vec2 are C++ classes, and not C structs like CGPoint, meaning the Box2D data structures generally can’t be used with the ccp methods.

The TableSetup class itself is initialized by the PinballTableLayer class, which is based on the HelloWorldLayer class of the Box2D project from Chapter 12. Listing 13-6 shows the interface of the PinballTableLayer class.

Listing 13-6.  PinballTableLayer Class Header File

#import "cocos2d.h"
#import "Box2D.h"
#import "GLES-Render.h"
#import "ContactListener.h"
 
@interface PinballTableLayer : CCLayer
{
    b2World* world;
    ContactListener* contactListener;
    GLESDebugDraw* debugDraw;
}

+(id) scene;
@end

It contains references to the ContactListener and GLESDebugDraw classes. The latter I’ll get to shortly; the ContactListener class will play a role when you’re adding the pinball game’s plunger. For now, look at the initialization of the PinballTableLayer class in Listing 13-7.

Listing 13-7.  Initialization of the PinballTableLayer Class

#import "PinballTableLayer.h"
#import "BodySprite.h"
#import "Constants.h"
#import "Helper.h"
#import "GB2ShapeCache.h"
#import "TableSetup.h"
 
@implementation PinballTableLayer
-(id) init
{
    if ((self = [super init]))
    {
        // pre load the sprite frames from the texture atlas
        [[CCSpriteFrameCache sharedSpriteFrameCache]←
        addSpriteFramesWithFile:@"pinball.plist"];
 
        // load physics definitions
        [[GB2ShapeCache sharedShapeCache] addShapesWithFile:@"pinball-shapes.plist"];
 
        // init the box2d world
        [self initPhysics];
 
        // load the background from the texture atlas
        CCSprite* background = [CCSprite spriteWithSpriteFrameName:@"background"];
        background.anchorPoint = ccp(0,0);
        background.position = ccp(0,0);
        [self addChild:background z:-3];
 
        // Set up table elements
        TableSetup* tableSetup = [TableSetup setupTableWithWorld:world];
        [self addChild:tableSetup z:-1];
 
        [self scheduleUpdate];
    }
    return self;
}
 
. . .
 
@end

The CCSpriteFrameCache loads the sprite frames from the texture atlas created with TexturePacker by loading the pinball.plist file.

More importantly, this is followed by loading the pinball-shapes.plist file into the GB2ShapeCache class. It is important to do this first before calling any other Box2D method so that the shape cache’s ptmRatio is correctly set to the value from the PhysicsEditor setting PTM-Ratio. If you’ll recall, PTM-Ratio was one of the first PhysicsEditor settings you modified at the beginning of this chapter.

The pixel-to-meters macro PTM_RATIO used in Chapter 12 has been changed from a simple constant value to the following definition, which you’ll find in the Constants.h header file:

#import "GB2ShapeCache.h"
#define PTM_RATIO ([GB2ShapeCache sharedShapeCache].ptmRatio * 0.5f)

This improved PTM_RATIO macro now retrieves the points-to-meter ratio from the GB2ShapeCache class. And that’s why you have to make sure the shape cache is initialized before using the PTM_RATIO macro.

The shape cache’s ptmRatio is divided by two (multiplied by 0.5f) because the shapes created in PhysicsEditor were based on the Retina-resolution images, whereas cocos2d’s resolution-independent point coordinates used for positioning nodes always assume the iPhone’s screen resolution to be 480x320 points.

The initPhysics method still performs the initialization of the Box2D physics engine. The Box2D init code is essentially the same as in the previous chapter, with two exceptions: the static screen boundary shape that keeps dynamic objects inside the screen area defines no top or bottom shapes, allowing dynamic bodies to fall outside the screen through the bottom. We’ll need that for the ball to be able to roll into the table’s drain. You don’t need the top shape because there will be a pinball table shape blocking the entire upper area of the screen.

In addition to that, the collision parameters for the left and right boundary are set in the init code, because these shapes aren’t defined with the help of PhysicsEditor. The categoryBits must be set to the Wall bit (collision bit 4 in PhysicsEditor), and the maskBits must be set to the Ball bit (collision bit 0 in PhysicsEditor). The bit values are specified in hexadecimal format, indicated by the leading 0x. You’ll find that PhysicsEditor displays those hexadecimal values below the Cat. and Mask columns under Fixture Parameters.

The following is the initBox2dWorld method, with the changes and additions, compared to the Box2D initialization code from Chapter 12 highlighted:

-(void) initPhysics
{
    b2Vec2 gravity;
    gravity.Set(0.0f, -10.0f);
    world = new b2World(gravity);
    world- > SetAllowSleeping(true);
    world- > SetContinuousPhysics(true);
 
    contactListener = new ContactListener();
    world- > SetContactListener(contactListener);
 
    debugDraw = new GLESDebugDraw(PTM_RATIO);
    world- > SetDebugDraw(debugDraw);
 
    uint32 flags = 0;
    flags + = b2Draw::e_shapeBit;
    flags + = b2Draw::e_jointBit;
    // flags + = b2Draw::e_aabbBit;
    // flags + = b2Draw::e_pairBit;
    // flags + = b2Draw::e_centerOfMassBit;
    debugDraw- > SetFlags(flags);
    // Define the ground body.
    b2BodyDef groundBodyDef;
 
    // Call the body factory which allocates memory for the ground body
    // from a pool and creates the ground box shape (also from a pool).
    // The body is also added to the world.
    b2Body* groundBody = world- > CreateBody(&groundBodyDef);
 
    // Define the ground box shape.
    CGSize screenSize = [CCDirector sharedDirector].winSize;
    float boxWidth = screenSize.width / PTM_RATIO;
    float boxHeight = screenSize.height / PTM_RATIO;
    b2EdgeShape groundBox;
    int density = 0;
 
    // left
    groundBox.Set(b2Vec2(0, boxHeight), b2Vec2(0, 0));
    b2Fixture* left = groundBody- > CreateFixture(&groundBox, density);
    // right
    groundBox.Set(b2Vec2(boxWidth, boxHeight), b2Vec2(boxWidth, 0));
    b2Fixture* right = groundBody- > CreateFixture(&groundBox, density);
 
    // set the collision flags: category and mask
    b2Filter collisonFilter;
    collisonFilter.groupIndex = 0;
    collisonFilter.categoryBits = 0x0010; // category = Wall
    collisonFilter.maskBits = 0x0001; // mask = Ball
 
    left- > SetFilterData(collisonFilter);
    right- > SetFilterData(collisonFilter);

}

You’ve enabled continuous physics for this project because the ball can move at high speeds. Without continuous physics it’s possible that the ball may penetrate or pass through other objects, or leave the table entirely.

If you run the project now, you’ll see . . . a pinball table. Great. But how do you know that the collision shapes are properly placed and active?

Box2D Debug Drawing

This is where the GLESDebugDraw class defined in the GLES-Render files comes in handy. It’s also the reason why in Listing 13-7 you added all child objects using a negative z-order. Remember that any drawing done by OpenGL ES code in a node’s draw method is drawn at a z-order of 0. If you want the OpenGL ES drawings to actually be drawn over other nodes, you need to give those nodes a negative z-order.

Have another look at the part of the initPhysics method of the PinballTableLayer class that initializes the Box2D debug drawing:

    debugDraw = new GLESDebugDraw(PTM_RATIO);
    world- > SetDebugDraw(debugDraw);
 
    uint32 flags = 0;
    flags + = b2Draw::e_shapeBit;
    flags + = b2Draw::e_jointBit;
    // flags + = b2Draw::e_aabbBit;
    // flags + = b2Draw::e_pairBit;
    // flags + = b2Draw::e_centerOfMassBit;
    debugDraw- > SetFlags(flags);

An instance of the GLESDebugDraw class is created, using the pixel-to-meter ratio given by the PTM_RATIO macro, and then stored in the PinballTableLayer instance variable debugDraw. The debugDraw instance is then passed to the Box2D world via the SetDebugDraw method. You can define what to draw by setting the bits defined in b2DebugDraw, with the e_shapeBit being the most important because it draws the collision shapes of all bodies. Feel free to try out the other bits too, but too much information is a real concern with debug drawing. It makes it harder to see what you actually want to be seeing, and the more that’s drawn, the lower the performance will be.

You also have to override the draw method of the PinballTableLayer class and call the debugDraw- > DrawDebugData() method to actually draw the debug info. Because you don’t want the end user to see the debug info, the draw method is enclosed in an #ifdef DEBUG . . . #endif statement so that it’s visible only in debug builds:

#if DEBUG
-(void) draw
{
    [super draw];
 
    ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position);
    kmGLPushMatrix();
    world- > DrawDebugData();
    kmGLPopMatrix();
}
#endif

Adding the Ball

Can you imagine a pinball game without a pinball? I can’t, so let’s add one to the PhysicsBox2DPinball01 project and have a look at its implementation. The aptly named Ball class is derived from BodySprite and also implements the CCTargetedTouchDelegate protocol for experimentation purposes (see Listing 13-8).

Listing 13-8.  The Ball Class’s Interface

#import "BodySprite.h"
#import "Box2D.h"
 
@interface Ball : BodySprite < CCTargetedTouchDelegate>
{
    BOOL moveToFinger;
    CGPoint fingerLocation;
}
 
+(id) ballWithWorld:(b2World*)world;
@end

There’s the usual static initializer ballWithWorld, which takes a b2World pointer as input. Then you have the member variable moveToFinger, which determines whether the ball should move toward the touch location, and the fingerLocation CGPoint variable, which specifies the actual location of the finger. We can use those to have a little fun with the ball as long as the pinball game doesn’t yet have any other interactive elements to move the ball. Take a look at the Ball initialization and cleanup methods in Listing 13-9.

Listing 13-9.The init and cleanup Methodsof the Ball Class

#import "Ball.h"
 
@implementation Ball
-(id) initWithWorld:(b2World*)world
{
    if ((self = [super initWithShape:@"ball" inWorld:world]))
    {
     // set the parameters
     physicsBody- > SetType(b2_dynamicBody);
     physicsBody- > SetAngularDamping(0.9f);
 
     // set random starting point
     [self setBallStartPosition];
 
     // enable handling touches
     [[CCDirector sharedDirector].touchDispatcher addTargetedDelegate:self
     priority:0
     swallowsTouches:NO];
 
     // schedule updates
     [self scheduleUpdate];
    }
    return self;
}
 
+(id) ballWithWorld:(b2World*)world
{
    return [[self alloc] initWithWorld:world];
}
 
-(void) cleanup
{
    [super cleanup];
    [[CCDirector sharedDirector].touchDispatcher removeDelegate:self];
}

Just as with the TablePart class, the initialization begins by calling the BodySprite init method initWithShape, which takes care of setting up the body and sprite. Well, it almost does—because you do have to set the body type to be a b2_dynamicBody to let Box2D know that this body should be treated as a movable object.

In addition, the angular damping value of the body is set to 0.9f, which makes the ball’s angular motion more resistant to change. This allows the ball to slide over a surface without rolling too much, which is standard behavior for heavy pinballs made of metal.

Tip  Tweaking physics values is usually very labor-intensive and requires careful consideration of each change. It’s also frequently underestimated by both designers and programmers alike. That’s because all physics attributes are interrelated or interdependent. For example, if you change the density (mass), friction, or restitution of one object, you’ll inevitably alter the behavior of any colliding object. Notice that this example pinball game, although it does a fairly good job of simulating a pinball game, would still require a lot of tweaking and fine-tuning of the ball’s behavior and collision responses for everything to feel just right and to be a fair and fun pinball table. PhysicsEditor can help you with the tweaking of values by providing you a single, convenient interface for editing any shape’s physics parameters.

The setBallStartPosition method repositions the ball somewhere in the area where you’ll later add the plunger. By slightly randomizing the ball’s position, the plunger will later shoot the ball into play more realistically, meaning unpredictably to some extent. Whenever the ball falls into the drain, the setBallStartPosition method is called again to place the ball back to its start position.

-(void) setBallStartPosition
{
    // set the ball's position
    float randomOffset = CCRANDOM_0_1() * 10.0f - 5.0f;
    CGPoint startPos = CGPointMake(305 + randomOffset, 80);
 
    physicsBody- > SetTransform([Helper toMeters:startPos], 0.0f);
    physicsBody- > SetLinearVelocity(b2Vec2_zero);
    physicsBody- > SetAngularVelocity(0.0f);
}

The physicsBody- > SetTransform method positions the ball’s body, and as with all bodies in this example game, the PhysicsSprite class takes care of synchronizing the body’s sprite with the body’s position. The position in pixels must of course be converted to Box2D’s meter units, which is done by using the Helper class’s toMeters method. The second parameter of the SetTransform method is the rotation of the body.

Just changing the body’s position isn’t enough. The body would still keep its current velocity (angular and linear) and would simply keep on moving. The last two lines of the setBallStartPosition method thus reset the linear and angular velocity of the body to 0. Linear velocity determines a body’s speed and direction, whereas angular velocity determines how fast and in which direction the body rotates.

To actually make the ball appear on the pinball table, you also have to add it to the scene. Do that in the init method of the TableSetup class by adding these lines below the initialization of the TablePart objects:

#import "TableSetup.h"
#import "Constants.h"
#import "TablePart.h"
#import "Ball.h"
 
@implementation TableSetup
-(id) initTableWithWorld:(b2World*)world
{
    if ((self = [super initWithFile:@"pinball.pvr.ccz" capacity:5]))
    {
        . . .
 
        Ball* ball = [Ball ballWithWorld:world];
        [self addChild:ball z:-1];
    }
    return self;
}

If you ran the game now, you’d see the ball in the lower left-hand corner drop to the ground, and that’s it. You don’t have any way to move it yet, so you need to add some simple ball movement code for testing the bumpers before you get to add the plunger.

Forcing the Ball to Move

So far, the ball is just dropping down, and that’s it. You need a way, at least temporary, to control the ball. The Ball class implements the CCTargetedTouchDelegate and has registered itself to receive touches. Let’s check what the touch delegate methods do:

-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
    moveToFinger = YES;
    fingerLocation = [Helper locationFromTouch:touch];
    return YES;
}
 
-(void) ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
{
    fingerLocation = [Helper locationFromTouch:touch];
}
 
-(void) ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
    moveToFinger = NO;
}

These methods specify that while a finger is touching the screen, the ball moves toward the finger; and while the finger is moving, the fingerLocation is constantly updated.

Next, take a quick look at the update method of the Ball class:

-(void) update:(ccTime)delta
{
    if (moveToFinger == YES)
    {
        [self applyForceTowardsFinger];
    }
 
    if (sprite.position.y < -(sprite.contentSize.height * 10))
    {
        [self setBallStartPosition];
    }
 
    // limit speed of the ball
    const float32 maxSpeed = 6.0f;
    b2Vec2 velocity = physicsBody- > GetLinearVelocity();
    float32 speed = velocity.Length();
    if (speed > maxSpeed)
    {
        velocity.Normalize();
        physicsBody- > SetLinearVelocity(maxSpeed * velocity);
    }
 
    // reset rotation of the ball
    physicsBody- > SetTransform(physicsBody- > GetWorldCenter(), 0.0f);
}

I’ll get to the applyForceTowardsFinger method next. But while we’re here, notice how you check to see whether the ball has gone down the drain. The sprite’s y position is compared with the sprite image’s height multiplied by 10. Why the multiplication? That’s just to give the impression that it takes a short moment for the ball to roll back before it reappears. If the ball has fallen down far enough outside the screen area, the setBallStartPosition resets the ball’s position, and the fun begins anew.

Problem is, that doesn’t seem to work initially. Unfortunately, the PhysicsSprite class doesn’t update the sprite’s position property at all. But that’s an easy fix. You only need to add one line near the end of the nodeToParentTransform method in the PhysicsSprite class:

-(CGAffineTransform) nodeToParentTransform
{
    b2Vec2 pos = physicsBody- > GetPosition();
    float x = pos.x * PTM_RATIO;
    float y = pos.y * PTM_RATIO;
 
    . . .
    self.position = CGPointMake(x, y);
 
    // Rot, Translate Matrix
    transform_ = CGAffineTransformMake(c, s, -s, c, x, y);
    return transform_;
}

The Ball’s update method also ensures that the ball has a maximum speed that it will never exceed. I’ve chosen the maxSpeed value to be 6.0 merely by trial and error. The length of the linear velocity vector of the ball’s body is the ball’s current speed. If the body’s speed exceeds maxSpeed, the body should be slowed down to maxSpeed without changing its direction. You achieve that by first normalizing the velocity vector, which results in a vector that still points in the same direction but has a length of exactly one unit; this is called a unit vector. With a length of one unit, all you need to do is to multiply this unit vector by maxSpeed to cap the body’s velocity to maxSpeed.

The last line simply causes the body’s rotation to be reset; in other words, the body never rotates. This is to prevent the ball’s sprite from rotating. Because the ball’s image has a highlight and a shadow, you can only create the illusion of a light source shining on the ball if the highlight and shadow of the ball stay in place.

Because the body’s world center is set as the position, the position of the body remains the same, and only its rotation is updated. Doing so doesn’t stop the physical effect of a spinning ball colliding with or sliding along hard surfaces because that effect is calculated from the body’s angular velocity, which remains unaffected. You can safely reset the ball’s rotation, because from the perspective of the physics engine, the ball’s shape being a circle means that the ball’s surface features are the same from any direction. A circle is completely symmetrical.

Now have a look at the applyForceTowardsFinger method, which makes the ball accelerate toward the finger, as in Listing 13-10.

Listing 13-10.  Accelerating the Ball Toward the Touch Location

-(void) applyForceTowardsFinger
{
    b2Vec2 bodyPos = physicsBody- > GetWorldCenter();
    b2Vec2 fingerPos = [Helper toMeters:fingerLocation];
 
    b2Vec2 bodyToFingerDirection = fingerPos - bodyPos;
    bodyToFingerDirection.Normalize();
 
    b2Vec2 force = 2.0f * bodyToFingerDirection;
    physicsBody- > ApplyForce(force, physicsBody- > GetWorldCenter());
}

You have the two positions of the body and the finger, and then you subtract the finger position from the body position. For example, if the body’s position were at the screen center (160, 240), and the finger is touching near the upper right-hand corner of the screen at (300, 450), then subtracting the body position from the finger position, being the subtraction of the individual x and y coordinates, results in (300 - 160, 450 - 240) = (140, 210). The vector bodyToFingerDirection is now (140, 210). It’s called the direction from bodyPos to fingerPos because if you added bodyToFingerDirection to bodyPos, you’d get to the coordinates of fingerPos.

Note  The b2Vec2 struct makes use of a technique called operator overloading, which makes it possible to subtract, add, or multiply two or more b2Vec2 structs with each other. Operator overloading is a feature of the C++ language; it’s not available in Objective-C—so you can’t subtract, add, or multiply CGPoint variables this way.

So, the bodyToFingerDirection vector is now pointing from the body to the finger. When Normalize is called on the bodyToFingerDirection vector, it’s turned into a unit vector, as mentioned earlier. A unit vector is a vector of length 1—or one unit. This allows you to multiply it with a fixed factor, in this case doubling its length, to create a constant force vector pointing in the direction of the finger. You can then use the ApplyForce method of the body to apply this as an external force to the body’s center. You could also use a position other than the center, but in that case, the body would start spinning.

The end result is that the ball accelerates toward the point on the screen that your finger is touching. The ball will usually overshoot, slow down, and return. A little bit like the gravitational pull the sun exerts on our planet, albeit a lot more dramatically.

However, as someone interested in astronomy, I do have to correct myself. Gravity is a force that falls off by the square of the distance between two objects pulling on each other. If you want a more realistic simulation of gravity in your game, simply replace the applyForceTowardsFinger code with that in Listing 13-11.

Listing 13-11.  Simulating Gravitational Pull

-(void) applyForceTowardsFinger
{
    b2Vec2 bodyPos = physicsBody- > GetWorldCenter();
    b2Vec2 fingerPos = [Helper toMeters:fingerLocation];
    float distance = bodyToFingerDirection.Length();
    bodyToFingerDirection.Normalize();
 
    // "real" gravity falls off by the square over distance
    float distanceSquared = distance * distance;
    b2Vec2 force = ((1.0f / distanceSquared) * 20.0f) * bodyToFingerDirection;
    physicsBody- > ApplyForce(force, physicsBody- > GetWorldCenter());
}

The multiplication by 20.0f in this case is a magic number. It’s just there to make the gravitational pull noticeable enough. Now the ball will speed up more the closer it gets to your finger and will barely move if you touch the screen relatively far away from the ball.

Although the applyForceTowardsFinger code serves only as a temporary control mechanism, you could use the gravity code in Listing 13-11 to create magnetic objects on your pinball table.

Adding the Bumpers

Now that you have a ball you can move with your finger, let’s make things a bit more interesting by introducing bumpers to the game. What are bumpers? They’re the round, mushroom-shaped objects that force the ball away when it touches them.

Note  Sometimes people confuse bumpers with the flippers that the player controls or the usually triangular slingshots just above the flippers. If you want to refresh your memory about pinball terminology, the Wikipedia article on pinball can help clarify it: en.wikipedia.org/wiki/Pinball.

Listing 13-12 shows the, once again, rather simple header file of the Bumper class.

Listing 13-12.  The Bumper Class’s Interface

#import "BodySprite.h"
 
@interface Bumper : BodySprite
{
}
 
+(id) bumperWithWorld:(b2World*)world position:(CGPoint)pos;
@end

Once more, the Bumper class is derived from BodySprite. The initialization looks very much like Listing 13-9, in which the ball was initialized, so I’ll just focus on the important part in Listing 13-13.

Listing 13-13.  Initializing the Bumper

#import "Bumper.h"
 
@implementation Bumper
-(id) initWithWorld:(b2World*)world position:(CGPoint)pos
{
    if ((self = [super initWithShape:@"bumper" inWorld:world]))
    {
        // set the body position
        physicsBody- > SetTransform([Helper toMeters:pos], 0.0f);
    }
    return self;
}
 
+(id) bumperWithWorld:(b2World*)world position:(CGPoint)pos
{
    return [[self alloc] initWithWorld:world position:pos];
}
@end

The only key ingredient for the Bumper class is to set its restitution parameter to above 1.0f—in this case you’ve already set it to 1.5f in PhysicsEditor. This gives any rigid body touching the surface of the bumper an impulse that is 50 percent higher than the force with which the bumper was hit. The result is something that’s not possible in the real world (except in real pinball): the impacting object increases its velocity after hitting the bumper’s surface. It’s physics engine magic, and in this case it’s very desirable because you save yourself a lot of headaches in implementing the bumper’s logic. Box2D does it for you.

What’s left is to add some bumpers by adding the following lines in the init method of the TableSetup class, and don’t forget to import the Bumper.h file. Feel free to reposition the bumpers as you desire:

// add some bumpers
[self addBumperAt:ccp( 76, 405) inWorld:world];
[self addBumperAt:ccp(158, 415) inWorld:world];
[self addBumperAt:ccp(239, 375) inWorld:world];
[self addBumperAt:ccp( 83, 341) inWorld:world];
[self addBumperAt:ccp(157, 294) inWorld:world];
[self addBumperAt:ccp(260, 286) inWorld:world];
[self addBumperAt:ccp( 67, 228) inWorld:world];
[self addBumperAt:ccp(183, 189) inWorld:world];

To make adding bumpers more convenient, I added the method addBumperAt to the TableSetup class:

-(void) addBumperAt:(CGPoint)pos inWorld:(b2World*)world
{
    Bumper* bumper = [Bumper bumperWithWorld:world position:pos];
    [self addChild:bumper];
}

Have a look now and try how the bumpers feel in the PhysicsBox2DPinball01 project—pretty close to actual pinball bumpers, I think.

The Plunger

I hate to take control away from you, but for now I must. You’re adding the plunger now, and being able to control the ball with your fingers might get in the way. So, go into the Ball class’s update method and comment out the call to applyForceTowardsFinger:

if (moveToFinger == YES)
{
    // disabled: no longer needed
    // [self applyForceTowardsFinger];

}

Now you can add the Plunger class, which I’ve already done in the PhysicsBox2DPinball01 project. Listing 13-14 shows the Plunger class’s interface, which is also derived from BodySprite.

Listing 13-14.  The Plunger’s Header File

#import "BodySprite.h"
 
@interface Plunger : BodySprite
{
    b2PrismaticJoint* joint;
}
 
+(id) plungerWithWorld:(b2World*)world;
@end

The Plunger class has a member variable for b2PrismaticJoint, which it’s going to use to propel itself upward. A prismatic joint allows only one axis of movement—a telescope bar would be a good example of a prismatic joint in the real world. You can only move the smaller pipe inside the larger pipe, which allows the telescope bar to be extended and retracted, but only in one direction.

Initializing the plunger is also straightforward, as Listing 13-15 shows. I edited the plunger’s physics settings in PhysicsEditor. In particular, I set the friction to a very high value and set restitution to 0. This ensures that the ball is launched smoothly by remaining in close contact with the plunger during the time the plunger is propelled upward.

Listing 13-15.  Initializing the Plunger

#import "Plunger.h"
 
@implementation Plunger
-(id) initWithWorld:(b2World*)world
{
    if ((self = [super initWithShape:@"plunger" inWorld:world]))
    {
        CGSize screenSize = [CCDirector sharedDirector].winSize;
        CGPoint plungerPos = CGPointMake(screenSize.width - 13, -32);
 
        physicsBody- > SetTransform([Helper toMeters:plungerPos], 0);
        physicsBody- > SetType(b2_dynamicBody);
 
        [self attachPlunger];
    }
    return self;
}
+(id) plungerWithWorld:(b2World*)world
{
    return [[self alloc] initWithWorld:world];
}

Most interesting is the call to attachPlunger and the actual creation of the prismatic joint in this method, shown in Listing 13-16.

Listing 13-16.  Creating the Plunger’s Prismatic Joint

-(void) attachPlunger
{
    // create an invisible static body to attach joint to
    b2BodyDef bodyDef;
    bodyDef.position = physicsBody- > GetWorldCenter();
    b2Body* staticBody = physicsBody- > GetWorld()- > CreateBody(&bodyDef);
 
    // Create a prismatic joint to make plunger go up/down
    b2PrismaticJointDef jointDef;
    b2Vec2 worldAxis(0.0f, 1.0f);
    jointDef.Initialize(staticBody, physicsBody, physicsBody- > GetWorldCenter(),←
        worldAxis);
    jointDef.lowerTranslation = 0.0f;
    jointDef.upperTranslation = 0.35f;
    jointDef.enableLimit = true;
    jointDef.maxMotorForce = 80.0f;
    jointDef.motorSpeed = 40.0f;
    jointDef.enableMotor = false;
 
    joint = (b2PrismaticJoint*)physicsBody- > GetWorld()- > CreateJoint(&jointDef);
}

First, a static body is created at the same location as the plunger’s dynamic body. It acts as the larger pipe of a telescope bar held in place so that only the inner pipe may spring upward when released. Remember physics (or Mythbusters, for that matter): every action has an equal and opposite reaction. To avoid the opposite reaction—a downwards-oriented movement of the other body of the prismatic joint—it’s turned into a static, unmovable body holding the plunger in place.

The worldAxis restricts the prismatic joint’s movement to the y-axis (in other words, up and down). The worldAxis is expressed as a normal vector with values from 0.0f to 1.0f; when the y-axis is set to 1.0f, the worldAxis becomes parallel to the y-axis. If you were to set both x- and y-axes to 0.5f, the worldAxis would be a 45-degree angle. b2PrismaticJointDef is initialized with the staticBody and the world center position of the plunger’s dynamic body. As the anchor point for the joint the worldAxis is used, which restricts motion of the prismatic joint along the y-axis.

Now follows a set of parameters. The lower and upper translations define how far along the axis the plunger is allowed to move. In this case, it’s allowed to move 0.35f meters upward, which is exactly 42 points or 84 pixels on Retina display devices and 42 pixels on non-Retina devices. The enableLimit field is set to true so that this movement limit is actually adhered to by the connected bodies. Because the static body doesn’t move, the plunger moves the full extent. If both were dynamic bodies, both bodies would be able to move, which would be undesirable in this case, as mentioned earlier.

Next, set maxMotorForce to 80.0f (the unit in this case is Newton-meters, which is a unit of torque). In Chipmunk this is called the body’s moment. The maxMotorForce value limits the torque, or energy, of the joint’s movement. The motorSpeed then determines how quickly, if at all, this maxMotorForce is reached. I determined both values merely by trial and error until it felt about right. The ball is now catapulted up and around with just about the right speed. The motor is initially disabled because I only want the plunger to go off when there’s a ball touching it.

The joint is then created using the world’s CreateJoint method and stored in the joint member variable. Because CreateJoint returns a b2Joint pointer, it has to be cast to a b2PrismaticJoint pointer before assignment.

Notice that it’s unnecessary to destroy the joint that the Plunger class is keeping as a member variable. The joint is automatically destroyed when either body it’s attached to is destroyed, and in this case the BodySprite’s dealloc method destroys the body.

The plunger must also be added to the table. As usual, you do this in the initTableWithWorld method of the TableSetup class after importing the Plunger.h header file. Simply append the following code:

// Add plunger
Plunger *plunger = [Plunger plungerWithWorld:world];
[self addChild:plunger z:-1];

Creating a Universal Contact Listener

To launch the ball automatically on contact, you need some way to react to collisions. The Box2D physics engine includes the b2ContactListener class. To process collisions, you have to create a custom class that inherits from b2ContactListener and overrides at least one of the collision callback methods BeginContact, EndContact, PreSolve, and PostSolve. You’ve already created a ContactListener C++ class in Chapter 12. Now it’s time to apply some Objective-C to collision callbacks.

On the C++ side you commonly end up with code that looks similar to Listing 13-17. In principle, you retrieve the two colliding bodies from the b2Contact class. In this particular case, each body’s user data pointer holds a pointer to a BodySprite object, which lets you compare the classes to decide how to further process the collision. The verbosity of the code is further increased because a contact’s two bodies may be in any order, even if the same objects collide frequently. That means during any contact, the plunger might be either bodyA or bodyB, and the ball would be the corresponding other body. So, you always have to test for both cases.

Listing 13-17.  A Very Common but Tedious Way to Process Box2D Collisions

void ContactListener::BeginContact(b2Contact* contact)
{
    b2Body* bodyA = contact- > GetFixtureA()- > GetBody();
    b2Body* bodyB = contact- > GetFixtureB()- > GetBody();
    BodySprite* bodySpriteA = (BodySprite*)bodyA- > GetUserData();
    BodySprite* bodySpriteB = (BodySprite*)bodyB- > GetUserData();
 
    if ([bodySpriteA isKindOfClass:[Plunger class]] &&←
        [bodySpriteB isKindOfClass:[Ball class]])
    {
        Plunger* plunger = (Plunger*)bodySpriteA;
        // . . . perform custom code for collision handling
    }
    else if ([bodySpriteB isKindOfClass:[Plunger class]] &&←
             [bodySpriteA isKindOfClass:[Ball class]])
    {
     Plunger* plunger = (Plunger*)bodySpriteB;
     // . . . perform custom code for collision handling
    }
}

The previous approach also requires you to import the header files for each colliding BodySprite class. Over time, the collision-handling class knows about most game objects and can become quite complex and hard to read. Instead, you want to have each BodySprite class handle the collision events that it’s involved in. This keeps the code cleanly separated and easier to maintain and turns the job of the collision handling class to one of delegating the collision events to the colliding objects.

The ContactListener class (Listing 13-18) in the PhysicsBox2DPinball01 project defines two additional methods next to the regular Box2D collision callback methods to perform the delegation of collision events.

Listing 13-18.  The ContactListener Class Definition

class ContactListener : public b2ContactListener
{
private:
    void BeginContact(b2Contact* contact);
    void PreSolve(b2Contact* contact, const b2Manifold* oldManifold);
    void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse);
    void EndContact(b2Contact* contact);
 
    void notifyObjects(b2Contact* contact, NSString* contactType);
    void notifyAB(b2Contact* contact,
                 NSString* contactType,
                 b2Fixture* fixtureA,
                 NSObject* objA,
                 b2Fixture* fixtureB,
                 NSObject* objB);
};

The regular Box2D contact methods are implemented in Listing 13-19. The BeginContact and EndContact methods simply delegate the contact information to the notifyObjects methods but also provide the information about whether it was a begin or end contact event by passing an appropriate NSString object. The reason it’s a string and not a flag or enumeration will become clear shortly. Because you don’t care about the PreSolve and PostSolve events, they remain empty stubs, but could be extended by also calling notifyObjects with the contact and an appropriate string.

Listing 13-19.  Implementation of the Box2D Contact Methods

/// Called when two fixtures begin to touch.
void ContactListener::BeginContact(b2Contact* contact)
{
    notifyObjects(contact, @"begin");
}
/// Called when two fixtures cease to touch.
void ContactListener::EndContact(b2Contact* contact)
{
    notifyObjects(contact, @"end");
}
 
void ContactListener::PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
{
    // do nothing
}
 
void ContactListener::PostSolve(b2Contact* contact, const b2ContactImpulse* impulse)
{
    // do nothing
}

What does the notifyObjects method do? Extracting the two colliding bodies and obtaining their user data pointer is similar to Listing 13-17. But take note of the differences:

void ContactListener::notifyObjects(b2Contact* contact, NSString* contactType)
{
    b2Fixture* fixtureA = contact- > GetFixtureA();
    b2Fixture* fixtureB = contact- > GetFixtureB();
 
    b2Body* bodyA = fixtureA- > GetBody();
    b2Body* bodyB = fixtureB- > GetBody();
 
    NSObject* objA = (NSObject*)bodyA- > GetUserData();
    NSObject* objB = (NSObject*)bodyB- > GetUserData();
 
    if ((objA ! = nil) && (objB ! = nil))
    {
     notifyAB(contact, contactType, fixtureA, objA, fixtureB, objB);
     notifyAB(contact, contactType, fixtureB, objB, fixtureA, objA);
    }
}

In this case, you simply assume the user data pointer to be a pointer to an Objective-C class derived from NSObject. This provides the flexibility that any object can respond to collision events, not just BodySprite objects. If both user data pointers are not nil, then the notifyAB method is called twice, the second time with the A and B variables switched. This ensures that both objA and objB receive the collision notification, and the notifyAB method only needs to handle one case.

The job of the notifyAB method is to construct the selector that should be called and, if possible, call the selector with any contact information that the receiving object might need in order to handle the collision. Listing 13-20 shows the implementation of the notifyAB method.

Listing 13-20.  Implementation of the Box2D Contact Methods

void ContactListener::notifyAB(b2Contact* contact,
                             NSString* contactType,
                             b2Fixture* fixture,
                             NSObject* obj,
                             b2Fixture* otherFixture,
                             NSObject* otherObj)
{
    NSString* format = @"%@ContactWith%@:";
    NSString* otherClassName = NSStringFromClass([otherObj class]);
    NSString* selectorString = [NSString stringWithFormat:format, contactType,←
                       otherClassName];
    SEL contactSelector = NSSelectorFromString(selectorString);
 
    if ([obj respondsToSelector:contactSelector])
    {
        Contact* contactInfo = [[Contact alloc] initWithObject:otherObj
                                   otherFixture:otherFixture
                                   ownFixture:fixture
                                   b2Contact:contact];
        [obj performSelector:contactSelector withObject:contactInfo];
        contactInfo = nil;
    }
}

The format string defines the general naming format of the selectors that will be called. The syntax of the selectors that notifyAB calls is as follows:

<contactType > ContactWith < otherClassName>:(Contact*)contactInfo

The contactTypeis the string you pass to the notifyObjects method, which will be either “begin” or “end” in the current implementation. The otherClassName string is obtained from the NSStringFromClass method, which takes the class of the otherObj. For the collision events of the ball and the plunger, the selectorString will be one of the following, depending on the contactType and the otherObj class name:

beginContactWithBall
endContactWithBall
beginContactWithPlunger
endContactWithPlunger
// and so on . . .

Before performing the selector, notifyAB first checks whether obj actually responds to that selector. In the current implementation, only the Plunger class implements one of the selectors: beginContactWithBall. All other selectors will never be performed because they don’t exist (yet).

The Contact class is also defined in ContactListener.h and merely acts as a container object holding any collision information that you might want to pass to receiving classes. By using a container class, the selector format doesn’t need to change when you decide to pass more or less information, simply because the only parameter is a pointer to a Contact object, and the information is encapsulated within the Contact class.

Tip  There’s also a technical reason for using a container class. The performSelector method of the NSObject class knows only three variants: with zero, one, or two parameters. Because you definitely like to pass on more than two parameters to the receiving object, there’s simply no other choice than to use a container class. Whenever you find the performSelector method limiting, remember that you can always create a container class holding any information that you’d like to pass on to the class implementing the selector.

Because Contact is such a simple class, Listing 13-21 shows both interface and implementation in one listing.

Listing 13-21.  Interface and Implementation of the Contact Class

@interface Contact : NSObject
{
@private
    NSObject* otherObject;
    b2Fixture* ownFixture;
    b2Fixture* otherFixture;
    b2Contact* b2contact;
}
-(id) initWithObject:(NSObject*)otherObject_
     otherFixture:(b2Fixture*)otherFixture_
     ownFixture:(b2Fixture*)ownFixture_
     b2Contact:(b2Contact*)b2contact_;
@end
 
 
@implementation Contact
-(id) initWithObject:(NSObject*)otherObject_
     otherFixture:(b2Fixture*)otherFixture_
     ownFixture:(b2Fixture*)ownFixture_
     b2Contact:(b2Contact*)b2contact_
{
    self = [super init];
    if (self)
    {
     otherObject = otherObject_;
     otherFixture = otherFixture_;
     ownFixture = ownFixture_;
     b2contact = b2contact_;
    }
    return self;
}
@end

The only notable aspect of the Contact class is that you can’t keep the contact for later use. The notifyAB method sets the object to nil immediately after the contactSelector message was sent to indicate that it’s only a temporary object. Directly after the call to the Box2D contact methods, the b2Contact object is released by Box2D, and so the otherFixture, ownFicture, and b2contact pointers will be invalid, and accessing them would cause a crash.

Responding to Contact Events

Now whenever the ball and the plunger get in contact, the beginContactWithBall method in the Plunger class is called:

-(void) endPlunge:(ccTime)delta
{
    // stop the motor
    joint- > EnableMotor(NO);
}
 
-(void) beginContactWithBall:(Contact*)contact
{
    // start the motor
    joint- > EnableMotor(YES);
 
    // schedule motor to come back, unschedule in case the plunger is hit repeatedly
    [self scheduleOnce:@selector(endPlunge:) delay:0.5f];
}

As soon as the ball touches the plunger, the plunger’s motor is enabled, which propels the plunger and thus the ball upward. The endPlunge method is scheduled to stop the motor after a short time. I took extra care to correctly unschedule the selectors. For example, it’s very likely that the beginContactWithBall method is called repeatedly within a short time period, because there may be more than one contact point (Box2D reports each contact point individually); or simply, the ball might bounce a little and lose contact, but the plunger’s motor ensures that the plunger will touch the ball again after a short time.

Similarly, you’ll find the two methods beginContactWithPlunger and beginContactWithBumper implemented in the Ball class. Both types of contact will simply play a sound effect:

-(void) playSound
{
    float pitch = 0.9f + CCRANDOM_0_1() * 0.2f;
    float gain = 1.0f + CCRANDOM_0_1() * 0.3f;
    [[SimpleAudioEngine sharedEngine] playEffect:@"bumper.wav"
     pitch:pitch
     pan:0.0f
     gain:gain];
}
 
-(void) endContactWithBumper:(Contact*)contact
{
    [self playSound];
}
 
-(void) endContactWithPlunger:(Contact*)contact
{
    [self playSound];
}

Tip  Keep in mind that Box2D reports each individual contact of two colliding objects, causing the contact methods to be called more than once for the same two objects. In some cases, you may want to set a Bool variable to YES in order to note that a contact has already happened. The corresponding contact method should first check whether the variable is set, and if it is, skip the code. You should later set the variable back to NO in a scheduled update method to reenable contact events. By doing so, you can avoid contact code being run multiple times and avoid undesirable side effects like too many sounds played at once.

The Flippers

The final ingredients are the flippers, with which you control the action. The two flippers will be controlled by touching the screen on either the left or right side, as Listing 13-22 shows.

Listing 13-22.  The Flipper Interface

#import "BodySprite.h"
 
typedef enum
{
    kFlipperLeft,
    kFlipperRight,
} EFlipperType;

@interface Flipper : BodySprite < CCTargetedTouchDelegate>
{
    EFlipperType type;
    b2RevoluteJoint* joint;
    float totalTime;
}
 
+(id) flipperWithWorld:(b2World*)world flipperType:(EFlipperType)flipperType;
@end

Each flipper is anchored using a b2RevoluteJoint. Take a look at the flipper initWithWorld method in Listing 13-23 to see how the flippers are created.

Listing 13-23.  Creating a Flipper

-(id) initWithWorld:(b2World*)world flipperType:(EFlipperType)flipperType
{
    NSString* name = (flipperType == kFlipperLeft) ? @"flipper-left" : @"flipper-right";
    self = [super initWithShape:name inWorld:world];
 
    if (self)
    {
        type = flipperType;
 
        // set the position depending on the left or right side
        CGPoint flipperPos = (type == kFlipperRight) ? ccp(210, 65) : ccp(90, 65);
 
        // attach the flipper to a static body with a revolute joint
        [self attachFlipperAt:[Helper toMeters:flipperPos]];
 
        // receive touch events
        [[CCDirector sharedDirector].touchDispatcher addTargetedDelegate:self
        priority:0
        swallowsTouches:NO];
    }
    return self;
}
+(id) flipperWithWorld:(b2World*)world flipperType:(EFlipperType)flipperType
{
    return [[self alloc] initWithWorld:world flipperType:flipperType];
}
-(void) cleanup
{
    [super cleanup];
 
    // stop listening to touches
    [[CCDirector sharedDirector].touchDispatcher removeDelegate:self];
}

The Flipper class also registers itself with the CCTouchDispatcher to receive touch input events. The common misconception is that only the CCLayer class can receive input, but in fact CCLayer is merely conveniently preconfigured to receive touches. Any class can register itself with the CCTouchDispatcher class as a delegate, provided that the class also removes itself as a touch delegate, usually in the cleanup method.

As with the other pinball elements, the flippers are added to the TableSetup class after importing the Flipper.h file and initialized as left and right flipper by using the EFlipperType enum from Listing 13-22.

// Add flippers
Flipper *left = [Flipper flipperWithWorld:world flipperType:kFlipperLeft];
[self addChild:left];
 
Flipper *right = [Flipper flipperWithWorld:world flipperType:kFlipperRight];
[self addChild:right];

Note  I could have used the flipper’s shape names instead, but I wanted to hide this implementation detail. No one but the Flipper class should be concerned with what the flipper’s frame and shape names are.

The attachFlipperAt method creates the revolute joints (see Listing 13-24), with a few modifications for the right flipper in order to change the direction and upper limit of the right flipper’s rotation. The point the flippers rotate around will be the anchor point of their shapes, which is editable in PhysicsEditor.

Listing 13-24.  Creating the Flipper Revolute Joint

-(void) attachFlipperAt:(b2Vec2)pos
{
    physicsBody- > SetTransform(pos, 0);
    physicsBody- > SetType(b2_dynamicBody);
 
    // turn on continuous collision detection to prevent tunneling
    physicsBody- > SetBullet(true);
 
    // create an invisible static body to attach to'
    b2BodyDef bodyDef;
    bodyDef.position = pos;
    b2Body* staticBody = physicsBody- > GetWorld()- > CreateBody(&bodyDef);
 
    // setup joint parameters
    b2RevoluteJointDef jointDef;
    jointDef.Initialize(staticBody, physicsBody, staticBody- > GetWorldCenter());
    jointDef.lowerAngle = 0.0f;
    jointDef.upperAngle = CC_DEGREES_TO_RADIANS(70);
    jointDef.enableLimit = true;
    jointDef.maxMotorTorque = 100.0f;
    jointDef.motorSpeed = -40.0f;
    jointDef.enableMotor = true;
 
    if (type == kFlipperRight)
    {
        // mirror speed and angle for the right flipper
        jointDef.motorSpeed * = -1;
        jointDef.lowerAngle = -jointDef.upperAngle;
        jointDef.upperAngle = 0.0f;
    }
 
    // create the joint
    joint = (b2RevoluteJoint*)physicsBody- > GetWorld()- > CreateJoint(&jointDef);
}

You may be wondering why the flipper’s body is set as a bullet. Trust me, I’m not going to shoot flippers at you! Physics engines traditionally have a problem with detecting collisions of objects moving at high speeds because such an object can travel great distances between two collision tests, seemingly “tunneling” through other collidable objects. That may be fine for subatomic particles, but not for our flippers and the ball.

The SetBullet method enables a special, continuous collision detection method for fast-moving objects that takes into account the path the object must have taken between two collision tests. Thus, the bullet mode is able to detect collisions that would have otherwise been missed, at the expense of performance. The bullet mode should be used judiciously and only when absolutely needed. In the pinball game, I noticed that both the flippers and the ball would sometimes “miss” each other, so I have them both treated as fast-moving objects to get more accurate collision detection.

The static body is created to attach the flipper to an unmovable body, to keep the flipper anchored in place. b2RevoluteJointDef uses lowerAngle and upperAngle as the rotation limits, which are in radians. I’ll set the upperAngle to 70 degrees and convert it to radians with the CC_DEGREES_TO_RADIANS macro provided by cocos2d.

The revolute joint also has maxMotorTorque and motorSpeed fields, which are used to define the speed and immediacy of the movement of the flippers. However, unlike the plunger, the motor is enabled all the time, and changes direction every time the sign of the motorSpeed variable is changed. While the flippers are down, the motor will force them down so that they don’t bounce when the ball hits them.

In the ccTouchBegan method, the location of the touch is obtained, which is validated with the isTouchForMe method before actually reversing the motor.

-(BOOL) ccTouchBegan:(UITouch*)touch withEvent:(UIEvent*)event
{
 
    BOOL touchHandled = NO;
    CGPoint location = [Helper locationFromTouch:touch];
    if ([self isTouchForMe:location])
    {
     touchHandled = YES;
     [self reverseMotor];
    }
 
    return touchHandled;
}
-(void) ccTouchEnded:(UITouch*)touch withEvent:(UIEvent*)event
{
    CGPoint location = [Helper locationFromTouch:touch];
    if ([self isTouchForMe:location])
    {
     [self reverseMotor];
    }
}

The isTouchForMe method implements the check to figure out on which side of the screen the touch was and whether the current instance of the class is the correct flipper to respond to this touch.

-(bool) isTouchForMe:(CGPoint)location
{
    if (type == kFlipperLeft && location.x < [Helper screenCenter].x)
    {
     return YES;
    }
    else if (type == kFlipperRight && location.x > [Helper screenCenter].x)
    {
     return YES;
    }
 
    return NO;
}

Reversing the motor speed then simply allows the flipper to spring up and to spring back down again when the touch ends and the motor speed is reversed again.

-(void) reverseMotor
{
    joint- > SetMotorSpeed(joint- > GetMotorSpeed() * -1);
}

The rest is just physics. If the ball is on the flipper, and you touch the screen on the correct side, the flipper will be accelerated upward, pushing the ball with it. Depending on where on the flipper the ball lands, it will be propelled more or less straight upward.

Summary

In this chapter, you learned how to use the PhysicsEditor tool to define the collision shapes for the bodies used in the pinball game. With just the ball in place, I illustrated how you can simulate acceleration toward a point, including how to model the effects of gravity or magnetism more or less realistically.

I hope this chapter gave you an impression of how much fun physics can be, regardless of what you may have experienced in physics class. But then again, you didn’t build pinball machines in physics class—or did you?

If you’d like to go beyond this example—for example, using more joints or taking more control of the collision process—I refer you to the Box2D manual, at www.box2d.org/manual.html.

On the other hand, if you need more information about individual classes and structs, you should look at the Box2D API reference. It’s provided in the Documentation folder of the Box2D download, which you can obtain from http://code.google.com/p/box2d. Because the Box2D API reference isn’t available online, I’m hosting it on my site at www.learn-cocos2d.com/api-ref/latest/docs.html.

To get help with Box2D, you can check out the official Box2D forums at www.box2d.org/ forum/index.php and look at the Physics subsection of the cocos2d forums at www.cocos2d-iphone.org/forum/forum/7.

If you’re interested in learning more about PhysicsEditor, I can recommend the PhysicsEditor blog (www.physicseditor.de/blog) in which Andreas Löw shows off some cool PhysicsEditor tips and tricks. If you have a support request for Andreas, you can simply write an e-mail to [email protected].

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.197.163