はじめに
この記事は、Godot 4.xでスマホ横向き向けFPS視点の3D脱出ゲームを作る連載の第2回です。
前回までの記事はこちら:
- 環境構築とプロジェクト作成
- Claude Code + godot-mcp セットアップ
- 【Godot 4.x】3D脱出ゲームを作ってみる①〜3Dプロジェクト作成とシーン構成〜(前回)
この記事でやること
部屋を配置して、バーチャルなジョイスティックでFPS視点で移動、画面右側をスワイプすることで視点を回転、までの実装となります
-
bed_scene1.tscnを使った寝室シーンの配置 -
CharacterBody3D+Camera3DによるFPSプレイヤーの作成 - 左側バーチャルジョイスティックで移動
- 右側スワイプで視点回転
想定環境
| 項目 | 内容 |
|---|---|
| OS | macOS(Apple Silicon) |
| Godot | 4.7 stable |
| Claude Code | 最新版 |
| godot-mcp | セットアップ済み |
0. claudeにプランを立ててもらう
3D脱出ゲームを作ってみる①、の続きから
プロンプト:
実装を始めます
質問:
3D脱出ゲームのカメラ操作スタイルはどちらにしますか?
- 固定視点(ポイント&クリック):プレイヤーは動かず、クリックで視点を切り替える昔ながらの脱出ゲームスタイル。実装がシンプルで記事向き
- 自由移動(FPS/TPS):WASDやクリックで部屋を歩き回れる。より没入感があるが実装が複雑
回答:
自由移動(FPS/TPS):WASDやクリックで部屋を歩き回れる。より没入感があるが実装が複雑
プロンプト:
それでは、FPSプレイヤーの実装CharacterBody3D + Camera3D、バーチャルジョイスティック+スワイプで操作、を実装します
1. 寝室シーンの配置
前回インポートした Low Poly Bedroom Asset Pack の bed_scene1.tscn を使います。このシーンにはベッド・ドレッサー・ランプなどが一式まとめて配置されています。
1-1. メインシーンの準備
scenes/main.tscn を開き、以下のノード構成を作ります。
Main (Node3D)
├── WorldEnvironment // 前回作成済み
├── DirectionalLight3D // 前回作成済み
├── Camera3D // 前回作成済み
├── BedRoom // (bed_scene1.tscnのインスタンス)
└── Player // (CharacterBody3D) ← 今回作成
1-2. bed_scene1.tscn のインスタンス化
前回、ベットを配置していたら削除しておいてください
- FileSystemパネルで
assets/models/bedroom/Scenes/bed_scene1.tscnを探す - シーンにドラッグ&ドロップ、またはメニューから
シーン > インスタンスを追加 - ノード名を
BedRoomにリネーム - Transformの Position・Rotation を調整して部屋の中心に配置
bed_scene1.tscn にはコリジョン(当たり判定)が含まれていない場合があります。その場合は各家具に StaticBody3D + CollisionShape3D を手動で追加するか、後述のプレイヤー実装後に動作確認しながら対応してください。
2. FPSプレイヤーシーンの作成
プレイヤーは独立したシーンとして作成し、メインシーンにインスタンスとして追加します。
2-1. 新規シーンの作成
シーン > 新規シーン- ルートノードを生成 > その他のノードで、[Character]で検索して
CharacterBody3Dを選択 - ノード名を
Playerにリネーム -
scenes/player.tscnとして保存
2-2. ノード構成
Player (CharacterBody3D)
├── CollisionShape3D # プレイヤーのコリジョン(カプセル形状)
├── Camera3D # FPS視点カメラ
└── RayCast3D # インタラクション用(④で使用)
CollisionShape3D の設定
-
Shapeプロパティで「新規 CapsuleShape3D」を選択 - Radius:
0.4、Height:1.8(人の体型に近いサイズ)
Camera3D の設定
- Position を
(0, 0.8, 0)に設定(カプセルの上部=目線の高さ) -
CurrentをOnにする(このカメラをメインカメラとして使用)
claude code + mcp でプロンプトに上記を投げると作ってもらえます
3. プレイヤースクリプトの実装
Player ノードに GDScript を追加します。
3-1. スクリプトの全体像
extends CharacterBody3D
# 移動速度
const SPEED = 4.0
const GRAVITY = -9.8
# スワイプ視点回転の感度
const LOOK_SENSITIVITY = 0.005
@onready var camera = $Camera3D
# スワイプ追跡用
var _look_touch_index: int = -1
var _look_last_pos: Vector2 = Vector2.ZERO
func _ready():
# マウスカーソルを非表示(エディタでのテスト用)
# スマホでは不要だが、デスクトップテスト時に有効
# Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
pass
func _physics_process(delta):
# 重力
if not is_on_floor():
velocity.y += GRAVITY * delta
# 移動入力(バーチャルジョイスティックから受け取る)
var move_dir = Vector2.ZERO
move_dir.x = Input.get_axis("move_left", "move_right")
move_dir.y = Input.get_axis("move_forward", "move_back")
if move_dir != Vector2.ZERO:
var direction = (transform.basis * Vector3(move_dir.x, 0, move_dir.y)).normalized()
velocity.x = direction.x * SPEED
velocity.z = direction.z * SPEED
else:
velocity.x = move_toward(velocity.x, 0, SPEED)
velocity.z = move_toward(velocity.z, 0, SPEED)
move_and_slide()
func _input(event):
# スワイプ(右側タッチ)で視点回転
if event is InputEventScreenTouch:
_handle_touch(event)
elif event is InputEventScreenDrag:
_handle_drag(event)
func _handle_touch(event: InputEventScreenTouch):
var screen_center_x = get_viewport().get_visible_rect().size.x / 2.0
if event.pressed and event.position.x > screen_center_x:
# 画面右半分のタッチ開始 → 視点操作として追跡
_look_touch_index = event.index
_look_last_pos = event.position
elif not event.pressed and event.index == _look_touch_index:
_look_touch_index = -1
func _handle_drag(event: InputEventScreenDrag):
if event.index != _look_touch_index:
return
var delta = event.position - _look_last_pos
_look_last_pos = event.position
# 左右:プレイヤー本体をY軸回転
rotate_y(-delta.x * LOOK_SENSITIVITY)
# 上下:カメラのX軸回転(上下限あり)
camera.rotation.x = clamp(
camera.rotation.x - delta.y * LOOK_SENSITIVITY,
deg_to_rad(-80),
deg_to_rad(80)
)
Input.get_axis("move_left", "move_right") はInputMapのアクション名です。次のステップでバーチャルジョイスティックのアクションを設定します。
ここも、このスクリプトをclaude code + mcp に投げてplayer.gdを作成してもらいました
「Player ノードに GDScript を追加します。」の一文を添えておくと安心です
4. InputMapの設定
バーチャルジョイスティックから移動方向を受け取るため、プロジェクト設定 > インプットマップ にアクションを追加します。
-
プロジェクト > プロジェクト設定 > インプットマップタブを開く - 「アクションを追加」で以下の4つを追加(キー割り当ては任意)
| アクション名 | キー(PC テスト用) |
|---|---|
move_forward |
W |
move_back |
S |
move_left |
A |
move_right |
D |
追加したアクションの右側の「+」をクリックして、「W」を入力

スマホ実機ではバーチャルジョイスティックから Input.action_press() でこれらのアクションを呼び出します。PCでのテスト時はWASDキーで代用できます。
5. バーチャルジョイスティックの実装
画面左側に表示するバーチャルジョイスティックをUIシーンとして作成します。
5-1. UIシーンの作成
シーン > 新規シーン- ルートノードに
Controlを選択、名前をVirtualJoystickにリネーム -
scenes/ui/virtual_joystick.tscnとして保存
5-2. ノード構成
VirtualJoystick (Control)
├── Base (TextureRect) # ジョイスティックの台座
└── Stick (TextureRect) # 動くスティック部分
Control の設定
-
アンカープリセットを「全画面」に設定 -
マウスフィルターをIgnoreに設定(右側スワイプを妨げないため)
Base(台座)の設定
- ColorRectを使用
- Position:
(80, 画面高さ - 200)程度(左下に配置) - Size:
(160, 160) -
Color:Color(1, 1, 1, 0.3)(半透明の白)
Stick(スティック)の設定
- ColorRectを使用
- Base の中心に重なるように配置
- Size:
(80, 80) -
Color:Color(1, 1, 1, 0.6)(やや濃い半透明の白)
こちらもclaude code + mcp のプロンプトに投げて作成してもらうと楽です
今回は ColorRect で代用していますが、後から円形テクスチャの TextureRect に差し替えることで見た目をブラッシュアップできます。
5-3. ジョイスティックスクリプト
VirtualJoystick に以下のスクリプトを追加します。
extends Control
@onready var base = $Base
@onready var stick = $Stick
var _touch_index: int = -1
var _base_pos: Vector2 = Vector2.ZERO
const MAX_DISTANCE = 60.0
func _ready():
_base_pos = base.position + base.size / 2
func _input(event):
if event is InputEventScreenTouch:
_on_touch(event)
elif event is InputEventScreenDrag:
_on_drag(event)
func _on_touch(event: InputEventScreenTouch):
var screen_center_x = get_viewport().get_visible_rect().size.x / 2.0
if event.pressed and event.position.x < screen_center_x:
# 画面左半分のタッチ → ジョイスティック操作
_touch_index = event.index
elif not event.pressed and event.index == _touch_index:
_touch_index = -1
_reset_stick()
func _on_drag(event: InputEventScreenDrag):
if event.index != _touch_index:
return
var offset = event.position - _base_pos
if offset.length() > MAX_DISTANCE:
offset = offset.normalized() * MAX_DISTANCE
stick.position = _base_pos - stick.size / 2 + offset
# 移動アクションを入力に反映
var ratio = offset / MAX_DISTANCE
if ratio.x < -0.3:
Input.action_press("move_left")
else:
Input.action_release("move_left")
if ratio.x > 0.3:
Input.action_press("move_right")
else:
Input.action_release("move_right")
if ratio.y < -0.3:
Input.action_press("move_forward")
else:
Input.action_release("move_forward")
if ratio.y > 0.3:
Input.action_press("move_back")
else:
Input.action_release("move_back")
func _reset_stick():
stick.position = _base_pos - stick.size / 2
Input.action_release("move_left")
Input.action_release("move_right")
Input.action_release("move_forward")
Input.action_release("move_back")
Claudeのクレジット・セッションを節約したい場合は自身で作成しても良いと思います
6. メインシーンへの組み込み
作成した Player と VirtualJoystick をメインシーンに追加します。
6-1. Player の追加
-
scenes/main.tscnを開く - シーンパネルの
Mainノードを選択した状態で、チェーンアイコン(🔗)「子シーンをインスタンス化」をクリック -
scenes/player.tscnを選択して「開く」 - Position を部屋の入口付近に設定(例:
(0, 1.0, 2.0))
チェーンアイコンはシーンパネル上部のツールバーにあります。または Main ノードを右クリック →「子シーンをインスタンス化」でも同じ操作ができます。
6-2. VirtualJoystick の追加
UIはCanvasLayerを介して追加します。
Main (Node3D)
├── WorldEnvironment
├── DirectionalLight3D
├── BedRoom
├── Player
└── CanvasLayer ← 追加
└── VirtualJoystick ← インスタンスを追加
-
MainにCanvasLayerノードを追加 -
CanvasLayerを選択した状態でチェーンアイコン(🔗)をクリックし、scenes/ui/virtual_joystick.tscnを選択
CanvasLayer を使うことで、UIが3Dカメラの動きに影響されず画面上に固定表示されます。
7. 動作確認
PCでタッチ操作をテストする場合:
プロジェクト > プロジェクト設定 > 入力デバイス > ポインティング > マウスからタッチをエミュレート を ON にしてください。これを有効にするとマウス操作がタッチイベントとして扱われ、ジョイスティックと右側スワイプが動作するようになります。
プロジェクトを実行し、以下を確認します。
-
寝室(
bed_scene1.tscn)が表示される - 左下のバーチャルジョイスティックで移動できる
- 画面右側のスワイプで視点が回転する
- 上下視点が -80° 〜 +80° にクランプされている
- 家具に当たって止まる(コリジョンが効いている)
マウスやキーボードが反応しない場合は、実行ウィンドウの左上の「入力」をONにしてみてください
実機確認はAndroid/iOS向けにエクスポートが必要です。エディタ上でのテストはWASDキー+マウスドラッグで代用できます。
iOS実機確認方法はこちら
成果物
バーチャルジョイスティックやキーボードのWASDキーでプレイヤー視点でカメラが動きます

まとめ
今回は以下を実装しました。
-
bed_scene1.tscnをメインシーンに配置 -
CharacterBody3D+Camera3DでFPSプレイヤーを作成 - 画面右側スワイプで視点回転(上下クランプあり)
- 画面左側バーチャルジョイスティックで移動
-
CanvasLayerでUIを3D空間上に固定表示
連載リンク
- 前提:環境構築とプロジェクト作成
- 前提:Claude Code + godot-mcp セットアップ
- 前回:【Godot 4.x】3D脱出ゲームを作ってみる①〜3Dプロジェクト作成とシーン構成〜





















