ラベル cocos2d 0.8 の投稿を表示しています。 すべての投稿を表示
ラベル cocos2d 0.8 の投稿を表示しています。 すべての投稿を表示

2009年10月4日日曜日

cocos2dでParticleSystemを使うときのメモ

懲りずにcocos2dで遊んでます。今回はParticleSystemを触ったときに気づいたことなどをメモしてみます。


■ParticleSystemが放出するParticleを回転させたい
http://www.cocos2d-iphone.org/api-ref/latest-stable/interface_particle_system.html
こちらの公式リファレンスには2009/10/02現在記載されていませんが、startSpin, startSpinVar, endSpin, endSpinVarというプロパティが用意されており、こちらの値を調整することで放出される個々のParticleに回転を与えることが出来ます。
        // angle of particles
startSpin = 90;
startSpinVar = 0;
endSpin = 1800;
endSpinVar = 3600;
注意点として、PointParticleSystemを継承したParticleSystemでは、spin系のプロパティは利用できないみたいです。パフォーマンスで劣るそうですが、QuadParticleSystemを継承するようにしましょう。

結果はこんな感じ。青いミミズみたいなのがParticleSystemから放出しているParticleです。
回転の中心軸の位置がおかしいのかな?


■Particleの大きさを変化させたくない
endSizeにkParticleStartSizeEqualToEndSizeを指定すると、放出されたParticleの大きさが変化しなくなります。
        // size, in pixels
startSize = 90.0f;
startSizeVar = 30.0f;
endSize = kParticleStartSizeEqualToEndSize;
endSizeVar = 30.0f;


■応用:Particleの回転を変化させたくない
現在のcocos2dではまだサポートされていませんので、適当にParticleSystem.mのコードを書き換えます。具体的には180行目のあたりを以下のようにします。
 // angle
float startA = startSpin + startSpinVar * CCRANDOM_MINUS1_1();
particle->angle = startA;
if (endSpin == kParticleStartSpinEqualToEndSpin )
particle->deltaAngle = 0;
else {
float endA = endSpin + endSpinVar * CCRANDOM_MINUS1_1();
particle->deltaAngle = (endA - startA) / particle->life;
}
これで放出されたParticleがくるくる回転するのを防げます。でもこのマジックナンバーを使った実装、正直イマイチです。
 // angle
float startA = startSpin + startSpinVar * CCRANDOM_MINUS1_1();
particle->angle = startA;
if (spinFixed)
particle->deltaAngle = 0;
else {
float endA = endSpin + endSpinVar * CCRANDOM_MINUS1_1();
particle->deltaAngle = (endA - startA) / particle->life;
}
個人的にはこの方に実装するほうが好きです。パフォーマンスは悪いかも。

2009年9月1日火曜日

多少マシになったかな




相変わらずこんなしょぼっちい物を作っていたりします。


■前回からちょっとだけ改善
敵が出るようになりました(キャラの向き滅茶苦茶ですが)。
弾が出るようになりました(タダの自機狙い3Wayですが)。
ちょっとずつデザエモン程度には近づいてきたかなぁと。
あとは自機が動いて弾が出れば一応STGっぽくはなります。

今回作ったところまでの経過をgithubにうpしてみました。
http://github.com/akisute/MyShooting/tree/338e164b4716ec27a6a9b8d384f7714243f53d2e

最新のソースはこちら。
http://github.com/akisute/MyShooting/tree/master


■今回の気づき
弾の発射とか敵の出現タイミングをフレーム単位で計測して実行しているのですが、cocos2dのフレーム時間はかなりばらつくようです。弾が200発程度出ただけでフレーム時間が2倍になったりします。要するに弾の発射間隔が2倍になります。ちょっとこれはいただけない感じ。毎フレームごとに前フレームから何秒経過したかが取得できるので、それを元に時間単位で発射タイミングを測った方がいいかもしれません。

あとはActionがやたら重いです。何が重いってActionの動き自体よりもActionオブジェクトの作成と解放に時間がかかっている感じです。頻繁に起動が変わったり単純直線移動しかしない動きを再現するとき(たとえば高速弾や自機コントロール)は、昔ながらのvx, vyを使った方法のほうが軽くて良さそうです。

2009年8月29日土曜日

どのクラスのインスタンスを作っているかに注意しましょう・・・

突然ですが問題です。以下のソースはコンパイルできますがバグがあります。
http://github.com/akisute/MyShooting/blob/4570825e7384157d3572b689cc23f9bb3acc0b82/Classes/MSTGObjectFactory.m

さて、どこが間違っているでしょうか?




■なんて突然言ってもわからないですが
正解はこちら。
+ (MSTGObject *)cannon
{
// ここが間違い。MSTGObjectではなくてMSTGCannonのインスタンスを生成する必要がある
MSTGObject *object = [MSTGObject spriteWithFile:@"cannon.png"];
return object;
}
で、MSTGObjectはライフがデフォルトゼロなので、生成された瞬間に即死して画面から退場してくださるという悲しいバグが。うーむJavaではこんな情けないバグ作ったりはしないのですが・・・ともかくこれで一歩前進。

イニシャライザとかコンポジットとか考えた結果がこれだよ!

前回の記事から引き続きCocos2Dでちょこちょこアプリを書いてます。
ソースコードも公開してみました。
http://github.com/akisute/MyShooting/tree/master


■で、あれからどうなった
全面作り直しorz

コーディングを進めていくうちに次々と問題点が噴出してきたのですが、その中でもこれはまずいと思ったのが2点。


■問題点その1.オブジェクトリストの管理が面倒
画面への描画を担当してくれるCocosNodeにSpriteを登録するのとは別にオブジェクトを管理するためのマネージャクラスを持っていたため、オブジェクトを追加するたびにこんなコードを書かなくちゃいけなかったんです。
// MSObjectの親ノード(Layer)の中で・・・
MSObject *object = [[[MSBullet alloc] initWithPosition:ccp(100,100)
angle:0.0
speed:200.0] autorelease];
[self addChild:object.node];
[[MSObjectManager sharedInstance] addObject:object
forOwner:kMSObjectOwnerEnemy];
これだけならまだしも、今度は消すのが面倒。
[self removeChild:object.node];
[[MSObjectManager sharedInstance] removeObject:object];
どちらかを消し忘れるとエラーになりますし、ループ中にMSObjectManagerからインスタンスを消すとエラーになったりと結構散々です。この問題点1はまだCocosNodeにaddMSObject:forOwner:とかremoveMSObject:みたいなメソッドを追加すればなんとか対応できたのですが、もっと深刻な問題点2が。


■問題点その2.オブジェクトクラスの中でscheduleが使えない
これで完全に積みました。要するにこんなコードが書けないわけです。
// MSObjectの中で
// 毎フレームごとに呼び出されるメソッドを定義する
- (void)onFrame:(ccTime)dt
{
// 死亡判定してみたり
// 衝突判定してみたり
// ライフを回復したり無敵フレームを設定してみたり
}

- (id)init
{
if(self = [super initWithFile:@"ship.png"])
{
// sceduleメソッドはCocosNodeのメソッドなのでこうやって登録する
// しかしschedule出来るのはCocosNode自身が持っているメソッドのみ
// そのためこの方法でメソッドを登録しても
// 実行時に「spriteにはonFrameなんてメソッドがありません」とエラーになる
[self.sprite schedule:@selector(onFrame:)];
}
return self;
}
これを回避するにはMSObjectにスケジューラを自分で実装する必要が出てきて・・・ってそれもうCocos2dでやる意味なくね?ということで結論。Cocos2dはCocosNodeを継承して使え。


■作り直しの結果
MSObjectをコンポジットではなくSpriteのサブクラスにして解決。ついでに名前をMSTGObjectに改名しました。イニシャライザが漏れてややこしいと言う問題が発生してしまいますが、そこは以下のようにファクトリクラスを使って解決。
MSTGObject *ship = [MSTGObjectFactory ship];
MSTGObject *bullet = [MSTGObjectFactory bullet];

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

2009年8月7日金曜日

cocos2d for iPhone Project Template v0.8 真似して作ってみました



cocos2d導入キット(http://d.hatena.ne.jp/Seasons/20090511/1241990196)としてid:Seasonsさんが公開されているキットを元に、私もcocos2d用のプロジェクトテンプレートを作ってみることにしました。id:Seasonsさん、すばらしいキットをありがとうございます!

今回の作業にあたり参考にしたページはこちら。
http://d.hatena.ne.jp/griffin-stewie/20090315/p1

作成したテンプレートはgitに公開いたしましたので、よろしければ使ってみてください。
http://github.com/akisute/cocos2d-xcode-template/tree/master


■インストール方法
downloadタブから0.8をダウンロードしてきて、解凍したファイルを以下のパスに配置してください。
~/Library/Application Support/Developer/Shared/Xcode/Project Templates
配置後、.gitと.gitignoreファイルを削除してください。削除しないと後からテンプレを使って新しいプロジェクトを作ったときに、これらのファイルがコピーされてしまいgitを使おうとしたときに問題が発生する可能性があります。


■テンプレートの内容
id:Seasonsさんが公開されている0.8beta用のテンプレートを元に、一部自分の気になった箇所を自分好みに修正しました。また、使用するcocos2dのバージョンを0.8betaから0.8リリース版に変更しました。一応ビルドして画面が出るところまでは確認しています。ただし、全機能をテストしたわけではないので、一部不具合があるかもしれません!
また注意点として、簡素化のため元のcocos2dや外部ライブラリに付随していたREADMEやドキュメント、LICENSEファイルなどをすべて削除してしまっています。おそらくは大丈夫だと思いますが、ライセンス的に問題が発生するかもしれません。大変申し訳ありませんが、このテンプレートを利用した際に生じる一切の不具合について当方では責任を負いきれませんのでご了承ください。


■id:Seasonsさんのテンプレートから変更したところ一覧
  1. 使用するcocos2dのバージョンを0.8リリース版にした。
  2. プロジェクトのグループ構成およびディレクトリ構成を変更した。この変更により、本来システムが利用する/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application以下ではなく、ユーザーのライブラリである~/Library/Application Support/Developer/Shared/Xcode/Project Templates以下にテンプレートを配置しても動作するようになった。
  3. MenuSceneとGameSceneが最初のテンプレとして用意されていたのを統廃合し、MainSceneひとつだけにした。
  4. ビルドの設定を必要に応じて変更し、またGCC_PREPROCESSOR_DEFINITIONSにDEBUGを追加した。これにより最初からデバッグビルド時にCCLOGマクロが正しく機能する。
  5. ソースコードのインデントがまちまちになっていたのを、すべてApple式のインデントに修正。
  6. ソースコード中から日本語コメントをすべて除去し英語コメントに置換。
  7. 一部使用されていたdepreciatedメソッドを除去。[[Director sharedDirector ] setDeviceOrientation:CCDeviceOrientationLandscapeRight];を使うようにした。


■思わぬ収穫
この作業によってXcodeの仕組みとテンプレートの作り方、ビルドの仕方などにかなり詳しくなることができました。たとえば・・・
  • Xcodeのテンプレートファイルは単なる普通のXcodeプロジェクトと何ら変わらない。そのままビルドして実行することもできる。
    唯一異なる点が、xcodeprojバンドルの中に、TemplateIcon.icnsとTemplateInfo.plistというファイルが存在し、これらを変更することでテンプレートとしての設定を変更することが出来る点。
  • libhogehoge.a(静的ライブラリ?)の作り方。ターゲットに新規ターゲットを追加して、必要なソースをぽいぽい投げ込むだけ。antでbuild.xmlを書くより簡単。
  • ___PROJECTNAMEASIDENTIFIER___.pchの配置パスを変更したときは、ターゲットのビルド設定のPrefix Headerの値を書き換える必要がある
  • 同様に、___PROJECTNAMEASIDENTIFIER___-Info.plistのパスを変更したときも、ターゲットのビルド設定のInfo.plistファイルの値を書き換える必要がある
  • main.mは何処に置いておいても大丈夫らしい

ということで、Xcodeプロジェクトのテンプレートを作成するのは凄く勉強になります。皆さんも一度自分好みのテンプレートを作成してみてはいかがでしょうか?