Geek Song #3: The Eye of the Kernel

The Eye of the Kernel

( Based on The Eye of the Tiger)

Risin’ up, back with the 1337
Played with time, took my chances
Gamed the system, now I’m back on my feet
Just a geek and his will to survive

So many times, it happens too fast
You trade your “indie” for money
Don’t lose your grip on the dreams of the past
You must code just to keep them alive

It’s the eye of the kernel, it’s the code that we write
Risin’ up to the challenge of our rival
And the last known debugger stalks its prey in the night
And it’s watchin’ us all with the eye of the kernel…

Face to screen, code to the beat
Hackin’ lean, hackin’ dirty
The stack is odd still we’re scripting our deeds
Making code as a way to survive

It’s the eye of the kernel, it’s the code that we write
Risin’ up to the challenge of our rival
And the last known debugger stalks its prey in the night
And it’s watchin’ us all with the eye of the kernel…

Risin’ up, straight to the top
Had the scripts, got the glory
Wrote the software, now I’m not gonna stop
Just a geek and his will to survive

It’s the eye of the kernel, it’s the code that we write
Risin’ up to the challenge of our rival
And the last known debugger stalks its prey in the night
And it’s watchin’ us all with the eye of the kernel…

The eye of the kernel…

(With apologies to Survivor)

Geek Art: Geek & Dad Engine Room

Geek & Dad’s engine room.

This is the engine room here at Geek & Dad corporate headquarters…

Okay, just kidding, this is a picture I made with Bryce, a 3D modeling program which is currently free. This is the fictional engine room here at Geek & Dad corporate headquarters. Game engine room, that is. This is where we keep the game engines we’re experimenting with. Here you can see cocos2d-iphone, cocos2d-x, and Unity3D, which we’ve been considering for one of our projects.

Cocos2D-iPhone Performance tests

So, I did some tests of cocos2d’s performance as a preliminary to a summer project. I created a 1-layer max size isometric tmx map (16k tiles) and randomly placed z-ordered animating sprites over it (with no CCSpriteBatchNode), then scrolled around. Below are the FPS on a variety of test devices (arm6 is no thumb, arm7 compiled to thumb; Xcode 4.2.1, LLVM-gcc):

Device 100 Sprites 600 Sprites
iPad 1 17-21 8
iPad 2 60 55-60
iPad 3 60 60
iPhone 3GS 50-60 27-30
iPhone 4 60 25-40

Geek Song #2: Taking Care of Business, geek style

You get up every morning
From your alarm clock’s warning
Take the 8:15 into the city
There’s a whistle up above
And people pushin’, people shovin’
And the girls who try to look pretty

And if your train’s on time
You can get to work by nine
And start your slaving job to get your pay
If you ever get annoyed
Look at me I’m self-employed
I love to work at nothing all day

And I’ll be…
[Refrain]
Taking care of business every day
Taking care of business every way
I’ve been taking care of business, it’s all mine
Taking care of business and working overtime
Work out!

If it was simple as a hammer
You could be a programmer
If you can juggle ones and zeroes
Get a second-hand laptop
You could make it to the top
If you get in with the right bunch of fellows

People see you having fun
Just a-lying in the sun
Tell them that you like it this way
It’s the work that we avoid
And we’re all self-employed
We love to work at nothing all day

And we be…
[Refrain]

[Spoken] Take good care of my business
When I’m away, every day whoo!

[Repeat first 2 verses]

[Refrain]

Takin’ care of business [4x]

[Refrain]

Takin’ care of business [repeat, fade]

(with apologies to BTO)

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

Dice Graph

I was curious how big the difference would be between rolling and adding three dice or rolling four dice and adding the highest three. These are two of the most common methods for generating character stats in D&D. I knew that three dice would give me a bell curve, but I had no idea what the highest three of four would look like. I used Numbers to make a graph of the probabilities, and posted them here in case anyone else was curious.

probability of rolling various numbers on three dice added together versus on four dice, with the highest three added.