在开发模拟类游戏或仿真系统时,当我们需要加速仿真(例如通过 Unity 的 Time.timeScale 实现 4 倍速运行)时,Unity 和 JSBSim 的时间步长可能会不同步,导致仿真数据不一致。同样的方法也能扩展到其他的仿真系统上
背景
我们开发了一个基于 Unity 的城市交通场景,其中车辆通过 TrafficSystem 和 TrafficCar 脚本动态生成和移动。为了加速仿真,我们编写了一个 TimeSpeeder.cs 脚本,通过调整 Time.timeScale 实现游戏速度的倍增(例如 4 倍速)。同时,场景中的飞行器数据由 JSBSim 提供,Unity 通过 TCP/IP 协议与 JSBSim 通信。为了让 JSBSim 的仿真速度与 Unity 保持一致,我们需要将 Unity 的时间倍率传递给 JSBSim,并调整 JSBSim 的仿真时间步长。
实现方案
1. Unity 端:通过timescale进行编写加速脚本
private void ApplyTimeScale(float scale)
{
Time.timeScale = scale;
// 优化物理时间调整
Time.fixedDeltaTime = 0.02f * Mathf.Max(1f, scale); // 避免过小的 fixedDeltaTime
}
功能说明
- 加速控制:通过 UI 或快捷键(1-5 键)设置时间倍率(0.5x 到 4x)。
- TCP 客户端:连接到 JSBSim 服务器(默认 127.0.0.1:1138),每次调整 Time.timeScale 时发送 timescale:<value> 消息。
- 物理同步:调整 Time.fixedDeltaTime,确保 Unity 的物理模拟与时间倍率同步。
2. JSBSim 端:修改仿真速度
为了让 JSBSim 根据 Unity 发送的时间倍率调整仿真速度,我们需要修改 JSBSim 的源代码。
2.1 配置 JSBSim 的输入/输出
在 JSBSim 的脚本文件(例如 scripts/C1723.xml)中,添加以下配置:
<output name="localhost" type="SOCKET" port="1138" rate="20"> <property>position/lat-gc-deg</property> <property>position/long-gc-deg</property> <property>position/h-agl-ft</property> </output> <input type="SOCKET" port="1139"/>
- <output>:JSBSim 将飞行数据发送到 localhost:1138(Unity 连接的端口)。
- <input>:JSBSim 从 localhost:1139 接收时间倍率数据。
2.2 修改 JSBSim 源代码
我们需要修改 JSBSim 的 FGInput 和 FGFDMExec 类,以解析时间倍率并调整仿真速度。
-
修改 FGInput::Read 方法(src/input_output/FGInput.cpp):
void FGInput::Read(int socket) { char buffer[256]; int bytes_received = recv(socket, buffer, sizeof(buffer) - 1, 0); if (bytes_received > 0) { buffer[bytes_received] = '\0'; std::string message(buffer); if (message.find("timescale:") == 0) { std::string value = message.substr(10); float timeScale = std::stof(value); if (FDMExec) { FDMExec->SetSimulationRate(timeScale); } } } }
-
添加 SetSimulationRate 方法(src/FDM/JSBSim/FGFDMExec.h 和 FGFDMExec.cpp):
// FGFDMExec.h class FGFDMExec { public: void SetSimulationRate(float rate); private: float simulationRate = 1.0f; double delta_t = 1.0 / 120.0; // 默认时间步长 double base_delta_t = 1.0 / 120.0; }; // FGFDMExec.cpp void FGFDMExec::SetSimulationRate(float rate) { simulationRate = std::max(0.1f, std::min(rate, 10.0f)); if (simulationRate != 0.0f) { delta_t = base_delta_t / simulationRate; } }
-
编译 JSBSim:
- 使用 CMake 和 C++ 编译器(例如 Visual Studio 或 GCC)重新编译 JSBSim。
- 替换旧的 JSBSim 可执行文件。
功能说明
- 解析时间倍率:JSBSim 解析 Unity 发送的 timescale:<value> 消息。
- 调整仿真速度:通过 SetSimulationRate 修改仿真时间步长 delta_t,与 Unity 的 Time.timeScale 同步。
3. 使用步骤
3.1 在 Unity 中设置
- 添加 TimeSpeeder.cs:
- 创建一个空的 GameObject(例如命名为 "TimeSpeeder")。
- 将 TimeSpeeder.cs 脚本附加到该 GameObject。
- 配置 TCP 参数:
- 在 Unity 编辑器的 Inspector 中,设置 Server IP(默认 127.0.0.1)和 Server Port(默认 1138),确保与 JSBSim 的输出端口匹配。
- 运行 Unity 场景:
- 运行场景,调整时间倍率(通过 UI 或快捷键 1-5)。
3.2 启动 JSBSim
- 修改 JSBSim 脚本:
- 确保 C1723.xml(或你的 JSBSim 脚本)包含上述 <input> 和 <output> 配置。
- 运行 JSBSim:
- 打开终端,运行以下命令:
jsbsim --script=scripts/C1723.xml
- JSBSim 将监听 1139 端口,等待 Unity 发送时间倍率。
- 打开终端,运行以下命令:
3.3 测试同步
- 在 Unity 中将时间倍率设置为 4 倍速(Time.timeScale = 4)。
- 观察 JSBSim 的仿真速度是否同步加速(例如飞机的位置更新频率应为原来的 4 倍)。
4. 运行时行为
4.1 预期结果
- 数据传输:
- Unity 每次调整 Time.timeScale 时,通过 TCP 发送 timescale:<value> 消息。
- JSBSim 接收消息并调整仿真速度。
- 同步加速:
- Unity 和 JSBSim 以相同的倍率运行(例如 4 倍速)。
- 两者的时间步长保持一致,仿真数据同步更新。
- 日志输出:
- Unity 控制台:
Connected to JSBSim server at 127.0.0.1:1138 Sent timescale 4 to JSBSim
- (需添加日志):
Received timescale: 4, adjusting simulation rate
- Unity 控制台:
4.2 可能的问题及解决方法
- 连接失败:
- 问题:Unity 无法连接到 JSBSim。
- 解决:检查 JSBSim 是否启动并监听 1139 端口,使用 netcat 测试: 确保 serverIP 和 serverPort 正确。
- 数据不同步:
- 问题:Unity 和 JSBSim 的仿真速度不同步。
- 解决:检查网络延迟,考虑使用 UDP 协议(修改 JSBSim 的 <input> 为 protocol="udp",并在 Unity 中使用 UdpClient)。