📘

Phaserでマップを利用した仕組み

2022/06/10に公開

tilemapを利用したマップの仕組み

マップエディタ(Tiled)で作成したtilemapを利用して、マップを作成します。初歩的なtilemapを利用したパターンです

マップの使用

  • タイルは64x64
  • 横は12マス、縦は12マス
  • レイヤーは1個だけ、レイヤー名はstage
  • レイヤーstageにはプレイヤーは衝突する

ここで目指すもの

  • とりあえずtilemapの表示
  • プレイヤーは上下左右に移動、1個の移動で64移動する
  • プレイヤーは移動時にアニメーションする

マップ内にライトを導入してプレイヤー位置をライトで照らす仕組みは次の課題にする

利用するスプライト画像

以下の画像を利用したtilemapとプレイヤーを作成します。1個のタイルのサイズは横64、縦64です

こちらの画像

preloadメソッド

this.load.spritesheetでスプライト画像を読み込みます。frameWidthとframeHeightでタイルのサイズを指定します。マップデータはJSONでエクスポートします。this.load.tilemapTiledJSONで読み込みます

mainScene.preload = function() {
    // タイル画像
    this.load.spritesheet('tile', 'assets/images/sokoban_tilesheet.png', { 
        frameWidth: 64, 
        frameHeight: 64,
    });
    // マップのJSONファイルの読み込み
    this.load.tilemapTiledJSON('map01', 'assets/data/map01.json');
};

createメソッド

マップとプレイヤーのみを作成します

mainScene.create = function() {
    // マップ作製
    this.createMap();
    // プレイヤー作成
    this.createPlayer();
};

tilemapによるマップデータを表示

マップを表示します

mainScene.createMap = function() {
    // 中略
};

JSONデータmap01を読み込みます。

    // JSON形式のマップデータの読み込み Tilemapオブジェクトの作成
    this.map = this.make.tilemap({key: 'map01'});

マップデータに対応したスプライト画像を反映

第1引数はmapデータ内のタイル名、第2引数はpreloadで読み込んだ画像の名前です。戻り値groundTilesは画像情報を持つTilesetオブジェクトです

    // タイル画像をマップデータに反映する Tilesetオブジェクトの作成
    const groundTiles = this.map.addTilesetImage("tile", "tile");

画像の倍率調整のための計算

これはなくてもいいけど、サイズ調整のために。

    // 地面レイヤー作成 DynamicTilemapLayerオブジェクト作成
    const layerWidth = 64 * 1 * 12;
    const layerHeight = 64 * 1 * 12;

レイヤーの作成

マップデータ内のレイヤstageを作成します。第1引数はレイヤー名、第2引数はレイヤーに反映するTilesetオブジェクトです。第3引数はX座標、第4引数はY座標です。レイヤーのサイズは上記で計算したサイズに設定します

    this.groundLayer = this.map.createDynamicLayer('stage', groundTiles, 0, 0);
    this.groundLayer.setDisplaySize(layerWidth, layerHeight);

レイヤーの衝突設定

レイヤーでタイルが設定されていないマスのインデックスは「-1」なので、「-1」のタイル、つまりからのタイルの位置には設定しないようにする。逆に言えば、空のタイル以外の場所はすべて衝突する。

    this.groundLayer.setCollisionByExclusion([-1]);

ゲーム全体のサイズを変更

レイヤーのサイズとゲームシーン全体のサイズを連動します。今回は、マップが大きくないのであまり意味はないです

    // ゲームワールドの幅と高さの設定
    this.physics.world.bounds.width = this.groundLayer.displayWidth;
    this.physics.world.bounds.height = this.groundLayer.displayHeight;
    // カメラの表示サイズの設定をする。マップのサイズがカメラの表示サイズ
    this.cameras.main.setBounds(0, 0, this.physics.world.bounds.width, this.physics.world.bounds.height);

プレイヤー作成

createPlayerでプレイヤーキャラを作成します。プレイヤーの画像は、マップに利用したスプライト画像します。プレイヤーの上下左右アニメーションもスプライト画像を利用します

mainScene.createPlayer = function() {
    // プレイヤー作成
    this.player = this.physics.add.sprite(32, 64 + 32, 'tile');
    // 衝突サイズの調整
    // プレイヤーのサイズ変更
    this.player.setDisplaySize(64,64);
    // プレイヤーの最初の向きは右
    this.player.setFrame(52);
    // プレイヤーの衝突時のバウンス設定
    this.player.setBounce(0);
    // プレイヤーがゲームワールドの外に出ないように衝突させる
    this.player.setCollideWorldBounds(true);
    // プレイヤーが地面レイヤーと衝突する設定
    this.physics.add.collider(this.player, this.groundLayer);
    
    // 移動量
    this.player.dx = 64;
    this.player.dy = 64;
    
    // 下向きのアニメーション
    this.anims.create({
        key: 'down',
        frames: this.anims.generateFrameNumbers('tile', { start: 52, end: 54 }),
        frameRate: 10,
        repeat: -1
    });
    // 上向きのアニメーション
    this.anims.create({
        key: 'up',
        frames: this.anims.generateFrameNumbers('tile', { start: 55, end: 57 }),
        frameRate: 10,
        repeat: -1
    });

    // 左向きのアニメーション
    this.anims.create({
        key: 'left',
        frames: this.anims.generateFrameNumbers('tile', { start: 81, end: 83 }),
        frameRate: 10,
        repeat: -1
    });
    // 右向きのアニメーション
    this.anims.create({
        key: 'right',
        frames: this.anims.generateFrameNumbers('tile', { start: 78, end: 80 }),
        frameRate: 10,
        repeat: -1
    });
    this.cursors = this.input.keyboard.createCursorKeys();
};

updateメソッド

プレイヤーの移動処理を行います。タイルの画像に合わせて移動します。横64、縦64の移動です。移動に合わせてアニメーションを行います

mainScene.update = function() {
    // 中略
};

左方向の移動

this.input.keyboard.checkDownはようするにonkeydownを細かく調整するためのメソッドです。第1引数はキーがプレスダウンされているかを確認するキー、第2引数は待機時間のミリ秒です。ここでは左カーソルキーがプレスダウンされているかどうかを判定します

左カーソルキーであれば、プレイヤーのX座標から64差し引いた一のタイルを取り出します。タイルのindexがー1であれば、空のタイルなので、X座標を移動します。同時に左アニメーションを実行します

    if (this.input.keyboard.checkDown(this.cursors.left, 100)) {
        const tile = this.groundLayer.getTileAtWorldXY(this.player.x - this.player.dx, this.player.y, true);
        if (tile.index === -1) {
            this.player.x -= this.player.dx;
            this.player.anims.play('left', true);
        }

全体の移動判定

上下左右の移動は以下の通りです

mainScene.update = function() {
    if (this.input.keyboard.checkDown(this.cursors.left, 100)) {
        const tile = this.groundLayer.getTileAtWorldXY(this.player.x - this.player.dx, this.player.y, true);
        if (tile.index === -1) {
            this.player.x -= this.player.dx;
            this.player.anims.play('left', true);
        }
    } else if (this.input.keyboard.checkDown(this.cursors.right, 100)) {
        const tile = this.groundLayer.getTileAtWorldXY(this.player.x + this.player.dx, this.player.y, true);
        if (tile.index === -1) {
            this.player.x += this.player.dx;
            this.player.anims.play('right', true);
        }
    } else if (this.input.keyboard.checkDown(this.cursors.up, 100)) {
        const tile = this.groundLayer.getTileAtWorldXY(this.player.x, this.player.y - this.player.dy, true);
        if (tile.index === -1 ) {
            this.player.y -= this.player.dy;
            this.player.anims.play('up', true);
        }
    } else if (this.input.keyboard.checkDown(this.cursors.down, 100)){
        const tile = this.groundLayer.getTileAtWorldXY(this.player.x, this.player.y + this.player.dy, true);
        if (tile.index === -1) {
            this.player.y += this.player.dy;
            this.player.anims.play('down', true);
        }
    } else {
        this.player.anims.stop();
    }
};

実行結果

Tilemapを利用したマップを表示します。プレイヤーはマップ内を移動します。左上の角のみプレイヤーが飛び出すとエラーになります。厳密には、移動する際にタイルがない場合、エラーになるので、移動の判定をするようにしないといけないです

プレイヤーの移動に合わせて、ライトで周辺を照らすこと、ライト以外の場所を暗くすることをやりたいのですが、それは次に目指す。

最終的に実現したコード

https://github.com/hiroshees/phaser-game-sample/blob/main/src/work07.html

Discussion