Unity 无人机物理模拟开发日志:从零打造穿越机手感

Unity 无人机物理模拟开发日志:从零打造穿越机手感

摘要:本文记录了在 Unity 中构建一个高拟真 FPV 穿越机(Drone)物理模拟系统的过程。从基础的 PID 控制到引入空气动力学阻力、地面效应和电机惯性,一步步逼近真实的飞行手感。


环境:Unity 2022.3.57c1f1 Window10

开源仓库地址

Unity引擎开发的无人机模拟系统

演示视频:

Unity无人机仿真-bilbil


一、功能介绍

输入系统

最初的实现使用键盘鼠标控制,但这对于模拟穿越机来说完全不够。真实的穿越机需要细腻的模拟量输入。


核心物理引擎

Unity 的 Rigidbody 提供了基础物理,但要飞得像穿越机,必须手动计算力和力矩。

PID 控制器 (Rate Loop)

这是飞控的灵魂。我们实现了三个独立的 PID 控制器分别控制 Pitch、Roll 和 Yaw 的角速度
PID 的介绍请看我的另外一篇文档: PID算法

  • 目标:摇杆输入 = 目标角速度(例如满杆 200度/秒)。
  • 反馈rb.angularVelocity
  • 输出:PID 计算出的修正力矩。

混控器 (Mixer)

将 PID 输出分配到四个电机。采用标准的 Quad X 布局:

csharp 复制代码
// FL (左前): +Pitch +Roll -Yaw
// FR (右前): +Pitch -Roll +Yaw
// BL (左后): -Pitch +Roll +Yaw
// BR (右后): -Pitch -Roll -Yaw

物理细节的打磨

基础 PID 能飞,但手感像"在真空中飞行"或者"完美的刚体"。为了真实感,我引入了三个关键特性:

电机惯性 (Motor Inertia)

真实的电机从 0 加速到 100% 需要时间。

  • 实现 :使用 Mathf.Lerp 对油门输入进行低通滤波。
  • 效果:消除了"瞬移般"的响应,给油门带来了一丝"肉"感和延迟,极大提升了重量感。
csharp 复制代码
    // 更新动力 (在 FlightController 的 FixedUpdate 中调用)
    public void UpdatePhysics(float targetThrottle)
    {
        // 模拟电机惯性 (一阶低通滤波)
        // 从当前油门平滑过渡到目标油门
        float dt = Time.fixedDeltaTime;
        currentThrottle = Mathf.Lerp(currentThrottle, targetThrottle, dt * motorResponseSpeed);

        // 1. 施加升力 (垂直于机臂向上)
        Vector3 force = transform.up * (currentThrottle * maxThrust);
        rb.AddForceAtPosition(force, transform.position);

        // 2. 施加反扭矩 (Yaw控制)
        // 顺时针旋转的电机,会给机身施加逆时针的扭矩,反之亦然
        float torqueDir = isClockwise ? -1f : 1f;
        float torqueMagnitude = currentThrottle * maxThrust * torqueFactor * torqueDir;
        rb.AddTorque(transform.up * torqueMagnitude, ForceMode.Force);
    }

地面效应 (Ground Effect)

当无人机贴近地面时,下洗气流受阻,升力会增加。

  • 实现:向下发射射线检测高度。高度 < 0.5m 时,根据距离非线性增加升力系数。
  • 效果:降落时会有明显的"气垫感",不会直接"砸"向地面,起飞也更轻盈。
csharp 复制代码
            // --- 地面效应 (Ground Effect) ---
            RaycastHit hit;
            if (Physics.Raycast(transform.position, Vector3.down, out hit, groundEffectMaxHeight))
            {
                float ratio = 1.0f - (hit.distance / groundEffectMaxHeight);
                float groundEffectMultiplier = 1.0f + (ratio * groundEffectLiftFactor);
                
                m1 *= groundEffectMultiplier;
                m2 *= groundEffectMultiplier;
                m3 *= groundEffectMultiplier;
                m4 *= groundEffectMultiplier;
            }

非线性空气阻力 (Quadratic Drag)

Unity 默认的 Drag 是线性的 ( F ∝ v F \propto v F∝v),这让无人机感觉像在水里游。

  • 实现 :手动计算平方阻力 F = − v ⋅ ∣ v ∣ ⋅ k F = -v \cdot |v| \cdot k F=−v⋅∣v∣⋅k。
  • 差异化阻力
    • 垂直方向:机身扁平,阻力系数大 (1.0)。下落会有明显的终端速度。
    • 水平方向:机身流线,阻力系数小 (0.2)。允许长距离惯性滑行。
  • 效果:这是手感提升最明显的一步。前飞松杆后的滑行感,以及高空下落时的速度平衡,都非常接近真机。
csharp 复制代码
        // 计算平方阻力: F = -v * |v| * dragFactor
        Vector3 dragForceLocal = Vector3.zero;
        dragForceLocal.x = -localVel.x * Mathf.Abs(localVel.x) * dragFactors.x;
        dragForceLocal.y = -localVel.y * Mathf.Abs(localVel.y) * dragFactors.y; // 垂直阻力通常较大
        dragForceLocal.z = -localVel.z * Mathf.Abs(localVel.z) * dragFactors.z;

飞行模式

无人机操作一共配置了两种模式

Acro Mode (手动模式)

  • 逻辑 :摇杆控制角速度。松杆后无人机保持当前姿态。

Angle Mode (自稳模式)

  • 逻辑双环 PID 控制
    • 外环 :摇杆控制角度 (例如满杆 45 度)。计算出目标角速度。
    • 内环:执行目标角速度。
  • 特性:松杆自动回平。
csharp 复制代码
       if (mode == FlightMode.Angle)
        {
            // --- Angle Mode (自稳模式) ---
            // 摇杆输入映射为目标角度 (-45 ~ 45 度)
            float targetPitchAngle = input.Pitch * maxTiltAngle;
            float targetRollAngle = -input.Roll * maxTiltAngle; // Unity Z轴旋转方向可能需要反转

            // 获取当前角度 (将 0-360 转换为 +/- 180)
            Vector3 currentEuler = transform.localEulerAngles;
            float currentPitch = Mathf.DeltaAngle(0, currentEuler.x);
            float currentRoll = Mathf.DeltaAngle(0, currentEuler.z);

            // 外环 P 控制: 角度误差 -> 目标角速度
            // Pitch: 目标 - 当前 (因为后面混控器 Pitch 反转了,所以这里保持 目标-当前)
            targetPitchRate = (targetPitchAngle - currentPitch) * angleKP;
            
            // Roll: 当前 - 目标 (反转逻辑,防止正反馈翻滚)
            // 右滚是负角度,如果不反转,误差为正,导致继续右滚
            targetRollRate = (currentRoll - targetRollAngle) * angleKP;
            
            // Yaw 轴通常保持 Rate 模式
            targetYawRate = input.Yaw * 150f;
        }
        else
        {
            // --- Acro Mode (特技/手动模式) ---
            // 摇杆输入直接映射为目标角速度 (-200 ~ 200 度/秒)
            targetPitchRate = input.Pitch * 200f;
            targetRollRate = input.Roll * 200f;
            targetYawRate = input.Yaw * 150f;
        }

稳定性与体验优化

除了核心物理,还有很多细节决定了模拟器的可用性:

  • 怠速保护 (Idle Protection)
    • 问题:地面待机时,PID 积分项累积导致无人机"抽搐"或乱跳。
    • 解决:油门 < 5% 时,强制关闭电机并 Reset PID
csharp 复制代码
        // --- 怠速保护逻辑 ---
        if (throttleBase < 0.05f)
        {
            m1 = m2 = m3 = m4 = 0f;
            pitchPID.Reset();
            rollPID.Reset();
            yawPID.Reset();
        }
  • PID 限幅 (Authority Limit)
    • 限制 PID 对电机的最大控制权 (例如 30%),防止极端情况下 PID 输出过大导致电机饱和甚至侧翻。
csharp 复制代码
         // --- PID 限幅保护 ---
         float pOut = Mathf.Clamp(pitchCorrection * correctionScale, -maxPIDAuthority, maxPIDAuthority);
         float rOut = Mathf.Clamp(rollCorrection * correctionScale, -maxPIDAuthority, maxPIDAuthority);
         float yOut = Mathf.Clamp(yawCorrection * correctionScale, -maxPIDAuthority, maxPIDAuthority); 
  • 一键重置
    • 添加手柄 Y 键 复位功能,炸机后瞬间回到原点并清空物理速度,方便反复练习。

二、源码

代码结构

代码结构比较简单,一共就4个代码就实现了整个无人机模拟,以下是流程图:

架构分层 核心脚本 角色定位 主要功能与逻辑 数据流向 / 物理作用
输入层 DroneInput.cs 信号预处理 1. 读取硬件 :接收物理手柄输入。 2. 数据清洗:处理摇杆死区、应用映射曲线。 输出 :标准化控制信号 • 油门 (Throttle): 0 ~ 1 •俯仰/横滚/偏航: -1 ~ 1
控制层 FlightController.cs 大脑 (Brain) 1. 模式处理 :根据 Angle/Acro 模式将输入转为目标角速度。 2. PID 调度 :调用 PID.cs 计算误差。 3. 混控 (Mixer) :混合主油门与 PID 修正值。 4. 环境模拟:计算非线性阻力与地面效应。 输入 :标准化信号 + 刚体物理状态 输出:4 个电机的最终目标油门
算法层 PID.cs 纯数学计算 1. 误差计算 :对比目标值与当前值。 2. 修正输出:计算比例§+积分(I)+微分(D)的总和。 输入 :目标值、当前值、时间差 (dt) 输出:PID 修正值
执行层 MotorEngine.cs 四肢 (Limbs) 1. 惯性模拟 :通过低通滤波模拟电机响应延迟。 2. 物理交互:将油门值转换为具体的物理力。 输入 :目标油门值 作用 :向 Rigidbody 施加 • 推力 (Force) • 反扭矩 (Torque)

总结

通过以上步骤,我们从一个简单的刚体运动,进化到了一个具备空气动力学特性的飞行模拟器。目前的物理手感已经能传达出穿越机的"惯性"和"风阻"。

如果能帮助到你!可以给我来个点赞吗? 谢谢!u😘

相关推荐
harrain1 天前
拟合模型与虚幻引擎
游戏引擎·数字孪生·虚幻
努力长头发的程序猿1 天前
在Unity2d中,根据Y轴决定渲染顺序(URP项目适用)
unity
DaLiangChen2 天前
Unity 精准 Mesh 点击检测:穿透遮挡 + 单击双击识别
unity·游戏引擎
IT猿手2 天前
基于控制障碍函数的多无人机编队动态避障控制方法研究,MATLAB代码
开发语言·matlab·无人机·openclaw·多无人机动态避障路径规划·无人机编队
迪普阳光开朗很健康2 天前
Unity中new() 和实例化有什么区别?
unity·游戏引擎
mxwin2 天前
Unity Shader 极坐标特效 从数学原理到实战案例
unity·游戏引擎·shader·uv
IT猿手2 天前
基于 ZOH 离散化与增量 PID 的四旋翼无人机轨迹跟踪控制研究,MATLAB代码
开发语言·算法·matlab·无人机·动态路径规划·openclaw
IT猿手2 天前
基于控制障碍函数(Control Barrier Function, CBF)的无人机编队三维动态避障路径规划,MATLAB代码
开发语言·matlab·无人机·动态路径规划·无人机编队
IT猿手2 天前
基于 CBF 的多无人机编队动态避障路径规划研究,无人机及障碍物数量可以自定义修改,MATLAB代码
开发语言·matlab·无人机·动态路径规划
IT猿手2 天前
基于强化学习Q-learning算法的无人机三维路径规划算法原理与实现,MATLAB代码
算法·matlab·无人机·路径规划·动态路径规划