自キャラを固定配置する

kobold2dのサンプルに入っていたParallax-Side-Scrollerを改造して自分のゲームにしている件です。
前回、元々横方向だった敵キャラの移動を縦方向にしました
今回は、自キャラの1つである都市と基地を設置します。

Xcodeで作業を始める前に、都市と基地の絵をTexture Packerで読み込みます。

130512 0003
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」に変えました。
それから、キャラクターの設置を一先ず都市だけで試してみることにしました。

typedef enum
{
    city =0,
	BaseType_MAX,
} BaseTypes;

こちらはBaseEntity.hでの定義です。

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の座標に固定値を入れました。

-(void) spawn:(int)baseID
{
	//CCLOG(@"spawn base");

    float xPos = 100;
    float yPos = 100;
    self.position = CGPointMake(xPos, yPos);

次に、BaseCache.mです。

-(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へ差し替えます。

-(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を削除しなければならなかったのです。

if ((self = [super initWithSpriteFrameName:enemyFrameName]))
{
	//[self addChild:[StandardMoveComponent node]];

StandardMoveComponentクラスは何をしているかと言うと、読んだ親クラスをひたすら移動させるようにされていました。
都市は動かないので、このクラスの呼び出しは不要という訳です。

130509 0001
表示されたのが、この画面です。
ヤッター!都市が1つ表示されただけですが、嬉しいの何のって。こうやってモチベーションを上げていく訳です。

さて、今度はいよいよ都市を並べて行きます。画面が狭いので、札幌、東京、大阪、福岡の4つを表示することにしました。
で、どこで都市を生成しようか悩んだのですが、まずBaseCache.mのinitBasesメドッド内の以下で試してみました。

-(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という配列を置きました。

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も引数で渡して行くように変更しました。

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が受け取れるように変更しました。

+(id) baseWithType:(BaseTypes)baseType :(int)baseID;

BaseEntity.hの宣言を変更しました。

+(id) baseWithType:(BaseTypes)baseType :(int)baseID
{
	id base = [[self alloc] initWithType:baseType :baseID];

こちらはBaseEntity.mのbaseWithTypeの実装です。
当然、ここでコールしているinitWithTypeメソッドにもjをbaseIDとして渡すので、、、

-(id) initWithType:(BaseTypes)baseType :(int)baseID
{
	type = baseType;
	
	NSString* baseFrameName;
	initialHitPoints = 1;

initWithTypeも引数を追加するように変更しなければなりません。
そして、initWithType内からspawnメソッドをコールするので、、、

//[self initSpawnFrequency];
[self spawn:baseID];

その際にjをbaseIDとして渡すようにしました。
元々コールしてたinitSpawnFrequencyメソッドではなく、spawnメソッドを直接コールするようにしたのは、基地は1度出現したら何度も出直さないからです。
(多分、initSpawnFrequencyメソッドは、出現頻度を管理しているんだと思います)

-(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の直下へ移動しました。

@implementation BaseEntity

@synthesize initialHitPoints, hitPoints;

int base_point[4][2] = {
    {30,65},//base:福岡
    {120,75},//base:大阪
    {200,80},//base:東京
    {230,130},//base:札幌
    //missile
};

130512 0002
これでやっとこさ、都市を並べて表示できました!
う〜ん感動的ですが、時間がかかりすぎです。

130514 0001
で、これって3.5インチの画面で見たらどうなるんだろう?という素朴な疑問から、iPhoneシミュレーターを3.5インチにしたところ、、、。
そりゃそうですね。見た目の背景は位置を変えましたが、絶対座標は相変わらず左下がx=0,y=0なワケですから。。。
次は、3.5インチ(iPhone4s以下)と4インチ(iPhone5以降)で絶対座標が変わるようにできないか検討してみたいと思います。