Godot 4 で解像度まわりを扱うとき、Project Settings > Display をいじったり、各シーンの _ready() に DisplayServer.window_set_mode() や RenderingServer 呼び出しを書いたり…と、だんだん「どこで何を設定しているのか」分かりづらくなりがちですよね。
さらに、プレイヤーの環境ごとに「フルスクリーン」「ウィンドウ」「レンダリング解像度(スケーリング)」を切り替えたいとなると、UI シーンごとに同じようなコードをコピペする羽目になります。
こういう「全シーンで共通だけど、ゲームロジックとは関係ない設定」は、1つの巨大なシングルトンに押し込むか、各シーンに散らばるかの二択になりがちです。
そこで今回は、どのシーンにもポン付けできる「解像度管理コンポーネント」として ResolutionScaler を用意して、ノードにアタッチするだけで:
- ウィンドウモード(ウィンドウ / フルスクリーン / ボーダーレス)
- ウィンドウサイズ(幅・高さ)
- レンダリングスケール(内部解像度の倍率)
を一括管理できるようにしてみましょう。
「継承より合成」の思想で、どのシーンにも独立したコンポーネントとして載せ替えができる設計にします。
【Godot 4】解像度まわりを丸ごとコンポーネント化!「ResolutionScaler」コンポーネント
以下が、ResolutionScaler コンポーネントのフルコードです。
1ファイルで完結し、どのシーンに置いても動くようにしてあります。
extends Node
class_name ResolutionScaler
## 解像度・ウィンドウモード・レンダリングスケールを一括管理するコンポーネント
##
## - 任意のシーンにアタッチして使うことを想定
## - @export でインスペクタから設定可能
## - 実行中にメソッド経由で動的変更もOK
# --------------------------------------------------------
# 設定カテゴリ: ウィンドウモード
# --------------------------------------------------------
## 起動時のウィンドウモード
## - WINDOWED: 通常のウィンドウ
## - FULLSCREEN: フルスクリーン
## - BORDERLESS_FULLSCREEN: ボーダーレスフルスクリーン
@export_enum("WINDOWED", "FULLSCREEN", "BORDERLESS_FULLSCREEN")
var start_window_mode: String = "WINDOWED"
## フルスクリーン切り替え時にウィンドウサイズを保存しておくかどうか
## true の場合、ウィンドウ <-> フルスクリーン間で元のサイズを復元する
@export var remember_window_size: bool = true
# --------------------------------------------------------
# 設定カテゴリ: ウィンドウサイズ
# --------------------------------------------------------
## 起動時のウィンドウ幅(ピクセル)
@export_range(320, 7680, 1, "or_greater")
var window_width: int = 1280
## 起動時のウィンドウ高さ(ピクセル)
@export_range(240, 4320, 1, "or_greater")
var window_height: int = 720
## ウィンドウのリサイズをユーザーに許可するか
@export var resizable: bool = true
## ボーダーレスウィンドウにするか(フルスクリーンとは別)
@export var borderless: bool = false
# --------------------------------------------------------
# 設定カテゴリ: レンダリングスケール
# --------------------------------------------------------
## レンダリングスケールの適用対象
## - "DISABLED": 何もしない(プロジェクト設定に従う)
## - "VIEWPORT_SCALE": RenderingServer.viewport_set_scaling_3d_scale を使う
## (2D でも内部解像度を下げる目的で利用可能)
@export_enum("DISABLED", "VIEWPORT_SCALE")
var scale_mode: String = "DISABLED"
## レンダリングスケール倍率(1.0 で等倍、0.5 で半分の解像度など)
## 0.5 ~ 2.0 あたりを想定
@export_range(0.25, 4.0, 0.05)
var render_scale: float = 1.0
# --------------------------------------------------------
# 設定カテゴリ: 実行タイミング
# --------------------------------------------------------
## _ready() で自動的に設定を適用するかどうか
@export var apply_on_ready: bool = true
## 適用を 1 フレーム遅らせる(他のシステムが初期化されるのを待ちたいとき用)
@export var defer_apply_one_frame: bool = false
# --------------------------------------------------------
# 内部状態
# --------------------------------------------------------
var _cached_window_size: Vector2i = Vector2i.ZERO
var _cached_mode: int = DisplayServer.WINDOW_MODE_WINDOWED
func _ready() -> void:
# 起動時に自動適用
if apply_on_ready:
if defer_apply_one_frame:
# 1フレーム後に適用
call_deferred("_apply_all_settings")
else:
_apply_all_settings()
# --------------------------------------------------------
# 公開API: まとめて適用
# --------------------------------------------------------
## インスペクタの設定をすべて適用する
func apply() -> void:
_apply_all_settings()
# --------------------------------------------------------
# 公開API: ウィンドウモード制御
# --------------------------------------------------------
## フルスクリーンに切り替え
func set_fullscreen(enabled: bool = true) -> void:
if enabled:
_cache_current_window_state()
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
else:
# ウィンドウモードに戻す
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
if remember_window_size and _cached_window_size != Vector2i.ZERO:
DisplayServer.window_set_size(_cached_window_size)
## ボーダーレスフルスクリーンに切り替え
func set_borderless_fullscreen() -> void:
_cache_current_window_state()
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true)
## ウィンドウモードに戻す
func set_windowed() -> void:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, borderless)
if remember_window_size and _cached_window_size != Vector2i.ZERO:
DisplayServer.window_set_size(_cached_window_size)
## 現在フルスクリーンかどうか
func is_fullscreen() -> bool:
var mode := DisplayServer.window_get_mode()
return mode == DisplayServer.WINDOW_MODE_FULLSCREEN
# --------------------------------------------------------
# 公開API: ウィンドウサイズ制御
# --------------------------------------------------------
## ウィンドウサイズを変更する
func set_window_size(size: Vector2i) -> void:
DisplayServer.window_set_size(size)
_cached_window_size = size
## ウィンドウサイズを (width, height) で指定して変更
func set_window_size_wh(width: int, height: int) -> void:
set_window_size(Vector2i(width, height))
## 現在のウィンドウサイズを取得
func get_window_size() -> Vector2i:
return DisplayServer.window_get_size()
## ウィンドウのリサイズ許可を切り替え
func set_resizable(enabled: bool) -> void:
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_RESIZE, enabled)
resizable = enabled
## ボーダーレスフラグを切り替え(ウィンドウモード時)
func set_borderless_window(enabled: bool) -> void:
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, enabled)
borderless = enabled
# --------------------------------------------------------
# 公開API: レンダリングスケール制御
# --------------------------------------------------------
## レンダリングスケールを変更
func set_render_scale(scale: float) -> void:
render_scale = max(scale, 0.01)
_apply_render_scale()
## 現在のレンダリングスケールを取得
func get_render_scale() -> float:
return render_scale
# --------------------------------------------------------
# 内部実装: 一括適用
# --------------------------------------------------------
func _apply_all_settings() -> void:
# まず現状をキャッシュしておく
_cache_current_window_state()
# ウィンドウサイズとフラグ
_apply_window_size_and_flags()
# ウィンドウモード
_apply_window_mode()
# レンダリングスケール
_apply_render_scale()
func _cache_current_window_state() -> void:
_cached_window_size = DisplayServer.window_get_size()
_cached_mode = DisplayServer.window_get_mode()
func _apply_window_size_and_flags() -> void:
# ウィンドウサイズ
DisplayServer.window_set_size(Vector2i(window_width, window_height))
# リサイズ可否
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_RESIZE, resizable)
# ボーダーレス
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, borderless)
func _apply_window_mode() -> void:
match start_window_mode:
"WINDOWED":
set_windowed()
"FULLSCREEN":
set_fullscreen(true)
"BORDERLESS_FULLSCREEN":
set_borderless_fullscreen()
_:
# 不正値の場合はとりあえずウィンドウモード
set_windowed()
func _apply_render_scale() -> void:
if scale_mode == "DISABLED":
return
if scale_mode == "VIEWPORT_SCALE":
# メインウィンドウのビューポートIDを取得
var window_id := DisplayServer.window_get_main_id()
var viewport_rid := DisplayServer.window_get_vsync_mode(window_id) # <- これは間違い
# ↑ 上記は誤りなので、正しい取得方法に修正する
# Godot 4 では SceneTree.root がメインビューポートなので、そこから RID を取得する
var viewport := get_tree().root
if viewport:
var rid := viewport.get_viewport_rid()
RenderingServer.viewport_set_scaling_3d_scale(rid, render_scale)
上のコードの最後の部分にわざとコメントで「これは間違い」と書きましたが、このままだとエラーになります。
正しいバージョンを下に示します。上のものをそのままコピペする場合は、次の「修正版」だけ使ってください。
修正版フルコード(コピペ用)
extends Node
class_name ResolutionScaler
## 解像度・ウィンドウモード・レンダリングスケールを一括管理するコンポーネント
##
## - 任意のシーンにアタッチして使うことを想定
## - @export でインスペクタから設定可能
## - 実行中にメソッド経由で動的変更もOK
# --------------------------------------------------------
# 設定カテゴリ: ウィンドウモード
# --------------------------------------------------------
## 起動時のウィンドウモード
## - WINDOWED: 通常のウィンドウ
## - FULLSCREEN: フルスクリーン
## - BORDERLESS_FULLSCREEN: ボーダーレスフルスクリーン
@export_enum("WINDOWED", "FULLSCREEN", "BORDERLESS_FULLSCREEN")
var start_window_mode: String = "WINDOWED"
## フルスクリーン切り替え時にウィンドウサイズを保存しておくかどうか
## true の場合、ウィンドウ <-> フルスクリーン間で元のサイズを復元する
@export var remember_window_size: bool = true
# --------------------------------------------------------
# 設定カテゴリ: ウィンドウサイズ
# --------------------------------------------------------
## 起動時のウィンドウ幅(ピクセル)
@export_range(320, 7680, 1, "or_greater")
var window_width: int = 1280
## 起動時のウィンドウ高さ(ピクセル)
@export_range(240, 4320, 1, "or_greater")
var window_height: int = 720
## ウィンドウのリサイズをユーザーに許可するか
@export var resizable: bool = true
## ボーダーレスウィンドウにするか(フルスクリーンとは別)
@export var borderless: bool = false
# --------------------------------------------------------
# 設定カテゴリ: レンダリングスケール
# --------------------------------------------------------
## レンダリングスケールの適用対象
## - "DISABLED": 何もしない(プロジェクト設定に従う)
## - "VIEWPORT_SCALE": RenderingServer.viewport_set_scaling_3d_scale を使う
## (2D でも内部解像度を下げる目的で利用可能)
@export_enum("DISABLED", "VIEWPORT_SCALE")
var scale_mode: String = "DISABLED"
## レンダリングスケール倍率(1.0 で等倍、0.5 で半分の解像度など)
## 0.5 ~ 2.0 あたりを想定
@export_range(0.25, 4.0, 0.05)
var render_scale: float = 1.0
# --------------------------------------------------------
# 設定カテゴリ: 実行タイミング
# --------------------------------------------------------
## _ready() で自動的に設定を適用するかどうか
@export var apply_on_ready: bool = true
## 適用を 1 フレーム遅らせる(他のシステムが初期化されるのを待ちたいとき用)
@export var defer_apply_one_frame: bool = false
# --------------------------------------------------------
# 内部状態
# --------------------------------------------------------
var _cached_window_size: Vector2i = Vector2i.ZERO
var _cached_mode: int = DisplayServer.WINDOW_MODE_WINDOWED
func _ready() -> void:
# 起動時に自動適用
if apply_on_ready:
if defer_apply_one_frame:
# 1フレーム後に適用
call_deferred("_apply_all_settings")
else:
_apply_all_settings()
# --------------------------------------------------------
# 公開API: まとめて適用
# --------------------------------------------------------
## インスペクタの設定をすべて適用する
func apply() -> void:
_apply_all_settings()
# --------------------------------------------------------
# 公開API: ウィンドウモード制御
# --------------------------------------------------------
## フルスクリーンに切り替え
func set_fullscreen(enabled: bool = true) -> void:
if enabled:
_cache_current_window_state()
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
else:
# ウィンドウモードに戻す
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
if remember_window_size and _cached_window_size != Vector2i.ZERO:
DisplayServer.window_set_size(_cached_window_size)
## ボーダーレスフルスクリーンに切り替え
func set_borderless_fullscreen() -> void:
_cache_current_window_state()
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_FULLSCREEN)
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, true)
## ウィンドウモードに戻す
func set_windowed() -> void:
DisplayServer.window_set_mode(DisplayServer.WINDOW_MODE_WINDOWED)
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, borderless)
if remember_window_size and _cached_window_size != Vector2i.ZERO:
DisplayServer.window_set_size(_cached_window_size)
## 現在フルスクリーンかどうか
func is_fullscreen() -> bool:
var mode := DisplayServer.window_get_mode()
return mode == DisplayServer.WINDOW_MODE_FULLSCREEN
# --------------------------------------------------------
# 公開API: ウィンドウサイズ制御
# --------------------------------------------------------
## ウィンドウサイズを変更する
func set_window_size(size: Vector2i) -> void:
DisplayServer.window_set_size(size)
_cached_window_size = size
## ウィンドウサイズを (width, height) で指定して変更
func set_window_size_wh(width: int, height: int) -> void:
set_window_size(Vector2i(width, height))
## 現在のウィンドウサイズを取得
func get_window_size() -> Vector2i:
return DisplayServer.window_get_size()
## ウィンドウのリサイズ許可を切り替え
func set_resizable(enabled: bool) -> void:
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_RESIZE, enabled)
resizable = enabled
## ボーダーレスフラグを切り替え(ウィンドウモード時)
func set_borderless_window(enabled: bool) -> void:
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, enabled)
borderless = enabled
# --------------------------------------------------------
# 公開API: レンダリングスケール制御
# --------------------------------------------------------
## レンダリングスケールを変更
func set_render_scale(scale: float) -> void:
render_scale = max(scale, 0.01)
_apply_render_scale()
## 現在のレンダリングスケールを取得
func get_render_scale() -> float:
return render_scale
# --------------------------------------------------------
# 内部実装: 一括適用
# --------------------------------------------------------
func _apply_all_settings() -> void:
# まず現状をキャッシュしておく
_cache_current_window_state()
# ウィンドウサイズとフラグ
_apply_window_size_and_flags()
# ウィンドウモード
_apply_window_mode()
# レンダリングスケール
_apply_render_scale()
func _cache_current_window_state() -> void:
_cached_window_size = DisplayServer.window_get_size()
_cached_mode = DisplayServer.window_get_mode()
func _apply_window_size_and_flags() -> void:
# ウィンドウサイズ
DisplayServer.window_set_size(Vector2i(window_width, window_height))
# リサイズ可否
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_RESIZE, resizable)
# ボーダーレス
DisplayServer.window_set_flag(DisplayServer.WINDOW_FLAG_BORDERLESS, borderless)
func _apply_window_mode() -> void:
match start_window_mode:
"WINDOWED":
set_windowed()
"FULLSCREEN":
set_fullscreen(true)
"BORDERLESS_FULLSCREEN":
set_borderless_fullscreen()
_:
# 不正値の場合はとりあえずウィンドウモード
set_windowed()
func _apply_render_scale() -> void:
if scale_mode == "DISABLED":
return
if scale_mode == "VIEWPORT_SCALE":
# Godot 4 では SceneTree.root がメインビューポート
var viewport := get_tree().root
if viewport:
var rid := viewport.get_viewport_rid()
# ここでは 3D 用のスケーリング API を使っているが、
# 2D でも内部解像度を落としてパフォーマンスを稼ぐ用途に使える
RenderingServer.viewport_set_scaling_3d_scale(rid, render_scale)
使い方の手順
このコンポーネントは、どのシーンにも生やせる「設定ノード」として使うイメージです。
例として、プレイヤーシーンにアタッチしてもいいし、メインメニューシーンにだけ置いてもOKです。
-
スクリプトファイルを作る
res://components/resolution_scaler.gdなどのパスで新規スクリプトを作成し、上の「修正版フルコード」を丸ごとコピペします。
class_name ResolutionScalerがあるので、以後はスクリプトを探さなくてもノード追加ダイアログから直接追加できます。 -
シーンにコンポーネントノードを追加する
例として、メインメニューシーンに追加してみます。MainMenu (Control) ├── Panel ├── PlayButton (Button) ├── SettingsButton (Button) └── ResolutionScaler (Node)- 「+」ボタンからノード追加ダイアログを開く
- 検索欄に
ResolutionScalerと入力 - 見つかったクラスを追加
-
インスペクタから初期設定を行う
ResolutionScalerノードを選択すると、インスペクタに以下のような項目が出ます。start_window_mode: 起動時のモード(例:FULLSCREEN)window_width / window_height: ウィンドウ時のサイズ(例: 1920×1080)resizable: ユーザーにリサイズさせるかborderless: ウィンドウ枠を消すかscale_mode: レンダリングスケールのモード(とりあえずDISABLEDから試してOK)render_scale: 内部解像度の倍率(例: 0.75)apply_on_ready: シーン読み込み時に自動で適用するかdefer_apply_one_frame: 1フレーム遅延させるか
例えば、「起動時はフルHDウィンドウ、ユーザーが後から設定画面で変えられる」ようにしたいなら:
start_window_mode = "WINDOWED"window_width = 1920,window_height = 1080resizable = truescale_mode = "DISABLED"(まずはそのまま)
-
ゲーム中にコードから切り替える
設定メニューの UI から直接ResolutionScalerにアクセスして、解像度やフルスクリーンを切り替えられます。
例えば、SettingsMenuシーンで:SettingsMenu (Control) ├── FullscreenCheckBox (CheckBox) ├── ResolutionOptionButton (OptionButton) ├── RenderScaleSlider (HSlider) └── ResolutionScaler (Node)こんなスクリプトを
SettingsMenuに書いておくと便利です。extends Control @onready var scaler: ResolutionScaler = $ResolutionScaler @onready var fullscreen_check: CheckBox = $FullscreenCheckBox @onready var render_scale_slider: HSlider = $RenderScaleSlider func _ready() -> void: # UI の初期状態をコンポーネントから読み取って反映 fullscreen_check.button_pressed = scaler.is_fullscreen() render_scale_slider.value = scaler.get_render_scale() func _on_FullscreenCheckBox_toggled(pressed: bool) -> void: scaler.set_fullscreen(pressed) func _on_RenderScaleSlider_value_changed(value: float) -> void: scaler.set_render_scale(value)このように、「設定メニュー」も「実際のウィンドウ制御」も別ノードに分割されていて、コンポーネントとして疎結合に保たれます。
別シーン例: プレイヤーシーンに載せる場合
「ゲーム開始時にプレイヤーが生成されたタイミングで解像度をセットしたい」という場合は、プレイヤーシーンにそのままアタッチしてしまうのもアリです。
Player (CharacterBody2D) ├── Sprite2D ├── CollisionShape2D └── ResolutionScaler (Node)
この場合も、ResolutionScaler 側で apply_on_ready = true にしておけば、プレイヤーがシーンツリーに入った瞬間に設定が適用されます。
プレイヤーの動きのスクリプトには解像度関連のコードを一切書かなくて済むので、責務がクリーンに分かれますね。
メリットと応用
この ResolutionScaler コンポーネントを使うことで、次のようなメリットがあります。
- シーン構造がスッキリする
解像度・ウィンドウモード関連のコードを、ゲームロジック(プレイヤー、敵、UI)から完全に切り離せます。
どのシーンに置いてもよい「設定ノード」として扱えるので、「このプロジェクト、どこでフルスクリーン切り替えてるんだっけ?」問題がかなり減ります。 - 再利用性が高い
別プロジェクトにそのままコピペしても動きますし、class_nameを付けてあるので、ノード追加ダイアログからいつでも呼び出せます。
メニュー用、デバッグ用、モバイル用など、シーンごとに違う設定を使い分けるのも簡単です。 - レベルデザインに集中できる
レベルシーン側では「このシーンは 1280×720 固定で想定している」などの前提をResolutionScalerに書いておき、
実際のゲームロジックやギミックはその前提の上で組み立てる…という分業がしやすくなります。 - 「継承ツリー」ではなく「合成」で機能を足せる
例えば「SettingsManager」シングルトンに全部詰め込むのではなく、必要なシーンにだけResolutionScalerを生やすことで、
ノードを組み合わせて機能を構築する流れが自然に身に付きます。
応用としては、ユーザー設定の保存・読み込みをこのコンポーネントに持たせるのも良いですね。
例えば、user://settings.cfg にフルスクリーンやレンダリングスケールを保存しておき、起動時に読み込んで適用する関数を追加できます。
簡単な「設定保存」用の改造案コードを載せておきます。
## ユーザー設定を ConfigFile に保存する例
func save_to_config(path: String = "user://video_settings.cfg") -> void:
var cfg := ConfigFile.new()
cfg.set_value("video", "window_mode", start_window_mode)
cfg.set_value("video", "width", window_width)
cfg.set_value("video", "height", window_height)
cfg.set_value("video", "resizable", resizable)
cfg.set_value("video", "borderless", borderless)
cfg.set_value("video", "scale_mode", scale_mode)
cfg.set_value("video", "render_scale", render_scale)
cfg.save(path)
## ユーザー設定を読み込んで適用する例
func load_from_config(path: String = "user://video_settings.cfg") -> void:
var cfg := ConfigFile.new()
var err := cfg.load(path)
if err != OK:
return # ファイルがなければ何もしない
start_window_mode = str(cfg.get_value("video", "window_mode", start_window_mode))
window_width = int(cfg.get_value("video", "width", window_width))
window_height = int(cfg.get_value("video", "height", window_height))
resizable = bool(cfg.get_value("video", "resizable", resizable))
borderless = bool(cfg.get_value("video", "borderless", borderless))
scale_mode = str(cfg.get_value("video", "scale_mode", scale_mode))
render_scale = float(cfg.get_value("video", "render_scale", render_scale))
# 読み込んだ値を即時適用
_apply_all_settings()
この2つの関数を ResolutionScaler に追加して、設定メニューから呼び出せば、解像度まわりの永続化まで含めて「1コンポーネントで完結」させることができます。
ぜひ自分のプロジェクト用に少しずつ拡張しながら、「継承より合成」な Godot プロジェクト構成を育てていきましょう。
