この記事では、Godot Engine での Isometric (クォータービュー) の基本的な作り方を説明します。
目次
クォータービューの作り方
画像データのダウンロード
今回は公式のサンプルのデータを使用します。
https://godotengine.org/asset-library/asset/112
ここから、Downloadをクリックしてダウンロードします。
念のため現時点(2021.12.18 / Godot 3.4)でのデータもアップロードしておきましたので、これを使うこともできます。
シーンの作成
プロジェクトを作成したら、Node2Dを作成します。
名前を “Dungeon01” にリネームしておきます。

Tilemapノードの作成
次に、Tilemapノードを作成します。

そしてノードの名前を “Floor” に変更しておきます。

そうしたら、このタイルマップのインスペクタの設定をします。
インスペクターから、Modeを “Isometric” に変更します。

これによりタイルマップを、ひし形のクオータービューで扱うことができます。
次に、使用するタイル設定のファイルを作成します。
Tile Setの [空] をクリックして、「新規 Tileset」を選択します。

作成したら “保存” を選んで、”tileset.tres” として保存します。


続けて、”Centered Texture” にチェックを入れてオンにします。これはタイルセットの画像を “中央揃え” で作成するための設定となります。
また、Cell > Size > x の値を “128” に変更します。これはタイル1つのサイズを 128 x 64 で作成する設定です。
クオータービューでは、幅と高さの比率が 2:1 で作成するためこのような値となっています。
さらに “Tile Origin” の値を “Center” とします。これはタイルセットの原点を中心とするためです。

少しややこしいですが、Godotのタイルマップの仕組みは、「タイルマップ」と「タイルセット」の設定がそれぞれ必要となるため、このような操作をしています。
タイルセット編集シーンの作成
これで「タイルマップ」の設定はできました。次に「タイルセット」の設定を行います。
新たにシーンを作成し、シーン(Node2D)を作成します。名前は “TilesetEdit” に変更します。


そして、ダウンロードした素材から “isotiles.png” をファイルシステムに追加します。

次に Spriteノードを追加します。

名前は “Base” に変更します。

タイルセットを “Sprite” で管理する、というのがなかなかユニークな仕組みです。
この Sprite の Texture に “isotiles.png” を登録します。

そして、Region から “Enabled” にチェックを入れます。これは画像の1部分のみを使う設定となります。
そして Rect に適当な値を入れます。これは後で修正するので、何でも良いです。

ファイルシステムから “tileset.tres” をダブルクリックすると、下部分にタイルセットのビューが表示されます。

タイルセットビューから「ツール」をクリックして、「シーンから生成」を選びます。

これは現在開いているシーンからタイルセットを作る処理を行うものです。以下の確認ダイアログが表示されるので「OK」を選びます。

一見変化がないようですが、”isotiles.png” をクリックすると床がタイルセットに登録できていることが確認できます。

タイルセットの登録方法がわかったところで、再びBaseノード(Sprite)をクリックします。

すると下部分が「テクスチャ領域」のタブ名に変わっていることを確認して、「Snapモード」を「自動スライス」に変更します。

するとこのように、切り取り領域が自動で画像の不透明部分になります。

以前に設定した切り取り領域が少し大きいので、床画像をクリックすると領域がぴったり合うようになります。

そうしたら、再び「tileset.tres」をダブルクリックして、タイルセットビューを開き、「シーンから生成」を選んでタイルセットを再構築します。

これでひとまず床タイルのタイルセットが作成できました。
床タイルの配置
“Dungeon01” シーンを表示して、”Floor”ノードを選択すると、床タイルが配置できるようになります。

タイルマップの編集モードは以下のアイコンから変更します。

モードに対するマウス操作の一覧は以下のとおりです。
モード | 左クリック | 右クリック |
---|---|---|
ペイント | タイルを配置する | タイルを削除する |
塗りつぶし | 周囲を同一タイルで塗りつぶす | 周囲にある同一タイルを削除する |
スポイト | 選択しているタイルを取得する | タイルを削除する |
選択 | 複数タイルを選択する | 複数タイル選択を解除する |
では、大まかで構わないので以下のように床タイルを配置しておきます。

壁タイルを実装する
次に壁タイルを実装します。”TilesetEdit” シーンに戻って、”Base”ノードを複製し “Wall” にリネームします。

そして、Transform > Position > x の値を「200」にしておきます。

これはタイルセットの画像の変更をわかりやすくするためです。
そうしたら、下の部分にある「テクスチャ領域」から立方体になっている領域を選択します。

すると、Spriteが壁のスプライトになりました。
再び「tileset.tres」をダブルクリックして、タイルセットビューを開き、「シーンから生成」を選んでタイルセットを再構築します。

壁タイルの配置
そうしたら “Dungeon01″ シーンを表示して、”Floor” ノードを複製して “Walls” にリネームします。

このタイルマップに壁タイルを配置します。
ただ複製した影響で、床タイルがすでに配置されてしまっているので、これをすべて削除します。
削除するには塗りつぶしツールを選んで右クリックで削除できます。

正しく削除できたかどうかは、”Floor”ノードを表示・非表示して確認します。

そうしたら以下の手順で壁タイルを配置します。
- Wallsノードを選択
- Wallタイルを選択
- ペイントモードに変更
- 壁タイルを配置

特に「①Wallsノードの選択」を忘れて、間違って壁タイルを “Floor” ノードに配置してしまう…、というミスはやりがちなので、注意が必要です。
ただ配置すると気がつくと思いますが、壁タイルが床タイルから少し浮いてしまっています。

これを調整します。
床タイルの位置を調整する
“TilesetEditor” シーンに戻って、”Wall” ノードの 「Offset > Offset > y」を「-32」に修正します。

そうしたら、再び「tileset.tres」をダブルクリックして、タイルセットビューを開き、「シーンから生成」を選んでタイルセットを再構築します。

“Dungeon01” シーンを表示して確認します。
すると壁タイルをぴったりに配置できるようになりました(先程の配置は Yが1マスずれていましたが)

ピッタリ配置できるようになりましたので、床の外周を囲むように、壁タイルを配置しておきます。

壁にコリジョンを設定する
壁にコリジョンがないので、コリジョンを設定します。
“TilesetEditor”シーンを開いて “Wall” ノードを選択して、以下のようにノードを追加します。

壁は動かないので、”StaticBody2D” を作成し、その下に “CollisionPolygon” を追加します。
“CollisionPolygonのインスペクターから「Polygon > PoolVector2Array」をクリックして、
- サイズ: 4
- 0: (x, y) = (-64, 0)
- 1: (x, y) = (0, 32)
- 2: (x, y) = (64, 0)
- 3: (x, y) = (0, -32)

という設定を行うと、ひし形のコリジョンが作成されます。
そうしたら、再び「tileset.tres」をダブルクリックして、タイルセットビューを開き、「シーンから生成」を選んでタイルセットを再構築します。

コリジョンが正しく設定できたかどうかを確認します。
“Dungeon01″ シーンを表示して、Wallsノードを選択し、”Show Collision” にチェックを入れて、コリジョンが表示されれば設定が正しくできています。

プレイヤーを作成する
最後にプレイヤーを作成します。
シーンを新規作成し、「KinematicBody2D」ノードとします。

ノード名は「Troll」に変更しておきます。

トロル画像を追加
ダウンロードした画像素材から “troll.png” を追加します。

続けて “troll.png” をキャンバスにドラッグ&ドロップして、Spriteを作成します。

トロルさんの画像をタイルマップの基準座標に合わせたいので、「Transform > Position」を (x, y) = (-4, -28) とします。

コリジョンの作成
続けてコリジョンの作成です。「CollisionShape2D」を追加します。

シェイプの形状は「円(CircleShape2D)」として、半径(Radius)を 16px にします。

スクリプトの追加
ルートノードの「Troll」を選択して、スクリプトをアタッチします。

スクリプトは以下のように記述します。
extends KinematicBody2D
# 移動速度
const MOTION_SPEED = 20000
func _process(delta: float) -> void:
# 移動速度の計算
var motion = Vector2()
# 移動方向を計算する
motion.x = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
motion.y = Input.get_action_strength("ui_down") - Input.get_action_strength("ui_up")
# クオータービューの高さは半分
motion.y /= 2
motion = motion.normalized() * MOTION_SPEED * delta
# 移動処理
move_and_slide(motion)
特に複雑なことをしていませんが、クオータービューの縦横の比率が「2:1」なので、Yの移動量が半分になっていることに注意します。
ひとまず Trollシーンを単体で動かして (WindowsはF6、macOSはCmd+R) 動作を確認します。

Trollシーンを保存し、”Dungeon01″ シーンを表示します。
そして、Troll.tscn をドラッグ&ドロップして中央あたりに配置します。

では実行して動きを確認してみます。

一見うまく動いているように見えますが、下側に移動したときに描画順がおかしくなっています。これはトロルさんが常に最前面に描画されてしまっているためです。
正しい描画順にするには Y座標での描画ソートを有効にします。
まずは Troll ノードは、Wallsノードの下にぶら下げます。

次に、WallノードのインスペクターのCellから「Y Sort」にチェックを入れます。

これで Y座標の描画ソートが有効になります。
では実行して正しい描画順で表示されるのを確認します。

この描画順の制御ですが、これまで説明した手順を1つでも設定ミスすると正常に表示されない可能性があるので、正常に動作しない場合は、この記事の最後の完成プロジェクトと比較して設定を確認してみてください。
追従カメラの設定
最後にプレイヤーに追従するカメラを設定して、この記事の締めくくりとします。
Trollシーンを開いて、”Camera2D” を追加します。

そしてインスペクターから「Current」にチェックを入れます。

では実行してトロルさんの動きにカメラが追従するのを確認します。

完成プロジェクト
今回作成したプロジェクトは以下からダウンロードできます
クォータービューのライティングについて
クォータービューでライト(影)を使ってみようと色々と調べてみたのですが、クォータービューのライティングは今の所あまり良い感じには表示できないようです。そのため、どうしても影を表示したい場合は、3Dで実装してシャドウマッピングを使ったほうが良さそうです(それはそれで、2Dに強いGodotを使う意味がなくなりそうですが)。
以下、調べたことを備忘録として残しておきます。
おそらくですが、タイルマップで LightOccluder2D を配置する場合は、ピボットが原点に固定されるので原点を中心に配置する必要があります(コリジョンはピボットが変更可能)

セルフシャドウを消すのが難しい
クォータービューの場合には、通常はコリジョンが足元部分だけなので、そのサイズに合わせて LightOccluder2D を配置することになります。
しかし、これにより作られるセルフシャドウが自身と重なってしまい、これを消すのがかなり難しそうです。

Cull Modeを頂点の配置順に合わせることでセルフシャドウを消すことができる
1つの解決方法としては、LightOccluder2Dの頂点インデックス順に合わせてCull Modeを「ClockWise(時計回り)」または「CounterClockWise(反時計回り)」にすることで、自身への影を消すことができます。

ただカリングされるのは自身の LightOccluder2D のみなので、立体感のない2Dマップであれば問題ないですが、クォータービューではもう少しカリングをうまく処理しないとおかしな見た目となってしまいます。

Light Maskを無効にする
もう1つの方法としては、Wallsタイルマップの影のLight Mask を無効にします。

これでそれなりの見た目とはなるのですが、2点問題があります。
1つは キャラクターは Y Sortを有効にするために Wallsノードにぶら下げるのですが、それによりキャラクターが Light Maskの影響を受けるためか、Wallタイルとの重なりができるとその部分だけ暗くなってしまいます。
もう1つの問題は Wallタイルが影の影響を全く受けなくなることです。

LightOccluder2D を壁の形に合わせればできるかな…? と思ったのですが、これもダメでした。

エディタ上で見ると「これはやったか…?」となったのですが、

実行してみると、部分的におかしな影が出てしまいますし、LightOccluder2D に Light2D がめり込んでしまうと、ライトが消えてしまいます。

ライト(影)を適用するため、こちらのスレッドを参考にしました。
このスレッドでは他にも、LightOccluder2D の描画順を下げたり、ブレンドモードを変更する、といった方法が提示されていましたが、キレイに影を表示するには現状3Dで描画するしかなさそうな印象です。
ひとまず2番目の方法(Light Maskを無効にする) 方法で実装したプロジェクトを添付しておきます。