kobold2dのサンプルに入っていたParallax-Side-Scrollerを改造して自分のゲームにしている件です。
前回、元々横方向だった敵キャラの移動を縦方向にしました。
今回は、自キャラの1つである都市と基地を設置します。
Xcodeで作業を始める前に、都市と基地の絵をTexture Packerで読み込みます。
Texture Packerは、画像ソースを追加できます。
前回作成した.tpsファイルを開き、都市と基地のswfをTexture PackerのSprites枠へドラッグアンドドロップします。すると、即座にスプライトシートを広げてくれるので、Publishして.plistファイルと.pvr.cczファイルを生成します。
あとは、XcodeのResourcesへAdd Files to…してスプライトシートの差し替えします。ちなみに、Add Files toする前に、元の.plistファイルと.pvr.cczファイルを消さないと「Multiple errors occurred while copying the files.」というエラーが出て差し替えできません。
スプライトシートを追加したら、EnemyCacheとEnemyEntityの.hと.mをコピーしてそれぞれBaseCacheとBaseEntityとしました(ファイルはFinderでコピーして、XcodeへAdd Files toしました)。
あとは、Enemyのものをパクれば簡単だ!、、、と思っていたのですが、現実はそんなに甘くありませんでした。
まず、「Enemy」と付いたものは、コメントアウトも含めて片っ端から「Base」に変えました。
それから、キャラクターの設置を一先ず都市だけで試してみることにしました。
1 2 3 4 5 |
typedef enum { city =0, BaseType_MAX, } BaseTypes; |
こちらはBaseEntity.hでの定義です。
1 2 3 4 5 6 7 8 9 |
switch (type) { case city: baseFrameName = @"city.swf/0000"; break; default: [NSException exceptionWithName:@"BaseEntity Exception" reason:@"unhandled base type" userInfo:nil]; } |
EnemyEntity.mでenemyMissile.swfだった画像は、スプライトシートに追加したcity.swfをBaseEntity.mへ読み込みます。
弾を出さなかったり、フレームアニメをコメントアウトしたのは、EnemyEntity.mと同じです。
あと、試しで表示させたかったので、spawnメソッドではxとyの座標に固定値を入れました。
1 2 3 4 5 6 7 |
-(void) spawn:(int)baseID { //CCLOG(@"spawn base"); float xPos = 100; float yPos = 100; self.position = CGPointMake(xPos, yPos); |
次に、BaseCache.mです。
1 2 3 4 5 6 |
-(id) init { if ((self = [super init])) { // get any image from the Texture Atlas we're using CCSpriteFrame* frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:@"city.swf/0000"]; |
こちらのinitメソッドでも、spriteFrameByNameで読み込む画像をenemyMissile.swfからcity.swfへ差し替えます。
1 2 3 4 5 6 7 8 9 10 11 12 |
-(void) initBases { bases = [[CCArray alloc] initWithCapacity:BaseType_MAX]; // create the arrays for each type for (int i = 0; i < BaseType_MAX; i++) { int capacity; switch (i) { case city: capacity = 1; |
都市を1個だけ試しに表示できれば良いのでcapacityは1にしておきます。
多分、これで都市が表示されるハズ!っとRunしてみましたが、、、表示されませんでした。
ここで少し躓きました。BaseEntity.mのinitWithTypeメソッドにあるStandardMoveComponentのaddChildを削除しなければならなかったのです。
1 2 3 |
if ((self = [super initWithSpriteFrameName:enemyFrameName])) { //[self addChild:[StandardMoveComponent node]]; |
StandardMoveComponentクラスは何をしているかと言うと、読んだ親クラスをひたすら移動させるようにされていました。
都市は動かないので、このクラスの呼び出しは不要という訳です。
表示されたのが、この画面です。
ヤッター!都市が1つ表示されただけですが、嬉しいの何のって。こうやってモチベーションを上げていく訳です。
さて、今度はいよいよ都市を並べて行きます。画面が狭いので、札幌、東京、大阪、福岡の4つを表示することにしました。
で、どこで都市を生成しようか悩んだのですが、まずBaseCache.mのinitBasesメドッド内の以下で試してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
-(void) initBases { : : : 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]; [batch addChild:base z:200 tag:i]; [basesOfType addObject:base]; if(i ==0){ float xPos = base_point[baseID][0]; float yPos = base_point[baseID][1]; self.position = CGPointMake(xPos, yPos); } } } |
baseをaddChildしているので、その際にxとy座標で移動させれば良いかと考えた訳です。
そこで、initBasesから呼べるように、同じBaseCache.mの直下にbase_pointという配列を置きました。
1 2 3 4 5 6 7 |
int base_point[4][2] = { {30,65},//base:福岡 {120,75},//base:大阪 {200,80},//base:東京 {230,130},//base:札幌 //missile }; |
本当は、CCDictionaryとか使ってみたかったのですが、使い方がまだよく分からないので、1作目のとけいであそぶに使った多次元配列を使いました。
各都市の座標を格納する配列を定義します。最初は適当な数値で試し、後で画像ソフトを使って位置を測り直しました。
さっき1にしたcapacityも4にしておきます。
さて、これで上手く行くはずとRunしましたが、何故か表示される都市が最初の配列の1つだけしか表示されません。。。
また、ホントやめようかと思うくらい躓きました。そしてイロイロと試しまくりました。
結局、何で1つしか表示されないのかは分かりませんでしたが、BaseEntityクラスでbaseWithTypeをコールする時に、iだけじゃなくてjも引数で渡して行くように変更しました。
1 2 3 4 5 6 7 8 9 10 11 |
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]; [basesOfType addObject:base]; } |
そのため、BaseEntityのクラスメソッドであるbaseWithTypeでjが受け取れるように変更しました。
1 |
+(id) baseWithType:(BaseTypes)baseType :(int)baseID; |
BaseEntity.hの宣言を変更しました。
1 2 3 |
+(id) baseWithType:(BaseTypes)baseType :(int)baseID { id base = [[self alloc] initWithType:baseType :baseID]; |
こちらはBaseEntity.mのbaseWithTypeの実装です。
当然、ここでコールしているinitWithTypeメソッドにもjをbaseIDとして渡すので、、、
1 2 3 4 5 6 |
-(id) initWithType:(BaseTypes)baseType :(int)baseID { type = baseType; NSString* baseFrameName; initialHitPoints = 1; |
initWithTypeも引数を追加するように変更しなければなりません。
そして、initWithType内からspawnメソッドをコールするので、、、
1 2 |
//[self initSpawnFrequency]; [self spawn:baseID]; |
その際にjをbaseIDとして渡すようにしました。
元々コールしてたinitSpawnFrequencyメソッドではなく、spawnメソッドを直接コールするようにしたのは、基地は1度出現したら何度も出直さないからです。
(多分、initSpawnFrequencyメソッドは、出現頻度を管理しているんだと思います)
1 2 3 4 5 |
-(void) spawn:(int)baseID { float xPos = base_point[baseID][0]; float yPos = base_point[baseID][1]; self.position = CGPointMake(xPos, yPos); |
やっと、都市のx,y座標を指定できる場所を決めることができました。バケツリレー式に渡されたBaseCacheのinitBasesのj(baseID)をspawnメソッドで都市の座標を決めるのに使います。
もちろん、最初にBaseCache.mに置いていたbase_point配列は、spawnメソッドで使うためにBaseEntity.mの直下へ移動しました。
1 2 3 4 5 6 7 8 9 10 11 |
@implementation BaseEntity @synthesize initialHitPoints, hitPoints; int base_point[4][2] = { {30,65},//base:福岡 {120,75},//base:大阪 {200,80},//base:東京 {230,130},//base:札幌 //missile }; |
これでやっとこさ、都市を並べて表示できました!
う〜ん感動的ですが、時間がかかりすぎです。
で、これって3.5インチの画面で見たらどうなるんだろう?という素朴な疑問から、iPhoneシミュレーターを3.5インチにしたところ、、、。
そりゃそうですね。見た目の背景は位置を変えましたが、絶対座標は相変わらず左下がx=0,y=0なワケですから。。。
次は、3.5インチ(iPhone4s以下)と4インチ(iPhone5以降)で絶対座標が変わるようにできないか検討してみたいと思います。