使用ROS-Unity实现宇树人型机器人电机数据实时获取
引言
在机器人开发领域,ROS(Robot Operating System)作为行业标准框架,与Unity游戏引擎的结合正在开创全新的可能性。这种融合不仅让机器人仿真更加逼真,还为实时监控和控制提供了强大工具。本文将详细介绍如何配置ROS 2与Unity之间的通信系统,实现对宇树G1人型机器人电机位置和速度的实时获取。
一、系统架构概览
系统配置:ubuntu24.04(本次项目使用ROS2与Unity进行通信,ROS也同样支持)。ROS版本为Jazzy。人型机器人模型为宇树开源模型G1(自己的模型也同样可以)。
二、环境配置
本项目对于ROS2的安装,NIVIDA显卡驱动等不做详细叙述,详情请参考鱼香肉丝帖子进行ROS2的安装。
Unity:对于Unity的安装以及使用和训练,参考青龙人型机器人的开源项目,链接:https://github.com/loongOpen/Unity-RL-Playground
https://github.com/loongOpen/Unity-RL-Playground
该github项目详细叙述了Unity的安装以及训练方法,涵盖了多种人型机器人模型的训练以及urdf的文件描述。同时支持导入本地机器人进行强化训练开发。该项目相较于issacgym等,训练参数调整较少,训练时长明细降低。我训练400000step只花了8分钟时间,且行走效果较好。具体可参考青龙github开源项目,readme.md中有具体开发过程。
三、ROS-Unity通信配置
ROS与Unity之间的通信采用的是TCP通讯,其项目链接:https://github.com/Unity-Technologies/Unity-Robotics-Hub/tree/main/tutorials/ros_unity_integration
https://github.com/Unity-Technologies/Unity-Robotics-Hub/tree/main/tutorials/ros_unity_integration可以根据项目完成ROS与Unity之间通信,这里主要来详细去介绍怎么解决运行过程中的报错,首先文件夹的存放路径需要注意,在主目录下一共存在ros2_ws(自己创建的工作空间包含ROS-TCP-Endpoint-main-ros2、unity_robotics_demo_msgs、unity_robotics_demo)、ROS_unity(包含ROS-TCP-Connector,用来在unity中导入ROS-TCP-Connector软件包)、Unity-Robotics-Hub-main(包含ros2功能包用来获取UNITY消息unity_robotics_demo_msgs、unity_robotics_demo)。
报错总结:UNITY:1.没有播放按钮,无法进行训练以及演示。 解决:显卡驱动安装失败,无法调用显卡,重新安装显卡驱动(建议使用自带的软件与更新去下载附加。版本需要选open-kernel)。
2.可以进行播放,但是演示结果机器人并没有进行走路。解决:首先查看是否导入了训练后的ONNX,如果导入之后还是这样的话,估计会显示报错:DilNotFoundException: libdl.so assembly:<unknown assembly> type:<unknown type> member:(null)
Assimp.Unmanaged.UnmanagedLibrary+UnmanagedLinuxLibraryImplementation.NativeLoadLibrary。解决办法:
安装必需系统库
# Ubuntu/Debian
sudo apt-get update
sudo apt-get install libc6-dev
# CentOS/RHEL
sudo yum install glibc-devel
修复符号链接
# 查找现有库
ls -l /usr/lib/x86_64-linux-gnu/libdl.so*
# 创建必要链接
sudo ln -sf /usr/lib/x86_64-linux-gnu/libdl.so.2 /usr/lib/libdl.so
完成以上步骤即可解决。
3.解决报错ImportError: libpython3.13.so.1.0: cannot open shared object file: No such ......
解决办法为:终端命令行查找lib位置
find / -name libpython3.8.so.1.0 2>/dev/null
终端输出如下:
/home/user_name/miniconda3/envs/unitree-rl/lib/libpython3.8.so.1.0
/home/user_name/miniconda3/pkgs/python-3.8.20-he870216_0/lib/libpython3.8.so.1.
根据查找到的位置,终端输入命令(替换 你的库文件路径 为实际找到的libpython3.8.so.1.0 文件的目录)
export LD_LIBRARY_PATH=/home/user_name/miniconda3/envs/unitree-rl/lib/:$LD_LIBRARY_PATH
source ~/.bashrc # 或者对应的 shell 配置文件
我按照以上之后发现,每次打开都需进行替换,所以我们可以将其加入到bashrc文件中。
# 设置unity环境库路径
export LD_LIBRARY_PATH="/home/user_name/miniconda3/envs/unitree-rl/lib/:$LD_LIBRARY_PATH"
4.可以完成tcp连接,但是ros2 topic查看话题时发生报错。
raise UnsupportedTypeSupport(pkg_name)
rosidl_generator_py.import_type_support_impl.UnsupportedTypeSupport: Could not import 'rosidl_typesupport_c' for package 'unity_robotics_demo_msgs'sgs'
大概率是编译出现问题,解决办法:
cd ~/ros2_ws
# 完全清理
rm -rf build install log
# 使用 Python 3.13 重新编译
colcon build \
--symlink-install \
--merge-install \
--cmake-args \
-DPYTHON_EXECUTABLE=/usr/bin/python3.13 \
-DPYTHON_LIBRARY=/usr/lib/x86_64-linux-gnu/libpython3.13.so.1.0 \
-DPYTHON_INCLUDE_DIR=/usr/include/python3.13
# 更新 .bashrc
echo "export PYTHONPATH=/usr/lib/python3.13/site-packages:\$PYTHONPATH" >> ~/.bashrc
source ~/.bashrc
5.报错显示:
Usage of dash-separated 'script-dir' will not be supported in future
versions. Please use the underscore name 'script_dir' instead.
This deprecation is overdue, please update your project and remove deprecated
calls to avoid build errors in the future.
See https://setuptools.pypa.io/en/latest/userguide/declarative_config.html for details.
解决办法:
# 进入包源码目录(路径可能不同)
cd ~/ros2_ws/src/unity_robotics_Hub
找到unity_robotics_demo和 ros_tcp_endpoint包中的 setup.py或 setup.cfg文件。
将文件中的短横线改成下横线。
# 不推荐(即将废弃)
setup(install-scripts='bin', script-dir='bin')
# 推荐写法
setup(install_scripts='bin', script_dir='bin')
五、电机速度、位姿脚本编写
和ROS-Unity项目中的创建脚本一样,脚本内容如下所示。
using UnityEngine;
using Unity.Robotics.ROSTCPConnector;
using RosMessageTypes.Sensor;
using RosMessageTypes.Std; // 用于 HeaderMsg
using RosMessageTypes.BuiltinInterfaces; // 用于 TimeMsg
using System.Collections.Generic;
using System.Linq;
public class G1JointStatePublisher : MonoBehaviour
{
[Header("ROS 连接设置")]
public string topicName = "/g1/joint_states";
[Range(10, 100)]
public float publishFrequency = 50f;
[Header("机器人配置")]
public GameObject robotRoot;
public List<ArticulationBody> manualJoints = new List<ArticulationBody>();
private ROSConnection ros;
private List<ArticulationBody> joints = new List<ArticulationBody>();
private Dictionary<ArticulationBody, string> jointNameMap = new Dictionary<ArticulationBody, string>();
private float timeElapsed;
private float publishInterval;
private static readonly Dictionary<string, string> G1JointNameMapping = new Dictionary<string, string>
{
{"LF_HIP", "front_left_hip_joint"},
{"LF_THIGH", "front_left_thigh_joint"},
{"LF_CALF", "front_left_calf_joint"},
{"RF_HIP", "front_right_hip_joint"},
{"RF_THIGH", "front_right_thigh_joint"},
{"RF_CALF", "front_right_calf_joint"},
{"LH_HIP", "rear_left_hip_joint"},
{"LH_THIGH", "rear_left_thigh_joint"},
{"LH_CALF", "rear_left_calf_joint"},
{"RH_HIP", "rear_right_hip_joint"},
{"RH_THIGH", "rear_right_thigh_joint"},
{"RH_CALF", "rear_right_calf_joint"}
};
void Start()
{
publishInterval = 1f / publishFrequency;
ros = ROSConnection.GetOrCreateInstance();
ros.RegisterPublisher<JointStateMsg>(topicName);
ConfigureJoints();
if (joints.Count == 0)
{
Debug.LogError("未配置关节! 请手动分配关节或检查机器人模型");
}
else
{
Debug.Log($"已配置 {joints.Count} 个关节");
}
}
void FixedUpdate()
{
timeElapsed += Time.fixedDeltaTime;
if (timeElapsed >= publishInterval)
{
PublishJointStates();
timeElapsed = 0;
}
}
private void ConfigureJoints()
{
joints.Clear();
jointNameMap.Clear();
if (manualJoints.Count > 0)
{
foreach (var joint in manualJoints)
{
if (joint != null)
{
joints.Add(joint);
jointNameMap[joint] = FormatJointName(joint.gameObject.name);
}
}
return;
}
if (robotRoot == null)
{
Debug.LogWarning("未指定机器人根对象,尝试自动查找...");
robotRoot = GameObject.Find("G1") ?? GameObject.Find("UnitreeG1");
}
if (robotRoot != null)
{
var allJoints = robotRoot.GetComponentsInChildren<ArticulationBody>()
.Where(j => j.jointType != ArticulationJointType.FixedJoint)
.ToList();
foreach (var joint in allJoints)
{
joints.Add(joint);
if (TryGetStandardJointName(joint.gameObject.name, out string standardName))
{
jointNameMap[joint] = standardName;
}
else
{
jointNameMap[joint] = FormatJointName(joint.gameObject.name);
}
}
}
}
private bool TryGetStandardJointName(string gameObjectName, out string jointName)
{
foreach (var kvp in G1JointNameMapping)
{
if (gameObjectName.Contains(kvp.Key))
{
jointName = kvp.Value;
return true;
}
}
jointName = null;
return false;
}
private string FormatJointName(string originalName)
{
return originalName
.Replace("(Clone)", "")
.Replace(" ", "_")
.ToLower();
}
private void PublishJointStates()
{
var jointStates = new JointStateMsg
{
header = new HeaderMsg
{
// 使用 BuiltinInterfaces.TimeMsg
stamp = new TimeMsg
{
sec = (int)Time.timeAsDouble,
nanosec = (uint)((Time.timeAsDouble - (int)Time.timeAsDouble) * 1e9)
},
frame_id = "g1_base_link"
}
};
int count = joints.Count;
jointStates.name = new string[count];
jointStates.position = new double[count];
jointStates.velocity = new double[count];
jointStates.effort = new double[count];
for (int i = 0; i < count; i++)
{
var joint = joints[i];
jointStates.name[i] = jointNameMap[joint];
jointStates.position[i] = joint.jointPosition[0] * Mathf.Deg2Rad;
jointStates.velocity[i] = joint.jointVelocity[0] * Mathf.Deg2Rad;
jointStates.effort[i] = GetJointEffort(joint);
}
ros.Publish(topicName, jointStates);
}
private double GetJointEffort(ArticulationBody joint)
{
return 0.0;
}
[ContextMenu("自动查找关节")]
public void FindJointsInEditor()
{
ConfigureJoints();
if (joints.Count > 0)
{
manualJoints = new List<ArticulationBody>(joints);
Debug.Log($"找到 {joints.Count} 个关节并添加到手动列表");
}
}
}
创建完脚本后,将其拖拽到G1当中,并且在inspector对G1的关节进行添加。

在右边的manual joints中选者机器人的关节,可以先在左边点击具体关节,再确定测试的关节后将关节导入进脚本。并进行运行。重新打开终端输入:
建立通信
ros2 run ros_tcp_endpoint default_server_endpoint --ros-args -p ROS_IP:=198.168.122.1 -p ROS_TCP_PORT:=10000
发布话题后接受话题消息
ros2 topic echo /g1/joint_states

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)