【Godot】Godotの三人称カメラ入門:マウスで見回し、壁にめり込まず、肩越しに寄る

作成: 2026-09-23最終更新: 2026-10-10

SpringArm3Dでマウスで見回せる三人称カメラを作り、壁に入り込まずに右クリックで肩越しに寄せる方法を解説します。

三人称視点のゲームでは、カメラをキャラの後ろに付けてマウスで見回し、壁際では壁に入り込まないようにします。狙うときは、右クリックで肩越しに寄せます。Godot では、壁に当たると縮む SpringArm3D の先に Camera3D を付けて作ります。

肩越しにするためにカメラの h_offset(映す画面だけを横へずらす値)を変えると、壁際で画面が壁の中に入ります。見上げの角度を見下ろしと同じだけ広げると、カメラが足元へ寄って、キャラの体で画面がふさがります。

この記事では、見回しの感度と上下の上限を決め、右クリックで肩越しに寄る三人称カメラを作る方法を解説します。

人形の頭の上の台座から後ろへ腕が伸び、その先で、人形の背中の側にカメラが付いている

この記事でわかること

  • カメラを台座・腕・目に分けて、それぞれに任せる役割
  • マウスの感度を「1ピクセルで何度回るか」で決める方法
  • 見上げの上限を、見下ろしより狭くする理由
  • 壁に埋まらない肩越しの寄せ方

動作確認環境: Godot 4.7

Sponsored

カメラの組み立て(台座・腕・目)

三人称のカメラは、3つのノードを縦につないで作ります。

台座の Node3D が左右の向き、腕の SpringArm3D が上下の角度と長さ、目の Camera3D が映すだけ、と役割を分けて縦につないだ図
  • 台座(Node3D): キャラの頭のあたりに置く、回転の中心。左右の向きだけを持つ
  • 腕(SpringArm3D): 台座から後ろへ伸びる。上下の角度と長さを持ち、途中に壁があると縮む
  • 目(Camera3D): 腕の先で画面を映す。自分では動かない

SpringArm3D 1つで左右も上下も回しても、向きは作れます。分けておくと、上下の制限や肩越しのずれを、左右の向きとは別に触れます。キャラを歩かせるときも、本体を回さない作りなら、前の向きは台座の左右の角度から取れます。

このしくみで大事なのは、壁を調べているのが腕だけということです。腕は自分の +Z の向きへ伸び、壁に当たったらその手前で止まって、子のカメラを先端へ置き直します。この置き直しは物理の更新のたび(既定で1秒に60回)に行われるので、カメラの位置を自分で変えても、次の物理の更新で腕の先へ戻ります。カメラを動かしたいときは、腕のほうを動かします。

他のエンジンでいうと: Unity の Cinemachine 3rd Person Follow(Camera Distance と Shoulder Offset)や、Unreal Engine の Spring Arm(Target Arm Length と Socket Offset)に近い組み立てです。UE の Socket Offset が腕の先をずらすのに対し、Godot では腕の付け根をずらします。

マウスの感度(screen_relative)

マウスで見回すには、_unhandled_input()(UI が使わなかった入力だけが届く関数)でマウスの動きを受け取り、動いた量に感度を掛けて回します。

if event is InputEventMouseMotion:
	rotation.y -= event.screen_relative.x * SENSITIVITY       # 左右は台座
	arm.rotation.x -= event.screen_relative.y * SENSITIVITY   # 上下は腕(制限は次の節)

screen_relative は、マウスが画面の上で何ピクセル動いたかです。似た名前の relative は、画面の拡大率(プロジェクト設定の Stretch で決まる)に合わせて値が変わります。フルスクリーンなどで拡大率が変わると、振り向きの速さまで変わります。screen_relative は Godot 4.3 から使えます。

マウスを100ピクセル動かすと視点が約17度回る。screen_relative ならウィンドウの大きさを変えても同じ角度になる

感度の単位は、1ピクセルあたりのラジアンです。0.003 なら1ピクセルで約0.17度、マウスを100ピクセル動かすと約17度回ります。速すぎると感じたら数値を下げます。

この数値に delta(前のフレームからの経過秒)は掛けません。マウスの値は、前のイベントから動いた量そのものです。delta を掛けると、フレームレートで振り向きの速さが変わってしまいます。

Sponsored

上下の角度の上限

上下の角度は、腕の rotation.x で決まります。値を大きくするとカメラは下がって見上げ、小さくするとカメラは上がって見下ろします。

横から見た図。カメラを見上げ側へ回しすぎると腕の先が床に届くので、見下ろしの上限と、床に届く手前の見上げの上限のあいだだけを使う

困るのは見上げのほうです。台座が床から1.6メートル、腕が4メートルなら、約23度見上げたところで腕の先が床に届きます。床に当たった腕は縮み、カメラは床の上をキャラの足元へ寄っていきます。最後は、キャラの体で画面がふさがります。

見下ろしのほうは、カメラが空へ上がるだけなので広くとれます。この記事の数値では、見下ろしを60度、見上げを床に届く手前の20度で止めます。

arm.rotation.x = clampf(arm.rotation.x, -PITCH_DOWN, PITCH_UP)

clampf() は、値を下限と上限のあいだに収める関数です。rotation.x はラジアンなので、PITCH_DOWN と PITCH_UP は deg_to_rad(60.0) のように度から直した値にします。インスペクタの Rotation は度で表示されますが、コードの値はラジアンです。台座の高さや腕の長さを変えたら、見上げの上限も一緒に見直します。

SpringArm3D の壁の調べ方

SpringArm3D の Shape(壁を調べるときに使う形)を空のまま、直接の子に Camera3D を置くと、腕は点ではなく、小さな四角錐の形で壁を調べます。カメラの位置から、描画が始まる面(カメラのすぐ前)の四隅までを囲む形です。点で調べるより、画面の端が壁に入り込みにくくなります。

腕に沿ってカメラの前の小さな四角錐が進み、壁に触れる手前で止まる。カメラはそこへ置かれる

設定で見るのは2つです。

  • Spring Length: 壁がないときの腕の長さ。三人称なら 4 前後から試す
  • Collision Mask: 腕が調べる相手のレイヤー。既定はレイヤー1なので、壁の Collision Layer(自分がいるレイヤー)を1以外にしたら、ここでも同じ番号にチェックを入れる

インスペクタには Margin(当たった場所から手前へ引く距離)もありますが、子に Camera3D があるこの形では使われません。値を変えても、止まる位置は変わりません。

腕は、自分の体も壁として扱います。付け根の位置や角度によっては、腕が自分の体に当たって縮みます。そこで、キャラの RID(物理エンジンの中でその体を指す番号。CharacterBody3D の get_rid() で取る)を add_excluded_object() に渡して、自分の体を外しておきます。

画面が壁に入る原因でいちばん多いのは、腕が調べていない場所へカメラを出したときです。次の肩越しの節で、これが問題になります。

肩越しのずらし方

肩越しに寄せるには、カメラを右へずらして、キャラを画面の左へ寄せます。ずらし方は2つあり、壁際で結果が分かれます。

上から見た対比。h_offset は映す位置だけがずれて右後ろの柱に入り、腕の付け根をずらすと調べる線も動いて柱の手前で止まる
  • カメラの h_offset を変える: 映す位置だけが横へずれる。腕が調べているのは元の付け根から伸びる道筋なので、右後ろに柱があると、ずれた分だけ画面が柱に入る
  • 腕の position.x を変える: 腕の付け根ごと横へずれる。壁を調べる道筋も一緒に動くので、柱の手前で止まる

腕の付け根をずらします。ずらす量は、画面の真ん中がキャラの体から外れるくらいです。半径0.4のキャラなら0.5にします。小さすぎると、狙う先がキャラの体に隠れます。

寄せるときは、腕の長さも縮めます。長さとずれを Tween(数値を時間をかけて変える機能)で同時に動かすと、0.2秒ですっと寄ります。

if aim_tween != null and aim_tween.is_valid():
	aim_tween.kill()          # 動いている途中の Tween を止める
aim_tween = create_tween().set_parallel()   # 下の予約を同時に動かす
aim_tween.tween_property(arm, "spring_length", ARM_AIM if aiming else ARM_NORMAL, AIM_TIME)
aim_tween.tween_property(arm, "position:x", SHOULDER if aiming else 0.0, AIM_TIME)   # x だけを、狙っていれば SHOULDER、でなければ 0 へ

右クリックを素早く押し直すと、戻る途中の Tween と寄る Tween が同じ値を取り合って、カメラが震えます。新しい Tween を作る前に、前のものを kill() で止めます。止めた位置から次の Tween が始まるので、途中で折り返しても位置が飛びません。

kill() なしでは2つの Tween が同じ値を取り合って震え、kill() で止めてから作り直すと今の位置から折り返す
Sponsored

実践:右クリックで肩越しに寄る三人称カメラ

三人称シューティング(TPS)の射撃、弓で狙うアクションアドベンチャー、写真を撮って回る探索ゲーム —— 狙うときだけ肩越しに寄るカメラは、どれも同じ作りです。キャラは動かさず、カメラだけを作ります。歩かせる部分は CharacterBody3D入門 で扱っています。

実践の配置。キャラの左に壁、正面の奥に的があり、キャラの後ろ上から腕の先のカメラが映している

① 置くもの

新しい3Dシーンを作り、ルートを Node3D にして Main と名付けます。空と太陽は、3Dビューの上のツールバーで、太陽と環境のプレビューボタンの右にある「︙」から置きます。開いて「Add Sun to Scene」を押し、もう一度開いて「Add Environment to Scene」を押します。エディタのプレビューと同じ太陽と空が、DirectionalLight3D と WorldEnvironment としてシーンに入ります。残りは次のノードを置きます。

ノード型位置設定
GroundStaticBody3D(0, -0.5, 0)子に BoxShape3D (20, 1, 20) の CollisionShape3D と、同じ大きさの BoxMesh の MeshInstance3D
WallStaticBody3D(-2.5, 1.5, 0)子に BoxShape3D (0.5, 3, 6) の CollisionShape3D と、同じ大きさの BoxMesh の MeshInstance3D
TargetMeshInstance3D(1, 1, -8)BoxMesh (1, 1, 1)。狙う的
PlayerCharacterBody3D(0, 1, 0)子に CapsuleShape3D(高さ2・半径0.4)の CollisionShape3D と、同じ大きさの CapsuleMesh の MeshInstance3D
Player/CameraPivotNode3D(0, 0.6, 0)台座。次のスクリプトを付ける
Player/CameraPivot/SpringArm3DSpringArm3D(0, 0, 0)Spring Length を 4、Rotation の X を -15
Player/CameraPivot/SpringArm3D/Camera3DCamera3D(0, 0, 0)そのまま

プロジェクト設定の「インプットマップ」に aim を足し、マウスの右ボタンを割り当てます。

② 組み立て

CameraPivot に次のスクリプトを付けます。

# camera_rig.gd(CameraPivot に付ける)
extends Node3D

const SENSITIVITY := 0.003             # 1ピクセルあたりの回転(ラジアン)。100ピクセルで約17度
const PITCH_DOWN := deg_to_rad(60.0)   # 見下ろしの上限
const PITCH_UP := deg_to_rad(20.0)     # 見上げの上限。腕の先が床に届く約23度の手前
const ARM_NORMAL := 4.0
const ARM_AIM := 2.0
const SHOULDER := 0.5                  # 右肩へのずれ。画面の真ん中が体(半径 0.4)から外れる量
const AIM_TIME := 0.2

@onready var player := get_parent() as CharacterBody3D
@onready var arm: SpringArm3D = $SpringArm3D
var aim_tween: Tween

func _ready() -> void:
	arm.add_excluded_object(player.get_rid())    # 自分の体で腕が縮まないようにする
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED   # カーソルを隠して、画面の中に閉じ込める

func _unhandled_input(event: InputEvent) -> void:
	if Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
		if event.is_action_released("aim"):
			_set_aiming(false)                             # 捕まえていないあいだに離しても戻す
		if event is InputEventMouseButton and event.pressed:
			Input.mouse_mode = Input.MOUSE_MODE_CAPTURED   # 画面をクリックしたら捕まえ直す
		return

	if event is InputEventMouseMotion:
		rotation.y -= event.screen_relative.x * SENSITIVITY                   # 左右は台座
		arm.rotation.x = clampf(arm.rotation.x - event.screen_relative.y * SENSITIVITY,
			-PITCH_DOWN, PITCH_UP)                                             # 上下は腕
	elif event.is_action_pressed("aim"):
		_set_aiming(true)
	elif event.is_action_released("aim"):
		_set_aiming(false)
	elif event.is_action_pressed("ui_cancel"):
		_set_aiming(false)                                  # 狙ったまま Esc を押したときも戻す
		Input.mouse_mode = Input.MOUSE_MODE_VISIBLE         # Esc でカーソルを出す

func _set_aiming(aiming: bool) -> void:
	if aim_tween != null and aim_tween.is_valid():
		aim_tween.kill()                                    # 動いている途中の Tween を止める
	aim_tween = create_tween().set_parallel()   # 下の予約を同時に動かす
	aim_tween.tween_property(arm, "spring_length", ARM_AIM if aiming else ARM_NORMAL, AIM_TIME)
	aim_tween.tween_property(arm, "position:x", SHOULDER if aiming else 0.0, AIM_TIME)   # x だけを、狙っていれば SHOULDER、でなければ 0 へ

Esc でカーソルを出しているあいだは、見回しと狙いの操作が止まります。画面をクリックすると、そのクリックでカーソルを捕まえ直して、操作に戻ります。Web 版ではブラウザが Esc を先に使ってしまい、ゲームに届かないことがあります。狙ったまま捕捉が外れても、右ボタンを離したイベントが届けば戻せるように、捕まえていない側でも aim を離したら _set_aiming(false) を呼んでいます。

③ 動かして確かめる

実践の結果の3コマ。通常はキャラが中央、右クリックでキャラが左に寄って的が真ん中の右上に見え、壁側へ回ると腕が縮んで近づく

F6(いま開いているシーンを実行)で動かします。見るのは3つです。

まず見回しです。マウスを右へ動かすと視点が右へ回り、100ピクセルでおよそ17度です。上下は、見下ろすと60度で止まります。見上げると、カメラは床から約0.2メートルの高さまで下がって20度で止まり、床には届きません。見上げたときに足元へ寄るなら、PITCH_UP の値と、clampf() の下限と上限の順番を見ます。

次に壁です。上下を最初の少し見下ろす角度に戻し、視点を右へ90度ほど回すと、カメラがキャラの左、壁の側へ回り込みます。腕は4から約2.3まで縮み、カメラは壁に入り込まずに手前で止まります。壁に入り込むなら、壁の Collision Layer に1が入っているかを見ます(腕の Collision Mask は既定で1)。何もない場所で腕が縮むなら、自分の体を外す1行が抜けていないかを見ます。

仕上げに、視点を的のほうへ戻し、上下も少し見下ろすくらいにそろえてから、右クリックを押し続けます。0.2秒でカメラが近づいて右へずれ、キャラが画面の左半分に寄って、奥の的が画面の真ん中のすぐ右上に見えます。離すと元へ戻り、押し直しても途中から折り返します。

ポイントは2つです。

  • カメラを動かすときは、腕ごと動かす: 壁を調べているのは腕だけです。肩越しのずれも腕の付け根で作ると、壁の判定がずれた先まで付いてきます
  • 見上げの上限は、床に届く角度で決める: 台座の高さと腕の長さから、腕の先が床に届く角度が決まります。上限はそれより少し手前に置きます

感度や上下の反転をプレイヤーに選ばせるなら、設定画面の記事 の保存のしかたがそのまま使えます。

うまくいかないときは

症状原因直し方
Web に書き出すと、起動直後にマウスが捕まらないブラウザは、クリックなどの操作のあとでないとマウスを捕まえさせない画面をクリックしたら捕まえる作りのままにする
武器や盾を別の物理ボディとして持たせたら、何もない場所でカメラが顔に張り付く持たせた物の当たり判定に腕が当たっている。外したのは体だけその物の RID も add_excluded_object() に渡すか、そのレイヤーを腕の Collision Mask から外す
後ろに上り坂や段差があると、見上げの上限内でもカメラがキャラに寄るカメラ側の地面が高いと、腕の先が床に届く角度が小さくなる坂の多いステージでは、見上げの上限をさらに下げる。背後の段差が台座の高さ近くまであると、上限では防げない
右の壁に肩を付けて狙うと、画面に壁の中や向こうが映る右へずらした腕の付け根が壁に埋まり、腕が壁を正しく調べられない壁から少し離れて狙う。左側が空いていれば、左肩へ切り替える

おまけ:先に知っておくと良いこと

  • 左肩へ切り替える: SHOULDER の符号を反対にするだけです。右の壁に肩を付けて狙うときも、左側が空いていれば付け根が壁に埋まりません
  • 狙うときは画角も絞る: Camera3D の fov を 75 から 60 前後へ同じ Tween で動かすと、寄った感じが強まります
  • 少し遅れて追いかけるカメラ: SpringArm3D には追従を遅らせる設定がありません。台座の top_level(親の動きを受け継がない設定)をオンにして切り離し、キャラの頭の高さの点へ、物理の更新ごと(_physics_process())に少しずつ寄せる処理を自分で書きます。腕も物理の更新ごとに動くので、寄せる処理もそこへそろえます

まとめ

  • カメラは台座・腕・目に分ける: 台座が左右、腕が上下と長さ、目は映すだけ
  • 壁を調べるのは腕だけ: カメラの前の小さな四角錐で調べる。自分の体は add_excluded_object() で外す
  • 感度は1ピクセルあたりの角度で決める: screen_relative に掛ける。0.003 なら100ピクセルで約17度
  • 見上げは床に届く角度より手前で止める: 台座1.6メートル・腕4メートルなら約23度で届くので、見上げ20度、見下ろし60度
  • 肩越しは腕の付け根をずらす: 画面の真ん中が体から外れる量だけ。Tween は作り直す前に kill()

まずは実践のシーンで、PITCH_UP を見下ろしと同じ60度にして見上げてみてください。カメラが足元へ寄って体で画面がふさがるのを見ると、上限を分ける理由がつかめます。あなたのゲームで、肩越しに狙いたいのはどんな場面でしょうか。

次に読む学習パスの次の記事
3D

Godotの3D当たり判定入門:RigidBody3Dで木箱を倒し、Area3Dで宝箱の前を感知する

この記事を読む
Godot このセクションのノート104