ハコブくんをUnityとROS 2で繋ぐ通信開通全記録。Docker × ROS-TCP-EndpointからHakobuMove.csまで
ファーストセットアップ
シミュレーションの舞台となる2.0m幅の廊下と抽象化されたカプセルが完成した。次に行うのは、この「身体(Unity)」に「知能(ROS 2)」を吹き込むための通信パイプラインの構築です。
※薬剤搬送ロボットハコブくんは技術検証用の仮想設定です。 実用化目的ではなく、制御・UIデザイン・HRI(人間とロボットの相互作用)の本質を探求するコンセプトとして定義しています。
ターミナルから「脳(Docker)」にアクセスする
まず、Macのローカル環境ではなく、ROS 2がインストールされているDockerコンテナ(仮想環境)内へ潜り込む。
Bash
# 1. Dockerコンテナの状態を確認
docker ps -a
# 2. シミュレーション用のコンテナ(ros2_sim)を起動
docker start ros2_sim
# 3. コンテナ内のBashシェルにログイン
docker exec -it ros2_sim bash左側のプロンプトが root@xxxxxx に変われば、そこはもうROS 2の世界だ(と思ったんですが)
失敗:トラブルシューティングと「脳」への接続プロセス

環境構築にトラブルはつきものだ。ターミナルに command not found: docker という冷ややかなメッセージが表示された。これは Mac が Docker というツールの存在をまだ知らない、あるいは眠っている状態であることを示している。
Docker Desktop を目覚めさせる

まず、アプリケーションフォルダから Docker(クジラのアイコン) を起動する。メニューバーのクジラがプカプカと安定し、ダッシュボードの左下が緑色(Engine Running)になるのを待つ。これがすべての前提条件だ。
コマンドを正確に流し込む
さきほど command not found と拒絶されたターミナルに戻り、改めてDockerがMacと同期しているかを確認する。
Bash
# まずはこれだけを打って、Dockerが反応するか確認する
docker ps -aもしここでリストが表示されたら、いよいよコンテナ(ROS 2 の入った箱)を動かし、その中へダイブする。
Bash
# コンテナの起動(名前が ros2_sim の場合)
docker start ros2_sim
# コンテナの中に入る
docker exec -it ros2_sim bash知能のダウンロードと部屋の作成
ターミナルに以下の強力なコマンドを打ち込む。これは、ROS 2 Humble(最新の安定版)をダウンロードし、Unityとの通信ポート(10000番)を開放した状態で、ros2_sim という名前の部屋を作る命令だ。
Bash
docker run -it --name ros2_sim -p 10000:10000 osrf/ros:humble-desktop bash
環境のパーソナライズ

真っさらな部屋に入ったら、まずは自分がどこにいるかを確認し、ハコブくんの制御に必要なツールを揃えていく。プロンプトが root@xxxxxx:/# になれば、そこはもうMacの制約を離れた、ピュアなロボット開発環境だ。
Unityとの握手の準備
部屋(コンテナ)の中で、Unityからの信号を受け取るための ros_tcp_endpoint をインストールする。これにより、Mac上のUnityとDocker内のROS 2が、10000番ポートという専用の「糸電話」で繋がることになる。
脳内の知識を最新にアップデートする

まずは、コンテナ内のパッケージマネージャを更新し、新しいツールを受け入れる準備を整える。
Bash
# パッケージリストの更新
apt updateUnityとの通信用「電話機(TCP Endpoint)」を設置
Unity公式が提供している通信用パッケージをインストールする。これにより、ROS 2 のトピックを Unity へ届けるための「窓口」が完成する。
Bash
# 通信用エンドポイントのインストール
apt install -y ros-humble-ros-tcp-endpointエラー:パッケージ不在の壁をビルドで突破する

標準コマンドでツールが見つからない。そんな時こそ、GitHubから直接ソースコードを引き出し、自分の手で知能を組み上げる「ビルド」の出番だ。
開発用の作業場(ワークスペース)を作る
ROS 2では、自分のツールを置くための専用ディレクトリ(作業場)を作るのが作法だ。
Bash
# 1. ワークスペースとソースフォルダを作成
mkdir -p /ros2_ws/src
cd /ros2_ws/srcGitHubから通信プログラムをダウンロードする
Unity Technologiesが公開している最新の通信プログラムを、直接コンテナ内へ引き込む。
Bash
# 2. Gitをインストールし、ソースコードをクローン(複製)
apt update && apt install -y git python3-pip python3-colcon-common-extensions
git clone https://github.com/Unity-Technologies/ROS-TCP-Endpoint.git -b main-ros2ビルド(構築)の実行
バラバラのソースコードを、ハコブくんの知能として機能するように一本化する。
Bash
# 3. ワークスペースのルートへ戻り、ビルドを実行
cd /ros2_ws
colcon build(※ここで画面にたくさんの文字が流れますが、最後に Finished と出れば成功だ。)
知能をシステムに馴染ませる
ビルドした機能を、いつでも使えるように設定ファイル(.bashrc)へ登録する。
Bash
# 4. 新しく作ったツールの設定を読み込む
echo "source /ros2_ws/install/setup.bash" >> ~/.bashrc
source ~/.bashrc現在の root@...:/# の画面で、以下の固まり(ブロック)ごとにコピーして貼り付け、Enterを押してください。
準備とダウンロード
Bash
mkdir -p /ros2_ws/src && cd /ros2_ws/src
apt update && apt install -y git python3-pip python3-colcon-common-extensions
git clone https://github.com/Unity-Technologies/ROS-TCP-Endpoint.git -b main-ros2ビルドの実行
Bash
cd /ros2_ws
colcon build最終設定
Bash
echo "source /ros2_ws/install/setup.bash" >> ~/.bashrc
source ~/.bashrc完了の合図

これらがすべて終わった後、以下のコマンドを打ってみてください。
Bash
ros2 run ros_tcp_endpoint default_server_endpoint --ros-args -p ROS_IP:=0.0.0.0もし Starting server on 0.0.0.0:10000 と表示されたら、通信の開通作業は100%成功です。
Unity側での確認

Unity エディタへと移ります。
上部メニューの Robotics > ROS Settings を開き、
ROS IP Addressが127.0.0.1になっていることを確認。Scene画面で 再生(Play)ボタン をクリック。
画面左上の HUD(表示パネル)に 青い矢印アイコン と 「Connected」 の文字が浮かび上がる。
この瞬間、2.0mのデジタル廊下に立つ「カプセル」は、単なる3Dモデルから、ROS 2 という知能によって制御可能な「ロボット」へと進化しました。
カメラワークの修正。

システムは繋がった。しかし、Unityの「Gameビュー」には何も映っていない。インスペクターを確認すると、Main Camera の Position Y が -24.77 という、地面の下のはるか奈落に位置していた。これでは、2.0mの廊下で躍動するハコブくんを捉えることはできない。
カメラのトランスフォーム(位置・角度)を再設定
現実の撮影と同じように、カメラをハコブくんたちがよく見える場所へ移動させる。
Hierarchy で
Main Cameraを選択。Inspector の Transform に以下の数値を入力し、俯瞰(ふかん)で全体が見える位置にセットする。
Position:
X: 0, Y: 12, Z: -2Rotation:
X: 80, Y: 0, Z: 0
これで、1mおきのグリッドが敷かれた2.0m幅の廊下と、オレンジの看護師、白いハコブくんが画面中央に収まるはずだ。
沈黙するHUD。通信の実在を証明するためのデバッグ
設定はチェックした。スケールも戻した。しかし、画面左上にあの「Connected」の文字が現れない。このままではシミュレーションが本当にROS 2の知能で動いているのか、確信が持てない。この「沈黙」を破るため、3つのチェックポイントを設けた。
コンソールに隠されたエラーの捜索
HUD(画面上の表示)が沈黙していても、Unityの内部システム(Console)は真実を語っているはずだ。プロジェクト・ビューの隣にある Consoleタブ を開き、赤いエラーが出ていないか確認する。
もし SocketException や Connection Refused と出ていれば、物理的な接続自体が拒絶されている。
コンポーネントの実体化確認
UnityのRoboticsパッケージにおいて、HUDを表示させるのは ROSConnection というスクリプトの役割だ。これがシーン内のどこかのオブジェクト(例えば Hakobu_kun 本体)にアタッチされているか、あるいは Global なオブジェクトとして存在しているかを再確認する。
Hierarchy で
Hakobu_kunを選択し、Inspector を確認。もし
ROS Connectionコンポーネントが見当たらなければ、手動で Add Component > ROS Connection を追加し、Topic Nameなどの紐付けを再度チェックする。
物理的な再起動という最終手段
設定変更(Show HUDのチェックなど)が正しく反映されるよう、一度 Unityを完全に停止(Playオフ) し、ROSの設定画面を閉じてから、再度再生ボタンを叩く。これによりキャッシュされていた古い設定がクリアされ、HUDが正しく描画されるようになることが多い。
見落としていた接続の核。ROS Connection コンポーネントの手動配置

「Show HUD」を有効にしても HUD が現れない謎。その答えは Inspector の中にあった。Unity と ROS 2 を繋ぐための実体となる ROS Connection コンポーネントが、シーン内のどのオブジェクトにもアタッチされていなかった。設定画面(ROS Settings)はあくまで「設計図」であり、実際に通信を行う「電話機」を現場に置く作業が必要だった。
現場(ハコブくん)への電話機設置
ハコブくん自身に通信機能を持たせるため、Hakobu_kun オブジェクトに直接コンポーネントを追加する。
Hierarchy で
Hakobu_kunを選択。Inspector の一番下にある [Add Component] をクリック。
検索窓に
ROS Connectionと入力し、表示されたスクリプトを追加する。
インスタンスの紐付け
追加された ROS Connection の項目を確認する。通常、ここには先ほど設定した 127.0.0.1 や 10000 といった情報が自動で引き継がれる。これにより、ハコブくんは自分自身の状態をどのサーバーへ送るべきかを理解する。
再接続と HUD の覚醒
コンポーネントが正常に配置された状態で、三度再生ボタンを叩く。 今度は見切れることも、沈黙することもなく、Game ビューの左上に鮮やかな青い矢印と共に Connected のステータスが浮かび上がった。これで、デジタル双子(デジタルツイン)としてのハコブくんの「開通」が、名実ともに完了した。
ハコブくん初走行?だけど
ついに、これまでバラバラに構築してきた「2.0m幅の廊下」「ハコブくんの身体」「ROS 2という知能」が一つに繋がり、命が吹き込まれる瞬間がやってきました。
実寸大のシミュレーション空間で、初めてロボットが動き出すプロセスを記事として記録します。
Unity HUDの「Connected」
Unityエディタの再生(Play)ボタンをクリックする。画面左上のHUDを確認すると、青い矢印と共に Connected to 127.0.0.1:10000 の文字。Docker内のROS 2とUnityが、完全に同期している。
キーボード・テレオペレーション
サーバー(ROS-TCP-Endpoint)を動かしているターミナルはそのままに、Command + T で新しいタブを開き、ハコブくんへの命令系統を起動する。
Bash
# 司令室(Dockerコンテナ)へログイン
docker exec -it ros2_sim bash
# キーボード操作ノードを起動
ros2 run teleop_twist_keyboard teleop_twist_keyboard
ターミナル上に現れる操作ガイド。ここでの一打が、仮想世界の物理現象へと変換される。
iキーによる前進
ターミナルをアクティブにした状態で、運命の i キーを叩く。
通信を「動き」に変えるプログラムの実装
画面左上に輝くConnectedの文字。DockerとUnityの通信パイプラインは開通した。しかし、ターミナルで i キーを叩いても、ハコブくんは微動だにしない。
Unity側で「送られてきた速度指令(cmd_vel)を読み取り、座標を動かす」という翻訳作業がまだ実装されていないからとのこと。
司令塔「HakobuMove.cs」の確認
通信の開通を確認した次は、いよいよハコブくんに移動のルールを教え込む。Unity 6 の新しいインターフェースに従い、制御の要となるスクリプトを作成し、AIエディタ Cursor で実装を進める。
MonoBehaviour Scriptの作成
Unity の Project タブ(Assets フォルダ内) の何もないところで右クリック。メニューから Create > MonoBehaviour Script を選択する。これが、かつての「C# Script」に相当する、Unity で最も一般的なプログラムの器だ。
命名の瞬間:HakobuMove.cs
ファイル名が青く反転している間に、迷わず HakobuMove と入力して Enter を押す。
POINT: Unity の世界では、ファイル名とコード内の「クラス名」が一致していることが絶対条件だ。ここで名付けた
HakobuMoveが、そのままハコブくんの意志(クラス)の名前になる。
Cursor への移行と実装
作成した HakobuMove.cs をダブルクリックし、あらかじめインストールしておいた Cursor を立ち上げる。VS Code の操作感に AI が統合されたこのエディタなら、ROS 2 から届く「Twist メッセージ」を Unity の動きに変換するコードも、一寸の狂いなく記述できる。
既存の雛形をすべて削除し、以下の「翻訳ロジック」を流し込んで保存(Command + S)する。
C#
using UnityEngine;
using Unity.Robotics.ROSTCPConnector;
using RosMessageTypes.Geometry; // ROSのTwistメッセージを使用
public class HakobuMove : MonoBehaviour
{
ROSConnection ros;
public string topicName = "/cmd_vel"; // キーボード操作の標準チャンネル名
void Start()
{
// 通信のインスタンスを取得し、受信(Subscribe)を開始
ros = ROSConnection.GetOrCreateInstance();
ros.Subscribe<TwistMsg>(topicName, MoveHakobu);
}
void MoveHakobu(TwistMsg msg)
{
// ROSの速度指令をUnityの座標系に変換して適用
float linear = (float)msg.linear.x; // 前進速度
float angular = (float)msg.angular.z; // 回転速度
// ハコブくんの座標を動かす
transform.Translate(Vector3.forward * linear * Time.deltaTime);
transform.Rotate(Vector3.up * -angular * Time.deltaTime * 50f);
}
}
ハコブくんへのアタッチ
Unity に戻り、コンパイル(翻訳)が終わるのを待つ。その後、Hierarchy 上の Hakobu_kun オブジェクトに、この HakobuMove スクリプトをドラッグ&ドロップ。これで、ハコブくんは「自分の足で歩く」ための準備を完了した。
接続成功、しかし不動。
画面には「Connected」の文字。DockerとUnityは確かに握手している。しかし、ターミナルで i キーを叩いてもハコブくんは動かない。私は、この「沈黙」の裏に隠された、ROS 2 特有のトピック・サブスクリプションの不備を突き止めた。
HUDが語る「沈黙」の理由
Unity画面左上のHUDを確認する。表示されているのは接続先のIPアドレスのみ。本来、スクリプトが正常に動作していれば、そこに購読中のトピック名 /cmd_vel が現れるはずだ。これがないということは、スクリプト HakobuMove.cs が実行されていないか、通信の紐付けに失敗していることを意味する。
インスペクターの「コンポーネント」を再構成
インスペクターを確認すると、HakobuMove オブジェクトには Hakobu Move (Script) だけが存在し、ROS Connection コンポーネントが同居していない。 スクリプトが「どこからデータを取ればいいか」を迷わないよう、同じオブジェクトに直接「通信機」を搭載し直す必要がある。
トピック名の「完全一致」
ROS 2 は大文字・小文字、スラッシュの有無に極めて厳格だ。ターミナルの teleop ノードが投げている「声」と、ハコブくんが待ち構えている「住所」が、一文字の狂いもなく一致しているか。この調整こそが、自律走行への第一歩となる。
通信の「成功」と「断絶」が交差する瞬間

ターミナルに流れる無機質なテキスト。そこには、ハコブくんの「脳(ROS 2)」が「体(Unity)」と交わした対話のすべてが記録されている。エラーが出ているが、絶望する必要はない。むしろ、このログこそが完全開通への地図なのだ。
歓喜のログ:RegisterSubscriber(/cmd_vel) OK
ログの中ほどにあるこの一行。これこそが、私が最も待ち望んでいた言葉だ。 RegisterSubscriber(/cmd_vel, <class 'geometry_msgs.msg._twist.Twist'>) OK これは、Unity側の HakobuMove.cs が正常に起動し、ROSに対して「スピードの命令(/cmd_vel)を受け取る準備ができたよ!」と宣言し、ROS側がそれを承認したことを意味している。つまり、プログラムのロジック自体は完全に繋がっているのだ。
謎のエラー:Exception: No more data available
一方で、赤文字で表示されている ERROR。 これは、通信経路が突然途切れたときに出る典型的なメッセージだ。 Unityの再生ボタンを連打したり、スクリプトを修正したあとに再生し直したりすると、古い接続が残ったまま新しい接続を作ろうとして、このようなデータの供給不足が起きることがある。
切断の正体:Disconnected from 192.168.65.1
最後に出ている Disconnected。 192.168.65.1 は、Dockerから見たホストマシン(Mac)の住所だ。これが表示されているということは、一度は繋がったが、今は手が離れてしまった状態を示している。
この断絶を解消して動かすための手順
ROS側(ターミナル)が混乱している状態です。一度きれいに掃除して、再接続します。
ターミナルの「第1タブ」(Endpointを動かしている方)を一度止める
ターミナルをクリックして、
Control + Cを押してサーバーを止めます。
Unityの「再生(Play)」も一度止める
きれいな順番で立ち上げ直す
ターミナルで、例の長いコマンド(ros2 run ros_tcp_endpoint ...)を打ち、サーバーを起動。
(Starting server on 0.0.0.0:10000 と出ればOK)
Unityで再生ボタンを押す。
(左上に青い「Connected」が出ることを確認)
ターミナルの「第2タブ」(teleopを動かしている方)で、
iキーを叩く。
ハコブくん、デジタル世界の第一歩。
数時間前まで、2.0m幅の廊下に佇む白いカプセルは、ただの静止した3Dモデルに過ぎなかった。しかし今、私の手元のキーボード(teleop_twist_keyboard)から放たれた i キーの信号は、MacのOSを越え、Dockerコンテナ内のROS 2 Humbleという「脳」を駆け抜け、10000番ポートという「デジタル糸電話」を伝ってUnityへ届いた。
統合された「デジタルツイン」の完成
HakobuMove.cs が cmd_vel(速度指令)を受け取った瞬間、ハコブくんの Position Z が 0.5 から刻々と変化を始めた。これは単なる座標の書き換えではない。ROS 2 という本格的なロボット OS の意思が、Unity の物理世界において「移動」という事象に変換された、記念すべき第一歩だ。
苦難の末に掴んだ「Connected」
振り返れば、道筋は決して平坦ではなかった。
Docker Desktop の起動に始まり、ROS-TCP-Endpoint の手動ビルドという技術的な深淵に触れ、SocketException や Timeout という目に見えない通信の断絶と戦った。 しかし、そのすべてのエラーログこそが、ハコブくんの神経網をより強固なものにするための血肉となったのだ。
ハコブくん、2.0mの廊下を征く
オレンジ色の看護師さんの横を、滑らかに(あるいは powerMultiplier によって力強く)通り過ぎていくハコブくん。インスペクターの数値が 5.6 を越え、視覚的にも物理的にも「自走するロボット」としての命が吹き込まれた。
ここから先、ハコブくんの旅はさらに加速する。次は壁を検知する「眼(センサー)」を持つのか、あるいは目的地へ自動で向かう「計画(ナビゲーション)」を手に入れるのか。2.0mの廊下から始まったこの物語は、今、無限の可能性へと繋がった。
