Phaser3+typescriptでSRPGを開発してみる #005 Tiledの利用
前回、2D MAPを描いたのですが、背景の黒が出てきたり困ってたところ、インターネットで情報を集めると、Tiledというソフトウェアが良さそうで、かつMacも使えそう!ということなので、今日は使ってみたいと思います。
マップ作成(2) Tiledの活用
筆者の試行環境
たまに、書いておこうと思います。
macOS X10.14 (MacBookPro)
Tiledのインストール
Tiledは下記からダウンロードできます。 フリーで使えますが、寄付を募っておられます。
Macの場合は解凍すると、Tiled.appが出てくるので、任意のフォルダで実行するだけです。
Tiledの使い方
(0) 日本語モード
なんと日本語モードがあります。英語に触れる必要がないと信じて内資の会社に就職した私には、大変ありがたいです。 設定方法は、下図の通りです。
(1) タイルセット読込
まずはタイルセットを読み込みます。アイコンが羅列されているものですね。
メニューバーの【ファイル】→【新規】→【新しいタイルセット】を起動して、ファイルパスを【参照】ボタンを押下してファイルを選択します。 タイルセットの【名前】はファイル名に応じて、自動的に入ってくれます。 こんな感じで読み込まれます。
尚、ずらして画像を読みたい場合、新規で読み込むときにオフセットを調整すれば良いようです。読み込み後でも、画像ファイルの再指定ボタンを押下すると、新規読込時のポップアップが起動するので、変更可能です。
(2) マップの作成
続いて、同じく上部メニューバーの【ファイル】→【新規】→【新しいマップ】をクリックすると、マップが作れます。
白紙の画面が出てきます。 固定にしてしまうと幅・高さはグレーになっていて変えられないので、新規作成時に注意が必要そうですね。無限で作った方が良いかもです。
(3) 描画
あとは、ペタペタ貼り付けるだけです。
ちなみにレイヤーが指定できます!
まずは草原で塗りつぶしたあと、レイヤーを新規で追加(Layer2)。 そして、下を透過するようなチップを選ぶと、ご覧の通り、透過できます!
かなり便利です。
Phaserへの取込
ファイル構成
タイルセットの画像ファイルと、マップのJSONファイルを下記に配置して試行してみます。
<root> |- index.html |- images | |- map | |- tileset_demo.png |- props | |- game | |- map_demo.json |- src | |- index.ts | |- scene | |- Scenes.ts | |- GameScene.ts |- dist |- index.js
JSONを自動生成
さて、問題はこれをどのようにPhaserへ取り込むのか?です。
まず互換性のあるファイルとして出力するには?ですが、やはりJSONでの書き出しが必要ですよね。
日頃からJSONで管理している人はJSONその物を直接、Phasre3で読み込むことができます。もし、tsxで管理している場合には、メニューバーの【ファイル】ー【名前をつけてエクスポート】を選択して、ファイル種別として【JSONマップファイル (*.json)】を指定して出力してみてください。
但し、Phaser3では、Externalオプションに対応しておらず、Embed TilelayerをオフにしてJSON出力しないと、
External tilesets unsupported. Use Embed Tileset and re-export Uncaught TypeError: Cannot read property '2' of undefined AssignTileProperties @ 【ルートディレクトリ】/node_modules/phaser/dist/phaser.js:133637:48
というエラーが出ます。多少、わかりにくいアイコンなのですが、タイルセットのWindow下部の左から2番目のアイコンをオフにしてください。 すると、いきなりタブが2つになりますが、気にしないで大丈夫です。
ファイル保存形式
ちなみに、Tiledでは、tsxという拡張子がデフォルトなのですが、これは単にXMLというだけです。 ですが、この拡張子、React等ではプログラムとして認識されてしまい、トランスパイルに影響が出ることがあります。
ということでJSONでデータ保存する方が楽そうです。
自動生成されたjson
自動生成されたJSONは下記の通りです。
map_demo.json
{ "compressionlevel":-1, "height":5, "infinite":false, "layers":[ { "data":[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], "height":5, "id":1, "name":"Layer1", "opacity":1, "type":"tilelayer", "visible":true, "width":10, "x":0, "y":0 }, { "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 27, 28, 0, 0, 29, 14, 30, 0, 0, 0, 43, 44, 0, 0, 45, 14, 46, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "height":5, "id":2, "name":"Layer2", "opacity":1, "type":"tilelayer", "visible":true, "width":10, "x":0, "y":0 }], "nextlayerid":3, "nextobjectid":1, "orientation":"orthogonal", "renderorder":"right-down", "tiledversion":"1.7.0", "tileheight":32, "tilesets":[ { "columns":16, "firstgid":1, "image":"..\/..\/images\/map\/tileset_demo.png", "imageheight":384, "imagewidth":512, "margin":0, "name":"tileset_demo", "spacing":0, "tilecount":192, "tileheight":32, "tilewidth":32 }], "tilewidth":32, "type":"map", "version":"1.6", "width":10 }
長くて見づらくてすみませんが、この通り、mapのJSONファイルが出力されました。
jsonファイルの解析
ポイントは下記の通りです。
Layer1とLayer2が見つけられるでしょうか。
tilesets
の中身が展開されているでしょうか?(Externalモードにしていると、ここがtsxファイルを参照するだけの記述になっているはずです)
その他、いくつか気になることがあったので、触れさせていただきます。
data
前回記事(#004)では、左上を0としたのですが、今回は0
は空白(タイルを指定していないセル)で、1から始まっており、前回3
だったところが4
になってそうです。
これは、jsonファイルの下部にあるtileset.firstgrid
とリンクしてそうです。
tilesets
そのタイルセット(複数形)ですが、ここでタイルセットを複数定義できます。
タイルセットの数だけ、上部メニューの【ファイル】ー【新規】ー【新しいタイルセット】を選択して読み込み、全てのタイルセットの編集画面で、タイルセットのWindow下部の左から2番目のアイコン を押下して、埋め込みタイルセットをオフにしてください。
typescript側の書き方
ようやくtypescriptでの書き方ということで、このJSONファイルを読み込んでみます。
GameScene.ts
export default class GameScene extends Phaser.Scene { // タイルセットの1セルあたりの解像度(px) public static CELL_PX = { WIDTH : 32, HEIGHT : 32 }; // マップの開始位置(Offset) 左上が(0,0) private static MAP_POSITION = { X : 20, Y : 40 }; // タイルセットの画像ファイルのキー // タイルセット名(map_demo.jsonの中のtilesets.name属性を指定) private static TILESET_KEY_NAME = "tileset_demo" // Phaser3の中のタイルセットを指すキーフレーズ(任意の文言) private static TILESET_KEY_PLAIN = "PLAIN_MAP"; // マップJSONファイルのキー // Phaser3の中のマップJSONファイルを指すキーフレーズ(任意の文言) private static MAP_JSON_FILE_KEY = "MAP_JSON_FILE_KEY"; preload() { // tileset画像ファイルをロード(キー・ファイルパス) this.load.image(GameScene.TILESET_KEY_PLAIN, "/images/map/tileset_demo.png"); // mapファイルのロード this.load.tilemapTiledJSON(GameScene.MAP_JSON_FILE_KEY, "/props/game/map_demo.json"); } create() { // タイルマップの作成 var tilemap:Phaser.Tilemaps.Tilemap = this.add.tilemap(GameScene.MAP_JSON_FILE_KEY); // タイルセットをリンク付け let planeTiles0:Phaser.Tilemaps.Tileset = tilemap.addTilesetImage( GameScene.TILESET_KEY_NAME, // 第1引数は マップのjsonファイル中のtilesets.name属性を指定 GameScene.TILESET_KEY_PLAIN); // 第2引数はPhaser3内のキーワード // レイヤーを作成 let mapGroundLayer0:Phaser.Tilemaps.TilemapLayer = tilemap.createLayer( "Layer1", // Tiledで設定したレイヤー名 planeTiles0, // 定義したタイルセット 20,40 // 第3引数・第4引数は、左上の座標を(0,0)とした時の配置位置(x,y) ); let mapGroundLayer1:Phaser.Tilemaps.TilemapLayer = tilemap.createLayer( "Layer2", planeTiles0, 20,40); }; }
今回のポイントです。
タイルセットと画像のリンクづけは、
addTilesetImage
メソッドを使用するaddTilesetImage
の第1引数は マップのjsonファイル中のtilesets[].name
属性、すなわちTiledで設定した名前を指定するレイヤーはレイヤーの数だけ、
createLayer
を呼ぶ。その時の第1引数(レイヤーID)はマップのJSONファイル中のlayers[].name
属性、すなわちTiledで設定したレイヤー名を指定する
キーがJSONのname
属性と合致している必要があるので注意です。合っていないとInvalid Tilemap Layer ID: 【レイヤーID】
【ルートDir】
が出ます。
尚、今回は読み込むtilesetを一つにしていますが、複数のtilesetを読み込ませたい場合もPhaser3で実装可能で、createLayer
メソッドの第2引数を配列で渡します。
let seaTiles:Phaser.Tilemaps.Tileset = tilemap.addTilesetImage("WorldMap-A1","TILESET_SEA"); let planeTiles:Phaser.Tilemaps.Tileset = tilemap.addTilesetImage("WorldMap-A2", "TILESET_PLAIN"); let layerBackground:Phaser.Tilemaps.TilemapLayer = tilemap.createLayer( "Background", [seaTiles, planeTiles], // Tilesetを順番に読込(配列形式で複数指定) 20, 40); // Offsetを指定
完成
実行すると、無事マップが表示されました。
まとめ
いかがでしたか? 捕まりそうなポイントもかなり網羅したつもりです。 これで、#004での疑問も解決し、ようやく2DのSRPGのゲーム画面ぽくなってきました。
Tiledがあるとかなり楽になることが伝われば幸いです。
次回は、ユニットを表示します。
過去の日記
タイトル | 記載内容 | |
---|---|---|
#001 | プロローグ | Phaser3とは |
#002 | 環境設定 | インストール・ビルド環境設定 |
#003 | はじめてのPhaser3 | 初回稼働・キャンバス表示 |
#004 | マップ作成 | タイルセットの読込、画面手動作成、Sceneクラスのライフサイクル |