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];