iOS Sprite Kitで使ってスペースインベーダーのようなゲームを作る。

<img src="http://oneworld2.sakura.ne.jp/blog/invaders.gif">

Sprite Kitを使い、スペースインベーダーのようなゲームを作ってみたいと思います。

主に

* 衝突処理簡素化するためのBitMask設定
* 同一nameのノード一括処理

あたりを考えておりまして、それ以外は、前回の
Angry Birdsのもので実現できたりします。
[iOS SpriteKitによるAngry Birdsのようなゲームを作る。](http://qiita.com/kitanoow/items/b60e793d0b78fd861a61 "Angry Birds")


##メンバ変数とdefineなどを。

```objc:MyScene.m
enum
{
    kDragNone,  //初期値
    kDragStart, //Drag開始
    kDragEnd,   //Drag終了
};
enum {
    kMoveRight = 0,
    kMoveLeft ,
};

#define kEnemyCategoryMask  0x1<<0
#define kPlayerCategoryMask 0x1<<1
#define kWallCategoryMask   0x1<<1 | 0x1<<0

//ミサイル
#define kPlayerContactMask  0x1<<0
#define kEnemyContactMask   0x1<<1

@implementation MyScene
{
    CFTimeInterval lastMoveTime;
    CFTimeInterval lastAttackTime;
    int status;
    int playerstatus;
    NSMutableArray *attackEnemyList;
    SKSpriteNode *player;
}

```

##プレイヤーとエネミー(敵)と、プレイヤーを守る壁のセットアップ


まずは、プレイヤーから

```objc:MyScene.m

-(void) setUpPlayer{
    player = [SKSpriteNode spriteNodeWithColor:[SKColor blueColor] size:CGSizeMake(30,10)];
    player.position =CGPointMake(160, 20);
    player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:player.size];
    player.physicsBody.categoryBitMask = kPlayerCategoryMask;
    player.physicsBody.dynamic = NO;
    player.name = @"player";
    [self addChild:player];
}

```

categoryBitMaskについては、kPlayerCategoryMaskとする事で
敵のミサイル(kEnemyContactMask)にはあたることになります。

次に敵の生成。
ひとまず5*5の25体を配置します。


```objc:MyScene.m

-(void)setUpEnemy
{
    for(int i=0;i<5;i++) {
        for(int j=0;j<5;j++) {
            SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"enemy"];
            sprite.size = CGSizeMake(30,21);
            sprite.position =CGPointMake(50+ j*35, 300 + i*25);
            sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
            sprite.physicsBody.categoryBitMask = kEnemyCategoryMask;
            sprite.physicsBody.dynamic = NO;
            sprite.name = @"enemy";
            [self addChild:sprite];
            [attackEnemyList addObject:sprite];
        }
    }
}


```

次に、「壁」を生成します。

```objc:MyScene.m

-(void) setUpWall:(int)pos_x
{
    for(int i=0;i<10;i++) {
        int start_j = 0;
        int end_j = 10;
        if( i == 0 || i == 9 ) { start_j = 2;end_j = 7;}
        if( i == 1 || i == 8 ) { start_j = 2;end_j = 8;}
        if( i == 2 || i == 7 ) { start_j = 2;end_j = 9;}
        if( i == 3 || i == 6 ) { start_j = 4;end_j = 10;}
        if( i == 4 || i == 5 ) { start_j = 5;end_j = 10;}
        for(int j=start_j;j<end_j;j++) {
            SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithColor:[SKColor grayColor] size:CGSizeMake(5,5)];
            sprite.position =CGPointMake(pos_x+i*5, 50 + j*5);
            sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
            sprite.physicsBody.categoryBitMask =  kWallCategoryMask;
            sprite.physicsBody.dynamic = NO;
            [self addChild:sprite];
        }
    }
    
}

```

これで一つの壁を生成させます。
これを後四つ配置する方向で考えています。

ひとまず、それぞれのメソッドができたので、

initWithSize
に実装します。

また
生成したEnemyを、attackEnemyListに追加していましたが、
それの初期化します。

```objc:MyScene.m

        attackEnemyList = [[NSMutableArray alloc] init];
        
        for(int i=0;i<4;i++) {
            [self setUpWall:30 + i*70];
        }
        [self setUpPlayer];
        [self setUpEnemy];
        
        self.physicsWorld.contactDelegate = self;

```

また、衝突の検出も行うため、

physicsWorld.contactDelegateにselfを指定します。

##プレイヤーの動きについて

プレイヤーについては、
まずプレイヤー上でドラッグすると位置の変更し、
それ以外をタップするとミサイルを発射するようにします。

```objc:MyScene.m

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */
    
    for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self];
        SKNode *node = [self nodeAtPoint:location];
        if(node != nil && [node.name isEqualToString:@"player"]) {
            playerstatus = kDragStart;
            break;
        }
        [self attack:CGPointMake(player.position.x, player.position.y + 10)
             bitMask:kPlayerContactMask
            moveToPoint:CGPointMake(player.position.x,  player.position.y + 10 + self.frame.size.height)
         attackColor:[SKColor blueColor]
         ];
    }
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    if(playerstatus == kDragStart ){
        UITouch *touch = [touches anyObject];
        CGPoint touchPos = [touch locationInNode:self];
        player.position = CGPointMake(touchPos.x, player.position.y);
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if(playerstatus == kDragStart ){
        playerstatus = kDragEnd;
    }
}

-(void)attack:(CGPoint)location bitMask:(uint32_t)bitMask moveToPoint:(CGPoint)moveToPoint attackColor:(SKColor*)attackColor
{
    SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithColor:attackColor size:CGSizeMake(5,5)];
    sprite.position = location;
    sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
    sprite.physicsBody.dynamic = YES;
    sprite.physicsBody.contactTestBitMask = bitMask;
    SKAction* bulletAction = [SKAction sequence:@[[SKAction moveTo:moveToPoint duration:1.0],
                                                  [SKAction removeFromParent]]];
    [sprite runAction:bulletAction];
    [self addChild:sprite];
    
    
}

```


##敵の動き

こちらについては、一定間隔ごとに、
横に動き、横の移動向きが変わる段階で一つ下に落ちるようします。

SKSceneであれば、元々updateメソッドがありますので、
そこで実現したいと思います。

また、ランダムに敵を選出し、そこからミサイルを出すようにもしています。
指定した同一nameのノードに対して一括で処理を行う場合は、
enumerateChildNodesWithName
を使うと便利です。

```objc:MyScene.m

-(void)update:(CFTimeInterval)currentTime {
    /* Called before each frame is rendered */
    
    [self checkMoveStatus];
    [self moveEnemy:currentTime];
    [self attackFromEnemy:currentTime];
}


-(void)checkMoveStatus
{
    [self enumerateChildNodesWithName:@"enemy" usingBlock:^(SKNode *node, BOOL *stop) {
        if(node.position.x > 300 && status == kMoveRight) {
            status = kMoveLeft;
            [self enumerateChildNodesWithName:@"enemy" usingBlock:^(SKNode *node, BOOL *stop) {
                node.position = CGPointMake(node.position.x, node.position.y-10);
            }];
        } else if(node.position.x < 30 && status == kMoveLeft) {
            status = kMoveRight;
            [self enumerateChildNodesWithName:@"enemy" usingBlock:^(SKNode *node, BOOL *stop) {
                node.position = CGPointMake(node.position.x, node.position.y-10);
            }];
        }
     }];
}

-(void)moveEnemy:(CFTimeInterval)currentTime
{
    if(lastMoveTime + 1.5 >= currentTime) return;
    [self enumerateChildNodesWithName:@"enemy" usingBlock:^(SKNode *node, BOOL *stop) {
        if(status == kMoveRight) {
            node.position = CGPointMake(node.position.x+10, node.position.y);
        } else {
            node.position = CGPointMake(node.position.x-10, node.position.y);
        }
        
    }];
    lastMoveTime = currentTime;
}


-(void)attackFromEnemy:(CFTimeInterval)currentTime
{
    if(lastAttackTime + 1.0 >= currentTime) return;
    NSUInteger enemyIndex = arc4random_uniform([attackEnemyList count]);
    SKNode* enemy = attackEnemyList[enemyIndex];
    CGPoint location = enemy.position;
    CGPoint moveToPoint = CGPointMake(location.x, location.y - self.frame.size.height );
    [self attack:location bitMask:kEnemyContactMask moveToPoint:moveToPoint attackColor:[SKColor redColor]];
    lastAttackTime = currentTime;
}

-(void)attack:(CGPoint)location bitMask:(uint32_t)bitMask moveToPoint:(CGPoint)moveToPoint attackColor:(SKColor*)attackColor
{
    SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithColor:attackColor size:CGSizeMake(5,5)];
    sprite.position = location;
    sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
    sprite.physicsBody.dynamic = YES;
    sprite.physicsBody.contactTestBitMask = bitMask;
    SKAction* bulletAction = [SKAction sequence:@[[SKAction moveTo:moveToPoint duration:1.0],
                                                  [SKAction removeFromParent]]];
    [sprite runAction:bulletAction];
    [self addChild:sprite];
    
    
}


```

##衝突について

最後は衝突についてです。
今回はパーティクルなど実装していません。

bitMaskの指定により衝突のメソッドが実行される時点で、
それらは両方とも非表示にするとしたいと思います。

ただし、敵だった場合については、
attackEnemyListからの削除をするようにしています。

```objc:MyScene.m

- (void)didBeginContact:(SKPhysicsContact *)contact
{
    if(contact.bodyA.node.physicsBody.categoryBitMask == kEnemyCategoryMask) {
        [attackEnemyList removeObject:contact.bodyA.node];
    } else if(contact.bodyB.node.physicsBody.categoryBitMask == kEnemyCategoryMask) {
        [attackEnemyList removeObject:contact.bodyB.node];
    }
    [contact.bodyA.node removeFromParent];
    [contact.bodyB.node removeFromParent];
}


```

以下ソースになります。
いろいろと中途半端ではありますが、参考になれば幸いです。

[SpaceInvadersLikeGame](https://github.com/kitanoow/SpaceInvadersLikeGame "SpaceInvadersLikeGame")