その1:ジョイント編 の振り返り
Unityで産業用ロボット風クレーンを制作しました。摩擦を確実に確保するため、全可動部を物理演算で駆動させています。前回はHingeJointの角度制限をハックすることで移動時の揺れを克服しました。今回の「その2」では、圧力センサーを用いた最適な把持力(はじりょく)制御の実装を目指します。
摩擦力対重力の関係
二つの爪に挟まれたワークが落下しない条件を物理的に整理します。
ワークの質量を $m$、重力加速度を $g$、爪がワークを押す力を $F$、爪とワークの間の摩擦係数を $\mu$ とすると、ワークにかかる重力が最大静止摩擦力 $2\mu F$ よりも小さければワークは落ちません。
$$2\mu F > mg$$
計算例:みかん1個(約100g)にかかる重力は約1Nです。摩擦係数 $\mu$ を0.6とした場合、爪がワークを押す力 $F$ が約1N以上あれば理論上は保持できます。
今回は、以下の動作仕様を目指します:
- 角速度: 50度/秒で指を回転。
- 目標把持力: 3N(振動等による滑り落ちを考慮し、理論値1Nに対し安全圏の3倍を設定)。
感圧センサーの実装
今回の方式は、指をトルクで押し付けるのではなく、HingeJointの角度制限(Limits)を動かして強引に回転させる方式です。そのため、爪先の力が目標の3Nに達した瞬間に回転を止める(あるいは微調整する)ための「センサー」が必要になります。
Unityでは、Collision.impulse を利用することで感圧センサーを擬似的に実装できます。
Collision impulseとは
Unityのマニュアルによれば、Collision.impulse は衝突を解決するために加えられた合計の力積(Impulse)です。力積 $I$ は、力 $F$ と時間 $\Delta t$ の積で表されます。$$I = F \cdot \Delta t$$逆に言えば、力積を時間(FixedDeltaTime)で割ることで、そのフレームでかかっている力を算出できます。$$F = \frac{I}{\Delta t}$$
PressureSensor.cs
この理論に基づき、接触時の力と圧力を計算するスクリプトを作成し、指のオブジェクトにアタッチします。
using UnityEngine;
public class PressureSensor : MonoBehaviour
{
private float _lastForce;
public float LastForce => _lastForce;
private bool _isColliding;
public bool IsColliding => _isColliding;
private void OnCollisionEnter(Collision collision)
{
_isColliding = true;
CalculateForce(collision);
}
private void OnCollisionStay(Collision collision)
{
CalculateForce(collision);
}
private void OnCollisionExit(Collision collision)
{
_isColliding = false;
_lastForce = 0f;
}
private void CalculateForce(Collision collision)
{
var impulse = collision.impulse.magnitude;
if (collision.contactCount > 0)
{
// 力積をFixedDeltaTimeで割り、ニュートン(N)換算の力を求める
_lastForce = impulse / Time.fixedDeltaTime;
}
}
}
指定された力で爪をワークへ押し付ける動作の実現
物理挙動の精密化(Project Settings)
正確な制御のために、Unityの物理エンジン設定を調整します。
1. Fixed Delta Time の短縮
デフォルトの0.02s(50Hz)から 0.01s(100Hz) へ変更し、物理計算の解像度を高めます。
2. Default Contact Offset の調整
デフォルトの0.01(1cm)では、接触前から反発が始まってしまいます。これを 0.0005(0.5mm) まで下げ、見た目通りの接触で力が検知されるようにします。
3. Friction Type の選択
より正確な物理挙動のためには One/Two Directional が理想ですが、本プロジェクトでは安定性を優先し Patch Friction Type を採用しました。
Project Settings => Physics => Friction Type

注意: Patch Frictionの場合、Unity上の設定値は現実の約半分として扱われるため、摩擦係数0.3の設定で現実の0.6相当となります。
力に応じた角度制限(Limits)の動的制御
制御のキモは、HingeJointの limits.min/max を「目標の力」に合わせて微増減させる点にあります。
- 接近: 指を閉じ方向に回転させる。
- 減速: 衝突を検知したら、角速度を1/200(slowDownDenominator)に落とし、オーバーシュートを防ぐ。
- フィードバック: 目標値(3N)を超えたら、角度をわずかに戻す(forceReductionDenominator)。
ArmController.cs(主要ロジック抜粋)
void FixedUpdate()
{
// 接触中は制御を安定させるために回転速度を大幅に落とす
float gripSpeedAdjustment = (_pressureSensorL.IsColliding && _pressureSensorR.IsColliding)
? gripSpeed / slowDownDenominator : gripSpeed;
if (Keyboard.current.leftArrowKey.isPressed)
{
_targetGripAngle = Mathf.MoveTowards(_targetGripAngle, closedAngle, gripSpeedAdjustment * Time.deltaTime);
}
// 目標値(targetForce)を超えた場合、角度を微調整して力を一定に保つ
if (_targetGripAngle - _lastGripAngle > 0 &&
_pressureSensorL.LastForce > targetForce && _pressureSensorR.LastForce > targetForce)
{
_targetGripAngle = _lastGripAngle - (_pressureSensorL.LastForce + _pressureSensorR.LastForce - 2 * targetForce)
/ forceReductionDenominator;
}
SetHingeLimits(_hingeL, _targetGripAngle);
SetHingeLimits(_hingeR, _targetGripAngle);
_lastGripAngle = _targetGripAngle;
}
private void SetHingeLimits(HingeJoint hinge, float angle)
{
var limits = hinge.limits;
limits.min = angle - 0.01f; // ガタつき防止のため隙間を最小限に
limits.max = angle;
hinge.limits = limits;
}
動作確認
クレーンゲームとしてのピック動作が劇的に安定しました。
設定したターゲットフォースを維持してワークを掴み続けることができます。
把持力を1N以下に設定すると、持ち上げた瞬間にスルリと滑り落ちます。まさに物理学の教科書通りの挙動です。
「掴めたはずなのに、振動で滑り落ちる」というクレーンゲーム特有の絶妙な難しさを再現することができました。
まとめと次のステップ
今回はHingeJointと力積計算を組み合わせることで、擬似的なサーボモータと圧力センサーの系を構築しました。
次は、より高度なロボティクスシミュレーションに向けた手法である ArticulatedBody を使った実装にチャレンジします。
(2025/12/28 追記)今月参加した国際ロボット展、産業用ロボットの世界では、感圧センサーのことを力覚センサーと呼んでいた。あるブースでは、ロボットの指先にかかる力の大きさを力覚センサーで検知し食料品を柔らかく掴んでいた。
本記事で紹介した制作物
以下のGitHubプロジェクトで公開します。




