2009年8月10日月曜日

cocos2dのドキュメントをdoxygenで生成してみました




cocos2dの公式ブログに、ドキュメントをdoxygenでビルドしてXcodeのドキュメントビューワで見られるようにする方法が記載されていたので試してみました。
http://www.cocos2d-iphone.org/archives/358

■前準備
  • cocos2d for iPhone 0.8をダウンロードしておく。
  • doxygenをインストールする。どこからでもかまいませんが、私はMacPortsを使ってインストールしました。

■それでは早速doxygenでビルドしてみましょう
ダウンロードしてきたcocos2d for iPhoneの、cocos2d-iphone.xcodeprojをXcode開きます。開いたらターゲットからcocos2d-documentationを選択し「情報を見る」をクリックします。「ビルド」タブの中を調べると、上記の画像のようにDOXYGEN_PATHという項目がありますので、そこを自分の環境に合ったdoxygenへのパスに変更します。私の場合は/opt/local/bin/doxygenです。あとはこのcocosed-documentationをビルドするだけです。全部自動的にやってくれます。


■おまけに学んだこと
作成されたドキュメントは以下のパスに配置されるみたいです。
~/Library/Developer/Shared/Documentation/DocSets/
つまり、ここにDocSet形式のファイルを置けば自由にドキュメントをXcodeに追加することができるってわけですね。自分のプロジェクト用のドキュメントをdoxygenで生成したり出来そうです。

何でもいいから作ってみようと思って作った結果がこれだよ!




製作期間二日。この程度の物に二日とか許されるのは小学生までよねー・・・orz
一応自機も動くしあたり判定もあるしゲームオーバーにもなるよ!すごいよcocos2d!



以下、全然関係ないけど設計のお話です。

(2009/08/30追記) 以下の内容に従って作ってみた結果がこちら:http://akisute.com/2009/08/blog-post_29.html
見ての通り大失敗してます。やっぱり継承して作った方がうまくいくっぽいです・・・


Javaの継承システムでは、サブクラスはスーパークラスのコンストラクタを引き継がない。また、暗黙的にスーパークラスのデフォルトコンストラクタを最初に呼びだすことになっている。だから、これはコンパイルが通らないし、
class Parent {
    private String name;
    public Parent(String name) {
        this.name = name;
    }
}

class Child extends Parent {
    int age;
    public Child(int age) {
        // デフォルトコンストラクタがないし、明示的にSuper(String)を呼んでいない
        this.age = age;
    }
}
これもコンパイルが通らない。
class Parent {
    private String name;
    public Parent(String name) {
        this.name = name;
    }
}

class Child extends Parent {
    int age;
    public Child(int age) {
        super("child");
        this.age = age;
    }
}

public static void main(String[] args) {
    // ChildにはSuperのコンストラクタが継承されないのでこれはできない
    Child child = new Child("akisute");
}
ところがObjective-Cの継承システムでは、サブクラスがスーパークラスのイニシャライザをすべて受け継いでしまう。なぜならイニシャライザも何の変哲もないタダのメソッドだから。だから、こんなコードが書けてしまう。
@interface Parent : NSObject {
    NSString *name;
}
@end

@interface Child : Parent {
    int age;
}
@end

@implementation Parent
    - (id)initWithName:(NSString *)aName
    {
       if (self = [super init])
       {
           name = aName;
       }
       return self;
    }
@end

@implementation Child
    - (id)initWithAge:(int)anAge
    {
        // Parentにはinitが無いが、NSObjectにはあるので
       if (self = [super init])
       {
           age = anAge;
       }
       return self;
    }
@end

int main (int argc, char const *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    // Childには無いinitWithNameが呼び出せてしまう
    Child *child = [[[Child alloc] initWithName:@"akisute"] autorelease];
    NSLog(@"@%", child);
    [pool release];
    return 0;
}
で、何が一体問題になるのかというと、この仕様のせい(おかげ?)で安易な継承が出来なくなっている。たとえば今回のcocos2dを使ったゲームでは、キャラは全部Spriteのサブクラスにしたかったんだけれども、そうするとSpriteクラスに最初から用意されているイニシャライザ(initWithFile:とかinitWithTexture:とか)が外に漏れてしまう。しかもこいつらを呼びだされると本来自分の作ったキャラクラスで呼びだしたかったイニシャライザが回避されて、任意の画像でへんちくりんなキャラを作られてしまう危険性がでてしまう。そこで、今回はこういうふうに継承ではなくてコンポジットを使ったコードにした。
@interface MSObject : NSObject {
    /** Sprite for this object. Can be changed to AtlasSprite for further performance. */
    Sprite *sprite;
    /** Hit box size */
    CGSize hitBoxSize;
}
後から考えたらこの方が正解だった。だって、こうしておけば外へ公開するAPIを変化させずに、中身のSpriteをよりパフォーマンスの良いAtlasSpriteに後からいくらでも差し替えられる!

2009年8月8日土曜日

cocos2d細かいところメモ

cocos2dを使っていて適当に気づいたところとかメモしてみます。

■ログ出力
CCLOGというマクロccMacros.hで定義されています。
#ifdef DEBUG
#define CCLOG(...) NSLog(__VA_ARGS__)
#else
#define CCLOG(...) do {} while (0)
#endif
注意点としてDEBUGがdefineされていないと使えません。


■ベクトル演算とか角度変換とか
CGPointを拡張してベクトル演算をするためのメソッドが追加されています。なかなか便利です。たとえばこんな感じ。
    // 二つのベクトルのdot(内積)とlength(ベクトルの長さ)を計算してcosθを求める
float dot = ccpDot(lastAccerelometerVector, convertedVector);
float a = ccpLength(lastAccerelometerVector);
float b = ccpLength(convertedVector);
float cosTheta = dot / (a*b); // 注意:a*bが0だとゼロ除算で死にます。真似しないでね><
// 角度→ラジアン変換用のマクロ。逆ももちろんあります
float threshold = cos(CC_DEGREES_TO_RADIANS(120.0));
Chipmunkにも同様のメソッドがあります。お好きな方をご利用いただけますが、個人的には全部cocos2dでやる方が好きです。唯一の問題は、こんなメソッドを使っているとベクトル内積の計算式を忘れます。


■音を出力する方法
最初からCocoa Touchで用意されているAudio Queue Service, OpenAL, AVAudioPlayer, Audio Unitに加えて、さらにcocos2dについてくる音再生用ライブラリとして
  • CocosDenshionとSimpleAudioEngine
  • sound-engine
これだけたくさんの中から選択できます。今回は一番簡単なSimpleAudioEngineという奴を使ってみました。
        // prefetch sound resources
SimpleAudioEngine *audioEngine = [SimpleAudioEngine sharedEngine];
[audioEngine preloadEffect:@"bell.aif"];
[audioEngine preloadEffect:@"gong.aif"];
[audioEngine playEffect:@"gong.aif"];
たったのこれだけです。playEffectでサウンドエフェクトが、playBGMでBGMが再生できます。同時再生数とか使用可能なファイルタイプとか細かいところは不明ですが、そこそこの量が同時に出せるみたいです。
注意点としてSimpleAudioEngineはCocosDenshionを使用しています。CocosDenshionのライセンスはcocos2dと独立しており、こちらは年間2500ドル以上の利益が出ている場合には500ドルのライセンス料を徴収するとかなんとかそういう条項があります。(http://www.cocos2d-iphone.org/wiki/doku.php/cocosdenshion:licenseを参照)


今はこのcocos2dの上にどのような構造でアプリを組んでやろうかとか考えてます。・・・あー、こんなこと考えてるからいつまで経っても本題のアプリが完成しないんだー ><