ハコブくん、ついに目と声を持つ。デジタルツインで実現するロボットの安全制御
ハコブくんに「近接センサー」を搭載
ついに、ハコブくんに周囲の状況を察知するための「目(センサー)」を搭載する作業を行いました。 今回使用したのは、UnityのRaycast(レイキャスト)という機能です。これは現実のロボットのLiDARや超音波センサーと同じように、目に見えないレーザーを飛ばして対象物までの距離を測る技術です。
この実装により、ハコブくんは単に命令通りに動くだけのマシンから、周囲の状況を判断して自ら事故を防ぐ「知的なロボット」へと進化しました。
※薬剤搬送ロボットハコブくんは技術検証用の仮想設定です。 実用化目的ではなく、制御・UIデザイン・HRI(人間とロボットの相互作用)の本質を探求するコンセプトとして定義しています。
Raycastによる距離測定の実装
まず、Unityの Physics.Raycast を使い、ハコブくんの正面に「見えない杖」を持たせる処理を記述しました。 床から少し浮いた位置から前方1.5m先まで常にレーザーを照射し、障害物がないかを確認し続ける設定です。これがハコブくんの安全を守る守護神となります。
物理エンジンへの安全介入
次に、ROS 2からの命令とUnityの物理演算の間に、安全のための介入処理を加えました。 たとえROS 2側から「速度0.5で前進せよ」という命令が届き続けていても、Unity側のセンサーが壁を検知した瞬間に、プログラム内で速度を強制的に 0 へ書き換える仕組みです。 「脳(ROS)」が前進を指示しても、「身体(Unity)」が危険を察知して足を止める。自律型ロボットにおける安全制御の基本を形にしました。
壁の手前で止まるハコブくん
実際にターミナルから i キーを長押しし、ハコブくんを壁に向かわせるテストを行いました。 壁に激突するかと思われた瞬間、ハコブくんは壁のわずか手前でピタリと停止しました。コンソールには 「壁を検知!自動ブレーキ作動!!」 という警告が表示され、正しく安全機能が働いていることを確認できました。
ハコブくんを安全仕様にするためのコード修正
HakobuMove.cs をアップデートし、FixedUpdate 内にセンサーのロジックを組み込みました。
C#
using UnityEngine;
using Unity.Robotics.ROSTCPConnector;
using RosMessageTypes.Geometry;
[RequireComponent(typeof(Rigidbody))]
public class HakobuMove : MonoBehaviour
{
public ROSConnection ros;
public string topicName = "/cmd_vel";
private Rigidbody rb;
private Vector3 lastLinearVelocity;
private float lastAngularVelocity;
[Header("Sensor Settings")]
public float sensorRange = 1.5f; // 1.5メートル以内に壁があれば停止
void Start()
{
rb = GetComponent<Rigidbody>();
rb.useGravity = true;
// 転倒防止の設定
rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
if (ros == null) { ros = ROSConnection.GetOrCreateInstance(); }
ros.Subscribe<TwistMsg>(topicName, MoveHakobu);
Debug.Log("ハコブくん、センサーシステム起動完了。");
}
void MoveHakobu(TwistMsg msg)
{
// ROSの座標系をUnityの前進方向へ変換
lastLinearVelocity = transform.forward * (float)msg.linear.x;
lastAngularVelocity = -(float)msg.angular.z * Mathf.Rad2Deg;
}
void FixedUpdate()
{
float powerMultiplier = 3.0f;
float currentSpeed = lastLinearVelocity.magnitude;
// --- Raycastによる前方検知 ---
RaycastHit hit;
// 前方にセンサーを飛ばす
bool isObstacleAhead = Physics.Raycast(transform.position, transform.forward, out hit, sensorRange);
if (isObstacleAhead)
{
// 壁を検知した場合、前進速度を0にする(後退は許可)
currentSpeed = 0;
Debug.LogWarning("壁を検知!自動ブレーキ作動!! 対象: " + hit.collider.name);
}
// 物理エンジンへの速度適用
rb.linearVelocity = transform.forward * (currentSpeed * powerMultiplier);
rb.angularVelocity = new Vector3(0, lastAngularVelocity * Mathf.Deg2Rad, 0);
// Scene画面での視覚化(検知時は赤、安全時は緑)
Debug.DrawRay(transform.position, transform.forward * sensorRange, isObstacleAhead ? Color.red : Color.green);
}
}
作業完了後の最終確認
今回の実装が正しく反映されているか、以下の手順で動作確認を行いました。
壁のCollider(衝突判定)の確認 廊下の壁(Cube)を選択し、Inspectorで Box Collider が付いていることを確認しました。これがないとセンサーが壁を突き抜けてしまうためです。
Sceneビューでの可視化テスト 再生(Play)ボタンを押し、ハコブくんを壁に近づけました。Scene画面上で、前方のデバッグ用ラインが壁に触れた瞬間に 緑から赤へ変わることを確認しました。
動作確認の成果
壁に向かって
iキーを押し続けても、ぶつかる直前で確実に停止しました。コンソールに「自動ブレーキ作動!!」のメッセージが表示されました。
以上の手順により、ハコブくんを安全機能付きのインテリジェント・ロボットへとアップグレードすることができました。
センサー感度の微調整
自動ブレーキのロジック自体は完璧でしたが、再生した瞬間にハコブくんが「壁だ!」と判断して停止してしまいました。インスペクターを確認すると、検知対象は本来避けるべき前方ではなく、横の壁(Wall_Left)でした。 そこで、センサーの視界と壁との距離感を適正にするための対策を講じました。
自分の体への誤反応を防ぐオフセット設定
これまではハコブくんの中心(transform.position)からレーザーを飛ばしていましたが、これだとハコブくん自身の厚みによって、自分自身のコライダーを「障害物」と誤判定してしまう可能性がありました。 そこで、レーザーの開始地点を少し前方にずらし、ハコブくんの「おでこ」から光が出るようにプログラムを修正しました。
廊下の幅とセンサー射程の最適化
2.0m幅の廊下に対して、センサー射程の 1.5m は少し敏感すぎたようです。また、壁の位置が初期設定のままだと走行ルートに干渉していたため、壁の配置を見直しました。 具体的には、センサーの射程を 0.5m 程度に短縮し、適度な距離感を保てるようにしました。
調整の結果:スムーズな走行と確実な停止
これらの調整を行った結果、起動直後に止まってしまう現象が解消されました。 i キーを叩くとハコブくんは軽快に廊下を進み、本当に危険な突き当たりの壁に近づいた時だけ、静かに、そして確実に自動ブレーキがかかるようになりました。
センサーを最適化するコードの修正内容
自分自身の誤検知を防ぐために、レーザーの発射地点を少し前にずらす(オフセット)処理を追加しました。
C#
void FixedUpdate()
{
float powerMultiplier = 3.0f;
float currentSpeed = lastLinearVelocity.magnitude;
// --- 修正点:発射地点を少し前にずらす ---
// transform.forward * 0.4f を足すことで、自分自身のコライダーに当たるのを防ぎます
Vector3 rayStart = transform.position + transform.forward * 0.4f;
RaycastHit hit;
bool isObstacleAhead = Physics.Raycast(rayStart, transform.forward, out hit, sensorRange);
if (isObstacleAhead)
{
// 当たった対象が自分自身(Hakobu_kun)でないことを確認してブレーキをかけます
if (hit.collider.gameObject != this.gameObject)
{
currentSpeed = 0;
Debug.LogWarning("壁を検知!自動ブレーキ作動!! 対象: " + hit.collider.name);
}
}
// 物理エンジンへの適用
rb.linearVelocity = transform.forward * (currentSpeed * powerMultiplier);
rb.angularVelocity = new Vector3(0, lastAngularVelocity * Mathf.Rad2Deg, 0);
// デバッグ用の線も新しい開始地点に合わせます
Debug.DrawRay(rayStart, transform.forward * sensorRange, isObstacleAhead ? Color.red : Color.green);
}インスペクターでの設定変更
コードの修正に合わせ、Unityエディタ上で以下の微調整を行いました。
センサー射程(Sensor Range)の変更
Hakobu_kunを選択し、Hakobu Moveスクリプト内のSensor Rangeを0.5に設定しました。
廊下の幅(壁の位置)の調整
廊下を 2.0m 幅として正しく機能させるため、
Wall_Leftの Position X を-1.0、Wall_Rightの Position X を1.0に移動させ、ハコブくんの通り道を確保しました。
警告音の実装記録
自動ブレーキが正常に機能するようになりましたが、静かに止まるだけでは「なぜ止まったのか」が分かりにくい場合があります。そこで、センサーが壁を検知した瞬間に「ポーン!」という警告音を鳴らす機能を加えました。 これにより、ハコブくんは視覚的な回避だけでなく、聴覚的なコミュニケーション能力を手に入れました。
AudioSourceコンポーネントの搭載
まず、ハコブくんにスピーカーの役割を果たす AudioSource コンポーネントを追加しました。 Unityでは、このコンポーネントが音を再生する心臓部となります。今回は起動時に勝手に音が鳴らないよう Play On Awake をオフにし、プログラムから必要なタイミングでだけ鳴らす設定にしました。
鳴らしっぱなしを防ぐ制御ロジック
センサーが壁を検知している間、毎フレーム音が鳴ってしまうと「ポポポポポン!」と騒がしくなってしまいます。 そこで、「まだ音が鳴っていなければ鳴らす」というフラグ管理、あるいは PlayOneShot という機能を使うことで、壁を見つけた瞬間に一度だけ、きれいに音が響くように工夫しました。
警告音と共に止まるハコブくん
再びテスト走行を行いました。
壁に近づき、デバッグの線が赤に変わったその瞬間、スピーカーから「ポーン!」という軽快な警告音が響きました。停止の理由が音で伝わるようになり、シミュレーションとしての臨場感と安全性が一段と向上しました。
音声を組み込んだコードの修正内容
HakobuMove.cs に、オーディオを制御するための変数を追加しました。
C#
using UnityEngine;
using Unity.Robotics.ROSTCPConnector;
using RosMessageTypes.Geometry;
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(AudioSource))] // 自動でAudioSourceを追加
public class HakobuMove : MonoBehaviour
{
public ROSConnection ros;
public string topicName = "/cmd_vel";
private Rigidbody rb;
private AudioSource audioSource; // オーディオ用の変数
public AudioClip warningSound; // 再生する音のファイル
private bool isWarningPlayed = false; // 音の重複再生防止
private Vector3 lastLinearVelocity;
private float lastAngularVelocity;
[Header("Sensor Settings")]
public float sensorRange = 0.8f;
void Start()
{
rb = GetComponent<Rigidbody>();
audioSource = GetComponent<AudioSource>();
rb.useGravity = true;
rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
if (ros == null) { ros = ROSConnection.GetOrCreateInstance(); }
ros.Subscribe<TwistMsg>(topicName, MoveHakobu);
Debug.Log("ハコブくん、オーディオシステム起動。");
}
void MoveHakobu(TwistMsg msg)
{
lastLinearVelocity = transform.forward * (float)msg.linear.x;
lastAngularVelocity = -(float)msg.angular.z * Mathf.Rad2Deg;
}
void FixedUpdate()
{
float powerMultiplier = 3.0f;
float currentSpeed = lastLinearVelocity.magnitude;
Vector3 rayStart = transform.position + transform.forward * 0.4f;
RaycastHit hit;
bool isObstacleAhead = Physics.Raycast(rayStart, transform.forward, out hit, sensorRange);
if (isObstacleAhead)
{
if (hit.collider.gameObject != this.gameObject)
{
currentSpeed = 0;
// --- 修正点:音がまだ鳴っていなければ鳴らす ---
if (!isWarningPlayed && warningSound != null)
{
audioSource.PlayOneShot(warningSound);
isWarningPlayed = true; // 鳴らしたことを記録
Debug.LogWarning("壁を検知!自動ブレーキ作動!!");
}
}
}
else
{
// 壁がなくなれば、フラグをリセットして次回の検知に備える
isWarningPlayed = false;
}
rb.linearVelocity = transform.forward * (currentSpeed * powerMultiplier);
rb.angularVelocity = new Vector3(0, lastAngularVelocity * Mathf.Deg2Rad, 0);
Debug.DrawRay(rayStart, transform.forward * sensorRange, isObstacleAhead ? Color.red : Color.green);
}
}
Unityエディタでの設定手順
コードの保存が終わったら、最後にUnity上で音の設定を完了させました。
音源ファイル(WAVやMP3)の準備: 「ポーン」という短い音ファイルを、Projectウィンドウにドラッグ&ドロップしてインポートします。
スクリプトへの紐付け:
Hakobu_kunを選択し、Inspectorに新しく現れたWarning Soundの空欄に、準備した音ファイルをセットしました。AudioSourceの確認: 自動で追加された AudioSource コンポーネントの「Play On Awake」がオフになっていることを確認しました。
ハコブくんの警告音ダウンロード
ハコブくんの安全機能をより確実なものにするため、センサーが反応した際に周囲へ注意を促す「警告音」を準備することにしました。 ただ停止するだけでなく、短く印象的な音を鳴らすことで、シミュレーションとしての臨場感と実用性を高めるのが狙いです。
適切な音源の選定
ハコブくんにふさわしい「声」を探すため、無料の音素材サイトを活用しました。ロボットの電子音やシステム音に適した素材が豊富に揃っており、非常に助かりました。
OtoLogic: 清潔感のある電子音が多いため、病院の廊下を走るハコブくんのイメージに合う素材を選びました。
音源のダウンロード
まずはテストとして、以下のサイトなどで「決定音」や「通知音」を探してダウンロードします。
おすすめ:短く歯切れの良い音。
Unityへのインポート作業
入手したWAVファイルやMP3ファイルを、Unityの Projectウィンドウ(Assetsフォルダ)の中にドラッグ&ドロップして取り込みました。 Unityが自動的にファイルを認識してくれるため、これでいつでもハコブくんが「発声」できる準備が整いました。
スクリプトへの実装
ハコブくん(Hakobu_kun)のインスペクターにある Warning Sound の空欄に、取り込んだ音ファイルを割り当てました。 これにより、プログラム上の warningSound という変数と実際の音源が紐付き、センサーが壁を検知した瞬間に音が鳴るようになりました。
