要在 Unity 中实现一个 F-16 的 DCS 风格模拟器,并完全基于自制的物理引擎,这是一个复杂而庞大的项目,但可以明确拆分成几个子模块和开发阶段。
✅ 项目目标概述
目标:用 Unity 作为可视化平台,自制 C++ 物理引擎负责所有仿真逻辑,实现可控的 F-16 战斗机高保真物理飞行模拟(DCS 风格)。
📦 一、功能模块拆分(需你引擎支持的部分)
| 类别 | 子模块 | 是否必须 | 描述 |
|---|---|---|---|
| 🧠 动力学建模 | 六自由度模型(6DOF) | ✅ 必须 | 模拟飞机的真实运动状态(力、力矩、姿态) |
| 🌀 气动模型 | 升力、阻力、侧力 | ✅ 必须 | 基于气动数据表或公式计算 |
| 🛠️ 飞控系统 | FBW 控制律、控制面响应 | ✅ 必须 | 模拟飞行控制器输入到控制面的映射 |
| ⚙️ 引擎系统 | 喷气发动机推力建模 | ✅ 必须 | 包含推力、油门响应等 |
| 🧩 碰撞系统 | 地面、障碍物检测 | ⭕ 可选 | 初期可跳过,只做空中动力学 |
| 👨✈️ 控制输入 | 舵杆、踏板、油门等 | ✅ 必须 | 绑定键盘/外设输入映射 |
| 🧪 传感器建模 | 空速、高度、姿态 | ⭕ 推荐 | 为未来飞控或武器系统做准备 |
| 🌍 环境建模 | 风、气压、重力变化 | ⭕ 可选 | 增强仿真真实性 |
| 🧩 GUI 显示 | 仪表、HUD、MFD 等 | ⭕ 可选 | 模拟座舱感知,可后期加入 |
| 🎮 Unity桥接 | 模型绑定、输入桥、状态更新 | ✅ 必须 | Unity 调用物理引擎 DLL,展示效果 |
🗂️ 二、开发流程阶段划分(建议按阶段开发)
🧱 第一阶段:骨架构建与最小运行
目标:实现刚体 F-16 的最简飞行模拟(6DOF 模型)
| 步骤 | 工作内容 | 输出 |
|---|---|---|
| 1 | 设计 DLL 接口结构(输入控制、输出状态) | PhysicsAPI.h/.cpp |
| 2 | 实现刚体积分器(6DOF 运动方程) | 支持姿态和速度更新 |
| 3 | 构建基础飞机力模型(升力/阻力/推力) | 简化版 F-16 仿真 |
| 4 | Unity 插件桥接 DLL 接口 | 实现模型位置更新 |
| 5 | 构建 Unity 场景(天空、模型、摄像机) | 初步可飞行 Demo |
📌 建议用简化气动模型,如平板模型或线性近似(可后期替换成真实数据表)。
🛫 第二阶段:真实 F-16 飞行特性建模
目标:更贴近 F-16 实际表现,加入 FBW 控制与高保真气动
| 步骤 | 内容 | 说明 |
|---|---|---|
| 1 | 加入真实气动数据表(CL, CD, CY vs AoA) | 可从 NASA 或 F-16 公开数据集中获取 |
| 2 | 实现控制面(副翼、升降舵、方向舵)与气动耦合 | 用 delta deflection 计算 ΔCL、ΔCm |
| 3 | 加入 Fly-By-Wire 稳定器(Pitch SAS, Roll Damping) | 类似 PID 控制器 |
| 4 | 推力建模(喷气曲线 vs 油门) | 基于 RPM 或 Thrust table |
| 5 | 接入 Joystick / HOTAS 外设(如有) | 用 Unity Input System 支持 XInput |
🚀 第三阶段:飞行环境 + 更高保真系统
目标:构建真实飞行环境和系统感知
| 步骤 | 内容 |
|---|---|
| 1 | 地形与碰撞检测(地面高度场) |
| 2 | 传感器数据模拟(空速计、陀螺仪、GPS) |
| 3 | 天气系统(风、湍流、风切变) |
| 4 | HUD、仪表板仿真 |
| 5 | 武器、雷达系统扩展(可选) |
🧭 三、DLL 接口建议设计
extern "C" {
void InitF16Model(); // 初始化模型
void SetControlInputs(float throttle, float pitch, float roll, float yaw); // 输入
void StepSimulation(float dt); // 时间步进
void GetAircraftState(float* pos, float* vel, float* euler, float* angVel); // 获取状态
}
Unity 用 DllImport 引入上述接口,完成数据交互。
📄 四、推荐资料
| 类型 | 推荐资源 |
|---|---|
| 飞行动力学 | 《Introduction to Flight》 by John Anderson |
| 控制系统 | 《Aircraft Control and Simulation》 by Stevens & Lewis |
| F-16 数据 | NASA Technical Report: “F-16 Flight Test Data” |
| 气动数据库 | [NASA FoilSim, OpenVSP, USAF Datcom] |
| Unity 外设支持 | Unity Input System / Rewired 插件 |
二. Unity中尝试运行VehicleDemo
Unity中文件导入路径:Assets/…
怎样将c++格式的函数导出为unity可用的c格式
VS添加现有项
VehicleCAPI.h/.cpp
#pragma once
#include "common/utils/Macro.h"
#ifdef __cplusplus
extern "C" {
#endif
// 车辆控制
DAMPS_C_API void Vehicle_SetThrottleInput(void *vehicle, float throttle);
DAMPS_C_API void Vehicle_SetSteeringInput(void *vehicle, float steering);
DAMPS_C_API void Vehicle_SetBrakeInput(void *vehicle, float brake);
DAMPS_C_API void Vehicle_SetGear(void *vehicle, int gear);
DAMPS_C_API int Vehicle_GetGear(void *vehicle);
// 车辆状态获取
DAMPS_C_API void Vehicle_GetPosition(void *vehicle, float *x, float *y, float *z);
DAMPS_C_API void Vehicle_GetRotation(void *vehicle, float *x, float *y, float *z, float *w);
#ifdef __cplusplus
}
#endif
#include "vehicle/bridge/VehicleCAPI.h"
#include "common/math/Coordsys.h"
#include "common/math/Vector.h"
#include "vehicle/base/VehicleBase.h"
using namespace Damps;
extern "C" {
// 注意这里直接用 void* 转 VehicleBase*
static VehicleBase *toVehicleBase(void *v) { return static_cast<VehicleBase *>(v); }
void Vehicle_SetThrottleInput(void *vehicle, float throttle)
{
auto veh = toVehicleBase(vehicle);
veh->setThrottleInput(throttle);
}
void Vehicle_SetSteeringInput(void *vehicle, float steering)
{
auto veh = toVehicleBase(vehicle);
veh->setSteeringInput(steering);
}
void Vehicle_SetBrakeInput(void *vehicle, float brake)
{
auto veh = toVehicleBase(vehicle);
veh->setBrakeInput(brake);
}
// Gear相关接口,需要你在VehicleBase或派生类实现getter/setter
void Vehicle_SetGear(void *vehicle, int gear)
{
auto veh = toVehicleBase(vehicle);
veh->getTransmission()->setGear(gear);
}
int Vehicle_GetGear(void *vehicle)
{
auto veh = toVehicleBase(vehicle);
return veh->getTransmission()->getGear();
}
void Vehicle_GetPosition(void *vehicle, float *x, float *y, float *z)
{
auto veh = toVehicleBase(vehicle);
auto pos = veh->getWorldTransform().getPosition();
*x = (float)pos.x();
*y = (float)pos.y();
*z = (float)pos.z();
}
void Vehicle_GetRotation(void *vehicle, float *x, float *y, float *z, float *w)
{
auto veh = toVehicleBase(vehicle);
auto rot = veh->getWorldTransform().getRotation();
*x = (float)rot.x();
*y = (float)rot.y();
*z = (float)rot.z();
*w = (float)rot.w();
}
}
VehicleWorldCAPI.h/.cpp
#pragma once
#include "common/utils/Macro.h"
#ifdef __cplusplus
extern "C" {
#endif
// 创建与销毁
DAMPS_C_API void *VehicleWorld_Create();
DAMPS_C_API void VehicleWorld_Destroy(void *world);
// 加载与保存
DAMPS_C_API void VehicleWorld_Load(void *world, const char *path);
DAMPS_C_API void VehicleWorld_Save(void *world, const char *path);
// 更新
DAMPS_C_API void VehicleWorld_Update(void *world, float dt);
// 车辆操作
DAMPS_C_API int VehicleWorld_GetVehicleCount(void *world);
DAMPS_C_API int VehicleWorld_AddVehicle(void *world, void *vehicle);
DAMPS_C_API void VehicleWorld_RemoveVehicle(void *world, int id);
DAMPS_C_API void *VehicleWorld_GetVehicle(void *world, int index);
#ifdef __cplusplus
}
#endif
#include "vehicle/bridge/VehicleWorldCAPI.h"
#include "vehicle/core/VehicleWorld.h"
using namespace Damps;
extern "C" {
void *VehicleWorld_Create() { return new VehicleWorld(); }
void VehicleWorld_Destroy(void *world) { delete static_cast<VehicleWorld *>(world); }
void VehicleWorld_Load(void *world, const char *path) { static_cast<VehicleWorld *>(world)->load(std::string(path)); }
void VehicleWorld_Save(void *world, const char *path) { static_cast<VehicleWorld *>(world)->save(std::string(path)); }
void VehicleWorld_Update(void *world, float dt) { static_cast<VehicleWorld *>(world)->update(dt); }
int VehicleWorld_GetVehicleCount(void *world) { return static_cast<VehicleWorld *>(world)->getVehicles().size(); }
DAMPS_C_API void *CreateWheeledVehicle() { return new Ref<VehicleBase>(CreateRef<WheeledVehicle>()); }
int VehicleWorld_AddVehicle(void *world, void *vehiclePtr)
{
auto vehSharedPtr = *static_cast<Ref<VehicleBase> *>(vehiclePtr);
return static_cast<VehicleWorld *>(world)->addVehicle(vehSharedPtr);
}
void VehicleWorld_RemoveVehicle(void *world, int id) { static_cast<VehicleWorld *>(world)->removeVehicle(id); }
void *VehicleWorld_GetVehicle(void *world, int index)
{
return static_cast<VehicleWorld *>(world)->getVehicle(index).get();
}
}
重新编译,导出dll文件放在Assets/Plugins目录下
可能的问题:怎样检查dll动态库导出的函数名
- 打开Developer Command Prompt for VS 2022
- cd E:\aStudy\FLight\dampsengine\build\bin
- dumpbin /dependents DampsEngine.dll
- dumpbin /exports DampsEngine.dll 核心
Unity中导入dll的格式
// DLL函数声明
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr VehicleWorld_Create();
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern void VehicleWorld_Destroy(IntPtr world);
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern void VehicleWorld_Load(IntPtr world, string filePath);
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern void VehicleWorld_Update(IntPtr world, float dt);
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr VehicleWorld_GetVehicle(IntPtr world, int index);
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern void Vehicle_SetThrottleInput(IntPtr vehicle, float throttle);
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern void Vehicle_SetSteeringInput(IntPtr vehicle, float steering);
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern void Vehicle_SetBrakeInput(IntPtr vehicle, float brake);
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern void Vehicle_SetGear(IntPtr vehicle, int gear);
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern int Vehicle_GetGear(IntPtr vehicle);
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern void Vehicle_GetPosition(IntPtr vehicle, out float x, out float y, out float z);
[DllImport("DampsEngine", CallingConvention = CallingConvention.Cdecl)]
private static extern void Vehicle_GetRotation(IntPtr vehicle, out float x, out float y, out float z, out float w);
三. 尝试构建基本的F-16Demo-基于DCS的F-16模块源码设计
1. 基本函数组件部分
下面是变量的物理意义对应表格,基于航空气动力学中常见的系数和导数:
📋 航空气动力参数说明表
| 变量名 | 含义(中文) | 物理意义 / 备注 |
|---|---|---|
Cx | 轴向力系数(无控制面偏转) | 作用在飞行器轴向(通常为 X 轴)上的气动力系数 |
Cx_total | 总的轴向力系数(包括控制面影响) | Cx + 各控制面偏差项(如 Cx_delta_lef 等) |
Cx_delta_lef | 左前缘襟翼(LEF)偏转对轴向力的影响系数 | 控制面对 Cx 的附加影响 |
dXdQ | 俯仰角速度 q 对 X 向力的导数 | ∂Cx/∂q × 标准化因子 |
Cxq | q 对 Cx 的导数系数 | 表示动态稳定性贡献 |
Cxq_delta_lef | q 和 LEF 联合作用对 Cx 的影响 | 交叉导数项 |
Cz | 垂向力系数(无控制面偏转) | 通常垂直于飞行器纵向,在 Z 轴 |
Cz_total | 总的垂向力系数 | 包括所有控制面贡献 |
Cz_delta_lef | LEF 对 Cz 的影响系数 | 控制面偏转引起的附加升力或阻力 |
dZdQ | q 对 Z 向力的导数 | 反映动态响应特性 |
Czq | q 对 Cz 的导数系数 | 动态气动导数之一 |
Czq_delta_lef | q 和 LEF 联合作用对 Cz 的影响 | 交叉导数项 |
Cm | 俯仰力矩系数(无控制面偏转) | 俯仰轴(通常为 Y 轴)上的力矩系数 |
Cm_total | 总的俯仰力矩系数 | 包括控制面贡献 |
Cm_delta_lef | LEF 对 Cm 的贡献 | 控制面偏转引起的俯仰力矩变化 |
Cm_delta | 通用控制面偏转对 Cm 的影响 | 可能是升降舵 delta_el |
Cm_delta_ds | 上下偏置对 Cm 的影响 | 可用于模拟舵面非对称偏转或偏心装配 |
Cmq | q 对 Cm 的导数系数 | 动态稳定性关键指标 |
Cmq_delta_lef | q 和 LEF 联合作用对 Cm 的影响 | 交叉导数项 |
eta_el | 升降舵效率因子 | 修正升降舵对气动响应的影响 |
Cy | 侧向力系数(无控制面偏转) | 与机体 Z 轴垂直的侧向方向(Y 轴)上的气动力 |
Cy_total | 总的侧向力系数 | 包括偏航舵、副翼等控制面作用 |
Cy_delta_lef | LEF 对 Cy 的影响 | 控制面附加效应 |
Cy_delta_r30 | 偏航舵(30度)对 Cy 的影响 | 偏航控制输入效应 |
Cy_delta_a20 | 副翼(20度)对 Cy 的影响 | 协调转弯控制面 |
Cy_delta_a20_lef | A20 与 LEF 联合作用对 Cy 的影响 | 高阶控制交叉效应 |
dYdail | ∂Cy/∂副翼 | 副翼线性导数(灵敏度) |
dYdR | ∂Cy/∂r(偏航角速度) | 侧滑稳定导数 |
dYdP | ∂Cy/∂p(横滚角速度) | 横滚诱导侧滑导数 |
Cyr | 偏航角速度 r 对 Cy 的导数 | 动态侧向力响应 |
Cyr_delta_lef | r 和 LEF 联合作用对 Cy 的影响 | 交叉导数 |
Cyp | 横滚角速度 p 对 Cy 的导数 | 侧滑耦合项 |
Cyp_delta_lef | p 和 LEF 联合作用对 Cy 的影响 | 交叉导数 |
Cn | 偏航力矩系数 | 绕 Z 轴的旋转力矩系数 |
Cn_total | 总的偏航力矩系数 | 控制面 + 本体贡献 |
Cn_delta_lef | LEF 对 Cn 的影响 | |
Cn_delta_r30 | 偏航舵对 Cn 的影响 | |
Cn_delta_beta | 侧滑角 β 对 Cn 的增益 | |
Cn_delta_a20 | A20(副翼)对 Cn 的影响 | |
Cn_delta_a20_lef | A20 和 LEF 联合作用对 Cn 的影响 | |
dNdail | ∂Cn/∂副翼 | |
dNdR | ∂Cn/∂r | |
dNdP | ∂Cn/∂p | |
Cnr | r 对 Cn 的导数 | |
Cnr_delta_lef | r 和 LEF 联合作用对 Cn 的影响 | |
Cnp | p 对 Cn 的导数 | |
Cnp_delta_lef | p 和 LEF 联合作用对 Cn 的影响 | |
Cl | 横滚力矩系数 | 绕 X 轴的旋转力矩系数 |
Cl_total | 总的横滚力矩系数 | |
Cl_delta_lef | LEF 对 Cl 的影响 | |
Cl_delta_r30 | 偏航舵对横滚的耦合影响 | |
Cl_delta_beta | 侧滑角 β 对横滚的影响 | |
Cl_delta_a20 | A20 对横滚的直接影响 | |
Cl_delta_a20_lef | A20 和 LEF 联合作用对 Cl 的影响 | |
dLdail | ∂Cl/∂副翼 | |
dLdR | ∂Cl/∂r | |
dLdP | ∂Cl/∂p | |
Clr | r 对 Cl 的导数 | |
Clr_delta_lef | r 和 LEF 联合作用对 Cl 的影响 | |
Clp | p 对 Cl 的导数 | |
Clp_delta_lef | p 和 LEF 联合作用对 Cl 的影响 |
🧠 简化理解:
C*表示某个力/力矩的基准系数*_delta_*表示某个控制面(如 lef, r, a)对其影响*q, *r, *p表示对角速度(俯仰 q、偏航 r、横滚 p)的导数,即动态导数*_total是包含控制面和动态导数影响的总系数dX/d*表示某种对状态变量(控制面、角速度)的线性灵敏度
按模块分类整理(代码分类:升力、阻力、侧向力 / 力矩模块):
🟥 一、阻力/轴向力(X 轴方向)相关变量
// == 阻力系数相关(X 方向) ==
double Cx_total = 0.0; // 总轴向力系数
double Cx = 0.0; // 基本轴向力系数
double Cx_delta_lef = 0.0; // LEF 对 Cx 的影响
double dXdQ = 0.0; // q 对 X 向力的导数
double Cxq = 0.0; // Cx 的 q 动态导数
double Cxq_delta_lef = 0.0; // q 和 LEF 联合作用对 Cx 的影响
🟦 二、升力/垂向力(Z 轴方向)相关变量
// == 升力系数相关(Z 方向) ==
double Cz_total = 0.0; // 总升力系数
double Cz = 0.0; // 基本升力系数
double Cz_delta_lef = 0.0; // LEF 对 Cz 的影响
double dZdQ = 0.0; // q 对 Z 向力的导数
double Czq = 0.0; // Cz 的 q 动态导数
double Czq_delta_lef = 0.0; // q 和 LEF 联合作用对 Cz 的影响
🟨 三、侧向力(Y 轴方向)相关变量
// == 侧向力系数相关(Y 方向) ==
double Cy_total = 0.0; // 总侧向力系数
double Cy = 0.0; // 基本侧向力系数
double Cy_delta_lef = 0.0; // LEF 对 Cy 的影响
double dYdail = 0.0; // 副翼对 Cy 的导数
double Cy_delta_r30 = 0.0; // 偏航舵(30°)对 Cy 的影响
double dYdR = 0.0; // 偏航角速度 r 对 Cy 的导数
double dYdP = 0.0; // 横滚角速度 p 对 Cy 的导数
double Cy_delta_a20 = 0.0; // 副翼(20°)对 Cy 的影响
double Cy_delta_a20_lef = 0.0; // A20 和 LEF 联合作用对 Cy 的影响
double Cyr = 0.0; // r 对 Cy 的动态导数
double Cyr_delta_lef = 0.0; // r 和 LEF 联合作用对 Cy 的导数
double Cyp = 0.0; // p 对 Cy 的动态导数
double Cyp_delta_lef = 0.0; // p 和 LEF 联合作用对 Cy 的导数
🟧 四、俯仰力矩(绕 Y 轴)相关变量
// == 俯仰力矩系数相关(Cm) ==
double Cm_total = 0.0; // 总俯仰力矩系数
double Cm = 0.0; // 基本俯仰力矩系数
double eta_el = 0.0; // 升降舵效率因子
double Cm_delta_lef = 0.0; // LEF 对 Cm 的影响
double Cm_delta = 0.0; // 升降舵偏角对 Cm 的影响
double Cm_delta_ds = 0.0; // 舵偏中心不对称项
double dMdQ = 0.0; // q 对 Cm 的导数
double Cmq = 0.0; // Cm 的 q 动态导数
double Cmq_delta_lef = 0.0; // q 和 LEF 联合作用对 Cm 的导数
🟩 五、偏航力矩(绕 Z 轴)相关变量
// == 偏航力矩系数相关(Cn) ==
double Cn_total = 0.0; // 总偏航力矩系数
double Cn = 0.0; // 基本偏航力矩系数
double Cn_delta_lef = 0.0;
double dNdail = 0.0; // 副翼对 Cn 的导数
double Cn_delta_r30 = 0.0; // 偏航舵(30°)对 Cn 的影响
double dNdR = 0.0; // 偏航角速度 r 对 Cn 的导数
double dNdP = 0.0; // 横滚角速度 p 对 Cn 的导数
double Cn_delta_beta = 0.0; // 侧滑角 β 对 Cn 的导数
double Cn_delta_a20 = 0.0;
double Cn_delta_a20_lef = 0.0;
double Cnr = 0.0; // r 对 Cn 的导数
double Cnr_delta_lef = 0.0;
double Cnp = 0.0; // p 对 Cn 的导数
double Cnp_delta_lef = 0.0;
🟪 六、横滚力矩(绕 X 轴)相关变量
// == 横滚力矩系数相关(Cl) ==
double Cl_total = 0.0; // 总横滚力矩系数
double Cl = 0.0; // 基本横滚力矩系数
double Cl_delta_lef = 0.0;
double dLdail = 0.0;
double Cl_delta_r30 = 0.0;
double dLdR = 0.0;
double dLdP = 0.0;
double Cl_delta_beta = 0.0;
double Cl_delta_a20 = 0.0;
double Cl_delta_a20_lef = 0.0;
double Clr = 0.0; // r 对 Cl 的导数
double Clr_delta_lef = 0.0;
double Clp = 0.0; // p 对 Cl 的导数
double Clp_delta_lef = 0.0;
你贴出的这些函数来自一个对接 DCS (Digital Combat Simulator) 飞行模拟框架的飞行器模型接口,主要用于描述和传入飞机当前状态、控制量、大气环境和质量特性。
这是飞行仿真中的标准函数接口结构,常见于 FM(Flight Model)模块开发。
我为你详细解释每个函数的含义、参数和用途:
✅ ed_fm_set_atmosphere(...)
功能:
设置当前大气环境参数(通常由仿真器提供),并更新飞行器内部状态。
参数说明:
| 参数名 | 含义 |
|---|---|
h | 海拔高度(单位:米) |
t | 当前温度(开尔文) |
a | 当前音速(米/秒) |
ro | 空气密度(kg/m³) |
p | 大气压强(N/m²) |
wind_vx/vy/vz | 世界坐标系下风速(包括乱流) |
内部操作:
更新 F16 模块中的大气参数(密度、温度、高度、压力等),供后续气动计算使用。
✅ ed_fm_set_current_mass_state(...)
功能:
设置当前飞行器质量、质心和惯量张量。
参数说明:
| 参数名 | 含义 |
|---|---|
mass | 当前质量(kg) |
center_of_mass_x/y/z | 质心位置 |
moment_of_inertia_x/y/z | 惯性矩:绕机体坐标轴的惯量 |
内部操作:
更新飞行器当前质量属性,包括计算重力 F16::weight_N = mass * g。
✅ ed_fm_set_current_state(...)
功能:
设置世界坐标系下的运动状态,包括线加速度、速度、位置和角速度。
参数说明(部分):
| 参数名 | 含义 |
|---|---|
ax/ay/az | 线加速度(世界坐标系) |
vx/vy/vz | 线速度(世界坐标系) |
px/py/pz | 位置(世界坐标系) |
omegax/y/z | 角速度 |
omegadotx/y/z | 角加速度 |
quaternion_x/y/z/w | 姿态四元数 |
内部操作:
这里只处理了 ay,可能是后续使用其他项的代码省略了(简化版本或尚未实现全部功能)。
✅ ed_fm_set_current_state_body_axis(...)
功能:
设置机体坐标系下的运动状态和风速,包括:
- 加速度 / 速度
- 风速
- 姿态角
- 角速度 / 加速度
- AoA、AoS
关键输入说明:
| 参数名 | 含义 |
|---|---|
ax/ay/az | 线加速度(机体系) |
vx/vy/vz | 速度(机体系) |
wind_* | 风速(机体系) |
omegax/y/z | 角速度 |
yaw/pitch/roll | 姿态角(单位:rad) |
common_angle_of_attack | 攻角(AoA) |
common_angle_of_slide | 侧滑角(AoS) |
内部操作:
- 把机体速度、风速等赋值给变量;
- 将攻角和侧滑角转为角度赋值给
F16::alpha_DEG、beta_DEG; - 更新角速度和加速度;
✅ ed_fm_set_command(...)
功能:
接收从控制器(如遥控器或玩家)发来的输入命令,比如油门、摇杆输入。
处理命令:
命令类型(command) | 处理方式 |
|---|---|
JoystickRoll | 横滚摇杆输入 [-1~1] 映射到 latStickInput |
JoystickPitch | 俯仰摇杆输入 [-1~1] 映射并取反 |
JoystickYaw | 偏航输入 [-1~1] 映射并取反 |
JoystickThrottle | 油门杆 [-1 |
✅ ed_fm_change_mass(...)
功能:
告诉 DCS 当前一帧是否有质量变化(燃油消耗、载荷抛弃等),用于更新质量/惯量信息。
参数说明:
| 参数名 | 含义 |
|---|---|
delta_mass | 质量变化量(kg) |
delta_mass_pos_xyz | 质量变化的质心位置(相对机体) |
delta_mass_moi_xyz | 对应的惯量变化 |
当前行为:
- 检查是否用户手动修改了惯性矩;
- 如果有变化 → 计算 delta_moi;
- 当前返回
false,禁用动态质量更新(可能还未实现或调试中)。
总结提供的接口
定义一个典型的 DCS 飞行器外部飞行模型(External Flight Model,简称 EFM)接口头文件,它是通过 ED_FM_TEMPLATE_API 向 DCS 引擎注册的一组C 接口函数,供仿真器调用。以下是对所有接口函数的详细分类讲解:
🟦 1. 力与力矩接口(Force & Moment)
| 函数名 | 功能说明 |
|---|---|
ed_fm_add_local_force(...) | 添加局部坐标系中的力(机体坐标系)及其作用点 |
ed_fm_add_global_force(...) | 添加全局坐标系中的力(世界坐标系)及其作用点 |
ed_fm_add_local_moment(...) | 添加机体坐标系下的力矩 |
ed_fm_add_global_moment(...) | 添加世界坐标系下的力矩 |
✅ 用于施加气动、推进或其他外力,通常在
ed_fm_simulate中调用。
🟦 2. 仿真主循环接口
| 函数名 | 功能说明 |
|---|---|
ed_fm_simulate(double dt) | 主物理仿真循环,每帧被 DCS 引擎调用,dt 为时间步长(秒) |
✅ 所有气动力、推力、操控计算都在这里进行。
🟦 3. 环境接口(Atmosphere)
| 函数名 | 功能说明 |
|---|---|
ed_fm_set_atmosphere(...) | 设置大气环境:海拔、温度、密度、音速、压力和风速 |
✅ 由 DCS 自动提供环境参数,用于气动模型。
🟦 4. 质量与惯量接口
| 函数名 | 功能说明 |
|---|---|
ed_fm_set_current_mass_state(...) | 设置质量、质心和惯性矩(用于重心与动态计算) |
ed_fm_change_mass(...) | 动态报告质量变化,如燃油消耗、载荷抛弃 |
ed_fm_set_internal_fuel(double) | 设置内部燃油量(单位:kg) |
ed_fm_get_internal_fuel() | 查询当前内部燃油量 |
ed_fm_set_external_fuel(...) | 设置外挂油箱燃油及其在机体中的位置 |
ed_fm_get_external_fuel() | 获取外挂燃油总量 |
✅ 内部质量状态用于求解 F=ma,Iα=ΣM 等物理方程。
🟦 5. 飞行状态接口(位置/速度/角速度等)
世界坐标系(World Coordinate System):
| 函数名 | 功能说明 |
|---|---|
ed_fm_set_current_state(...) | 设置线速度、加速度、角速度、姿态四元数(世界坐标) |
机体坐标系(Body Coordinate System):
| 函数名 | 功能说明 |
|---|---|
ed_fm_set_current_state_body_axis(...) | 设置机体系下的速度、加速度、角速度、姿态角、AoA、AoS |
✅ 通常需要两个状态函数一起使用,前者用于位置更新,后者用于计算气动力。
🟦 6. 输入控制接口
| 函数名 | 功能说明 |
|---|---|
ed_fm_set_command(int command, float value) | 接收控制命令,如摇杆/油门等输入,value 通常为 -1 ~ 1 的范围 |
✅ 对应 Lua 中的
Input.get(),需要你自己解析和响应。
🟦 7. 初始化与配置接口
| 函数名 | 功能说明 |
|---|---|
ed_fm_cold_start() | 地面冷启动(关闭发动机)初始化 |
ed_fm_hot_start() | 地面热启动(发动机运行)初始化 |
ed_fm_hot_start_in_air() | 空中初始化状态 |
ed_fm_configure(const char * cfg_path) | 配置文件路径(可用于读取自定义参数) |
✅ 初始化函数应清除状态、重设控制变量。
🟦 8. 可视化与参数查询
| 函数名 | 功能说明 |
|---|---|
ed_fm_set_draw_args(EdDrawArgument *drawargs, size_t size) | 设置用于渲染动画的可视参数(如襟翼角度、起落架位置) |
ed_fm_get_param(unsigned index) | 返回模型内部某个参数值(DCS 可用于显示) |
✅
drawargs对应 3D 模型动画,get_param通常用于传参至 HUD/MFD。
📌 整体结构总结:
| 分类 | 说明 |
|---|---|
| 输入接口 | ed_fm_set_* 和 ed_fm_set_command |
| 输出接口 | ed_fm_add_*, ed_fm_get_*, ed_fm_change_mass, ed_fm_get_param |
| 主流程控制 | ed_fm_simulate |
| 初始化与配置 | ed_fm_cold_start, ed_fm_hot_start, ed_fm_configure |
四. 实现的一些初步设想
考虑到以上的模块中,已经实现了大部分的气动参数计算过程,主要的工作是将这些模块集成在引擎主系统中。
需要的模块:
- 储存数据的单独头文件F16AeroData
- 用于计算气动参数的头文件F16Aero
- 用于控制机身参数的头文件F16FlightController
- 设置大气状态的头文件Atmosphere
- 控制面执行器F16Actuator
- 主文件 F16Demo.h和F16Demo.cpp 逻辑,主要工作是复用绑定刚体逻辑
- 主文件AircraftWorld,主要工作是复用气动逻辑,在这里对飞行器刚体,使用力
716 2CB 00024300 AircraftWorld_Create
717 2CC 00024330 AircraftWorld_Destroy
718 2CD 00024350 AircraftWorld_GetAircraft
719 2CE 000243D0 AircraftWorld_Load
720 2CF 00024420 AircraftWorld_Update
722 2D1 0009BD60 F16_ColdStart
723 2D2 0009BD70 F16_Create
724 2D3 0009BDA0 F16_Destroy
725 2D4 0009BDD0 F16_GetPosition
726 2D5 0009BE90 F16_GetRotation
727 2D6 0009BF40 F16_GetVelocity
728 2D7 0009C010 F16_HotStart
729 2D8 00023D00 F16_SetAtomosphere
730 2D9 0009C020 F16_SetCurrentStateWorld
731 2DA 0009C140 F16_SetEngineThrottle
732 2DB 0009C150 F16_SetPitchControl
733 2DC 0009C160 F16_SetRollControl
734 2DD 0009C170 F16_SetYawControl
735 2DE 0009C180 F16_Update
五. 问题记录
建模刚体时需要设置惯性张量,但是飞机自身需要自定义惯性张量,因此需要在RigidBody中添加这个自定义接口。这里的自定义与坐标轴的关系没有经过检验,需要后面再检测一下
DCS模组代码中的过滤器计算z有问题,已经过修改
飞行器平飞时,没有办法做到稳定起飞(向上的速度受到空气阻力的影响),需要一定攻角
飞行器在物理世界建模为一个AABB包围盒刚体,这可能使它的偏转运动与现实不符
添加项目的方法,添加对应的所有文件,改变cmakelist,重新cmake编译
这里直接基于了BodyBase继承创建了F16Demo类,并且基于RigidBody创建了Aircraft刚体,用指针在F16Demo初始化的时候创建。这个刚体的Shape为BoxShape,参数基于飞机的现实参数,长15,高5,宽(翼展)10。而飞机的气动参数在compute里面计算出来,然后通过刚体的addForce增加力。最后统一更新。
其中可能有缺陷的点是坐标系的变换,xyz轴的检测与各项气动参数的匹配,都需要经过更加细致的检查
同时,这里的飞行器参数是写死的,惯性张量直接在Aircraft类初始化时确定,F16Demo初始化时,确定了飞机刚体的长宽高和质量。同时所有气动参数的参考来自F16AeroData,是利用固定参数数组来做的线性插值,不具有普遍意义。因此,这里的F16Demo理论上应该和WheelVehicle一样,基于一个VehicleBase,再基于BodyBase,但实际设计上,我让它直接基于了BodyBase,没有创建飞行器基类。这一点后期有改进的空间,可能设计到其他飞行器的设计扩展
一个踩过的坑:在物理引擎源码的结构中,直接使用头文件定义一个完整的类,如果有多个文件使用了这个类,在后代中会出现编译错误,因此除去最后一层的Demo层,其他所有需要使用的类都需要进行分离操作。
另一个坑:在模拟器Demo中想要打开绘制时,需要在SimulatorBase类中,添加与创建的Demo对应的类选项,这样才可以打开
编译问题:注意Cmakelist的修改,添加新文件夹,或者新项目时,都需要修改对应的cmakelist
注意一些标准:
坐标系:飞机前进为x正向,右侧出为z轴,向上为y轴
注意:可能的错误来自未被正确初始化的环境参数,如大气静止压强
addForce和addTorque是累加在刚体的内置量上,因此在applyForce逻辑中需要先进行一个初始化,然后再设置新计算出来的力。
目前需要处理的问题是:飞行器的力矩控制仍然存在问题,以及如何避免发动机持续加速导致的向上速度爆炸问题
目前阶段实现的主要思路:
- 尝试通过数学计算的方式,实现飞机在空中的平稳飞行
- 彻底的重新确定一遍所有的飞机坐标系,世界坐标系,刚体坐标系是否有存在设定错误
发现问题: roll yaw pitch 对应不正确(1,3,5设定下)
alpha1_DEG_Limited: -3.011438
beta1_DEG_Limited: -4.993130
el_DEG_Limited: 0.150572
注意到,飞机最后做坐标系适应,dcs 世界的坐标系和opengl坐标系一致,因此这里的坐标不需要变化
IMPORTANT! COORDINATE CONVENTION:
DCS WORLD Convention:
Xbody: Out the front of the nose
Ybody: Out the top of the aircraft
Zbody: Out the right wing
Normal Aerodynamics/Control Convention:
Xbody: Out the front of the nose
Ybody: Out the right wing
Zbody: Out the bottom of the aircraft
This means that if you are referincing from any aerodynamic, stabilty, or control document
they are probably using the second set of directions. Which means you always need to switch
the Y and the Z and reverse the Y prior to output to DCS World
攻角,侧滑角和姿态角速度,都需要坐标系的转化,从世界坐标系转化到机体坐标系,否则会出现角度错误