kobold2dのサンプルに入っていたParallax-Side-Scrollerを改造して自分のゲームにしている件です。
前回、敵キャラを自キャラへ誘導する仕組みを作りました。
今回は、自キャラをランダムにロックオンする仕組みを敵キャラに実装します。
今回も過去に作ったFlashと同じ要領でやれば簡単と考えていましたが、クラス間での配列の共有と共有した配列のプロパティの取り出し方でドツボにハマりました。
何か試しては戻り、試しては戻りの繰り返しで、危うく挫折するところでした。トライ&エラーには、XcodeのSnapShotが大活躍でした。
段取りとしては、自キャラの入った配列を用意して、その配列からランダムに自キャラを取り出し、その自キャラの位置を前回やった敵キャラへの誘導に使うことになります。
まず、自キャラを管理しているBaseCacheクラスのヘッダで配列(_friendliesとしました)を宣言します。
NSArrayではなくCCArrayにしたのは、CCArrayの方が高速に配列内を検索できるらしいからです。
1 2 3 4 5 6 7 8 |
@interface BaseCache : CCNode { CCSpriteBatchNode* batch; CCArray* bases; CCArray* _friendlies; } @property (readonly, nonatomic) CCArray* friendlies; |
ここで躓いたのは、配列を共有するための方法です。
ググったところ、こちらのページ(「Smart Gadget Laboratory」)で見つけました。
でも、この@propertyと@synthesizeを使ってクラス間で変数を共有する方法って「Xcode4ではじめる Objective-Cプログラミング」という参考本で過去に勉強済みのことでした。本で勉強した時は分かったつもりでいましたが、やはり実戦で使わないとスキルは身に付かないですね。
次に、BaseCache.mのinitBaseメソッドで、BaseEntityインスタンスの生成と同時に配列を使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
_friendlies =[CCArray arrayWithCapacity:0];//配列の初期化 for (int i = 0; i < BaseType_MAX; i++) { CCArray* basesOfType = [bases objectAtIndex:i]; int numBasesOfType = [basesOfType capacity]; for (int j = 0; j < numBasesOfType; j++) { BaseEntity* base = [BaseEntity baseWithType:i :j]; [batch addChild:base z:200 tag:i]; [_friendlies addObject:base];//配列に追加していく [basesOfType addObject:base]; } } |
ここで今回最も躓いた箇所が、配列の初期化です。ヘッダファイルで宣言していたので、いきなり追加できると勘違いしていたのですが、Objective-Cでは初期化しないとオブジェクトを配列に加えてくれないみたいですね。。。
これに気付かず、何度NSLogで配列の中身を見ても空っぽで時間を無駄にしました。
ActionScriptなら、以下で終わっちゃうんですけどね。
1 2 |
friendlies:Array =new Array(); friendlies.push(base); |
これで、自キャラを配列に格納しました。
さて、次は敵キャラであるEnemyEntityクラスで目標をランダムに選びます。
まず、EnemyEntity.hで標的のX座標を格納するtargetBaseXを宣言します。これは前回のEnemyEntity.mのupdateメソッドで、標的のX座標をとりあえず的にCGFloat targetX=200としていたのを、ちゃんとクラス内のローカル変数にしたものです。
1 2 3 4 5 6 7 8 9 10 |
@interface EnemyEntity : Entity { EnemyTypes type; CGFloat targetBaseX; int initialHitPoints; int hitPoints; float ySpeed; float xSpeed; } |
あと、前回までと変えたところは、敵キャラの降下速度と誘導速度をそれぞれfloat ySpeed、float xSpeedとして変数で持つことにしました。
次に、EnemyEntity.mで先ほどのtargetXを@synthesizeに加え、spawnメソッド内で標的を決めます。
ここで躓いたのは、共有する時にインスタンスを使う必要があったことです。ただ、これはParallax-Side-Scrollerの様々なところで使われていた方法なので、すぐに分かりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// Finally set yourself to be visible, this also flag the enemy as "in use" self.visible = YES; //インスタンス生成 BaseCache* baseCache =[[BaseCache alloc] init]; BaseEntity* myTarget =[[BaseEntity alloc] init]; //ターゲットをランダムに取得 int length =baseCache.friendlies.count; srand(time(nil));//乱数初期化 int targetID = rand()%length;//0~Xまでの間で乱数を発生 myTarget =[baseCache.friendlies objectAtIndex:targetID]; targetBaseX =myTarget.position.x; |
先ほどBaseCacheクラスで生成した配列をEnemyEntitiyクラスで使うため、baseCacheクラスのインスタンスを作ります。
それから、配列から取り出した自キャラ(オブジェクト)を入れるためのmyTargetインスタンスを作ります。
配列friendliesの長さを取得して、ランダムな整数を発生させる際に使います。乱数の発生方法は、「iPhoneアプリ開発の虎の巻」で見つけました。
私の1作目のアプリ「とけいであそぶ」を作った時にも大変お世話になったサイトです。
ただ、このままだとワンパターンな乱数になってしまうので、後でチューニングするつもりです。
配列friendliesの長さ分のランダムな整数を、そのまま配列から抽出するさいのobjectAtIndexのキーとして使います。取り出した自キャラ(オブジェクト)は、先ほどのmyTargetインスタンスに格納し、myTargetのプロパティからposition.xと取り出して、ヘッダで宣言したtargetBaseXに格納します。
1 2 3 4 5 |
self.position =ccp(self.position.x + (targetBaseX - self.position.x) * xSpeed, self.position.y -ySpeed); if (abs(targetBaseX - self.position.x)<0.5) {//誘導完了 self.position =ccp(targetBaseX, self.position.y); } |
あとは前回の誘導ロジックへ渡して終わりです。
動画で見るとこんな感じになります。
少し、動きがゲームっぽくなって、テンション上がってきました!
次は、敵キャラが自キャラに衝突したかの当たり判定をやってみます。