FixedJointで「掴む」が成立した
LOG92R2R2R2026/3/14

FixedJointで「掴む」が成立した

ステートマシンのループは動いた。でもカップはアームに追従しなかった。原因はシンプルだった。connectedBody に渡すべき EndEffector の Rigidbody が存在していなかった。

その一点を修正して、把持を完成させた。

※注釈:デジタルツイン学習(R2R2R)の検証プロセス 本プロジェクトは、AIの判断を物理的動作へ変換する設計を検証するものです。現実空間をデジタルツイン化し、仮想空間での大量試行(Real-to-Render)から得た最適解を、実世界のモーター駆動(to-Real)へ還元する「R2R2Rパイプライン」の実装過程を記録・考察します。

問題:FixedJointのconnectedBodyがnull

LOG91で実装した把持コードはこうだった。

graspJoint = cup.AddComponent<FixedJoint>();
graspJoint.connectedBody = endEffector.GetComponent<Rigidbody>();

EndEffectorにRigidbodyがなければ connectedBody = null となり、FixedJointは宙に浮いた関節になる。カップはどこにも繋がれないので当然動かない。

修正:EndEffectorにRigidbodyを手動追加

  • Is Kinematic: ON(ArticulationBodyの末端なので必須)

  • Use Gravity: OFF

これだけでFixedJointが機能し始めた。

FixedJoint把持の仕組み

把持の実装方針はLOG91から変わっていない。親子関係(Parenting)ではなく、FixedJointを動的に生成・破棄する方式を採用している。

// GRASP:把持
graspJoint = cup.AddComponent<FixedJoint>();
graspJoint.connectedBody = endEffector.GetComponent<Rigidbody>();
graspJoint.breakForce  = Mathf.Infinity;
graspJoint.breakTorque = Mathf.Infinity;

// PLACE:解放
cupRb.velocity = Vector3.zero;
cupRb.angularVelocity = Vector3.zero;
Destroy(graspJoint);
graspJoint = null;

解放直前に速度をゼロリセットするのは、アームの慣性でカップが吹き飛ぶのを防ぐため。「精度より体験の成立を優先する」という設計哲学に従った処置だ。

ステートマシン全体像

REACH_CUP → GRASP → REACH_PLACE → PLACE → REACH_CUP(ループ)

各ステートの遷移条件:

ステート

遷移条件

REACH_CUP

dist ≤ 0.12m かつクールダウン終了

GRASP

FixedJoint生成完了(即遷移)

REACH_PLACE

dist ≤ 0.12m かつクールダウン終了

PLACE

FixedJoint破棄・カップリセット後に即遷移

確定したパラメータ

パラメータ

値

備考

graspThreshold

0.12m

Inspector設定

PlaceTarget Y

0.3

アームが自然姿勢を保てる高さ

CCD速度係数

0.05f

angle * 0.05f

PLACE後クールダウン

2.0秒

高速ループ防止

教訓

EndEffectorはArticulationBodyの末端だが、FixedJointのconnectedBodyにはRigidbodyが必要。

Unity物理の2つのシステム(ArticulationBodyとRigidbody)を橋渡しするとき、IsKinematic=ONのRigidbodyがインターフェースになる。この構造は今後も使う場面が出てくるはずだ。