人工知能に関する断創録

このブログでは人工知能のさまざまな分野について調査したことをまとめています(更新停止: 2019年12月31日)

コインブロック

今回は、下から叩くとコインになるブロック

f:id:aidiary:20100306224935j:plain f:id:aidiary:20100306224936j:plain

を導入します。左が叩く前で右が叩いた後のイメージです。ブロックはコインやアイテムと作り方が違うので注意してください。コインやアイテムはSpriteを拡張していますが、コインブロックはただのブロック

f:id:aidiary:20100306224938j:plain

と同じ扱いです。

mariolike12.jar

コインブロック

まずマップデータを見てください(等幅フォントでないとずれます)。

 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
 B                                                          B
 B                                                          B
 B                                                          B
 B                                                          B
 B                                                          B
 B                                                          B
 B       ooj                                            k   B
 B     BBBBB                                     BBBBBBBBBBBB
 B                  k                    B k B              B
 B                  ooo                  BBBBBBBB           B
 B             k                 BBB                        B
 B                                                          B
 B                        CCC                               B
 B                  BBB                            C        B
 B                                                          B
 B    BCB     BCB            ooo          BoooooooooooooooooB
 B                        BCBBBBBBB       BoooooooooooooooooB
 B       k                    a           B            k    B
 BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB

Cがコインブロックを表しています。Bは普通のブロックです。load()でマップファイルが読み込まれるとmapにCが代入されます。コイン、アイテム、栗ボーと違ってスプライトは作られない点に注意してください。読み込まれたマップは、draw()で描画されます。

    for (int i = firstTileY; i < lastTileY; i++) {
        for (int j = firstTileX; j < lastTileX; j++) {
            // mapの値に応じて画像を描く
            switch (map[i][j]) {
                case 'B' : // ブロック
                    g.drawImage(blockImage,
                      tilesToPixels(j) + offsetX,
                      tilesToPixels(i) + offsetY, null);
                    break;
                case 'C' : // コインブロック
                    g.drawImage(coinBlockImage,
                      tilesToPixels(j) + offsetX,
                      tilesToPixels(i) + offsetY, null);
                    break;
                case 'c' : // 叩かれた後のコインブロック
                    g.drawImage(coinBlockImage2,
                      tilesToPixels(j) + offsetX,
                      tilesToPixels(i) + offsetY, null);
                    break;
            }
        }
    }

ブロックだけでなく、コインブロックと叩かれた後のコインブロックを描画しています。叩かれた後のコインブロック(c)は初期マップにはなかったですが、叩いた後にmapをCからcに書き換えることで

f:id:aidiary:20100306224935j:plain

から

f:id:aidiary:20100306224936j:plain

に変化するようにしています。あとコインブロックもブロックの一種なので衝突する必要があります。すり抜けてしまったらプレイヤーかわいそうですよね(笑)getTileCollision()を見てください。

    // 衝突しているか調べる
    for (int x = fromTileX; x <= toTileX; x++) {
        for (int y = fromTileY; y <= toTileY; y++) {
            // 画面外は衝突
            if (x < 0 || x >= col) {
                return new Point(x, y);
            }
            if (y < 0 || y >= row) {
                return new Point(x, y);
            }
            // ブロックがあったら衝突
            if (map[y][x] == 'B' || map[y][x] == 'C' || map[y][x] == 'c') {
                return new Point(x, y);
            }
        }
    }

ブロック(B)だけでなく、コインブロック(C、c)も衝突するようにしています。

コインブロックを叩く

次にプレイヤーが下からコインブロックを叩くとコインが取れる処理を追加します。Playerクラスを見て下さい。プレイヤーとブロックが衝突しているかを判断してるのがPlayerクラスのupdate()です。というわけで、コインブロックと衝突しているかの判断もupdate()に実装します。

    // y方向の当たり判定
    // 移動先座標を求める
    double newY = y + vy;
    // 移動先座標で衝突するタイルの位置を取得
    // y方向だけ考えるのでx座標は変化しないと仮定
    tile = map.getTileCollision(this, x, newY);
    if (tile == null) {
        // 衝突するタイルがなければ移動
        y = newY;
        // 衝突してないということは空中
        onGround = false;
    } else {
        // 衝突するタイルがある場合
        if (vy > 0) {
            // 下へ移動中なので下のブロックと衝突(着地)
            // 位置調整
            y = Map.tilesToPixels(tile.y) - height;
            // 着地したのでy方向速度を0に
            vy = 0;
            // 着地
            onGround = true;
            // 着地すれば再び二段ジャンプ可能になる
            canJumperTwo = true;
        } else if (vy < 0) {
            // 上へ移動中なので上のブロックと衝突(天井ごん!)
            // 位置調整
            y = Map.tilesToPixels(tile.y + 1);
            // 天井にぶつかったのでy方向速度を0に
            vy = 0;
            // コインブロックがあるか
            if (map.isCoinBlock(tile.x, tile.y)) {
                map.knockCoinBlock(tile.x, tile.y);
            } else if (map.isCoinBlock(tile.x + 1, tile.y)) {
                // 1つ右側のブロックも叩いていることにする
                // この処理がないとブロックがちょっと叩きづらい
                map.knockCoinBlock(tile.x + 1, tile.y);
            }
        }
    }

コインブロックは下からジャンプして叩くことを想定してます(亀の甲羅とかしっぽとかで取ることは想定してません)。なので上へ移動中のところに処理を挿入しています。上へ移動中に衝突を検出した場合、そのブロックがコインブロックであるかをisCoinBlock()で調べています。そして、それがコインブロックであった場合、knockCoinBlock()でコインブロックを叩きます。isCoinBlock()と knockCoinBlock()はMapに実装されています。

    /**
     * コインブロックがあるか
     * @param x X座標
     * @param y Y座標
     * @return コインブロックがあったらtrue
     */
    public boolean isCoinBlock(int x, int y) {
        if (map[y][x] == 'C') {
            return true;
        }
        
        return false;
    }
    
    /**
     * コインブロックを叩かれた状態にする
     * @param x X座標
     * @param y Y座標
     */
    public void knockCoinBlock(int x, int y) {
        // コインの音を鳴らす
        coinSound.play();
        // 叩かれた後のブロックに変化
        map[y][x] = 'c';
    }

コメントの通りです。isCoinBlock()は引数で指定された位置にコインブロックがあればtrueを返します。knockCoinBlock()は引数で指定された位置のコインブロックを叩かれた後のコインブロックに変化させます。コインを取った枚数を数えたい場合はここに処理を追加すればいいでしょう。

先ほどのupdate()にelse ifが追加されていて1つ右側のブロックも叩いていることにするがなぜか疑問に思った方もいると思います。ここはぜひこのelse if部をはずして実行してみてください。ときどき叩けないことに気づくと思います。これは、下図のようにプレイヤーの左隅の座標とブロックで衝突検出をしているからです。

f:id:aidiary:20100306224939j:plain

左側はプレイヤーの左隅座標とブロックの座標が重なるので衝突したことになるのに対し、右側はプレイヤーの左隅座標とブロックの座標が重ならないので衝突したことになりません。右側も明らかにブロックと接触しているように見えるので衝突したことにしたいわけです。そのために1つ右側のブロックも叩いていることにしています。