[Godot 4.2] ピクセルパーフェクト(ドット絵)と高解像度のUIを同じ画面に出す(修正版)

:::note info
これは[前の記事](https://qiita.com/aka3/items/478923a4d6bdd14da786)の修正版ですが、必要なことは(一部コピペしつつ)この記事にすべて書いてあるため、前の記事を読む必要はありません。
一応、前の記事は失敗例として一部加筆して残してあります。
:::

# 今回やること
ドット絵で作られたキャラクターや背景などをピクセルパーフェクトで表示しつつ、高解像度のUIを前面に表示します。

公式ドキュメントの[複数の解像度 — Godot Engine (4.x)の日本語のドキュメント](https://docs.godotengine.org/ja/4.x/tutorials/rendering/multiple_resolutions.html)を読むと、一般的なシナリオの1つとして「ゲーム全体を低解像度で作ってピクセルパーフェクトで表示する方法」が書かれていますが、今回はそれに加えて高解像度のUIも表示します。

つまるところ、このような画面です。
![1600x900.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699119/b3dd110e-fed4-db4b-f178-b27bf894337b.png)

<details><summary>他の解像度のスクリーンショット</summary>

`320x180`
![320x180.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699119/7a5f2023-c75b-9afe-be75-9d6c301c2b22.png)

`1920x1080`
![1920x1080.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699119/be29ba22-7bb3-fbcb-f30f-c65a4b2c1427.png)
</details>

背景やキャラクター(の素体)のドット絵はピクセルパーフェクトで表示されていますが、画面中央と左上のUI要素は高解像度で表示されています(原寸表示で厳密な確認ができます)。

# 方法
主に以下の3点を行います。
* プロジェクト設定にて、ビューポートの解像度とストレッチモードの指定をする
* 解像度を操作するボタンなどを用意する
* SubViewportContainer を用いてドット絵を扱うシーンを表示させる

単純に画面要素のすべてがドット絵であるかそれと同等の低解像度を持つようなゲームを作る場合、[公式ドキュメントのシナリオ例](https://docs.godotengine.org/ja/4.x/tutorials/rendering/multiple_resolutions.html#desktop-game)にあるようなやり方で問題ありません。
しかし今回は高解像度のUIも表示するため、プロジェクト設定の解像度は高い値にします。
そして、ゲーム画面の解像度がピクセルパーフェクトの条件を満たすようにスクリプト等で制御します。

## プロジェクト設定について
プロジェクト設定で変更するのは以下の3点です。
いずれも「一般」-「表示」-「ウィンドウ」内にあるプロパティです。

* ビューポートの幅、高さを `1920` と `1080` にする[^common_resolution]
* 「サイズを変更可能」のチェックを外し、ウィンドウサイズの変更を禁止する
* 「ストレッチ」-「モード」を `canvas_items` にする

[^common_resolution]: `1920x1080` は公式ドキュメントで「最も広く使われている解像度」として紹介されていました。

ウィンドウサイズ変更を禁止していますが、これはそもそも、ピクセルパーフェクトを実現するためにはドット絵を整数倍スケーリングかつアスペクト比固定で表示する必要があるためです。
当然ながら解像度変更は必要な機能なので、後で解像度を選択するための OptionButton を追加してスクリプトから解像度変更を行う機能を追加します。

ストレッチモードを `viewport` にした場合、高解像度の表示が若干汚くなります。
各 CanvasItem を `1920x1080` の解像度に合わせてアンチエイリアスなどをかけてビューポートに描画した後、その画面を再び拡大・縮小するためです(もっと詳しい解説が公式ドキュメントにあります)。

<details><summary>プロジェクト設定のスクリーンショット</summary>

![プロジェクト設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699119/c3644bea-e71e-a9ec-f88d-b55458001ef5.png)
</details>

## 解像度をスクリプトから変更する
[`Window.size`](https://docs.godotengine.org/ja/4.x/classes/class_window.html#class-window-property-size) に解像度を表す [`Vector2i`](https://docs.godotengine.org/ja/4.x/classes/class_vector2i.html#class-vector2i) を代入します。

```gdscript:change_resolution.gd
# ウィンドウサイズを 320x180 に変更する
get_window().size = Vector2i(320, 180)
```
[`Node.get_window()`](https://docs.godotengine.org/ja/4.x/classes/class_node.html#class-node-method-get-window) を使えばウィンドウのインスタンスを取得できます。

あとは [OptionButton](https://docs.godotengine.org/ja/4.x/classes/class_optionbutton.html) などで解像度を指定できるようにすれば、実行中に解像度を変更できるようになります。

選択肢として用意する解像度は、ドット絵をピクセルパーフェクトで表示できる条件を満たすもののみにすることを忘れないでください。

## ドット絵を SubViewport を使って表示する
ドット絵は一旦小さな画面に描画して、その画面を整数倍に拡大表示することで高解像度の要素と共存させます。
そのための「小さな画面」として [SubViewport](https://docs.godotengine.org/ja/4.x/classes/class_subviewport.html#class-subviewport) を使います。
そしてその画面を拡大表示するために [SubViewportContainer](https://docs.godotengine.org/ja/4.x/classes/class_subviewportcontainer.html#class-subviewportcontainer) を使います。

「小さな画面」の解像度は、ドット絵を作るときにイメージしていた画面サイズを使います。
ただし、この解像度を整数倍するとプロジェクト設定の解像度になるようにする必要があります。
今回は小さなドット絵なので、 `320x180` です。6倍すると `1920x1080` になります。

### シーン構成
まず、シーン内に SubViewportContainer を追加します。
そして SubViewportContainer の子に SubViewport を追加します。
SubViewport の子がすべて「小さな画面」に描画され、出来上がった画面が SubViewportContainer 上に表示されます。
ただし、SubViewport の子はシーンビューで動かしたり拡縮・回転したりすることができません。
別のシーンとして作ってからインスタンス化すると作りやすいでしょう。
最終的にはこのようなシーンツリーが出来上がります。

![scene_tree.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699119/67933553-7649-ff14-a605-6bb01c6bfbfc.png)

### SubViewport の設定
ドット絵をピクセルパーフェクトで表示するには前述の条件に加え、ドット絵の表示座標がサブピクセル(ピクセルの中間地点)上に来ないようにする必要があります。
そのために [`Snap 2D Transform To Pixel`](https://docs.godotengine.org/ja/4.x/classes/class_viewport.html#class-viewport-property-snap-2d-transforms-to-pixel) と [`Snap 2D Transform To Pixel`](https://docs.godotengine.org/ja/4.x/classes/class_viewport.html#class-viewport-property-snap-2d-vertices-to-pixel) をオンにします(実際にはインスペクタ上のチェックボックスを操作します)。

さらに、この Viewport 上でのデフォルトのフィルタ処理をニアレストネイバー法にするために、 [`Default Texture Filter`](https://docs.godotengine.org/ja/4.x/classes/class_viewport.html#class-viewport-property-canvas-item-default-texture-filter) を `Nearest` にします。

![sub_viewport.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699119/595f926e-d8ff-b5e3-7ac6-a0671736d4a0.png)


:::note info
`Snap 2D ~` の2つのプロパティ、名前がなんとなくそれっぽいですが説明が一切書いてありません。
そのため詳細が不明ですが、とりあえずそれぞれの片方だけで試してみたところドット絵が崩れました。
![崩れたドット絵.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699119/1b6f779e-e4b5-e357-fca1-75f6669dd0ae.png)
:::

### SubViewportContainer の設定
こちらではドット絵を鮮明に拡大表示するための設定をします。

まずは `Size` をプロジェクト設定の解像度と同じ値にします。
画面いっぱいに SubViewportContainer が広がります。

次に `Stretch` をオンにします。そうすると子の SubViewport のサイズが自動で設定されるようになります。
`プロジェクト設定の解像度 / ドット絵を表示する解像度` で求められる値をその下の `Stretch Shrink` に設定します。
元々整数倍になるように解像度を決めていたはずなので、ここにも整数が入るはずです。

最後に、 `Filter` を `Nearest` に変更して終わりです。

![sub_viewport_container.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699119/d8e95791-34eb-5cad-6c5a-bf05ed60e8f9.png)


# 完成
これで、文字を含む高解像度の要素を鮮明に表示しつつ、ドット絵をピクセルパーフェクトで表示することができます。
SubViewportContainer を使っているため、ドット絵のクリック判定も行うことができます。

# 参考文献
* [複数の解像度 — Godot Engine (4.x)の日本語のドキュメント](https://docs.godotengine.org/ja/4.x/tutorials/rendering/multiple_resolutions.html)
* [How to change window size using GDscript - Archive - Godot Forum](https://forum.godotengine.org/t/how-to-change-window-size-using-gdscript/2861)
* [Using Viewports — Godot Engine (4.x)の日本語のドキュメント](https://docs.godotengine.org/ja/4.x/tutorials/rendering/viewports.html)