Neil Ang

Developer

A stunning likeness of Neil Ang
Hello world

SpriteKit: Tracking Missile

Posted on

Recently I've been working with Apple's SpriteKit framework and experimenting with creating "tracking" or "homing" missiles for a game. I don't have a background in game development so this took me a while to work out - which is also why I would like to share it. Please leave a comment below if you have any suggestions to improve the solution.

This is a short video of what I've managed to come up with. The full code is available on GitHub implemented in both Mac and iOS projects.

The core of my solution is using the game update loop to iterate over all missile nodes in the scene and adjust their velocity to move towards the target.

-(void)update:(CFTimeInterval)currentTime {
    [self enumerateChildNodesWithName:NA_MISSILE_LABEL usingBlock:^(SKNode *node, BOOL *stop){
        [self updateMissileVelocity:node];
    }];
}

-(void)updateMissileVelocity:(SKNode *)missile {
    SKNode *target = [missile.userData objectForKey:NA_TARGET_KEY];

    CGFloat x = missile.position.x - target.position.x;
    CGFloat y = target.position.y  - missile.position.y;
    CGFloat direction = atan2f(x, y) + M_PI_2;

    CGFloat velocityX = NA_MISSILE_THRUST * cos(direction);
    CGFloat velocityY = NA_MISSILE_THRUST * sin(direction);

    CGVector newVelocity = CGVectorMake(velocityX, velocityY);

    missile.physicsBody.velocity = newVelocity;
}

A drawback to this technique is that a missile won't move to intercept its target, just follow its moving position. However, from a game style point of view I actually like this behaviour better, as it is more of a chase effect.

Each missile has a particular target it is hunting, so to overcome it accidentally hitting the wrong node I've used the SKPhysicsContactDelegate to manage the destroy logic. This way if a missile encounters the wrong node it will simply pass through it.

-(void)didBeginContact:(SKPhysicsContact *)contact {

    SKPhysicsBody *firstBody;
    SKPhysicsBody *secondBody;

    if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
        firstBody  = contact.bodyA;
        secondBody = contact.bodyB;
    }
    else{
        firstBody  = contact.bodyB;
        secondBody = contact.bodyA;
    }

    if (firstBody.categoryBitMask == NACategoryMissile){
        [self handleMissile:firstBody.node contactWith:secondBody.node];
    }

}

-(void)handleMissile:(SKNode *)missile contactWith:(SKNode *)contactNode {

    // Check if it is an enemy node we've hit.
    if (contactNode.physicsBody.categoryBitMask != NACategoryEnemy) {
        return;
    }

    SKNode *missileTarget = [missile.userData objectForKey:NA_TARGET_KEY];

    // Check we haven't hit the wrong enemy
    if (missileTarget != contactNode) {
        return;
    }

    // At this point we can assume we've hit our target.

    [self explodeAtPoint:contactNode.position];

    [contactNode removeFromParent];
    [missile removeFromParent];

}

You can also play around with the solution to add other cool effects. Below is an example of randomly varying the missiles tracking path.

Please let me know what you think in the comments!