Unity机器人开发实战:从零构建你的TurtleBot3 Waffle Pi虚拟模型

最近在尝试用Unity做机器人仿真,发现不少朋友卡在了第一步——导入机器人模型。尤其是像TurtleBot3 Waffle Pi这样在ROS社区里很火的机器人,明明网上资料不少,实际操作起来却总是遇到各种奇怪的问题。不是包导入失败,就是模型显示异常,折腾半天连个机器人的影子都看不到。这篇文章我就结合自己踩过的坑,手把手带你走通整个流程,不止是导入模型,更重要的是理解背后的逻辑,让你以后遇到任何URDF模型都能从容应对。

1. 环境准备与核心工具安装

在开始导入TurtleBot3之前,我们需要先搭建好Unity的工作环境。这里有个常见的误区:很多人以为只要安装了Unity Hub和某个版本的Unity Editor就万事大吉了。实际上,对于机器人仿真来说,编辑器版本的选择和特定包的安装方式会直接影响后续所有步骤的顺利程度。

我推荐使用Unity 2021.3 LTS或2022.3 LTS版本。LTS(长期支持)版本稳定性更好,社区支持也更完善。避免使用最新的预览版或过旧的版本,因为URDF Importer包可能对版本有特定要求。

1.1 URDF Importer包的两种安装方式

URDF Importer是Unity官方提供的工具包,专门用于解析和导入ROS中使用的URDF(统一机器人描述格式)文件。安装这个包有两种主流方法,各有适用场景。

方法一:通过Git URL直接安装(推荐网络环境好的情况)

这是最直接的方法,在Unity Editor中操作即可:

  1. 打开Unity项目,点击顶部菜单栏的 Window → Package Manager
  2. 在Package Manager窗口左上角,点击“+”按钮,选择“Add package from git URL”
  3. 在弹出的输入框中粘贴以下URL:
    https://github.com/Unity-Technologies/URDF-Importer.git?path=/com.unity.robotics.urdf-importer#v0.5.2
    
  4. 点击“Add”按钮,Unity会自动下载并安装包及其依赖

注意:这里URL末尾的#v0.5.2指定了包的版本号。你可以根据需要修改为其他可用版本,但建议使用文档中明确测试过的版本,避免兼容性问题。

方法二:手动下载并本地安装(应对网络问题)

国内开发者经常遇到GitHub访问不稳定的情况,这时候手动安装就派上用场了:

  1. 访问URDF Importer的GitHub仓库:https://github.com/Unity-Technologies/URDF-Importer
  2. 下载整个仓库的ZIP文件(点击绿色的“Code”按钮,选择“Download ZIP”)
  3. 解压下载的文件,找到com.unity.robotics.urdf-importer文件夹
  4. 在Unity的Package Manager中,点击“+” → “Add package from disk”
  5. 导航到刚才解压的路径,选择com.unity.robotics.urdf-importer文件夹内的package.json文件

两种方法最终效果是一样的,但手动安装能让你在离线环境下也能工作,而且下载一次后可以重复使用。

安装完成后,你可以在Package Manager中看到“URDF Importer”包,版本号显示为0.5.2。这时候Unity的菜单栏会多出一个“Robotics”菜单项,证明安装成功。

1.2 项目结构与初始设置

在导入任何模型之前,合理的项目结构能让你后续工作事半功倍。我习惯这样组织我的机器人仿真项目:

Assets/
├── URDF_Models/          # 存放所有机器人模型
│   ├── TurtleBot3/       # TurtleBot3相关文件
│   ├── OtherRobot/       # 其他机器人模型
│   └── Common/           # 共享的材质、纹理等
├── Scripts/              # 自定义脚本
├── Scenes/               # Unity场景文件
└── Resources/            # 运行时加载的资源

创建一个新文件夹专门存放URDF模型是个好习惯。在Project窗口右键点击Assets,选择“Create” → “Folder”,命名为“URDF_Models”。在这个文件夹下再创建一个“TurtleBot3”子文件夹,我们后续的所有操作都在这里进行。

2. 获取与准备TurtleBot3模型文件

现在环境准备好了,接下来要获取TurtleBot3 Waffle Pi的模型文件。这里有个关键点:URDF文件只是文本描述,模型的实际外观(网格、纹理)需要额外的文件支持。

2.1 获取完整的模型描述包

TurtleBot3的官方模型文件存放在ROS的turtlebot3_description包中。即使你不使用ROS,也需要这个包里的文件。获取方式有两种:

直接从GitHub仓库下载:

访问 https://github.com/ROBOTIS-GIT/turtlebot3,下载整个仓库或只下载turtlebot3_description文件夹。我建议下载整个仓库,因为里面可能包含有用的示例和配置文件。

下载后,你会看到类似这样的目录结构:

turtlebot3_description/
├── urdf/
│   ├── turtlebot3_waffle_pi.urdf.xacro  # Xacro宏文件
│   └── ...其他URDF文件
├── meshes/
│   ├── waffle_pi/
│   │   ├── base_link.STL
│   │   ├── caster_back_link.STL
│   │   └── ...其他STL文件
│   └── ...其他网格文件
├── materials/
│   └── textures/  # 纹理图片
└── launch/  # ROS启动文件(Unity中不需要)

使用ROS命令生成(如果你有ROS环境):

如果你已经在电脑上安装了ROS,可以通过命令行生成URDF文件:

# 首先source ROS环境(假设是ROS Noetic)
source /opt/ros/noetic/setup.bash

# 进入turtlebot3_description包的urdf目录
roscd turtlebot3_description/urdf

# 使用xacro工具将.xacro文件转换为.urdf文件
xacro turtlebot3_waffle_pi.urdf.xacro > turtlebot3_waffle_pi.urdf

生成的.urdf文件是纯XML文本,包含了机器人的所有关节、连杆、碰撞体和视觉外观的描述。

2.2 理解URDF文件结构

在导入之前,花几分钟看看URDF文件的内容很有帮助。用文本编辑器打开turtlebot3_waffle_pi.urdf,你会看到类似这样的结构:

<?xml version="1.0"?>
<robot name="turtlebot3_waffle_pi">
  <!-- 材料定义 -->
  <material name="black">
    <color rgba="0.0 0.0 0.0 1.0"/>
  </material>
  
  <!-- 连杆定义 -->
  <link name="base_link">
    <visual>
      <geometry>
        <mesh filename="package://turtlebot3_description/meshes/waffle_pi/base_link.STL"/>
      </geometry>
      <material name="black"/>
    </visual>
    <collision>
      <geometry>
        <mesh filename="package://turtlebot3_description/meshes/waffle_pi/base_link.STL"/>
      </geometry>
    </collision>
  </link>
  
  <!-- 关节定义 -->
  <joint name="wheel_left_joint" type="continuous">
    <parent link="base_link"/>
    <child link="wheel_left_link"/>
    <origin xyz="0.0 0.143 0.0175" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
  </joint>
  
  <!-- 更多连杆和关节... -->
</robot>

几个关键点需要注意:

  1. package://前缀:这是ROS特有的路径格式,Unity的URDF Importer无法直接识别。这是导致导入失败的最常见原因之一。
  2. 网格文件路径:所有<mesh>标签的filename属性都需要调整为Unity能识别的相对路径。
  3. 材料定义:URDF中的材料可能在Unity中无法正确转换,需要手动调整。

3. 在Unity中导入与配置模型

准备工作完成后,现在进入核心环节——在Unity中实际导入模型。

3.1 组织模型文件

首先,在Unity项目中组织好文件结构:

  1. 在之前创建的Assets/URDF_Models/TurtleBot3/文件夹下,创建一个turtlebot3_description子文件夹
  2. 将下载的turtlebot3_description文件夹中的内容复制到Unity的这个文件夹中,保持原有目录结构

你的Unity项目现在应该有这样的结构:

Assets/
└── URDF_Models/
    └── TurtleBot3/
        └── turtlebot3_description/
            ├── urdf/
            │   └── turtlebot3_waffle_pi.urdf
            ├── meshes/
            │   └── waffle_pi/
            │       ├── base_link.STL
            │       ├── caster_back_link.STL
            │       └── ...(其他STL文件)
            └── materials/
                └── textures/
                    ├── turtlebot3.png
                    └── ...(其他纹理文件)

3.2 修改URDF文件路径

由于Unity无法识别ROS的package://路径,我们需要修改URDF文件。用文本编辑器打开turtlebot3_waffle_pi.urdf,将所有package://turtlebot3_description/替换为相对路径。

查找并替换:

  • 将 package://turtlebot3_description/meshes/ 替换为 meshes/
  • 将 package://turtlebot3_description/materials/ 替换为 materials/

替换后,一个典型的mesh引用应该从:

<mesh filename="package://turtlebot3_description/meshes/waffle_pi/base_link.STL"/>

变成:

<mesh filename="meshes/waffle_pi/base_link.STL"/>

重要提示:这里使用的是相对路径,相对于URDF文件本身的位置。因为我们的URDF文件在urdf/文件夹中,而mesh文件在../meshes/中,所以路径是meshes/waffle_pi/...。如果你的目录结构不同,需要相应调整。

3.3 执行导入操作

现在一切就绪,可以开始导入了:

  1. 在Unity的Project窗口中,导航到Assets/URDF_Models/TurtleBot3/turtlebot3_description/urdf/
  2. 找到turtlebot3_waffle_pi.urdf文件
  3. 右键点击这个URDF文件,在上下文菜单中选择“Import Robot from Selected URDF file”

这时会弹出一个配置对话框,有两个关键选项需要设置:

选项说明推荐设置
Choose Inertia是否从网格生成惯性参数勾选(如果URDF中没有明确定义惯性)
Selected Axis模型的上方向轴根据原始模型选择Y轴或Z轴

对于TurtleBot3,通常选择Y轴向上,因为这是ROS和大多数机器人仿真工具的默认设置。如果你不确定,可以先尝试Y轴,如果模型看起来是“躺倒”的,再尝试Z轴。

点击“Import URDF”按钮,Unity会开始处理。这个过程包括:

  • 解析URDF文件结构
  • 导入所有引用的网格文件(STL格式)
  • 创建对应的GameObject层级结构
  • 添加刚体、碰撞体和关节组件

处理时间取决于模型的复杂程度,TurtleBot3大约需要10-30秒。完成后,你会在Scene视图中看到完整的机器人模型,同时在Hierarchy窗口中看到一个以机器人命名的根GameObject。

4. 常见问题排查与高级配置

即使按照上述步骤操作,你可能还是会遇到一些问题。下面我整理了几个最常见的情况及其解决方法。

4.1 网格文件丢失或路径错误

问题现象: 导入后模型显示为红色错误材质,或在Console窗口看到“Mesh file not found”之类的错误。

解决方案:

  1. 检查URDF文件中的路径:确保所有<mesh>标签的filename属性都使用正确的相对路径。记住,路径是相对于URDF文件本身的位置。

  2. 检查文件扩展名大小写:Linux系统(ROS常用)对文件名大小写敏感,而Windows通常不敏感。确保URDF中引用的文件名与实际文件完全一致,包括大小写。

  3. 手动重新导入网格文件:有时Unity的自动导入可能失败。尝试在Project窗口中:

    • 找到有问题的网格文件(STL格式)
    • 右键选择“Reimport”
    • 或者直接删除该文件,然后从原始位置重新复制到Unity项目中
  4. 使用绝对路径作为临时解决方案:如果相对路径一直有问题,可以尝试使用绝对路径(仅用于调试):

    <!-- 将相对路径改为绝对路径(示例) -->
    <mesh filename="C:/YourProject/Assets/URDF_Models/TurtleBot3/turtlebot3_description/meshes/waffle_pi/base_link.STL"/>
    

    但这不是长久之计,因为绝对路径在不同电脑上不通用。

4.2 材质和纹理问题

问题现象: 模型显示为纯色(通常是白色或紫色),没有正确的纹理贴图。

原因分析: URDF中的材质定义可能没有正确转换到Unity的材质系统。URDF使用简单的颜色或纹理定义,而Unity使用更复杂的Shader和材质系统。

解决方案:

  1. 检查纹理文件格式:确保所有纹理图片都是Unity支持的格式(如PNG、JPG、TGA)。TIFF或BMP格式可能需要转换。

  2. 手动创建材质:

    • 在Project窗口中,右键点击Assets/URDF_Models/TurtleBot3/turtlebot3_description/materials/文件夹
    • 选择“Create” → “Material”,命名为合适的名称(如“turtlebot3_body”)
    • 将纹理图片拖到材质的Albedo槽中
    • 调整其他材质属性(金属度、光滑度等)
  3. 批量替换材质:如果模型有很多部分需要相同的材质:

    • 在Hierarchy中选择机器人根对象
    • 在Inspector窗口中,点击右上角的三个点,选择“Debug”模式
    • 找到MeshRenderer组件,直接替换材质球
  4. 使用脚本自动分配材质:对于大型或复杂模型,可以编写简单脚本:

    using UnityEngine;
    
    public class MaterialAssigner : MonoBehaviour
    {
        public Material robotMaterial;
        
        void Start()
        {
            MeshRenderer[] renderers = GetComponentsInChildren<MeshRenderer>();
            foreach (MeshRenderer renderer in renderers)
            {
                renderer.material = robotMaterial;
            }
        }
    }
    

    将这个脚本附加到机器人根对象,然后拖拽材质到脚本的robotMaterial字段。

4.3 关节和刚体配置问题

问题现象: 机器人部件没有物理属性,或者关节运动不正常。

解决方案:

  1. 检查刚体组件:导入后,每个连杆(Link)都应该有Rigidbody组件。如果没有:

    • 在Hierarchy中选择对应的GameObject
    • 点击Inspector底部的“Add Component”
    • 搜索并添加“Rigidbody”
  2. 配置关节参数:URDF Importer会根据URDF文件自动创建关节(Articulation Body或Hinge Joint)。检查关键参数:

    • 关节类型:旋转关节、平移关节、固定关节等
    • 运动范围:最小和最大角度/位置限制
    • 驱动参数:刚度、阻尼、最大力/扭矩
  3. 调整质量属性:如果URDF中没有明确定义质量,Unity会基于网格体积自动计算。有时这不够准确:

    // 通过脚本设置质量
    Rigidbody rb = GetComponent<Rigidbody>();
    rb.mass = 2.5f; // 设置质量为2.5千克
    rb.centerOfMass = new Vector3(0, 0.1f, 0); // 调整质心位置
    
  4. 碰撞体优化:默认情况下,Unity会为每个网格生成Mesh Collider。对于实时物理仿真,这可能导致性能问题。考虑:

    • 用简单的几何碰撞体(Box、Sphere、Capsule)替代复杂网格碰撞体
    • 在Import Settings中调整网格碰撞体的精度

4.4 坐标系和比例问题

问题现象: 模型尺寸不对,或者方向错误。

解决方案:

  1. 统一单位系统:ROS/URDF通常使用米(meters)作为长度单位,千克(kilograms)作为质量单位。确保Unity项目设置一致:

    • 在Unity中,1单位默认对应1米,这通常与URDF兼容
    • 检查导入的网格文件是否以正确的比例保存
  2. 调整轴向上方向:如果导入时选择了错误的向上轴(Y vs Z),模型可能会“躺倒”:

    • 最简单的方法是重新导入,选择另一个轴向选项
    • 或者,在导入后旋转整个模型根对象:
      // 如果模型应该Z轴向上但现在是Y轴向上
      transform.rotation = Quaternion.Euler(90, 0, 0);
      
  3. 检查局部坐标系:每个连杆的局部坐标系应该与URDF中定义的<origin>标签一致。在Scene视图中启用“Local”坐标系显示(点击Scene视图右上方的坐标系图标),检查每个部件的坐标系方向。

4.5 性能优化技巧

当机器人模型复杂或场景中有多个机器人时,性能可能成为问题。以下是一些优化建议:

简化碰撞体:

  • 用基本几何体代替网格碰撞体
  • 减少碰撞体数量,合并小的碰撞体
  • 使用Layer-based碰撞检测,减少不必要的碰撞计算

优化材质和着色器:

  • 使用Mobile或URP(Universal Render Pipeline)友好的着色器
  • 合并材质,减少Draw Call
  • 使用纹理图集,减少纹理切换

层级细节(LOD):

// 简单的LOD实现示例
public class SimpleLOD : MonoBehaviour
{
    public GameObject highDetailModel;
    public GameObject lowDetailModel;
    public float switchDistance = 10f;
    
    private Transform cameraTransform;
    
    void Start()
    {
        cameraTransform = Camera.main.transform;
        highDetailModel.SetActive(true);
        lowDetailModel.SetActive(false);
    }
    
    void Update()
    {
        float distance = Vector3.Distance(transform.position, cameraTransform.position);
        
        if (distance > switchDistance)
        {
            highDetailModel.SetActive(false);
            lowDetailModel.SetActive(true);
        }
        else
        {
            highDetailModel.SetActive(true);
            lowDetailModel.SetActive(false);
        }
    }
}

批处理静态部件: 如果机器人的某些部分永远不会移动(如外壳),可以将它们标记为Static,让Unity进行静态批处理。

5. 从导入到仿真:让机器人动起来

成功导入模型只是第一步,真正的价值在于让机器人在虚拟环境中“活”起来。这里我分享几个让TurtleBot3在Unity中动起来的实用方法。

5.1 手动控制测试

首先创建一个简单的脚本测试机器人的运动系统:

using UnityEngine;

public class SimpleRobotController : MonoBehaviour
{
    // 左右轮关节的引用
    public ArticulationBody leftWheel;
    public ArticulationBody rightWheel;
    
    // 运动参数
    public float maxSpeed = 5f; // 弧度/秒
    public float acceleration = 2f;
    
    // 当前速度
    private float leftSpeed = 0f;
    private float rightSpeed = 0f;
    
    void Update()
    {
        // 获取输入
        float vertical = Input.GetAxis("Vertical"); // W/S 或 上/下箭头
        float horizontal = Input.GetAxis("Horizontal"); // A/D 或 左/右箭头
        
        // 计算差速驱动
        float linear = vertical * maxSpeed;
        float angular = horizontal * maxSpeed;
        
        // 转换为左右轮速度
        float targetLeftSpeed = linear - angular;
        float targetRightSpeed = linear + angular;
        
        // 平滑加速
        leftSpeed = Mathf.MoveTowards(leftSpeed, targetLeftSpeed, acceleration * Time.deltaTime);
        rightSpeed = Mathf.MoveTowards(rightSpeed, targetRightSpeed, acceleration * Time.deltaTime);
        
        // 应用速度到关节
        if (leftWheel != null)
        {
            ArticulationDrive drive = leftWheel.xDrive;
            drive.targetVelocity = leftSpeed;
            leftWheel.xDrive = drive;
        }
        
        if (rightWheel != null)
        {
            ArticulationDrive drive = rightWheel.xDrive;
            drive.targetVelocity = rightSpeed;
            rightWheel.xDrive = drive;
        }
    }
    
    // 在编辑器中更容易找到轮子关节
    void OnValidate()
    {
        if (leftWheel == null || rightWheel == null)
        {
            ArticulationBody[] wheels = GetComponentsInChildren<ArticulationBody>();
            foreach (ArticulationBody wheel in wheels)
            {
                if (wheel.name.Contains("left", System.StringComparison.OrdinalIgnoreCase))
                    leftWheel = wheel;
                else if (wheel.name.Contains("right", System.StringComparison.OrdinalIgnoreCase))
                    rightWheel = wheel;
            }
        }
    }
}

将这个脚本附加到机器人根对象,然后在Inspector中分配左右轮关节。运行游戏后,你可以用WASD或方向键控制机器人移动。

5.2 添加传感器模拟

真实的机器人仿真需要传感器数据。为TurtleBot3添加虚拟传感器:

激光雷达(Lidar)模拟:

using UnityEngine;
using System.Collections.Generic;

public class SimulatedLidar : MonoBehaviour
{
    public int raysPerScan = 360;
    public float maxDistance = 10f;
    public float angleRange = 360f;
    public LayerMask obstacleLayers;
    
    private float[] distances;
    private Vector3[] directions;
    
    void Start()
    {
        distances = new float[raysPerScan];
        directions = new Vector3[raysPerScan];
        
        // 预计算射线方向
        for (int i = 0; i < raysPerScan; i++)
        {
            float angle = (i / (float)raysPerScan) * angleRange * Mathf.Deg2Rad;
            directions[i] = new Vector3(Mathf.Cos(angle), 0, Mathf.Sin(angle));
        }
    }
    
    void Update()
    {
        ScanEnvironment();
    }
    
    void ScanEnvironment()
    {
        for (int i = 0; i < raysPerScan; i++)
        {
            Ray ray = new Ray(transform.position, transform.TransformDirection(directions[i]));
            RaycastHit hit;
            
            if (Physics.Raycast(ray, out hit, maxDistance, obstacleLayers))
            {
                distances[i] = hit.distance;
                // 可视化调试
                Debug.DrawLine(ray.origin, hit.point, Color.red);
            }
            else
            {
                distances[i] = maxDistance;
                Debug.DrawRay(ray.origin, ray.direction * maxDistance, Color.green);
            }
        }
        
        // 这里可以将distances数组发布到ROS话题或用于本地算法
    }
    
    public float[] GetScanData()
    {
        return distances;
    }
}

摄像头传感器:

using UnityEngine;

public class CameraSensor : MonoBehaviour
{
    public RenderTexture renderTexture;
    public int width = 640;
    public int height = 480;
    public float fov = 60f;
    
    private Camera sensorCamera;
    private Texture2D captureTexture;
    
    void Start()
    {
        // 创建渲染纹理
        renderTexture = new RenderTexture(width, height, 24);
        
        // 获取或添加摄像头组件
        sensorCamera = GetComponent<Camera>();
        if (sensorCamera == null)
            sensorCamera = gameObject.AddComponent<Camera>();
        
        sensorCamera.targetTexture = renderTexture;
        sensorCamera.fieldOfView = fov;
        sensorCamera.enabled = true;
        
        // 创建纹理用于CPU访问
        captureTexture = new Texture2D(width, height, TextureFormat.RGB24, false);
    }
    
    public byte[] CaptureFrame()
    {
        // 渲染到纹理
        sensorCamera.Render();
        
        // 从GPU读取到CPU
        RenderTexture.active = renderTexture;
        captureTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
        captureTexture.Apply();
        RenderTexture.active = null;
        
        // 编码为JPEG或PNG
        byte[] imageBytes = captureTexture.EncodeToJPG(85);
        return imageBytes;
    }
}

5.3 与ROS通信(高级集成)

如果你需要与真实的ROS系统通信,Unity提供了ROS-TCP-Connector包。基本设置流程:

  1. 安装ROS-TCP-Connector:通过Package Manager安装
  2. 配置ROS端:在ROS机器上运行连接器节点
  3. 创建消息定义:根据你的需求定义ROS消息
  4. 编写发布者/订阅者:在Unity中发送和接收ROS消息

示例:发布机器人关节状态

using UnityEngine;
using RosMessageTypes.Sensor;
using Unity.Robotics.ROSTCPConnector;
using Unity.Robotics.ROSTCPConnector.ROSGeometry;

public class JointStatePublisher : MonoBehaviour
{
    public string topicName = "/joint_states";
    public float publishFrequency = 30f; // Hz
    
    private ROSConnection ros;
    private ArticulationBody[] joints;
    private float timeElapsed;
    
    void Start()
    {
        ros = ROSConnection.GetOrCreateInstance();
        ros.RegisterPublisher<JointStateMsg>(topicName);
        
        joints = GetComponentsInChildren<ArticulationBody>();
        timeElapsed = 0f;
    }
    
    void Update()
    {
        timeElapsed += Time.deltaTime;
        
        if (timeElapsed > 1f / publishFrequency)
        {
            PublishJointStates();
            timeElapsed = 0;
        }
    }
    
    void PublishJointStates()
    {
        JointStateMsg message = new JointStateMsg();
        
        // 设置时间戳
        var time = new TimeStamp(Clock.time);
        message.header.stamp = time.ToMsg();
        message.header.frame_id = "base_link";
        
        // 收集关节数据
        message.name = new string[joints.Length];
        message.position = new double[joints.Length];
        message.velocity = new double[joints.Length];
        message.effort = new double[joints.Length];
        
        for (int i = 0; i < joints.Length; i++)
        {
            message.name[i] = joints[i].name;
            message.position[i] = joints[i].jointPosition[0]; // 假设单自由度关节
            message.velocity[i] = joints[i].jointVelocity[0];
            message.effort[i] = joints[i].jointForce[0];
        }
        
        ros.Publish(topicName, message);
    }
}

5.4 创建测试环境

为了让仿真更有意义,创建一个简单的测试环境:

  1. 地面和墙壁:使用Unity的Cube创建基本环境
  2. 障碍物:添加各种形状的障碍物测试导航算法
  3. 目标点:创建可视化目标标记
  4. 路径可视化:实时显示规划路径
using UnityEngine;

public class TestEnvironmentBuilder : MonoBehaviour
{
    public GameObject obstaclePrefab;
    public int obstacleCount = 20;
    public Vector3 areaSize = new Vector3(20, 0, 20);
    
    void Start()
    {
        CreateGround();
        CreateWalls();
        CreateRandomObstacles();
    }
    
    void CreateGround()
    {
        GameObject ground = GameObject.CreatePrimitive(PrimitiveType.Plane);
        ground.transform.localScale = new Vector3(areaSize.x / 10, 1, areaSize.z / 10);
        ground.GetComponent<Renderer>().material.color = Color.gray;
        ground.name = "Ground";
    }
    
    void CreateWalls()
    {
        float wallHeight = 2f;
        float wallThickness = 0.2f;
        
        // 创建四面墙
        CreateWall(new Vector3(0, wallHeight/2, areaSize.z/2), 
                  new Vector3(areaSize.x, wallHeight, wallThickness));
        CreateWall(new Vector3(0, wallHeight/2, -areaSize.z/2), 
                  new Vector3(areaSize.x, wallHeight, wallThickness));
        CreateWall(new Vector3(areaSize.x/2, wallHeight/2, 0), 
                  new Vector3(wallThickness, wallHeight, areaSize.z));
        CreateWall(new Vector3(-areaSize.x/2, wallHeight/2, 0), 
                  new Vector3(wallThickness, wallHeight, areaSize.z));
    }
    
    void CreateWall(Vector3 position, Vector3 scale)
    {
        GameObject wall = GameObject.CreatePrimitive(PrimitiveType.Cube);
        wall.transform.position = position;
        wall.transform.localScale = scale;
        wall.GetComponent<Renderer>().material.color = Color.white;
        wall.name = "Wall";
    }
    
    void CreateRandomObstacles()
    {
        for (int i = 0; i < obstacleCount; i++)
        {
            Vector3 position = new Vector3(
                Random.Range(-areaSize.x/2 + 1, areaSize.x/2 - 1),
                0.5f,
                Random.Range(-areaSize.z/2 + 1, areaSize.z/2 - 1)
            );
            
            Vector3 scale = new Vector3(
                Random.Range(0.5f, 2f),
                Random.Range(0.5f, 2f),
                Random.Range(0.5f, 2f)
            );
            
            GameObject obstacle = Instantiate(obstaclePrefab, position, Quaternion.identity);
            obstacle.transform.localScale = scale;
            obstacle.name = $"Obstacle_{i}";
        }
    }
}

实际使用中,我发现在导入复杂URDF模型时,最大的挑战往往不是技术本身,而是耐心和细心。每个错误信息都是线索,Console窗口的警告和错误需要逐一排查。有时候问题可能很简单,比如一个文件路径的大小写错误,或者一个缺失的依赖文件。关键是要有系统性的排查方法:从URDF文件本身开始,检查路径、检查文件是否存在、检查Unity的导入设置,最后再检查运行时组件。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐