読者です 読者をやめる 読者になる 読者になる

apressのcocos2d本を読む - 第5章のメモ

iOS cocos2d

apressの"Learn iPhone and iPad cocos2d Game Development"を読みながらcocos2dを勉強していくメモ。第5章はScene, Layer, Spriteの関連クラスの使い方の説明。

Learn iPhone and iPad cocos2d Game Development

Learn iPhone and iPad cocos2d Game Development

cocos2dのメモリ管理の実装パターン

cocos2dのメモリ管理、インスタンス生成では、

+ (id)scene;

のようなメソッドでautoreleaseされたオブジェクトを返すのが流儀になっている。なので、自分でCCSceneのサブクラスを作る時には、この章のLoadingSceneクラスでやっているように、

@interface LoadingScene : CCScene {
    TargetScenes _targetScene;
}

+ (id) sceneWithTargetScene:(TargetScenes)targetScene;
- (id) initWithTargetScene:(TargetScenes)targetScene;
@end

sceneWithXXXのようなメソッドを宣言し、

+ (id) sceneWithTargetScene:(TargetScenes)targetScene
{
    return [[[self alloc] initWithTargetScene:targetScene] autorelease];
}

のようにautoreleaseされたオブジェクトを返すようにする。

addChildする時にはtagを有効に使おう

CCNodeのサブクラス(CCScene, CCLayer, CCSprite)といったクラスは、addChildメソッドを使って子ノードを追加することができる。SceneにLayerを追加する場合、LayerにSpirteを追加する場合といった具合にパターンはいくつか考えられるが、addChildする時に、- (void) addChild:(CCNode*)node ではなく、- (void) addChild: (CCNode*)node z:(NSInteger)z tag:(NSInteger)tag を使ってtagをつけておくと、- (CCNode*) getChildByTag:(NSIntegr)tag でtagを指定して子ノードを取得できるので便利なことがある。

複数のLayer, GameLayerとUserInterfaceLayerを子ノードとして持つMultiLayerSceneクラスから、GameLayerを取得する場合、

#import "cocos2d.h"

typedef enum
{
    LayerTagGameLayer,
    LayerTagUILayer,
} MultiLayerSceneTags;

@interface MultiLayerScene : CCLayer {
}

@property (readonly) GameLayer* gameLayer;
@property (readonly) UserInterfaceLayer* uiLayer;

@end

のように、tagのenumを定義しておき、

- (id) init
{
    if ((self = [super init])) {
        GameLayer* gameLayer = [GameLayer node];
        [self addChild:gameLayer z:1 tag:LayerTagGameLayer];

        UserInterfaceLayer* uiLayer = [UserInterfaceLayer node];
        [self addChild:uiLayer z:2 tag:LayerTagUILayer];
    }
    return self;
}

addChild:z:tag:で子ノードを追加すれば、

- (GameLayer*)gameLayer
{
    CCNode* layer = [self getChildByTag:LayerTagGameLayer];
    NSAssert([layer isKindOfClass:[GameLayer class]], @"%@ is not a GameLayer!");
    return (GameLayer*)layer;
}

のようにgetChildByTagでアクセスできる。

"is a"と"has a"関係

ゲームプレーヤーのキャラクターや敵といったものを表示していく時にはCCSpriteを使うことになると思うが、それをコードにするとき、CCSpriteを継承したPlayerSrpiteクラス、EnemySrpiteクラスのようなクラスを作るかどうかという議論が紹介されているが、この本の著者は本章では、プレーヤーはスプライトではない("is a"関係は成り立たない)、プレーヤーはスプライトを所有する("has a"関係が成り立つ)というオブジェクト指向の大原則にのっとって、NSObjectを継承したクラスがCCSpriteをプロパティとして持つという実装パターンを推奨している。

ただし、この後の6, 7, 8章ではシューティングゲームのようなサンプルが登場するのだが、その中では自機や敵機、弾丸といったクラスはすべてCCSpriteから継承しており、またこの方がコードが簡潔に見えるので、最終的にどういう風に実装するのがベストプラクティスなのかは良くわからない。

第5章ではランダムに動き回る蜘蛛をレイヤーに配置するというサンプルコードが含まれているのだが、この蜘蛛を表現するのに、

#import <Foundation/Foundation.h>
#import "cocos2d.h"

@interface Spider : NSObject {
    CCSprite* spiderSprite;
}

+ (id)spiderWithParentNode:(CCNode*)parentNode;
- (id)initWithParentNode:(CCNode*)parentNode;

@end

のようなNSObjectから直接継承したSpiderクラスを定義し、parentNodeという親ノードを指定してインスタンスを作成するというインターフェイスになっている。

#import "Spider.h"

@implementation Spider

+ (id) spiderWithParentNode:(CCNode *)parentNode
{
    return [[[self alloc] initWithParentNode:parentNode] autorelease];
}

- (id) initWithParentNode:(CCNode *)parentNode
{
    if ((self = [super init])) {
        spiderSprite = [CCSprite spriteWithFile:@"spider.png"];
        [parentNode addChild:spiderSprite];
    }
    return self;
}

この方法だと、一定期間ごとに表示を更新したり、タッチイベントを検出したりするのはどうするんだろう、ということになると思うが、表示更新には次のように、

- (id) initWithParentNode:(CCNode *)parentNode
{
    if ((self = [super init])) {
        // ... 省略

        // CCSchedulerにこのインスタンスを登録する
        [[CCScheduler sharedScheduler] scheduleUpdateForTarget:self priority:0 paused:NO];
    }
    return self;
}

- (void) update:(ccTime)delta
{
    // ここで一定期間ごとに表示を更新
}

CCSchedulerのscheduleUpdateForTargetを呼び出す方法が紹介されている。

また、タッチイベントの取得にはCCTargetedTouchDelegateプロトコルを実装することを宣言し、

@interface Spider : NSObject <CCTargetedTouchDelegate> {
  // ... 省略
@end

初期化処理の中で手動でCCTouchDispatcherに登録する方法が紹介されている。

- (id) initWithParentNode:(CCNode *)parentNode
{
    if ((self = [super init])) {
        // ...省略
        // CCTouchDispatcherに手動で登録
        [[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:-1 swallowsTouches:YES];
    }

    return self;
}

このあたりの議論と実装パターンは、この本の著者の見識はしっかりしているなと感じさせるもので、cocos2dを使ったアプリケーションのデザインを考える上でもかなり参考になった。