Godotでゲームを作っていると、こんな「画面の中にもう一つの画面が欲しい」場面に出会います。マップを見渡す ミニマップ を隅に置きたい。インベントリで装備を 3Dモデルでぐるぐる回して見せたい。監視カメラやポータルのように 別の場所の映像をリアルタイムに映したい。
これらは通常の1画面だけでは実現が難しく、専用のノード SubViewport の出番です。SubViewport は メイン画面とは独立した「仮想の画面」 を作り、その描画結果を テクスチャとして 自由に貼り付けられる機能。これを押さえると、UIとグラフィック表現の引き出しが一気に増えます。
この記事でわかること
SubViewportの正体—— 独立したレンダリング世界 とViewportTexture- ミニマップ の作り方(俯瞰カメラで別描画して隅に表示)
- 監視カメラ風 のレンダーテクスチャ+シェーダー演出
- 実践: インベントリの3Dプレビュー(ドラッグで回る装備)
Update Mode・解像度・cull_maskによる パフォーマンス最適化
動作確認環境: Godot 4.7
SubViewportのコア概念:独立したレンダリング世界
SubViewport を理解する鍵は、 「メインの描画から独立している」 という一点です。このノードの下に置いたシーンは、独自のカメラ・ワールド・レンダリング設定を持ち、 別の場所で描かれます 。
ただし、何が「独立」するかは2Dと3Dで違います。2Dは既定で本編とは別の世界(World2D)を持つので、中に置い たものしか映りません。3Dは既定で本編と同じ3D空間を見ていて(Own World 3D が既定でオフ)、本編の床やライトも映り込みます。
そして、その描画結果をメイン画面に持ち込む橋渡し役が ViewportTexture です。SubViewport の出力をこのテクスチャにすると、TextureRect(UI用)・Sprite2D・3Dモデルのマテリアルなど、 テクスチャを貼れる場所ならどこにでも その映像を映せます。

基本的なセットアップ
- ノード追加 :
SubViewportContainerを追加し、その子にSubViewportを置く(コンテナを使うとサイズ管理が楽)。 - 中身を作る :
SubViewportの子として、映したいシーン(2D/3Dノードとカメラ)を組む。 - 表示する :
SubViewportContainerを使えば、コンテナが中身をそのまま表示するので、これだけで映ります。コンテナを使わずにTextureRect・Sprite2D・3Dモデルのマテリアルなどへ貼りたいときは、そのTextureに「新規ViewportTexture」を設定して、対象のSubViewportを指定する。
この「独立した画面をテクスチャ化して貼る」流れさえ掴めば、以降の活用法はす べてこの応用です。
活用法1:ミニマップを作る
ミニマップは SubViewport の代表例です。 プレイヤーを追う俯瞰カメラで世界を別描画し、その映像を画面の隅に小さく表示 します。メイン画面とは別のカメラを持てるからこそ、同じ世界を「真上から」同時に映せるわけです。

プレイヤーだけでなく、敵やアイテムのアイコンも動的に表示する実践的なコードです。SubViewport 自身にアタッチして、ミニマップの描画ロジックをメインのゲームロジックから分離します。
extends SubViewport
@onready var minimap_camera: Camera2D = $MinimapCamera
# 敵アイコンを使い回すためキャッシュ(毎フレームの生成・検索を避ける)
var enemy_icons: Dictionary = {}
const ICON_NORMAL = preload("res://assets/enemy_icon.png")
const ICON_ALERT = preload("res://assets/enemy_alert_icon.png")
func _process(_delta: float) -> void:
var player := get_tree().get_first_node_in_group("player")
if not is_instance_valid(player):
return
# 俯瞰カメラをプレイヤーに追従させる
minimap_camera.global_position = player.global_position
# 倒されて消えた敵のアイコンを片付ける
for gone in enemy_icons.keys():
if not is_instance_valid(gone):
enemy_icons[gone].queue_free()
enemy_icons.erase(gone)
for enemy in get_tree().get_nodes_in_group("enemies"):
if not is_instance_valid(enemy):
continue
# アイコンが無ければ1つだけ生成してキャッシュ
if not enemy_icons.has(enemy):
var new_icon := Sprite2D.new()
add_child(new_icon)
enemy_icons[enemy] = new_icon
var icon: Sprite2D = enemy_icons[enemy]
# 警戒中かどうかでアイコンを差し替える(is_in_alert_state() は敵のスクリプトに用意しておく)
icon.texture = ICON_ALERT if enemy.is_in_alert_state() else ICON_NORMAL
icon.global_position = enemy.global_position
ポイントは、 ミニマップの中身(カメラ・アイコン)が SubViewport の中で完結している ことです。メインのゲームは自分の描画に集中でき、ミニマップの表示ロジックはこのノードだけを見れば分かる——役割がきれいに分かれます。
なお、2Dの SubViewport は既定で本編とは別の世界なので、このままだとミニマップに映るのは、ここで置いたアイコンだけです(レーダーのような表示)。本編のマップも映したいときは、_ready() で world_2d = get_tree().root.world_2d として世界を共有します。その場合、中に置いたアイコンも本編の世界に入って本編の画面にも映るので、アイコンの Visibility Layer を2番だけにし、get_tree().root.canvas_cull_mask から2番を外します。
活用法2:監視カメラ風レンダーテクスチャ
SubViewport の映像を シェーダーと組み合わせる と、単なる表示を超えた演出ができます。定番は、別の場所を映すカメラの映像に走査線やノイズを乗せた 「監視カメラ」 です。
