夏までにiPhone アプリつくってみっか!

趣味でiPhone/Androidアプリを開発し、日々勉強した事を書いています。オープンワールド系レースゲームをUnityで開発中です。

【cocos2d】タイルマップに壁を作る

以前のポスト、「【cocos2d】慣性スクロールの実装。背景をヌルヌル動かす」でキャラクターをタイルマップ上で自由自在に動かすことができるようになりました。
今回は、キャラクターの行く手を阻む壁をタイルマップ上に表現してみます。

例によって、この本を参考にしています。

また、Ray Wenderlichのチュートリアルも参考にさせていただきました。

前回使用したTiledに付属のDesertマップの壁の部分を実際にあたり判定のある壁にしてみます。

まず、メニューの「タイル・レイヤーの追加」で新たにcollidableというレイヤーを作成します。
collidableはプログラムからレイヤーを判別するためのただの文字列ですので、他の名前でもOKです。
f:id:takujidev:20130917230836j:plain

壁の部分に適当なタイルを並べます。今回はわかりやすいサボテンのタイルを使用しました。
Ray Wenderlichのチュートリアルでは手抜きをせずに専用のタイルセットを作っていますが、今回は手抜きです。

f:id:takujidev:20130917231333j:plain

サボテンを並べ終えたらサボテンのタイルのプロパティを設定します。タイルセットのサボテンタイルを右クリックすると「タイルの設定」というコマンドが出てくるのですが、なぜかグレイアウトしていて選択できません。その場合、タイルセット下左端の「タイルセットをインポート」ボタンを押すと選択できるようになります。
私はプロパティ名にcollidable, 値にYESを設定しました。これらも文字列としてアクセスされますので、自分がわかれば何でもかまいません。

f:id:takujidev:20130917232526j:plain:w300

ファイルをセーブします。元のサンプルデータはdesert.tmxとdesert.tsxの2ファイル構成ダッタのですが、セーブ後は全ての情報が.tmxファイルに入っていました。従いまして、Xcodeにはdesert.tmxファイルとtmw_desert_spacing.pngファイルだけをインポートしました。

プログラムではこのようにして追加してcolliableレイヤーにアクセスします。
このクラスはタイルマップのアドレスを知らないのでgetChildByTagメソッドで親(SpriteBatchNode)のそのまた親(ゲーム管理レイヤー)からタイルマップインスタンスのアドレスを得ています。
tilePosFromLocationメソッドで指定した位置のタイル位置を求めます。タイルは(0, 0)が左上になるので、y軸の位置の計算が若干複雑になっています。詳しい説明は本に書いてありますので、ここでは触れません。

今回Ray Wenderlichのサイトのサンプルプログラムを見て「あれっ」と思ったのが、NSDistionaryから値を得る方法です。下記のプログラムでコメントアウトしているのはこれまでの書き方なのですが、最近のObjective-Cではその下の行のように、まるでC言語の配列のようにアクセスする事ができるんですね。知りませんでした。
NSAarrayもobj = [array objectAtIndex:i];なんて長ったらしく書かずにobj = array[i];なんて書くのが今風なんですね。超便利!

- (BOOL)checkCollisionLayer:(CGPoint)position
{
    CCTMXTiledMap* tileMap = (CCTMXTiledMap *)[self.parent.parent getChildByTag:kTileMap];
    CGPoint tilePos = [self tilePosFromLocation:position tileMap:tileMap];
    CCTMXLayer* collisionLayer = [tileMap layerNamed:@"collidable"];
    int tileGID = [collisionLayer tileGIDAt:tilePos];
    
    if (tileGID != 0) {
        NSDictionary* properties = [tileMap propertiesForGID:tileGID];
        if (properties) {
 //           NSString* isCollidable = [properties valueForKey:@"collidable"];
            NSString* isCollidable = properties[@"collidable"];
            if ([isCollidable isEqualToString:@"YES"]) {
                return YES;
            }
        }
    }
    return NO;
}

// 指定したスクリーン座標にあるのタイルの位置を得る
- (CGPoint)tilePosFromLocation:(CGPoint)location tileMap:(CCTMXTiledMap *)tileMap
{
    CGPoint pos = ccpSub(location, tileMap.position);
    
    float pointWidth = tileMap.tileSize.width / CC_CONTENT_SCALE_FACTOR();
    float pointHeight = tileMap.tileSize.height / CC_CONTENT_SCALE_FACTOR();
    
    pos.x = (int)(pos.x / pointWidth);
    pos.y = (int)((tileMap.mapSize.height * pointHeight - pos.y) / pointHeight);
    
    pos.x = fmaxf(0, fminf(tileMap.mapSize.width - 1, pos.x));
    pos.y = fmaxf(0, fminf(tileMap.mapSize.height - 1, pos.y));
    return pos;
}

今回はとりあえずキャラクターと壁との衝突の検出まで行いました。
次回?は衝突したときにキャラクターがそこで止まるようにしたいと思います。