A “Touchy” Subject… (Updated)

Working on a little iPhone puzzle game using cocos2d-iphone and in this game the user can touch an object and drag it around.  The user can also tap in an open space to place an object.

Doing some testing of the touch handling code and first notice a minor item that didn’t match my expectations:

When you drag an object off the physical screen you don’t get touchesCancelled message, you get touchesEnded.  Unexpected.  Seems like a “cancel” to me.  But ok, can code around that easily enough.

Then I decided to test what happens when someone calls the phone while the user is dragging an object. This one gets stranger.

When the call comes in the “Answer / Decline” overlay comes up over the game screen and if you leave your finger down on the screen and move it defies expectations (at least mine!):

– first you get a touchesCancelled call (and no touchesEnded). This is good.

– Then you get a touchesBegan call (even though you didn’t release and re-touch! Even though your views aren’t even in the front any more!).

– Then you get a touchesMoved call (or many depending on your finger movement).

– Then, if you release to go press the “Decline” button (this game is good – they can leave a message! 🙂 ha!), you get a touchesEnded call.

If you instead release and then touch and drag and release again, you’ll get the Touches- Begin/Move/End sequence again even though you can’t see the game board and there is another view in front.

This is highly unexpected to me since I’m used to Cocoa where you touch the view you touch in (or window) and not THROUGH some view onto another (especially when the view you’re touching “through” has buttons and is thus obviously wanting it’s own touches!).

Actually, turns out this isn’t even what the docs suggest should happen. In Event Handling Guide for iOS: Event Types and Delivery say:

Touch events. The window object uses hit-testing and the responder chain to find the view to receive the touch event. In hit-testing, a window calls hitTest:withEvent: on the top-most view of the view hierarchy; this method proceeds by recursively calling pointInside:withEvent: on each view in the view hierarchy that returns YES, proceeding down the hierarchy until it finds the subview within whose bounds the touch took place. That view becomes the hit-test view.

Sure seems like the “Accept/Decline” view which is visually in front should get these events and not the view behind it…

So, how to tell if the touches are for your game board or the “Accept/Decline” overlay?

Turns out there is an applicationState property of the shared UIApplication that will indicate that you are in an “inactive” state.  So we can use that to ignore the touches when inactive.

Oh. Wait.  That’s iOS 4.0 and later only…  You want to support 3.1.3 also?  Or 3.2 on the iPad? (no calls on that obviously.  What else can come up and do something like this? push notification maybe…?).   Well, I don’t yet know if a) the problem exists there, or b) how to fix it there if it does!

I’ll have to test that when my iphone running 3.1.3 returns home tonight.  Update to follow…

10.10.10 – update:

So, I realized that Timer Done notifications will also create a situation like this.  Also SMS message received notifications.  (and likely Push Notifications which I didn’t test).

I tried the both the timer (in Clock application) case and the SMS message received case, both in iOS 4.1.  In both cases we get the same change in applicationState instance variable, however the system does not send a touchesCancelled event and the touch movement keeps going without getting an intermediate touchesBegan message as the system does for incoming phone call events.

In all three cases the UIApplicationDelegate protocol method applicationWillResignActive gets called so that’s definitely a place to consider “fixing” this issue.

Cocos2d-iPhone pauses the animation (well, slows it down dramatically), but does nothing about touch handling.

Haven’t had a chance to try the older iPhone running 3.1.3 yet (busy weekend).

10.10.10 – update 2:

So to work around this I just implemented code to use the applicationState instance variable on UIApplication sharedApplication and basically if I get a touch Moved/Ended event and I’m currently tracking a touch then I cancel the tracking and clear the tracking-a-touch flag that I set on touch Begin.  If I’m not currently tracking a touch and the application is not active, I just ignore any touch events.   Still need to create a 3.1.3 compatible version of this.

Some rough iPad OpenGL Performance and Techniques notes…

I took notes on various tests I was doing trying to figure out the best way to do a large map of grid squares.  I did a lot of tests and it was a good introduction to openGL.  But, while these experiments were fun, Geek was getting impatient to actually create a game already, and besides, the general advice seems to be to use a game engine for your first few games; don’t write your own game engine (amusing how few game developers follow this advice though! :-)).

So I decided to investigate Cocos2d-iphone since we are only playing around with 2d games at this point. Since this post is coming out after Geek’s tutorial on cocos2d-iphone tilemaps (a part 3 to Ray Wenderlich’sgreat set of tutorials), any reader following along already knows that we’ve found cocos2d-iphone to be pretty fun and capable so far.

Anyway, there are some useful bits of information in these notes and so rather than trash them because I’ve moved on and don’t feel like polishing it into a smooth blog post, I’m just going to post the raw notes here (so I can refer back to them if nothing else).  If you only want to read polished blog posts, you might want to skip this one.  🙂  If you like openGL nitty gritty tidbits, FPS trade-offs, and the like, this might be of interest.

So in a previous post on OpenGL I’d said this:

Some performance data:

Just as a test, I set my test app to allocate 4800 of the 32 x 32 pixel uncompressed GL_RGBA, GL_UNSIGNED_BYTE  textures.  Oh, and one big texture atlas that is 128 x 128 pixels, same depth etc.

This test is drawing 64,516 Point Sprites (point size is 16) using 8 different textures at 45 Core Animation Frames Per Second (CAFPS). Note that only 768 of them are visible on screen, the rest are offscreen and culled by OpenGL.  The points are x,y as GL_SHORTs in a single large vertex buffer and grouped by texture.  They are then drawn with eight glDrawArrays calls each frame (with the appropriate texture bound before each call) and GL_COORD_REPLACE_OES is true so the textures are drawing mapped over the points.

Actually, just for fun I made that selectable and without the VBO use the frame rate goes down to 40 CAFPS.  That’s good to know.

Showing 24-32MB Resource Bytes (Debug/Release, ?) and 69% Resource utilization.  Huh, that doesn’t change even when I only allocate 8 of the 32×32 bit textures..  odd.

Anyway, ES 1.1, obviously.  iPhone OS 3.2 on Wi-Fi iPad.

Data from some older test code:  Well I just ran a piece of other code that drew the 32×32 pixel “points” as two-triangle triangle-strips via glDrawElements, and setting the color with a call to glColor4ub only when it changed.  Same 254×254 map with same number actually on screen.   This is with the VBO for the vertexes, but not the triangles nor the colors.

Only 6 CAFPS!  Yow!  Only 4% RDU, but 100% CPU usage and 96% of that is in the glDrawElements call…   Clearly having the colors in a buffer would help some, but it’s interesting to see just how bad it can be…   Since we are drawing individual elements, we can actually be smart and only draw the ones that are visible on screen – making that change gets us back to 59/60 CAFPS.   Interesting set of tradeoffs.  Drawing with a mechanism that draws batches of things is faster per thing, but harder to draw only what is needed.  Drawing individual elements is much slower, but it is much easier to limit to just those things that are on-screen.  Also, limiting it to the things on screen seems to have dropped the CPU usage to essentially zero… which is suspicious.   Somehow it _knows_ that nothing is changing visually?  magic! hmm.  more to learn, clearly!

So I wonder if it’s just making fewer calls to gl* routines (i.e., drawing a bunch of elements with a single call, either via GL_POINTS or GL_TRIANGLE_STRIP) or having the data in the GPU?  hmm.  There is also the difference between glDrawElements vs glDrawArrays…   more testing is called for!

Converted this last sample to use glDrawTexture for each grid-square and we are down to 5 CAFPS and 100% CPU usage, even drawing just the ones on screen.  Calculating the vertexes and texture coordinates that are visible every time is clearly expensive.

—-

later…

I’ve tried a bunch of different openGL approaches now including GL Point Sprites (fast, but some serious limitations, as noted on earlier post), glDrawTexture calls for each grid square (slow due to passing all the data across to the video card/chip as noted above), glDrawElements with triangle strips with vertexes in buffers (VBOs) (fast, but it seems like you can’t texture map onto triangle strips due to shared vertexes, which I didn’t realize at first), and glDrawElements with triangles (but you have some map size limitations due to the elements index to the call only being a short). Oh, and tests with entire map in gl buffers and drawing all of it with one call, versus drawing just the visible portion, often requiring multiple calls.

The best so far that I’ve found for this large map of grid squares and texture mapping for each grid and supporting zoom to 25K triangles visible on screen:  using glDrawArrays with a dynamically constructed set of vertexes and texture coordinates stored in vector buffers (or VBOs) and only updating those when the scroll position (or zoom) changes.  One call to glDrawArrays for the whole visible map.  Textures are in a texture atlas and being careful about how many openGL state changes and I can get 55-60 fps at natural size of 1500 triangles on screen (32×32 grid size, 2 triangles for each grid square), 45-60 fps at zoomed state with 6K triangles on screen (16×16), and 30-40 fps with 25K triangles on screen (8×8) but nearly 50 fps when the map is stationary.  Lower end of ranges are when scrolling because I have to update the VBO data on the card, which I’m doing by updating the buffer and not by allocating new buffers.

Unfortunately, in my current most successful approach I’m getting texture bleed (when part of the next atlas image gets incorporated into the edge of the current one) which I haven’t solved yet – possibly I need to put space between the textures on the atlas and/or do something about mip-mapping.  Might also have to do with changing scale of the texture space to be 0-128 from 0 to 1.0 so I could use shorts as texture coords instead of floats -> saving 2 bytes per texture coordinate! For the 25K triangle test case, that means 300K of data that doesn’t have to go across to the graphics card or into buffers!  (25,000 triangles * 3 vertexes/triangle * 2 coords/vertex * 2 bytes per coord saved).

—-

One other thing I thought to try was to render-to-texture and use glDrawTexture once for the whole visible background instead of dynamically filling the VBO buffers and calling glDrawArrays with triangles and texture coordinates to draw lots of texture mapped triangles.  During scrolling/zoom will be the push – might be slower because we’d be drawing once to the big texture, and a second time that texture to the visible surface… Seems like that’s effectively two copies.  I’m guessing that it’ll be faster otherwise because the render should be faster (one big quad or two triangle triangle-strip instead of potentially 25K texture mapped triangles).

“For The Win” by Cory Doctorow – insanely good

Wow!  I just finished “For The Win” by Cory Doctorow – insanely good.

I don’t play MMORPGs so I have no idea where the here-and-now ends and the sci-fi starts in this book, but woah.  I sure hope more of it is sci-fi than my gut-feelings suggest… yikes!

This book hit me like William Gibson’s “Neuromancer” did way back when and makes me wonder if it’ll be as big an influence on the future as “Neuromancer”, “Virtual Light” (also by Gibson), and “Snow Crash” (by Neal Stephenson) all were.  It has that feel to it, frighteningly enough.

Here are some Amazon affiliate links that support Geek And Dad, if you haven’t read any of these yet*.

These are all sci-fi or, if you prefer, the “cyberpunk” sub-genre of sci-fi or, again if you prefer, “speculative fiction.”  And there are quite a few others that belong on this list, but other than the brand new “For the Win”, these are some of the books that influenced how the internet and “cyberspace” was actualized (Gibson is attributed as the person who coined the term “cypberspace” according to Wikipedia.  “Neuromancer” is the winner of the science-fiction “triple crown” — the Nebula Award, the Philip K. Dick Award, and the Hugo Award and, again according to Wikipedia, “is considered the archetypal cyberpunk work.”

‘nough said.  Go read! 🙂

* We do encourage you to use your locally owned bookstore (ours is Powell’s) or your local Library.  I read “For The Win” via our awesome Multnomah County Library, but I think I might need to buy it to add to the permanent collection where it’ll sit alongside the “Lensmen” series by EE “Doc” Smith, the others listed above, and a couple dozen other books.

Woah – just realized that the first book in the Lensman series, “Triplanetary,” is available as a FREE eBook!

Enemies and Combat: How to Create a Tile Based Game with Cocos2D Part 3

Update: Fully updated for Cocos2D 2.1 rc0Tiled 0.9.0 (released Feb 2013), Xcode 4.6, and ARC to match the newly updated part 1 & 2!

This is a continuation of one of Ray Wenderlich’s excellent tutorials for making games with Cocos2D for iPhone. If you haven’t read them already, visit his website and check them out. In particular, this is a “Part 3” to his “How to Make a Tile Based Game with Cocos2D” 2-part series. If you haven’t read those yet, you should start with his part 1.

In part 2, Ray covered how to make collidable areas in the map, how to use tile properties, how to make collectable items and modify the map dynamically, and how to make a “Heads up display” to show the score.

In this tutorial, we’ll add enemies, make it so your ninja can throw ninja stars at them, and add some win/lose game logic. But first, download this zip file of additional resources for the new enhancements (thank you for permission to use the art, Ray).

The zip file contains:

Don’t forget to add them to your project before continuing.

Adding Enemies

At the end of part 2, the project is very cool, but it’s not much of a game. Your ninja can wander around eating melons with no difficulty at all, and there’s no way to win or lose. If there were enemies who chased your ninja, the game would be a lot more exciting.

Enemy Spawn Points

Okay, go to Tiled (these screenshots use the Qt version) and open your Tile Map (TileMap.tmx).

Add an object to the Objects layer, somewhere not too near the player. This will be an enemy spawn point. Name it “EnemySpawn1”.

The objects in an object group are stored in an NSMutableDictionary, with the object name as the key. This means that each spawn point must have a unique name. Although we could iterate through all the keys to see which ones start with “EnemySpawn”, that is an inefficient operation. Instead, we will use a property to indicate that a given object represents an enemy spawn point.

EnemySpawnPoints

Give the object a property “Enemy”, with a value of 1. If you want to expand on this tutorial and add other types of enemies, you can use other values of the enemy property to indicate the type of enemy.

Now make six to ten more of these Enemy Spawn Point objects, at varying distances from the player. Give each one the “Enemy” property, with a value of 1. Save the map and go to Xcode.

Showing Enemy Spawn Points on the map in Tiled

Creating the Enemies

Okay, now we’ll make the enemies actually appear on the map. Add the following code in HelloWorldLayer.m:


// in the HelloWorldLayer class
-(void)addEnemyAtX:(int)x y:(int)y {
  CCSprite *enemy = [CCSprite spriteWithFile:@"enemy1.png"];
  enemy.position = ccp(x, y);
  [self addChild:enemy];
}

// in the init method - after creating the player
// iterate through objects, finding all enemy spawn points
// create an enemy for each one
for (spawnPoint in [objectGroup objects]) {
  if ([[spawnPoint valueForKey:@"Enemy"] intValue] == 1){
    x = [[spawnPoint valueForKey:@"x"] intValue];
    y = [[spawnPoint valueForKey:@"y"] intValue];
    [self addEnemyAtX:x y:y];
  }
}

The first loop iterates through the list of objects, checking to see if they are enemy spawn points. If they are, it gets their X and Y positions in the same way as the player’s. Then, it adds an enemy in the right location by calling the addEnemyAtX:y: method.

The addEnemyAtX:y: method is fairly basic. It just creates an enemy sprite at the x, y location passed in.

If you build and run this, you’ll see that there are enemies in the locations you specified in Tiled. Awesome!

There’s just one problem – the enemies don’t chase you!

Making them Move

So, now we’ll add some code to make the enemy sprites chase the player. Because the player might be moving, we must re-orient the enemy occasionally. To do this, we move the enemy in 10-pixel long segments, re-orienting the enemy before each segment.

Add the following code to HelloWorldLayer.m, in the HelloWorldLayer class:


// callback. starts another iteration of enemy movement.
- (void) enemyMoveFinished:(id)sender {
  CCSprite *enemy = (CCSprite *)sender;

  [self animateEnemy: enemy];
}

// a method to move the enemy 10 pixels toward the player
- (void) animateEnemy:(CCSprite*)enemy
{
  // speed of the enemy
  ccTime actualDuration = 0.3;

  // Create the actions
  id actionMove = [CCMoveBy actionWithDuration:actualDuration
    position:ccpMult(ccpNormalize(ccpSub(_player.position,enemy.position)), 10)];
  id actionMoveDone = [CCCallFuncN actionWithTarget:self
    selector:@selector(enemyMoveFinished:)];
  [enemy runAction:
    [CCSequence actions:actionMove, actionMoveDone, nil]];
}

// add this at the end of addEnemyAtX:y:
// Use our animation method and
// start the enemy moving toward the player
  [self animateEnemy:enemy];

The animateEnemy: method creates two actions. The first one tells it to move 10 pixels toward the player, over a duration of 0.3 seconds. You can change the duration to make the enemies move faster or slower. The second action will call the enemyMoveFinished: method. We combine these with a CCSequence action so that the enemyMoveFinished: method will be called when the enemy stops moving. In addEnemyAtX:y:, we call animateEnemy: to start the enemy moving toward the player.

The enemyMoveFinished: method calls the animateEnemy: method, continuing with the next segment of the animation. The math in the definition of actionMove basically calculates a path going 10 pixels toward the player.

Neat! But wouldn’t it look much more impressive if the enemies pointed toward the player? Add the following code to animateEnemy:


// immediately before creating the actions in animateEnemy
// rotate to face the player
CGPoint diff = ccpSub(_player.position,enemy.position);
float angleRadians = atanf((float)diff.y / (float)diff.x);
float angleDegrees = CC_RADIANS_TO_DEGREES(angleRadians);
float cocosAngle = -1 * angleDegrees;
if (diff.x < 0) {
  cocosAngle += 180;
}
enemy.rotation = cocosAngle;

This code finds the direction that the player is in relative to the enemy, and rotates the enemy to point towards the player.

Ninja Stars

Okay, but our player is a NINJA! He should be able to defend himself!

We will add modes to the game. Modes are not the best way to implement this, but they are significantly easier than the alternatives, and they will work in the simulator (because they don’t need multi-touch). Because of these advantages, we will use them in this tutorial. The UI will be set up such that the user can easily switch between moving mode and ninja star throwing mode. We will add a button to switch into ninja star throwing mode and back to moving mode.

Now, we will set up some properties to allow easy communication between the two layers. Add to HelloWorldLayer.h:

// in HelloWorldLayer, outside the curly braces
@property (assign) int mode;

And to HelloWorldLayer.m:

// just after the import statements at the top of the file, but before the @implementation HudLayer

@interface HudLayer ()
// if you are setting your deployment target for iOS 5.0 or later then replace "unsafe_unretained" with "weak" which is then auto set to nil by the runtime. Not supported before 5.0 though.
@property (unsafe_unretained) HelloWorldLayer *gameLayer;
@end

// in HelloWorldLayer's init method
_mode = 0;

// in HelloWorldLayer's scene method
// after layer.hud = hud
hud.gameLayer = layer;

For more detail about how the button works, visit Ray’s tutorial on the topic.

Add the folowing code, which defines a button, to HelloWorldScene.m:

// in HudLayer's init method
// define the button
CCMenuItem *on;
CCMenuItem *off;

on = [CCMenuItemImage itemWithNormalImage:@"projectile-button-on.png"
  selectedImage:@"projectile-button-on.png" target:nil selector:nil];
off = [CCMenuItemImage itemWithNormalImage:@"projectile-button-off.png"
  selectedImage:@"projectile-button-off.png" target:nil selector:nil];

CCMenuItemToggle *toggleItem = [CCMenuItemToggle itemWithTarget:self
  selector:@selector(projectileButtonTapped:) items:off, on, nil];
CCMenu *toggleMenu = [CCMenu menuWithItems:toggleItem, nil];
toggleMenu.position = ccp(100, 32);
[self addChild:toggleMenu];

In the HudLayer implementation add the new method to respond to the button tap:


// callback for the button
// mode 0 = moving mode
// mode 1 = ninja star throwing mode
- (void)projectileButtonTapped:(id)sender
{
  if (_gameLayer.mode == 1) {
    _gameLayer.mode = 0;
  } else {
    _gameLayer.mode = 1;
  }
}

Build and run. A button will appear on the lower left, and you will be able to turn it on and off, but it won’t actually affect the game. Our next step is to add projectile launching.

Launching Projectiles

Next, we will add code to detect which mode the user is in and determine what to do when the screen is tapped based on the mode.

Put the code currently in ccTouchEnded:withEvent: within the if portion of the following if/else block as indicated by the comment:

if (_mode == 0) {
  // old contents of ccTouchEnded:withEvent:
} else {
  // code to throw ninja stars will go here
}

This will result in movement only occuring in move mode. The next step is to add code that will launch a ninja star to the else portion of the if block.

Add the following cleanup method to HelloWorldLayer.m:


- (void) projectileMoveFinished:(id)sender {
  CCSprite *sprite = (CCSprite *)sender;
  [self removeChild:sprite cleanup:YES];
}

And now, in the else block where it says:

// code to throw ninja stars will go here

put the following code:

// Find where the touch is
CGPoint touchLocation = [touch locationInView: [touch view]];
touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
touchLocation = [self convertToNodeSpace:touchLocation];

// Create a projectile and put it at the player's location
CCSprite *projectile = [CCSprite spriteWithFile:@"Projectile.png"];
projectile.position = _player.position;
[self addChild:projectile];

// Determine where we wish to shoot the projectile to
int realX;

// Are we shooting to the left or right?
CGPoint diff = ccpSub(touchLocation, _player.position);
if (diff.x > 0)
{
  realX = (_tileMap.mapSize.width * _tileMap.tileSize.width) +
             (projectile.contentSize.width/2);
} else {
  realX = -(_tileMap.mapSize.width * _tileMap.tileSize.width) -
             (projectile.contentSize.width/2);
}
float ratio = (float) diff.y / (float) diff.x;
int realY = ((realX - projectile.position.x) * ratio) + projectile.position.y;
CGPoint realDest = ccp(realX, realY);

// Determine the length of how far we're shooting
int offRealX = realX - projectile.position.x;
int offRealY = realY - projectile.position.y;
float length = sqrtf((offRealX*offRealX) + (offRealY*offRealY));
float velocity = 480/1; // 480pixels/1sec
float realMoveDuration = length/velocity;

// Move projectile to actual endpoint
id actionMoveDone = [CCCallFuncN actionWithTarget:self
  selector:@selector(projectileMoveFinished:)];
[projectile runAction:
  [CCSequence actionOne:
		    [CCMoveTo actionWithDuration: realMoveDuration
        		                position: realDest]
                    two: actionMoveDone]];

This will launch the ninja star in the direction the player tapped. For exact details on how this works, check out the Shooting Projectiles section of Ray’s How to Make a Simple Game in Cocos2D tutorial. However, the comments should explain roughly what is happening.

The projectileMoveFinished: method removes the projectile from the layer once it gets off the map. This method is absolutely critical. Once we start doing collision detection, we will need to iterate through all of the ninja stars. If we don’t remove the ones that leave the screen, the list will grow larger and larger and the game will slow down.

Build the project and run it. Now, you can throw ninja stars at the enemies!

Collision Detection

The next step is to destroy the enemies when they are hit. Add the following two properties to the HelloWorldLayer class (in HelloWorldLayer.m) after @interface HelloWorldLayer() where the other properties are defined:

@property (strong) NSMutableArray *enemies;
@property (strong) NSMutableArray *projectiles;

Now, add the projectiles to the projectiles array with this code:

// at the end of the launch projectiles section of ccTouchEnded:withEvent:
  [self.projectiles addObject:projectile];

  // at the end of projectileMoveFinished:
  [self.projectiles removeObject:sprite];

Next, add the following code to the end of addEnemyAtX:y:

  [self.enemies addObject:enemy];

Next, add the following method to the HelloWorldLayer class:

- (void)testCollisions:(ccTime)dt {

  NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];

  // iterate through projectiles
  for (CCSprite *projectile in self.projectiles) {
    CGRect projectileRect = CGRectMake(
      projectile.position.x - (projectile.contentSize.width/2),
      projectile.position.y - (projectile.contentSize.height/2),
      projectile.contentSize.width,
      projectile.contentSize.height);

    NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];

    // iterate through enemies, see if any intersect with current projectile
    for (CCSprite *target in self.enemies) {
      CGRect targetRect = CGRectMake(
        target.position.x - (target.contentSize.width/2),
        target.position.y - (target.contentSize.height/2),
        target.contentSize.width,
        target.contentSize.height);

      if (CGRectIntersectsRect(projectileRect, targetRect)) {
        [targetsToDelete addObject:target];
      }
    }

    // delete all hit enemies
  for (CCSprite *target in targetsToDelete) {
      [self.enemies removeObject:target];
      [self removeChild:target cleanup:YES];
    }

    if (targetsToDelete.count > 0) {
      // add the projectile to the list of ones to remove
      [projectilesToDelete addObject:projectile];
    }
  }

  // remove all the projectiles that hit.
  for (CCSprite *projectile in projectilesToDelete) {
    [self.projectiles removeObject:projectile];
    [self removeChild:projectile cleanup:YES];
  }
}

Finally, initialize the enemy and projectile arrays and schedule the testCollisions: method to be called as often as possible by adding this code to HelloWorldLayer’s init method:

  // you need to put these initializations before you add the enemies,
  // because addEnemyAtX:y: uses these arrays.
  self.enemies = [[NSMutableArray alloc] init];
  self.projectiles = [[NSMutableArray alloc] init];
  [self schedule:@selector(testCollisions:)];

As above, for exact details on how this works, check out the Collision Detection section of Ray’s How to Make a Simple Game in Cocos2D tutorial. However, the comments should explain roughly what is happening. Build the project and run it. Try throwing ninja stars at the enemies now, and they will disappear! The next step: winning and losing.

Winning and Losing

The Game Over Scene

Now, let’s create a new scene that will serve as our “You Win” or “You Lose” indicator. In Xcode select the Classes folder in the project tree and go to File\New File, and choose Objective-C class, and make sure subclass of NSObject is selected. Click Next, then type in GameOverScene as the filename, and make sure “Also create GameOverScene.h” is checked.

Then replace the template code in GameOverScene.h with the following code:

#import "cocos2d.h"

@interface GameOverLayer : CCLayerColor {
}
@property (nonatomic, strong) CCLabelTTF *label;
@end

@interface GameOverScene : CCScene {
}
@property (nonatomic, strong) GameOverLayer *layer;
@end

Then replace the template code in GameOverScene.m with the following code:

#import "GameOverScene.h"
#import "HelloWorldLayer.h"

@implementation GameOverScene

- (id)init {

  if ((self = [super init])) {
    self.layer = [GameOverLayer node];
    [self addChild:_layer];
  }
  return self;
}

@end

@implementation GameOverLayer

-(id) init
{
  if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {

    CGSize winSize = [[CCDirector sharedDirector] winSize];
    self.label = [CCLabelTTF labelWithString:@"" fontName:@"Arial" fontSize:32];
    _label.color = ccc3(0,0,0);
    _label.position = ccp(winSize.width/2, winSize.height/2);
    [self addChild:_label];

    [self runAction:[CCSequence actions:
      [CCDelayTime actionWithDuration:3],
      [CCCallFunc actionWithTarget:self selector:@selector(gameOverDone)],
      nil]];

  }
  return self;
}

- (void)gameOverDone {

  [[CCDirector sharedDirector] replaceScene:[HelloWorldLayer scene]];

}

@end

The GameOverLayer just puts a label in the middle of the screen, and schedules a transition back to the HelloWorldLayer’s scene to occur 3 seconds in the future.

Winning

Now, let’s add code to end the game when the player has all the melons. Add the following code to HelloWorldLayer’s setPlayerPosition: method in HelloWorldLayer.m, right after updating the score:

  // put the number of melons on your map in place of the '6'
  if (self.numCollected == 6) {
    [self win];
  }

And create the win method in the HelloWorldLayer class in HelloWorldLayer.m, before the setPlayerPosition: method:

- (void) win {
  GameOverScene *gameOverScene = [GameOverScene node];
  [gameOverScene.layer.label setString:@"You Win!"];
  [[CCDirector sharedDirector] replaceScene:gameOverScene];
}

Now add the following code to the top of the HelloWorldScene.m file:

#import "GameOverScene.h"

Build and run. You will now win when you collect all of the melons. Victory!

Losing

For this simple tutorial, the player loses if a single enemy touches the ninja. Add the following loop to the end of HelloWorldLayer’s testCollisions: method, in HelloWorldLayer.m:


  for (CCSprite *target in _enemies) {
    CGRect targetRect = CGRectMake(
      target.position.x - (target.contentSize.width/2),
      target.position.y - (target.contentSize.height/2),
      target.contentSize.width,
      target.contentSize.height );

    if (CGRectContainsPoint(targetRect, _player.position)) {
      [self lose];
    }
  }

This loop iterates through all of the enemies and ends the game if any of them are touching the player.
Now, create the lose method in the HelloWorldLayer class in HelloWorldLayer.m:

- (void) lose {
  GameOverScene *gameOverScene = [GameOverScene node];
  [gameOverScene.layer.label setString:@"You Lose!"];
  [[CCDirector sharedDirector] replaceScene:gameOverScene];
}

Build and run, and when the monsters hit you, you will lose!

Get the Code!

The code’s moved to github and you can check it out or download it from the project page. Thank you for following along, that’s all for now.

Where to next?

Suggestions:

  • Multiple levels
  • Multiple types of enemy
  • Health, and a health bar in the Hud layer
  • More kinds of collectible items (healing, etc.)
  • A main menu
  • A better user interface for throwing ninja stars

OpenGL Point Sprites – 2 things to know…

Been playing around with OpenGL ES and most recently with Point Sprites (e.g., glDrawArrays( GL_POINTS, …) ). Today I got textures rendering onto the points (whose size > 1.0, obviously) but ran into two show-stoppers:

First, I do not see that it is possible to use a Texture Atlas with portions of a texture mapped onto Point Sprites in OpenGL ES 1.1.  You can only map the entire Texture onto the point.  Based on my reading, I can see that with a custom shader you should be able to do this in OpenGL ES 2.0, but haven’t tried that yet.

Looking around to see if I was doing something wrong I came across this from the spec and I think that’s what this is saying:

When point sprite is enabled and the GL_COORD_REPLACE_ARB state for a given texture unit is GL_TRUE, the texture coordinate set for that texture unit is (s,t,0,1) where the point sprite-overridden s and t are described in the amended Section 3.3 below. The important point is that r and q are forced to 0 and 1, respectively.

(emphasis mine).

This from:  http://www.opengl.org/registry/specs/ARB/point_sprite.txt

Certainly if someone knows how to map a portion of a Texture onto a Point in openGL ES 1.1, I’d like to hear about it.

Otherwise, hopefully this’ll save someone some time.

Second, note that if you are using them for a game and you want the object they represent to be partially off-screen, you are in trouble;  Point Sprites are clipped when the point is offscreen, and the point is in the center of the point sprite.  Thus, when your object gets just slightly over half off the screen, “POOF!”, it’s completely gone.

After Dinner question in seek of answer

Geek’s post-dinner mind-bending question:  Given a nonogon (9 sided polygon) and all 126 quadrilaterals that can be made using groups of 4 points (vertices) of the nonogon, determine a method of choosing 10 to 20 of these quadrilaterals such that if they were all drawn on the nonogon at the same time the result would be symmetrical.

…but by the time I wrote this down he’s already got two instances of sets that have the desired property, though perhaps not a general method for selecting them (hard to tell since it’s all in his head still).  So I think he’s got what he was looking for, but I thought I’d write this down just to demonstrate what it’s like being Geek’s Dad (!).

Reason for answering this?  This forms the theoretical backbone of a system of magic for the world in which an upcoming Geek And Dad trading card game is set.