
この記事では Godot 4 でダンジョンRPGのプレイヤーを移動させるための設定・コードについてお伝えします。この記事までを完成させることで、上記画像のようなダンジョンが実装可能です。(上記画像をクリックすると動きがわかります)
前回はダンジョンを作りました。その内容は以下のリンクからご参照ください。
プレイヤーの移動
このステップでは、迷宮の「セルの中心」にプレイヤーのカメラを配置し、キーボード入力(W,A,S,Dなど)に応じて1マス (2m) ずつの移動と、90度ごとの旋回を行う仕組みを構築します。
プレイヤー (Camera3D) の配置と設定

Main シーンを開き、Main ルートノードを右クリックし Add Child Node から Camera3D を追加してください。

Camera3D の Inspector を開き、Transform の Position の値を以下の通りに変更します。Rotation が 0 であることも確認してください。
| Position | X: 1.0, Y:1.0, Z:1.0 |
| Rotation | X: 0.0, Y:0.0, Z:0.0 |
GDScript の作成と割り当て

画面左上のツリーで Camera3D ノードを右クリックし Attach Script を選択してください。設定ウィンドウで以下の項目になっていることを確認し、作成をクリックします。
| 言語 | GDScript |
| パス | res://camera_3d.gd ※ デフォルト値 |
extends Camera3D
# Wiz系RPGの移動・旋回に関する定数定義
const MOVE_STEP: float = 2.0 # GridMapのセルサイズに合わせた1マスの移動距離
const ROTATE_STEP: float = 90.0 # 1回の旋回角度(度数法)
func _ready() -> void:
# 現時点では初期化処理は不要なためパス
pass
func _unhandled_input(event: InputEvent) -> void:
# キーボードのキーやボタンが「押された瞬間」だけを検知する
# 押しっぱなしによる連続移動(暴走)や、キーを離した瞬間のイベントを弾く
if not event.is_pressed() or event.is_echo():
return
# プロジェクト設定の「インプットマップ」で自作したアクション名と紐付け
if event.is_action_pressed("move_forward"):
move_forward()
elif event.is_action_pressed("move_backward"):
move_backward()
elif event.is_action_pressed("turn_left"):
rotate_horizontal(ROTATE_STEP) # 左回転(プラス方向)
elif event.is_action_pressed("turn_right"):
rotate_horizontal(-ROTATE_STEP) # 右回転(マイナス方向)
# 1マス前進する関数
func move_forward() -> void:
# カメラが現在向いている正面方向(ローカルの-Z方向)のベクトルを取得
var forward_vector: Vector3 = -global_transform.basis.z
# ピッチ(上下の首振り)によるY軸のブレを排除し、完全な水平ベクトルにする
forward_vector.y = 0
forward_vector = forward_vector.normalized()
# 現在の座標に「向き × 1マスの距離」を加算して移動
global_position += forward_vector * MOVE_STEP
# 1マス後退する関数
func move_backward() -> void:
var forward_vector: Vector3 = -global_transform.basis.z
forward_vector.y = 0
forward_vector = forward_vector.normalized()
# 前進とは逆に、現在の座標から「向き × 1マスの距離」を減算して移動
global_position -= forward_vector * MOVE_STEP
# 90度旋回する関数
func rotate_horizontal(deg: float) -> void:
# Godotの回転関数(rotate_y)はラジアンを要求するため、度数法(Degree)から変換して実行
rotate_y(deg_to_rad(deg))エディタがコード編集画面 (Scriptタブ) に切り替わったら、エディタ内にある初期コードをすべて消去し、上記のスクリプトをコピー&ペーストしてください。

コードを記述した後、メニューバーの Project から Project Settings を選択します。

Input Map タブをクリックします。新規アクションを追加欄に move_forward と入力し、Add をクリックして登録してください。

追加された move_forward の右側にある「+」ボタンを押し、キーボードの W を押して登録します。同じようにキーボードの↑キー、ゲームパッドの上ボタンでも同様に行います。
キーボードの↑キーやゲームパッドの上ボタンを押すだけで候補を選択できるので、一覧から探すより楽です。


同様に下、左、右についても登録します。
| move_forward | Wキー, キーボードの↑キー, ゲームパッドの上ボタン |
| move_backward | Sキー, キーボードの↓キー, ゲームパッドの下ボタン |
| turn_left | Aキー, キーボードの←キー, ゲームパッドの左ボタン |
| turn_right | Dキー, キーボードの→キー, ゲームパッドの右ボタン |
動作確認

画面右上にある再生ボタン (F5キーでも可) で、プロジェクトを実行します。初回起動時はメインシーンの選択を求められるので「現在のシーンを選択」をクリックしてください。

登録したボタンで上下左右に動き回れるかを確認してください。
衝突判定
残念ながら、上記の対応のままだと壁をすり抜けてしまいます。対策として衝突判定が必要です。
extends Camera3D
# Wiz系RPGの移動・旋回に関する定数定義
const MOVE_STEP: float = 2.0 # GridMapのセルサイズに合わせた1マスの移動距離
const ROTATE_STEP: float = 90.0 # 1回の旋回角度(度数法)
# メッシュライブラリ(MeshLibrary)で定義した「Wall(壁)」のアイテムID
# ※通常、ライブラリ内で最初に作ったものは 0 または 1 になります。
# もし壁をすり抜けてしまう場合は、この数値を 0 に変更してみてください。
const WALL_ITEM_ID: int = 1
# 迷宮データ(GridMap)への参照(型指定)
# ※Mainノードの直下にGridMapノードが配置されている想定のパスです
@onready var grid_map: GridMap = $"../GridMap"
func _unhandled_input(event: InputEvent) -> void:
# キーボードやボタンが「押された瞬間」だけを検知する(長押し暴走防止)
if not event.is_pressed() or event.is_echo():
return
# 自作インプットマップに応じた分岐
if event.is_action_pressed("move_forward"):
try_move(MOVE_STEP) # 前進を試みる
elif event.is_action_pressed("move_backward"):
try_move(-MOVE_STEP) # 後退を試みる
elif event.is_action_pressed("turn_left"):
rotate_horizontal(ROTATE_STEP)
elif event.is_action_pressed("turn_right"):
rotate_horizontal(-ROTATE_STEP)
# 移動の成否を判定して処理する関数
func try_move(step_distance: float) -> void:
# 1. カメラが現在向いている正面方向(ローカルの-Z方向)のベクトルを取得
var forward_vector: Vector3 = -global_transform.basis.z
# Y軸(上下)の傾きを排除し、完全な水平ベクトルにする
forward_vector.y = 0
forward_vector = forward_vector.normalized()
# 2. 移動「予定」のグローバル3D座標を算出
var target_global_pos: Vector3 = global_position + (forward_vector * step_distance)
# 3. グローバル座標を、GridMap内の3次元整数座標(マップの配列インデックス)に変換
# to_localでGridMap基準のローカル座標にし、local_to_mapで「何行何列目」の整数(Vector3i)にします
var map_coord: Vector3i = grid_map.local_to_map(grid_map.to_local(target_global_pos))
# 4. 指定したマス(整数座標)に配置されているアイテムのIDを取得
var cell_item_id: int = grid_map.get_cell_item(map_coord)
# 5. もし移動先のマスにあるアイテムIDが「壁」と同じなら、移動を拒否して関数を抜ける
if cell_item_id == WALL_ITEM_ID:
# ここに「壁にぶつかった時のドンという効果音や画面の揺れ」を後から追加できます
return
# 6. 壁がなければ(空。または床データであれば)実際にカメラの座標を更新する
global_position = target_global_pos
# 90度旋回する関数
func rotate_horizontal(deg: float) -> void:
rotate_y(deg_to_rad(deg))camera_3d.gd のコードを上記のように書き換えます。書き換えた後、再生ボタン(F5キー) を押してテストしてください。壁に向かって上ボタンを押したときに動きが止まれば成功です。
機能追加 (コントローラーのLRボタンで左右移動)
上記のままでも動きます。しかし、コントローラーのLRボタンで平行移動したい人もいるでしょう。その処理を追記していきます。

Project メニューから Project Setting を選択してください。Input Map タブをクリックします。

新規アクションを追加 で move_left, move_right を追加してください。そのあと、それぞれの右側にある「+」ボタンを押し、ゲームパッドのLボタンとキーボードのQキー、ゲームパッドのRボタンとを追加します。
| move_left | キーボードのQキー, ゲームパッドのLボタン |
| move_right | キーボードのEキー, ゲームパッドのRボタン |
extends Camera3D
const MOVE_STEP: float = 2.0
const ROTATE_STEP: float = 90.0
const WALL_ITEM_ID: int = 1
@onready var grid_map: GridMap = $"../GridMap"
func _unhandled_input(event: InputEvent) -> void:
if not event.is_pressed() or event.is_echo():
return
# 前後移動(Vector3の組み込み定数を使わず、固定の方向ベクトルを指定して確実化)
if event.is_action_pressed("move_forward"):
try_move_direction(Vector3(0, 0, -1)) # 前(-Z方向)
elif event.is_action_pressed("move_backward"):
try_move_direction(Vector3(0, 0, 1)) # 後(+Z方向)
# 左右の平行移動
elif event.is_action_pressed("move_left"):
try_move_direction(Vector3(-1, 0, 0)) # 左(-X方向)
elif event.is_action_pressed("move_right"):
try_move_direction(Vector3(1, 0, 0)) # 右(+X方向)
# 左右の90度旋回
elif event.is_action_pressed("turn_left"):
rotate_horizontal(ROTATE_STEP)
elif event.is_action_pressed("turn_right"):
rotate_horizontal(-ROTATE_STEP)
func try_move_direction(local_direction: Vector3) -> void:
var direction_vector: Vector3 = global_transform.basis * local_direction
direction_vector.y = 0
direction_vector = direction_vector.normalized()
var target_global_pos: Vector3 = global_position + (direction_vector * MOVE_STEP)
var map_coord: Vector3i = grid_map.local_to_map(grid_map.to_local(target_global_pos))
var cell_item_id: int = grid_map.get_cell_item(map_coord)
if cell_item_id == WALL_ITEM_ID:
return
global_position = target_global_pos
func rotate_horizontal(deg: float) -> void:
rotate_y(deg_to_rad(deg))camera_3d.gd のコードを上記のように書き換えてください。そのあと、実際に動かしてゲームパッドの L, R ボタンが機能するかを確認します。
コメント