【Godot】CharacterBody2Dで上下左右に移動する方法|壁の当たり判定も作る
この記事について
上から見下ろす2Dゲームでは、プレイヤーを上下左右へ動かし、壁に当たったら止める処理が基本になります。GodotではCharacterBody2Dに移動速度を設定し、move_and_slide()で衝突を調べながら動かします。
この記事では、矢印キーでキャラクターを動かし、オレンジ色の壁にぶつける場面を作ります。Godot 4.6.3のGDScriptを使い、エディターの項目名は英語表記で説明します。重力やジャンプを使わない見下ろし型の移動です。
この記事でわかること
- Input MapとInput.get_vector()で上下左右の入力を受け取る方法
- CharacterBody2DとCollisionShape2Dで壁にぶつかるキャラクターを作る方法
- velocityとmove_and_slide()の役割、およびdeltaを掛けない理由
基本手順
手順1:シーンと4方向の入力を用意する
空のプロジェクトでScene → New Sceneを開き、2D Sceneを選びます。ルートのNode2DをMainに名前変更し、res://main.tscnとして保存します。Project → Project Settings → Display → WindowでViewport Widthを800、Viewport Heightを520にします。
続けてProject SettingsのInput Mapタブを開きます。Add New Actionへmove_leftと入力してAddを押し、同じ方法でmove_right、move_up、move_downも追加します。それぞれのアクション右側の+からキー入力を登録し、次の組み合わせにします。
- move_left:左矢印キー
- move_right:右矢印キー
- move_up:上矢印キー
- move_down:下矢印キー
キー入力の追加画面で対応する矢印キーを押し、表示されたキーを確認してOKを押します。各アクションのDeadzoneは0.2にします。アクション名は後からコードで使うので、小文字とアンダースコアを含めて一致させます。

手順2:プレイヤーの画像と当たり判定を作る
Mainの子にCharacterBody2Dを追加し、Playerに名前変更します。PlayerのInspectorでTransform → PositionをX=160、Y=288、Motion ModeをFloatingにします。Floatingは床と天井を区別しない見下ろし型の移動に向いています。
Playerの子にSprite2DとCollisionShape2Dを追加します。移動用のキャラクター画像(ZIPをダウンロード)を保存して展開し、中のidle_0.svgをプロジェクトフォルダー内に作ったassetsフォルダーへ入れます。GodotのFileSystemに表示されたres://assets/idle_0.svgを、Sprite2DのTexture欄へドラッグします。
CollisionShape2Dを選び、Shapeの空欄からNew RectangleShape2Dを作成します。作成したリソースをクリックして展開し、SizeをX=32、Y=32にします。Sprite2DとCollisionShape2DのPositionはどちらもX=0、Y=0、ScaleはX=1、Y=1のままにします。
Main (Node2D) └ Player (CharacterBody2D) ├ Sprite2D └ CollisionShape2D
Sprite2Dは見た目、CollisionShape2Dは当たり判定です。今回の画像は40×40ピクセルですが、当たり判定は少し小さい32×32にしています。CollisionShape2DのDisabledとOne Way Collisionはオフにします。

手順3:ぶつかる壁と背景を置く
Mainの子にStaticBody2Dを追加してWallと名付け、PositionをX=432、Y=288にします。Wallの子へCollisionShape2Dを追加し、新しいRectangleShape2Dを作成します。SizeはX=48、Y=192です。PlayerのShapeを使い回さず、壁用のリソースを新しく作ります。
Wallの子にPolygon2Dを追加し、Visualに名前変更します。Visualを選び、InspectorのPolygon2D → Data → PolygonにあるPackedVec…と表示されたボタンをクリックして、頂点の配列を展開します。Sizeを4にして、要素0〜3のXとYへ次の値を入力します。VisualのPositionは(0, 0)のままです。
- 要素0:X=−24、Y=−96
- 要素1:X=24、Y=−96
- 要素2:X=24、Y=96
- 要素3:X=−24、Y=96
Colorをオレンジ色にすると壁の位置がわかりやすくなります。完成サンプルの色は、各成分を0〜1で表すとR=0.96、G=0.56、B=0.38、A=1です。数値欄が狭くて読みにくい場合は、Inspectorの左境界を左へドラッグして幅を広げてください。
Mainの子にもう1つPolygon2Dを作ってGroundと名付け、SceneドックでWallとPlayerより上へ移動します。Positionは(0, 0)のままにします。Visualと同じようにInspectorのPolygon2D → Data → Polygonを展開し、Sizeを4にして、次の値を入力します。
- 要素0:X=40、Y=144
- 要素1:X=760、Y=144
- 要素2:X=760、Y=464
- 要素3:X=40、Y=464
GroundのColorは暗い青にします。完成サンプルの色は、各成分を0〜1で表すとR=0.10、G=0.17、B=0.23、A=1です。Groundは見た目だけの背景なので、CollisionShape2Dは付けません。
Main (Node2D) ├ Ground (Polygon2D) ├ Wall (StaticBody2D) │ ├ Visual (Polygon2D) │ └ CollisionShape2D └ Player (CharacterBody2D) ├ Sprite2D └ CollisionShape2D
PlayerとWallのCollision → LayerとMaskは、どちらも1だけをオンにします。Layerは自分が所属する層、Maskは調べる相手の層です。この例では両方を同じ設定にそろえます。壁側のCollisionShape2DもDisabledとOne Way Collisionをオフにしてください。

手順4:velocityを設定してmove_and_slide()を呼ぶ
Playerを選んでAttach Scriptを押し、res://player.gdを作ります。Templateがオンなら外してください。スクリプト全体を次の内容にします。
extends CharacterBody2D
@export var speed: float = 200.0
func _physics_process(_delta: float) -> void:
var direction := Input.get_vector("move_left", "move_right", "move_up", "move_down")
velocity = direction * speed
move_and_slide()
Input.get_vector()へ渡す順番は「左、右、上、下」です。入力がなければdirectionは(0, 0)になるので、キーを離すとvelocityも0になります。左右と上下を同時に押してもベクトルの長さが1を超えないため、斜め移動だけ速度が速くなることを防げます。
speedは1秒あたりの移動量で、この例では200ピクセルです。velocityへ「方向×速度」を設定してから、move_and_slide()で実際に移動します。move_and_slide()は衝突すると速度を変更することがあるため、次の物理フレームでは入力からvelocityを設定し直します。
ここではvelocityにdeltaを掛けません。move_and_slide()が物理フレームの経過時間を使って移動量を計算するためです。引数の_deltaは使わないことを表す名前です。Godot 4ではmove_and_slide(velocity)という引数付きの書き方や、戻り値をvelocityへ代入する書き方もしません。詳しくは公式のvelocityの説明とInput.get_vector()の説明を参照できます。

手順5:上下左右の移動と壁沿いの移動を確かめる
シーンとスクリプトを保存し、Run Current Sceneからmain.tscnを実行します。Windows・Linuxの標準ショートカットはF6です。ゲーム画面をクリックしてから右矢印キーを押し、キャラクターが壁の左側で止まることを確認します。
続けて右矢印と上矢印を同時に押します。壁を突き抜けず、壁に沿って上へ移動できれば、衝突と滑りの処理が動いています。壁より上まで回り込んでから右へ進み、キーを離すと止まることも確かめます。
Debug → Visible Collision Shapesをオンにして実行すると、画像と当たり判定の位置を見比べられます。壁の左端はX=408、Playerの当たり判定の半幅は16なので、壁の左面に押し付けたときのPlayerの中心はX=392付近になります。衝突用の小さな余白があるため、小数点以下まで392と一致するとは限りません。

完成サンプルの画面上部には、Position、Actual speed、Wallという観察用の表示があります。順に位置、実際の速度、壁との衝突状態を表します。これらはHUDというCanvasLayerの表示で、プレイヤーを動かすために必須のノードではありません。また、Groundの外周には壁を作っていないため、背景の外へは移動できます。
うまくいかないときの確認箇所
キーを押しても動かない
最初にDebuggerのエラーを確認します。次に、ゲーム画面に入力フォーカスがあるか、4つのInput Map名とコードの文字列が一致するか、player.gdをPlayerへ付けたかを確認します。PlayerのInspectorに表示されるSpeedが0になっていないかも見てください。
壁をすり抜ける
WallがStaticBody2Dか、双方のCollisionShape2DにShapeが設定されているか、Disabledがオフかを調べます。CollisionShape2DはそれぞれPlayerとWallの直接の子にします。LayerとMaskを1に戻して試し、移動コードでpositionを直接書き換えていないかも確認します。Polygon2Dだけでは衝突しません。
移動が極端に遅い、斜めだけ速い
遅い場合はvelocityにdeltaを掛けていないか確認します。斜めだけ速い場合は、入力を独自に足し合わせるコードが残っていないか調べ、掲載したInput.get_vector()へそろえてください。
壁から離れて止まる、見た目が壁へ入り込む
Visible Collision Shapesで衝突形状を表示し、画像との大きさの違いを確かめます。この例の画像は40×40、衝突形状は32×32なので、画像の端が壁と少し重なることはあります。意図しないずれがあれば、子ノードのPositionとScale、ShapeのSizeを見直します。
まとめ
見下ろし型の移動は、Input.get_vector()で方向を読み、velocityへ速度を設定し、move_and_slide()で衝突を処理する流れです。見た目の画像と当たり判定は別に用意します。
最初は壁を1枚だけ置き、「自由に移動する」「壁で止まる」「壁に沿って滑る」の3つを確かめると、入力と衝突の問題を切り分けやすくなります。
サイトアイコン-2-150x150.png)