尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Unity机械臂运动仿真实战:关节搭建、正逆运动学与物理驱动

Unity机械臂运动仿真实战:关节搭建、正逆运动学与物理驱动 简介面向Unity开发者与机器人仿真爱好者的机械臂运动仿真项目zip包内含基于Direct3D的完整实现。共63个文件约11.54MB涵盖.x三维模型、C源码.cpp/.h、Visual Studio解决方案与工程文件.sln/.vcxproj以及可直接运行的exe、程序数据库和构建日志。项目以多关节机械臂为对象演示了正向运动学与反向运动学的计算流程从六个关节角度映射末端执行器位置或从目标位置反推关节角度涉及关节约束、骨骼层级更新、Direct3D渲染交互等关键环节。模型库包含底座、臂杆、SG90/335MG舵机等部件代码模块涵盖XFile模型加载、天空盒、粒子系统、摄像机与输入控制便于理解实时3D仿真框架。已有6657人浏览学习适合希望掌握机械臂运动学原理并在Visual Studio与Direct3D环境下实现仿真验证的开发者参考。1. Unity机械臂运动仿真从Gazebo转投Unity的落地路径机械臂的运动仿真在Unity里做最大的优势不是物理精度而是你能在同一个场景里把手眼标定、轨迹规划、UI面板全串起来看。我从Gazebo转投Unity机械臂方案的原因很直接Gazebo上装ROS插件、调URDF渲染、配置控制接口就花掉两个周末而Unity里用ArticulationBody加一段正逆运动学脚本当天就能让虚拟六轴跟着目标点平滑转动。这套方案最适合两类人一类是要交付机械臂数字孪生演示和教学仿真的工程师另一类是毕设需要对运动控制算法做可视化验证的同学。下面按我实际落地的顺序来拆模型导入与关节树搭建、正逆运动学与插值、物理驱动与抓取、常见翻车点最后补上轨迹录制和对外接口的进阶玩法。2. 机械臂模型与关节树把D-H参数映射到Unity层级2.1 FBX导入与坐标系对齐SolidWorks到Unity的第一关先说前提Unity是左手坐标系、Y轴向上SolidWorks默认Z轴向上URDF里的模型又常常是X轴朝前、Z轴朝上。三套坐标系不统一直接把FBX拖进场景的结果就是机械臂横躺在地板上关节轴心全偏后面调姿态能把人逼疯。我实际会这么做在SolidWorks里打开机械臂装配体把坐标系原点重定义到基座底面中心保证第一个关节的旋转轴和坐标系某一轴平行。导出时选FBX格式单位选毫米材质和骨骼不勾只导网格。在Unity的Project窗口选中FBX在Model页签里把Up Axis改成Y、Forward Axis改成-Z把Blend Shapes和Skin Weights关掉。这个动作不会改变机械臂各link之间的相对位置只是把整个装配体从SolidWorks坐标系搬到Unity坐标系下。场景里新建空物体命名为RobotRoot把FBX实例挂到它下面。不要在FBX实例上直接挂脚本或配置关节组件因为一旦重新导入FBX节点层级和挂接引用全部失效教训就是所有和逻辑相关的东西都挂在RobotRoot或它下层的空物体上。还有一处常见翻车FBX导入后出现多余的空节点每个link的Transform上没有归零。这时我会先看Import Settings的Scene页签把Convert Units和Bake Axis Inversion之类选项按需勾选。如果模型依然歪着干脆不依赖自动转换手动在RobotRoot下建空物体当作每个link的重新挂点把原始Mesh作为子物体摆放对齐这种做法最稳就是要多花半小时。单位问题也要在这里解决。SolidWorks以毫米为单位导出Unity默认场景单位是米导入时虽然会自动缩放但如果你在脚本里写D-H参数时混用了毫米和米末端位置会差一个数量级。我的习惯是Unity场景里所有距离都用米D-H参数表里存毫米进代码后统一乘以0.001换算。2.2 关节层级搭建六个空节点怎么组织父子关系六轴机械臂装成Unity的GameObject树时规律是每个关节一个空物体关节的旋转中心就是空物体的位置Mesh作为它的子物体显示外观。一个典型的六轴结构如下RobotRoot ├── Base │ └── J1空物体位置基座旋转中心 │ └── Link1_Mesh │ └── J2空物体位置肩关节旋转中心 │ └── Link2_Mesh │ └── J3空物体位置肘关节旋转中心 │ └── Link3_Mesh │ └── J4空物体位置腕部旋转中心 │ └── Link4_Mesh │ └── J5空物体 │ └── Link5_Mesh │ └── J6空物体位置法兰前端 │ └── Link6_Mesh └── EndEffectorTCP、吸盘或手爪这里的关键是空物体J1到J6的位置必须放在真实旋转轴上而不是放在Mesh中心。很多同学直接把胶囊体或模型套在关节模型中间结果转动时整个link绕着几何中心转而不是绕着转轴转。位置要不要加偏移建议去机械臂图纸或SolidWorks里量别凭感觉摆。每个关节还要约定一个统一的旋转轴方向我习惯把所有关节的旋转轴约定为本地Z轴。这样写驱动脚本时所有关节一套代码通吃D-H参数表里的alpha转轴方向和Unity本地坐标也能一一对应。如果模型本身旋转轴不是Z轴就在关节节点下再包一层把Z轴转到和实际旋转轴重合。2.3 关节限位与D-H参数表把机械臂的“出厂数据”存进脚本下面是一台常规六轴机械臂的D-H参数表具体数值视你手里的机械臂而定这里给的是量纲和排布方式关节d (mm)a (mm)alpha (度)运动范围度J13200-90-170 ~ 170J202400-135 ~ 135J302200-135 ~ 135J41300-90-170 ~ 170J5120090-120 ~ 120J66000-360 ~ 360这里的d、a、alpha是标准D-H参数定义d是沿z轴平移a是沿x轴平移alpha是绕x轴转角而theta是变量也就是每个关节实际要驱动的角度。注意很多国产机械臂手册给的可能是改进D-H参数两者矩阵形式不同用哪个就从头到尾统一用哪个不要混。把这组参数存成C#结构体我习惯这么做[System.Serializable] public struct DHJoint { public string name; // 关节名用于日志排查 public float d; // 沿z轴平移单位mm public float a; // 沿x轴平移单位mm public float alpha; // 绕x轴转角单位度 public float thetaOffset; // 初始角度偏置单位度 public float minAngle; // 限位下限 public float maxAngle; // 限位上限 }这个结构体后面正运动学、逆运动学、UI面板都共用所以字段命名要清晰。thetaOffset是必须的Unity场景里模型摆放好的初始姿态可能对应D-H表格里某个关节的90度或-90度如果不把初始姿态偏差折算进去正运动学算出来的末端位置和场景里眼睛看到的位置就对不上。有了DHJoint之后每个关节的旋转驱动脚本可以写成通用组件using UnityEngine; public class JointDriver : MonoBehaviour { public Vector3 localAxis Vector3.forward; // 统一约定本地Z轴为旋转轴 public float targetAngle; // 外部输入目标角度单位度 public float speed 45f; // 关节转动速度单位度/秒 public float minAngle -170f; public float maxAngle 170f; private float currentAngle 0f; void Update() { currentAngle Mathf.MoveTowards( currentAngle, Mathf.Clamp(targetAngle, minAngle, maxAngle), speed * Time.deltaTime); transform.localRotation Quaternion.AngleAxis(currentAngle, localAxis); } public void SetTarget(float angle) { targetAngle angle; } public float GetAngle() { return currentAngle; } }逻辑说明脚本不直接设置transform.localRotation的目标值而是用MoveTowards把当前角度一步步逼近目标角度这样能天然形成匀速运动。每次Update都会把角度夹在minAngle和maxAngle之间避免外部传入超限角度。如果你的机械臂是在ArticulationBody物理模式下运行别用这个脚本驱动应该用后面第四章的Drive模式两边同时驱动会打架。提示JointDriver只适用于纯运动学仿真也就是“我要看机械臂动线、做轨迹演示”这类需求。要仿真受力、碰撞反弹、抓取时被拖拽等物理行为必须换成ArticulationBody。3. 正逆运动学与插值让机械臂末端去追踪目标点3.1 正运动学D-H矩阵与末端坐标的C#实现正运动学解决的是“已知六个关节角度求末端位置和姿态”。标准做法是把每个关节的D-H矩阵依次左乘最终得到一个从基座到末端的齐次变换矩阵。Unity里写D-H矩阵时要注意Matrix4x4是列主序存的直接按行填下标特别容易错。我用SetColumn方式四列分别对应标准D-H矩阵的四列能少踩很多坑using UnityEngine; public class ForwardKinematics : MonoBehaviour { public DHJoint[] dhJoints; // 输入当前六个关节角度单位度返回基座坐标系下的TCP矩阵 public Matrix4x4 ComputeTCP(float[] currentAngles) { Matrix4x4 T Matrix4x4.identity; for (int i 0; i dhJoints.Length; i) { float theta (currentAngles[i] dhJoints[i].thetaOffset) * Mathf.Deg2Rad; float d dhJoints[i].d * 0.001f; // mm转m float a dhJoints[i].a * 0.001f; float alpha dhJoints[i].alpha * Mathf.Deg2Rad; T T * DHMatrix(d, theta, a, alpha); } return T; } Matrix4x4 DHMatrix(float d, float theta, float a, float alpha) { float c Mathf.Cos(theta), s Mathf.Sin(theta); float ca Mathf.Cos(alpha), sa Mathf.Sin(alpha); Matrix4x4 m new Matrix4x4(); // 标准D-H矩阵按Unity列主序填入 m.SetColumn(0, new Vector4(c, s, 0f, 0f)); m.SetColumn(1, new Vector4(-s * ca, c * ca, sa, 0f)); m.SetColumn(2, new Vector4(s * sa, -c * sa, ca, 0f)); m.SetColumn(3, new Vector4(a * c, a * s, d, 1f)); return m; } }逻辑说明每个DHMatrix生成的是当前关节相对于上一个关节的变换T累乘后得到基座到末端的齐次矩阵。最后一列前三行就是末端在基座坐标系下的位置。手写这个矩阵时最容易被绕晕的是列主序的存放规则所以推荐SetColumn而不是逐个填m[row, col]下标。参数说明currentAngles是六个关节当前角度和JointDriver里的GetAngle对应thetaOffset在矩阵里直接加进去避免每次手动调整模型初始姿态。ComputeTCP返回的是基座坐标系下的结果如果机械臂Root节点在世界里还有旋转和平移需要再拿根节点的Transform做一次变换才能得到Unity世界坐标下的末端位置。3.2 逆运动学CCD迭代法让六轴机械臂追点逆运动学方案有很多解析法对六轴机械臂有一定通用性但实现繁琐雅可比伪逆法又需要处理奇异值。对于Unity里的运动仿真和演示CCD迭代法是最容易落地且效果稳定的一个。CCD的思路概括一句话从末端关节开始逐个关节把末端拉向目标点每个关节只转一小步反复迭代几轮后整个机械臂会“卷”向目标。实现如下using UnityEngine; public class CCDIK : MonoBehaviour { public Transform target; // 目标点放在机械臂基座坐标系附近的物体 public Transform endEffector; // TCP节点 public Transform[] joints; // 从基座到末端的关节节点 public float[] angleLimits; // 每个关节单次修正角上限单位度 public int maxIterations 30; public float distanceThreshold 0.02f; public void Solve() { for (int iter 0; iter maxIterations; iter) { if (Vector3.Distance(endEffector.position, target.position) distanceThreshold) break; for (int i joints.Length - 1; i 0; i--) { Transform joint joints[i]; Vector3 toEnd endEffector.position - joint.position; Vector3 toTarget target.position - joint.position; if (toEnd.sqrMagnitude 1e-6f || toTarget.sqrMagnitude 1e-6f) continue; Quaternion delta Quaternion.FromToRotation(toEnd, toTarget); delta.ToAngleAxis(out float angle, out Vector3 axis); Vector3 localAxis joint.InverseTransformDirection(axis); float limit (angleLimits ! null angleLimits.Length i) ? angleLimits[i] : 45f; angle Mathf.Clamp(angle, -limit, limit); joint.Rotate(localAxis, angle, Space.Self); } } } }逻辑说明外层循环控制迭代轮数内层循环从距离末端最近的关节开始往前修。每次修正先算出末端到目标的旋转四元数delta再分解成旋转轴axis和旋转角angle然后把这个旋转量投影到关节自身上避免一个关节修正时把其余五个关节的姿态全破坏。单次修正角用Mathf.Clamp限制一是防止迭代发散二是天然模拟关节速度上限。参数说明maxIterations设30对六轴足够再多也不会显著提升精度反而让每帧计算量变大。distanceThreshold是末端到目标的允许误差按机械臂尺寸来一米臂展的机械臂设0.02米够用了。angleLimits数组长度必须和joints对齐如果某个关节不能绕任意轴随便转就必须在这里限制否则CCD会出现关节“反关节”翻转的诡异姿态。一个实战习惯把CCD的Solve放在FixedUpdate而不是Update里调用并把target设置成鼠标拖拽或UI拖动的目标物体。这样机械臂追点过程不会受渲染帧率波动影响。3.3 关节插值用S曲线避免机械臂末端“打摆子”如果逆运动学每次算完直接让所有关节一步跳转到目标角度机械臂动作会非常僵硬末端还会出现肉眼可见的抖动。原因是每个关节独立做线性插值时起始和结束瞬间速度突变整个机械臂结构受惯性影响产生震荡。我习惯在轨迹层做S曲线插值代码短效果明显using UnityEngine; public class SCurveInterpolator : MonoBehaviour { public JointDriver[] jointDrivers; public float duration 2f; private float startTime; private float[] startAngles; private float[] targetAngles; public void StartTrajectory(float[] from, float[] to) { startAngles (float[])from.Clone(); targetAngles (float[])to.Clone(); startTime Time.time; } void Update() { if (startAngles null) return; float t Mathf.Clamp01((Time.time - startTime) / duration); float smoothT t * t * (3f - 2f * t); // Smoothstep for (int i 0; i jointDrivers.Length; i) { float angle Mathf.Lerp(startAngles[i], targetAngles[i], smoothT); jointDrivers[i].SetTarget(angle); } if (t 1f) startAngles null; } }逻辑说明Smoothstep曲线让比例t在0和1两端加速缓慢、中间最快这样机械臂启动和停止都非常柔和。每个关节独立做Lerp虽然简单但如果要求末端在笛卡尔空间走直线单靠关节空间插值是做不到的那需要额外的笛卡尔轨迹规划这里不展开后面避坑章节会说明这个常见误区。参数说明duration根据机械臂工作范围调整六轴机械臂从姿态A到姿态B一般给1到3秒比较自然。如果多个关节角度变化范围差异大还可以给每个关节单独配duration我一般先跑一遍看最慢的关节会不会超速再整体压缩时间。4. 物理驱动与交互ArticulationBody、碰撞体和抓取逻辑4.1 ArticulationBody还是HingeJoint机械臂物理仿真的选型纯运动学方案能看动作但机械臂碰工件、被拖拽、抓取后工件随机械臂运动这些交互需要物理仿真。Unity里有两个选择HingeJoint和ArticulationBody。我的结论是六轴机械臂直接用ArticulationBody别再花时间调HingeJoint。维度HingeJointArticulationBody关节链结构每个刚体手动指定ConnectedBody天然支持父子树状结构子关节继承父关节约束关节驱动Motor和Spring参数分散调起来繁琐内置DriveTarget、DriveStiffness、DriveDamping多轴复合运动需要多个Joint叠加原生6自由度约束适合手腕这类多轴结构数值稳定性链式刚体容易在末端累积抖动自带隐式积分机械臂场景更稳ArticulationBody的配置要点是基座节点加一个FixedJoint类型并设置immovable其余六个关节设置成RevoluteJoint每个关节用xDrive或zDrive驱动。因为Unity默认RevoluteJoint绕本地X轴转动而我前面把关节旋转轴约定在Z轴所以这里用zDrive注意版本或建模习惯决定ArticulationBody body gameObject.AddComponentArticulationBody(); body.jointType ArticulationJointType.RevoluteJoint; body.linearDamping 0f; body.angularDamping 1f; ArticulationDrive drive body.zDrive; drive.stiffness 1000f; // 刚度 drive.damping 10f; // 阻尼 drive.target 90f; // 目标角度单位度 drive.driveType ArticulationDriveType.Target; body.zDrive drive;逻辑说明stiffness决定关节抵抗外部扰动的力度damping决定运动衰减速度。stiffness设太高会让关节像弹簧一样震荡damping设太低会让机械臂在停止后来回晃。这个组合需要根据机械臂质量调整不是固定的。参数说明这里drive.target和JointDriver里的targetAngle一样是角度值但ArticulationBody底层会把角度转成弧度处理你在Inspector里看到的是角度如果直接读底层属性要注意单位。另外ArticulationBody需要在机械臂根节点也挂一个ArticulationBody并设置immovable否则整个机械臂会因为重力直接掉落。4.2 碰撞体配置让机械臂能“摸到”工件而不是穿过去碰撞体配置的核心原则能用基本碰撞体就绝不用MeshCollider。六轴机械臂每个link外轮廓不复杂用BoxCollider或CapsuleCollider叠加即可。我一般会在每个link的Mesh节点下挂一个碰撞体组件手动调整center和size让它大致包住模型表面。手动调太麻烦的话可以写脚本在Awake里自动生成using UnityEngine; public class AutoColliderGenerator : MonoBehaviour { public Transform[] links; // 需要碰撞体的link节点 public float capsuleRadius 0.03f; void Awake() { foreach (Transform link in links) { MeshRenderer mr link.GetComponentInChildrenMeshRenderer(); if (mr null) continue; Bounds bounds mr.bounds; CapsuleCollider cc link.gameObject.AddComponentCapsuleCollider(); cc.center link.InverseTransformPoint(bounds.center); cc.height link.InverseTransformDirection(bounds.size).z capsuleRadius * 2f; cc.radius capsuleRadius; cc.direction 2; // Z轴方向 } } }逻辑说明自动生成的碰撞体以MeshRenderer的包围盒中心为胶囊体中心高度对齐到机械臂link的拉伸方向半径统一0.03米适合大多数小型六轴机械臂。如果你的机械臂大臂小臂差异明显半径也可以按link分别设置。参数说明CapsuleCollider的direction参数0、1、2分别对应X、Y、Z轴这里设2是因为我的关节约定Z轴为长度方向。MeshCollider不是完全不能用但非凸MeshCollider不能用于刚体碰撞只能做静态触发器不建议在机械臂动态运动主体上使用。4.3 抓取逻辑触发检测、吸附和释放机械臂仿真里最常见的交互就是抓取工件。Unity里最省事的方案是末端执行器下方加一个Trigger碰撞体检测到可抓取物体后给末端执行器挂一个FixedJoint把物体吸住。using UnityEngine; public class GripperController : MonoBehaviour { public Transform holdPoint; // 抓取点通常在夹爪中心 private FixedJoint fj; void OnTriggerEnter(Collider other) { if (fj ! null || holdPoint null) return; if (!other.CompareTag(Pickable)) return; Rigidbody rb other.attachedRigidbody; if (rb null) return; fj holdPoint.gameObject.AddComponentFixedJoint(); fj.connectedBody rb; } void Release() { if (fj ! null) { Destroy(fj); fj null; } } void Update() { if (Input.GetKeyDown(KeyCode.Space)) Release(); } }逻辑说明OnTriggerEnter触发时首先确认自己没有抓着东西然后检查工件标签和Rigidbody。FixedJoint加到holdPoint上connectedBody指向工件刚体这样工件就会跟随末端执行器运动。参数说明拾取前要给工件加上Rigidbody并关闭useGravity或启用重力但把质量调小否则抓取瞬间会因为重力和固定关节的相互作用产生晃动。Release时直接Destroy固定关节即可但要注意工件上可能残留物理速度一般给它一个零速度重置更干净。5. 避坑/常见问题机械臂Unity仿真里的五个翻车现场5.1 现象关节明明设置了限位机械臂还是转过界转到第120度直接卡在模型内部或者整个手臂反卷成麻花。原因JointDriver的限位只挡了它自己的Update通道但CCD逆运动学、控制面板或外部Python接口直接改transform.localRotation时所有限位形同虚设。解决让所有角度写入集中到JointDriver.SetTarget这一个入口无论UI面板、鼠标拖拽还是WebSocket数据进来一律调用SetTarget。ArticulationBody模式下限位还要在物理组件侧再做一遍比如设置关节的rotationDriveLimit防止物理引擎绕过脚本限位。5.2 现象FBX导入后轴心位置全乱机械臂零件飞散手臂模型导入Unity后每个link的坐标原点不对旋转关节时模型绕着奇怪的几何中心转。原因SolidWorks导出时每个零件的坐标系原点不一定在安装轴心上Unity导入时又经过了一次坐标系转换导致了双重偏移。解决不要挣扎着去改Mesh的Transform直接在每个link外面包一层空物体作为关节节点把Mesh挪到正确位置。这个包一层的方式我在2.2节里已经演示过看起来笨但对任何来源的模型都有效。另外在Import Settings里检查Pivot选项是Origin还是Mesh确认后用空物体做统一挂点。5.3 现象ArticulationBody模式下机械臂静止后一直在抖机械臂没执行任何动作但关节微微颤动末端能看到持续波动。原因关节刚度和阻尼参数不匹配刚度太高或阻尼太低根节点没设置immovable也会让整个机械臂在重力下震动。解决先确认根节点ArticulationBody的jointType是FixedJoint且immovable为true然后从stiffness500、damping10开始调逐步往上加。如果还是抖把Project Settings里的Fixed Timestep从0.02改成0.01物理迭代次数提高一倍抖动会明显下降。5.4 现象末端执行器算出来的位置和视觉位置差一截正运动学算出的TCP坐标和场景里看到的夹爪实际位置差了5到10厘米机械臂怎么都抓不准工件。原因D-H参数表里漏了Tool Center Point偏移或者d和a的单位在代码里混用了毫米和米。解决先在单位层面把D-H表统一成米然后做一个校验脚本让六个关节角度全部归零对比Unity里夹爪节点的世界坐标和ComputeTCP(0)算出的坐标差值就是TCP偏移。把这个偏移写进D-H表的最后一个关节之后让末端矩阵再乘一个只带平移的变换矩阵这样后续逆运动学算的抓取点始终对准真正的夹爪中心。5.5 现象机械臂动起来帧率骤降物理仿真跟不上关节一运动模型就一卡一卡Unity编辑器帧率掉到20FPS以下。原因每个link都带着高精度MeshRenderer和实时阴影物理仿真每帧还要做多次碰撞检测渲染和物理抢CPU。解决三个方向同时压。一是模型精度把高模FBX换成低模或用Unity的Simplify工具减面二是阴影把机械臂的Shadow Mode改成Off或只保留主光源阴影三是物理设置把Solver Iterations从默认值调低能提升碰撞检测性能但不影响视觉精度。最后把渲染相关的跟手逻辑放到LateUpdate里避免每帧两端抢执行时间。6. 进阶玩法轨迹录制回放与对外接口把仿真做成可交付演示6.1 轨迹录制与回放先让机械臂“自己走一遍”机械臂运动仿真做得好不好最终要看动作过程能不能被复现。轨迹录制的思路很简单以固定间隔采样各关节角度存成关键帧列表回放时按时间轴把关键帧依次喂给JointDriver。using UnityEngine; using System.Collections.Generic; public class TrajectoryRecorder : MonoBehaviour { public JointDriver[] jointDrivers; public float sampleInterval 0.02f; public Listfloat[] keyframes new Listfloat[](); private float timer; void Update() { timer Time.deltaTime; if (timer sampleInterval) { timer 0f; float[] angles new float[jointDrivers.Length]; for (int i 0; i jointDrivers.Length; i) angles[i] jointDrivers[i].GetAngle(); keyframes.Add(angles); } } public void Replay() { StartCoroutine(ReplayRoutine()); } IEnumerator ReplayRoutine() { for (int i 0; i keyframes.Count; i) { for (int j 0; j jointDrivers.Length; j) jointDrivers[j].SetTarget(keyframes[i][j]); yield return new WaitForSeconds(sampleInterval); } } }逻辑说明录制端的采样间隔可以比渲染帧率更高0.02秒正好对应50Hz符合大多数控制系统的刷新频率。回放时直接用关键帧角度覆盖SetTarget因为JointDriver内部自带速度限制回放不会瞬间跳变。最后回放完成后最好再执行一次所有关节角度校准防止累计误差。6.2 对外接口把Unity的角度数据变成真实的控制指令仿真做完终归要让真机或控制系统认这套数据。我最常用的是WebSocket转发方案Unity作为客户端或服务端把当前六个关节角度包装成JSON发送给上位机。这样在演示现场电脑上跑的是Unity画面后台接受的是实际控制系统画面和真机的动作完全同步。[System.Serializable] public class JointData { public float[] joints; } string ToJson() { JointData data new JointData(); data.joints new float[jointDrivers.Length]; for (int i 0; i jointDrivers.Length; i) data.joints[i] jointDrivers[i].GetAngle(); return JsonUtility.ToJson(data); }接线时注意Unity的JsonUtility和Newtonsoft.Json在字段命名上略有差异如果接的是ROS或MapEngine团队自研的上位机先确认对方接口是std_msgs/Float64MultiArray还是自定义JSON别在格式上翻车。从那以后我每次在Unity里搭机械臂运动仿真都会强制自己走一遍坐标轴对齐、D-H参数表和关节树校验再用轨迹回放检查整体动作最后才对接到外部接口。希望这套流程能帮到你。本文还有配套的精品资源点击获取
返回列表