夏までにiPhone アプリつくってみっか!

趣味でiPhone/Androidアプリを開発し、日々勉強した事を書いています。オープンワールド系レースゲームをUnityで開発中です。

【cocos2d, Objective-C】CCSpriteBatchNodeをシングルトンで実装してみる

今回はスプライトバッチノードをシングルトンとして実装してみたいと思います。シングルトンのインスタンスはクラスメソッドから取り出せるので、ヘッダファイルさえインポートしておけばどのクラスのどのメソッドからも[MyBatch sharedMybach]の形でアクセスできます。メソッド呼び出し時にパラメーターとして渡したりインスタンス変数に入れておいたりする必要がないので便利です。
今回追加したMyBatchクラスの実装ファイルです。MyBatchクラスはCCSpriteBatchNodeクラスを継承しています。

#import "MyBatch.h"


@implementation MyBatch

+ (MyBatch *)sharedMyBatch
{
    static MyBatch* sharedInstance = nil;
    if (sharedInstance == nil) {
        sharedInstance = [self batchNodeWithFile:@"icon-atlas.png"];
    }
    return sharedInstance;
}

@end

スタティック変数として宣言するのが重要です。スタティック変数はプログラム実行じに1度だけ初期化され、設定した値はプログラム終了まで保持されます。
上のプログラムの場合、初めてこのメソッドが呼ばれたときはsharedInstanceがnilなのでif文の中を通ります。クラスメソッド内のselfはクラスオブジェクトを指しますので、これによりMyBatchクラスのインスタンスが生成されます。ちなみにパラメーターをしてさきほど作成したテクスチャアトラスの.pngファイルを指定します。で、returnで生成されたインスタンスが返されます。
2回目以降にこのメソッドが実行されるとき、static変数の値はnilではなくなっていますので、インスタンスは生成されずに1回目の実行時に生成されたインスタンスが必ず返ります。
CCSpriteBatchノードのインスタンスは必ずどこかにaddChildしてあげないと役に立ちません。このプログラムのゲームレイヤーであるHelloWorldLayerの子ノードとしてaddするのが妥当ですので、イニシャライザにその処理を入れます。私はCCSpriteBatchノードをどこかにaddChildしなくてはいけない事に気づかず、画面に何も表示されないのでしばらく悩んでしまいました。こんな感じでHelloWorldLayerにaddChildしてあげました。その次の行では前回作成したテクスチャアトラスをスプライトフレームキャッシュに読み込んでいます。

- (id)init
{
    if( (self=[super init]) ) {
        [self addChild:[MyBatch sharedMyBatch]]; // 重要
        [[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@"icon-atlas.plist"];
        _myShip = [[MySprite alloc] initWithImage];
        [self addChild:_myShip];
        self.isTouchEnabled = YES;
    }
    return self;
}

MySpriteクラスの実装ファイルはこうなりました。イニシャライザのパラメーターでスプライトバッチノードを受ける必要がなくなりました。以前は[CCSprite spriteWithFile:@"Icon-72.png"];とファイルからテクスチャを読んでいましたが、スプライトフレームから読むように変更しています。元のファイル名をそのまま使うので違和感はありません。そして、今回実装したシングルトンのMyBatchにスプライトをaddChildします。ヘッダファイルをインポートさえすればこんな感じでどこからでもアクセスできるのは便利ですね。

#import "MySprite.h"
#import "MyBullet.h"
#import "MyBatch.h"


@implementation MySprite {
    CCSprite* _sprite;
    CCSprite* _option;
}

- (id)initWithImage
{
    
    if ((self = [super init])) {
        _sprite = [CCSprite spriteWithSpriteFrameName:@"Icon-72.png"];
        [[MyBatch sharedMyBatch] addChild:_sprite];
        _option = [CCSprite spriteWithSpriteFrameName:@"Icon-72.png"];
        [[MyBatch sharedMyBatch] addChild:_option];
        _option.opacity = 255 * 0.5; //半透明にする
        CGSize screen = [CCDirector sharedDirector].winSize;
        CGPoint myPosition = ccp(screen.width / 2, screen.height / 2);
        self.position = _sprite.position = myPosition;
        myPosition.x += _sprite.contentSize.width * 2; //オプションはちょっと右側に表示
        _option.position = myPosition;
        self.contentSize = _sprite.contentSize;
        [self scheduleUpdate];
        
        CCRotateBy* rotateby = [CCRotateBy actionWithDuration:2 angle:360];
        CCScaleBy* scaleUp = [CCScaleBy actionWithDuration:1 scale:2.0];
        CCScaleBy* scaleDown = [CCScaleBy actionWithDuration:1 scale:1.0/2.0];
        CCSequence* sequence = [CCSequence actions:rotateby, scaleUp, scaleDown, nil];
        CCRepeatForever* repeat = [CCRepeatForever actionWithAction:sequence];
        [self runAction:repeat];
        
        [self schedule:@selector(shoot) interval:0.1];
    }
    return self;
}

- (void)update:(ccTime)delta
{
    _sprite.position = self.position;
    _sprite.rotation = self.rotation;
    _sprite.scale = self.scale;
    _option.position = ccpRotateByAngle(ccpAdd(self.position, ccp(_sprite.contentSize.width * 2 * self.scale, 0)),
                                        self.position, CC_DEGREES_TO_RADIANS(-self.rotation));
    _option.rotation = self.rotation;
    _option.scale = self.scale;
}

- (void)shoot
{
    CGPoint vector = ccpSub(_option.position, self.position);
    CGFloat speed = 200;
    
    CCSpriteFrame* frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"Icon-Small.png"];
    
    MyBullet* bullet = [MyBullet bulletWithPosition:_option.position vector:vector speed:speed spriteFrame:frame];
    [self.parent addChild:bullet];
    
}

@end

shootメソッドのスプライトフレームを作るコードがずいぶん短くなりました。ちなみに前はこんな感じでした。

NSString* file = @"Icon-Small.png";
CCTexture2D* texture = [[CCTextureCache sharedTextureCache] addImage:file];
CGSize texSize = texture.contentSize;
CGRect texRect = CGRectMake(0, 0, texSize.width, texSize.height);
CCSpriteFrame* frame = [CCSpriteFrame frameWithTexture:texture rect:texRect];

プログラムの実行画面はこんな感じです。
左下の数値の1番上が1になっているのがスプライトバッチノードを使っている証です。
f:id:takujidev:20130410231223p:plain
次回こそは敵キャラを表示して撃ってみたいと思います。