今回は Godot Engine の物理システムの応用として、Joint2D の使い方を紹介します。
目次
概要
Joint2D とは、StaticBody2D や RigidBody2D などの剛体を結合して特殊な動きを作るノードとなります。
- DampedSpringJoint2D: 「バネ」の動きをシミュレート
- GrooveJoint2D: 「溝」の動きをシミュレート。一定の区間をスライドさせる
- PinJoint2D: 回転の中心を固定する「ピン」
なお、RigidBody2Dについての理解が浅い場合は以下の記事がおすすめとなります。

今回使用するプロジェクト
今回使用するプロジェクトを添付しておきます。
プロジェクトの説明
Main.tscn: メインシーン
Main.tscn はメインシーンです。

このシーンに様々な剛体と Joint2Dを配置してきます。また F5 (macOSでは Cmd+B) で開始するシーンに設定しています。
Ball.tscn: ボールシーン
Ball.tscn は RigidBody2D の剛体です。

“Ball.gd” というスクリプトをアタッチしており、Spaceキーを押すことで、右方向に衝撃が加わるようになっています。
extends RigidBody2D
func _process(delta: float) -> void:
if Input.is_action_just_pressed("ui_accept"):
apply_central_impulse(Vector2(1000, 0))
Cross.tscn: 十字形の剛体
Cross.tscn は十字形の剛体です。

「PinJoint2D」で中心を固定して回転する剛体のサンプルとして用意しています。
Pin.tscn: ピン
Pin.tscn は「DampedSpringJoint2D」でバネの動きをシミュレートするときに、バネを固定するための剛体として用意したものです。

動きを固定したいので、RigidBody2Dですが動作モードは「static」となっています。

「動きを固定したいなら、StaticBody2Dで良いのでは…?」という気がしますが、滑車の動きをシミュレートする「GrooveJoint2D」で使用したいため RigidBody2Dとしています。このあたりは後ほど説明します。
Pulley.tscn: 滑車シーン
Pulleyシーンは、「GrooveJoint2D」を使用した滑車のすべる部分として使うシーンです。

DampedSpringJoint2Dの解説
プロジェクトの説明が終わったところで、「DampedSpringJoint2D」の使い方を紹介します。
まずは「Main」シーンを開きます。

BallシーンとPinシーンの配置
そうしたら「Ball.tscn」を中央あたりに配置し、「Pin.tscn」を上あたりに配置します。

試しに実行すると Ball は落下して、Pinは落下しません。

これは Pin の動作モードが static となっているためです。

DampedSpringJoint2Dの作成と配置
次に「DampedSpringJoint2D」ノードをシーンに追加します。

「DampedSpringJoint2D」が配置されたら、Pinのインスタンスのところまで移動させます。

DampedSpringJoint2Dのパラメータ設定
そしてインスペクタから Joint2D の「Node A」に「Pin」、「Node B」に「Ball」を割り当てます。

さらに「Length」の値を Pin と Ball の中心がつながるように変更します。

では、実行して動きを確認してみます。

このように、DampedSpringJoint2Dを使用すると、2つの剛体をバネでつなげることができます
Line2Dで接続をわかりやすくする
ただ、このままだとボールが空中に浮いているように見えるので、PinからBallまで線を引くようにしてみます。
Line2DノードをMainシーンの直下に追加します。

Line2Dの線の太さをインスペクタのWidthから「4」に変更して細くしておきます。

そして、Mainノードにスクリプトをアタッチします。

スクリプトは以下のように記述します。
extends Node2D
onready var pin = $Pin
onready var ball = $Ball
onready var line = $Line2D
func _ready() -> void:
# ポイントを2つ追加しておく
line.add_point(Vector2.ZERO)
line.add_point(Vector2.ZERO)
func _process(delta: float) -> void:
# Pinの座標を設定
line.points[0] = pin.position
# Ballの座標を設定
line.points[1] = ball.position
これで PinとBallの間に線が引かれるようになります

ここまでの完成プロジェクト
ここまでの完成プロジェクトを添付しておきます。もしうまく動かない場合は、このプロジェクトファイルを参考になるかもしれません。
GrooveJoint2Dの解説
続けて、GrooveJoint2D (溝ジョイント) の説明を行います。
Pulley.tscn を配置する
溝ジョイントに接続する剛体として「Pulley.tscn」を Pinに重ねて配置します。
注意点として、Pulleyの左端とPinが重なるようにします。

また、Pulleyシーンは Mainノードの直下に移動させることで、再背面の描画にします。
GrooveJoint2Dの作成と配置
そして GrooveJoint2D ノードを作成します。

作成した「GrooveJoint2D」を原点(左上)から、Pinの中心に移動させておきます。

GrooveJoint2Dのパラメータ設定
GrooveJoint2Dのインスペクタから、Node A に「Pulley」、Node B に「Pin」を割り当てます。

また GrooveJoint2Dの Rotation Degreesの値を「-90」にします

回転角度を「-90」にしたことで GrooveJoint2D が右向きとなります。

さらに「Length」の値を「312」あたり、「Initial Offset」を「1」にします。
- Length: GrooveJoint2Dの長さ(溝の長さ)
- Initial Offset: 溝の開始位置のオフセット

Pinノードの動作モードを「Rigid」にする
最後にPinノードを選択しておき……

Pinのインスペクタから Mode を Rigid に変更します。

これは、Pinを移動可能な剛体にするためです。
動作確認
では実行して動きを確認します。

このように GrooveJoint2D は指定した範囲のみ移動可能となる Joint2D となります。
ここまでの完成プロジェクト
ここまでの完成プロジェクトを添付しておきます。もしうまく動かない場合はこちらのプロジェクトと比較してみると良いかもしれません
PinJoint2Dの解説
最後に PinJoint2Dです。
Cross.tscn と Pin.tscn を配置する
Cross.tscn を配置します。できれば Ball を移動させると当たるような位置が良いと思います。
そして Pin.tscn を Cross の中央に配置します。

PinJoint2Dの作成と配置
そして PinJoint2D ノードを作成します。

そして原点(左上)から、Cross の中心に PinJoint2D を移動させます。

PinJoint2Dのパラメータ設定
PinJoint2D のインスペクタから、Node A に「Pin2」Node Bに「Cross」を割り当てます。

動きの確認
では実行して動きを確認してみます。

完成プロジェクト
PinJoint2Dを実装したプロジェクトを添付しておきます