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 |
|
PLACE後クールダウン | 2.0秒 | 高速ループ防止 |
教訓
EndEffectorはArticulationBodyの末端だが、FixedJointのconnectedBodyにはRigidbodyが必要。
Unity物理の2つのシステム(ArticulationBodyとRigidbody)を橋渡しするとき、IsKinematic=ONのRigidbodyがインターフェースになる。この構造は今後も使う場面が出てくるはずだ。
