找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 1377|回复: 1

[X-Plane] 【X-Plane11/12 插件开发】Part 2 - 数据读取

[复制链接]
发表于 2023-5-24 21:08:12 | 显示全部楼层 |阅读模式
本帖最后由 5271081文字狱 于 2023-5-24 14:17 编辑

Part 2 - 数据读取
长文预告!

同步发表于
https://zhuanlan.zhihu.com/p/631703156,如有不足之处欢迎指正,共同学习进步。系列教程/笔记目的在于帮助广大飞友以及开发者创造属于自己的插件,亦可协助科研人员进行自定义仿真模拟等。


相关链接:Part 1 - 插件开发介绍




XP的飞行数据读写官方称为 Data References,可以通XPLMDataAccess 这个API获取。

数据访问 API 为您提供了一种通用、灵活、高性能的方式来 在 X-Plane 和其他插件中读取和写入数据。例如 此 API 允许您读取和设置导航无线电,获取飞机位置, 确定当前有效图形帧速率等。
该 API 使用不透明的数据引用工作。。。你不知道它来自哪里,但一旦你拥有它,你就可以阅读数据快速并可能写入它。

接下来这个项目将以获取XP的飞机俯仰角以及飞行时间数据为例展示如何进行数据读取。可以从这里下载即开即用的VS项目文件。


该插件运行的效果是在XP主程序目录内建立一个CustomDataRead.txt文件并保存飞行时间以及俯仰角信息,效果如下:

Untitled.png


若打开CustomDataRead.txt发现无内容,请在XP中禁用DIY Data Read插件并再次打开txt文件即可。

主程序的代码如下:

  1. /*********************************************************************
  2. * @file   main.cpp
  3. * @brief  记录并保存飞行数据。
  4. *
  5. * @author zswzy
  6. * @date   20230523
  7. *********************************************************************/

  8. #include <stdio.h>
  9. #include <string.h>

  10. #include "XPLMPlugin.h"
  11. #include "XPLMProcessing.h"
  12. #include "XPLMDataAccess.h"
  13. #include "XPLMUtilities.h"

  14. //======================================================================================

  15. static FILE* g_output_file;

  16. XPLMDataRef g_true_theta_deg_ref        = NULL;     //!< 相对地面的俯仰角 data ref
  17. XPLMDataRef g_total_flight_time_sec_ref = NULL;     //!< 总飞行时间 data ref

  18. float true_theta_deg = 0.0;
  19. float total_flight_time_sec = 0.0;

  20. //======================================================================================

  21. float CallBackDataReader(float elapsedMe, float elapsedSim, int counter, void* refcon);  // 主要功能所在的回调函数

  22. //======================================================================================

  23. /**
  24. * @brief 初始化函数。
  25. *
  26. * @param outName   插件名字
  27. * @param outSig    插件签名
  28. * @param outDesc   插件描述
  29. * @return 1: 正常
  30. */
  31. PLUGIN_API int XPluginStart(
  32.     char* outName,
  33.     char* outSig,
  34.     char* outDesc)
  35. {

  36.     // 插件信息
  37.     strcpy(outName, "DIY Data Read");
  38.     strcpy(outSig,  "zswzy.dataread");
  39.     strcpy(outDesc, "read flight attitude data");

  40.     // 打开记录文件
  41.     char    outputPath[255];
  42.     XPLMGetSystemPath(outputPath);
  43.     strcat(outputPath, "CustomDataRead.txt");
  44.     g_output_file = fopen(outputPath, "w");

  45.     // 寻找 data ref 数据
  46.     g_true_theta_deg_ref = XPLMFindDataRef("sim/flightmodel/position/true_theta");      // 相对地面的俯仰角
  47.     g_total_flight_time_sec_ref = XPLMFindDataRef("sim/time/total_flight_time_sec");    // 总飞行时间

  48.     // 注册回调函数:主要功能
  49.     XPLMRegisterFlightLoopCallback(CallBackDataReader, 1, NULL);

  50.     return 1;
  51. }

  52. //======================================================================================

  53. /**
  54. * @brief 游戏关闭时执行
  55. *
  56. * @return 无
  57. */
  58. PLUGIN_API void XPluginStop(void)
  59. {
  60.     // 注销回调函数
  61.     XPLMUnregisterFlightLoopCallback(CallBackDataReader, NULL);

  62.     // 关闭文件
  63.     fclose(g_output_file);
  64. }

  65. //======================================================================================

  66. /**
  67. * @brief 插件被Plugin Admin禁用时执行
  68. *
  69. * @return 无
  70. */
  71. PLUGIN_API void XPluginDisable(void)
  72. {
  73.     // flush 文件,可在游戏运行时禁用插件以查看数据记录文件
  74.     fflush(g_output_file);
  75. }

  76. //======================================================================================

  77. /**
  78. * @brief 插件启用时执行
  79. *
  80. * @return 无
  81. */
  82. PLUGIN_API int XPluginEnable(void)
  83. {
  84.     return 1;
  85. }

  86. //======================================================================================

  87. /**
  88. * @brief 提供主要功能:记录并保存数据
  89. *
  90. * @param inElapsedSinceLastCall
  91. * @param inElapsedTimeSinceLastFlightLoop
  92. * @param inCounter
  93. * @param inRefcon
  94. * @return float,XP将在不早于该时间间隔后再次执行该函数。
  95. */
  96. float CallBackDataReader(
  97.                 float       inElapsedSinceLastCall,
  98.                 float       inElapsedTimeSinceLastFlightLoop,
  99.                 int         inCounter,
  100.                 void*       inRefcon)
  101. {
  102.     // 获取数据
  103.     true_theta_deg = XPLMGetDataf(g_true_theta_deg_ref);
  104.     total_flight_time_sec = XPLMGetDataf(g_total_flight_time_sec_ref);

  105.     // 保存数据至文件
  106.     fprintf(g_output_file, "true pitch: %.4f deg, flight time: %.4f s.\n", true_theta_deg, total_flight_time_sec);

  107.     return 1.0; //!< 执行间隔时间:XP将在不早于该时间间隔后再次执行该函数。
  108. }
复制代码

笔者已经添加了许多注释,代码基本是自带解释的,很容易能够看懂。下文详解一些重点步骤。

代码详解
主体结构

XPluginStartXPluginStopXPluginDisableXPluginEnable组成了这个dll的主要接口。这四个函数必须要有。

  • - XPluginStart:初始化函数,包含注册回调事件,初始化数据等。
  • - XPluginStop:游戏关闭时执行的函数,销毁对象,释放内存等。
  • - XPluginDisable:插件被Plugin Admin禁用时执行的函数。
  • - XPluginEnable:插件被Plugin Admin启用时执行的函数

XPluginDisableXPluginStop的不同在于,前者是插件被禁用时执行,后者时游戏关闭时执行。插件可以在游戏中多次被启用或者禁用。

数据读取接口

游戏数据的读取需要三个步骤,以俯仰角为例。

1. 声明DataRef全局变量

  1. XPLMDataRef g_true_theta_deg_ref                = NULL;
复制代码

XPLMDataRef 是XP内置的数据格式,可以理解为保存内部数据的指针,初始化为NULL。从下图可以看到,XPLMDataRef 实际上是空指针, 在XPLMDataAccess.h中定义。


Untitled 1.png

2. 寻找对应数据——查找DataRef

  1. g_true_theta_deg_ref = XPLMFindDataRef("sim/flightmodel/position/true_theta");
复制代码
函数 XPLMFindDataRef 参数为字符串,XP中的每一个内部数据都有唯一的字符串标识,如此处的”sim/flightmodel/position/true_theta“对应飞机相对于正下方地球的俯仰角。
DataRef的标识符可以从Sim Innovations查到(仅含通用标识符,如姿态,速度,高度,通用航电等)。
也可以安装DataRefTool或者DataRefEditor插件查找(这俩个也可查找插件机的标识符)。比如,我们在Sim Innovations 查找俯仰角关键字 pitch, 可以发现如下结果。其中甚至包含正副驾驶仪表的俯仰角,并区分数据源(真空或者电子陀螺仪)。
Untitled 2.png
如果搜索俯仰角的数学符号$\theta$对应的 theta,可以发现以下结果。与”相对于正下方地球的俯仰角“相对的还有:仪表显示的俯仰角,OpenGL坐标系俯仰角等,这些量有细微差异。

Untitled 3.png
注意可能会有多种字面意思相近标识符,一定要搞清楚自己需要的是哪个。

若安装了DataRefTool也可以在游戏中查找到内部数据。该插件还能够及时更改可写数据(例如飞机位置,航电及飞控等),强烈建议安装。在之后的有关可写数据操作以及飞控指令执行部分,我们将依靠该插件和Sim Innovations共同查找正确的标识符。
Untitled 4.png
3. 获取数据
调用XPLMGetDataf可将刚才获取到的DataRef指针对应的数据保存下来。注意相应的数据格式。(不同数据格式的函数末尾不同,具体请参考XP官方文档
  1. float true_theta_deg;
  2. true_theta_deg = XPLMGetDataf(g_true_theta_deg_ref);
复制代码
XP的官方文档详细解释了各个接口的用法,可供参考:https://developer.x-plane.com/sdk/XPLMDataAccess/ 其中包含一些其他的常用接口,如获取DateRef信息(XPLMGetDataRefInfo),获取数据类型(XPLMGetDataRefTypes),判断数据是否可读(XPLMCanWriteDataRef)等。文档也提供了DataRef的几种格式:
Untitled 5.png
回调函数
回调函数包括了插件的主要功能,是程序中最重要的部分。函数能够保证插件以一定的时间间隔被执行。
  1. float CallBackDataReader(
  2.                 float       inElapsedSinceLastCall,
  3.                 float       inElapsedTimeSinceLastFlightLoop,
  4.                 int         inCounter,
  5.                 void*       inRefcon)
  6. {
  7.     // 获取数据
  8.     true_theta_deg = XPLMGetDataf(g_true_theta_deg_ref);
  9.     total_flight_time_sec = XPLMGetDataf(g_total_flight_time_sec_ref);

  10.     // 保存数据至文件
  11.     fprintf(g_output_file, "true pitch: %.4f deg, flight time: %.4f s.\n", true_theta_deg, total_flight_time_sec);

  12.     return 1.0; //!< 执行间隔时间:XP将在不早于该时间间隔后再次执行该函数。
  13. }
复制代码
可以看到我们的回调函数会在每次执行的时候获取数据并且写入文件。函数返回值是执行间隔时间,代表XP将在不早于该时间间隔后再次执行该函数。函数的输入包含至上一次被调用的时间,至上一个飞行Loop的时间等等,我们目前并不关心这些参数。可以打开函数原型了解详细信息。
  1. /*
  2. * XPLMFlightLoop_f
  3. *
  4. * This is your flight loop callback. Each time the flight loop is iterated
  5. * through, you receive this call at the end.
  6. *
  7. * Flight loop callbacks receive a number of input timing parameters. These
  8. * input timing parameters are not particularly useful; you may need to track
  9. * your own timing data (e.g. by reading datarefs). The input parameters are:
  10. *
  11. * - inElapsedSinceLastCall: the wall time since your last callback.
  12. * - inElapsedTimeSinceLastFlightLoop: the wall time since any flight loop was
  13. *   dispatched.
  14. * - inCounter: a monotonically increasing counter, bumped once per flight
  15. *   loop dispatch from the sim.
  16. * - inRefcon: your own ptr constant from when you regitered yor callback.
  17. *
  18. * Your return value controls when you will next be called.
  19. *
  20. *  - Return 0 to stop receiving callbacks.
  21. *  - Pass a positive number to specify how many seconds until the next
  22. *    callback. (You will be called at or after this time, not before.)
  23. *  - Pass a negative number to specify how many loops must go by until you
  24. *    are called. For example, -1.0 means call me the very next loop.
  25. *
  26. * Try to run your flight loop as infrequently as is practical, and suspend it
  27. * (using return value 0) when you do not need it; lots of flight loop
  28. * callbacks that do nothing lowers X-Plane's frame rate.
  29. *
  30. * Your callback will NOT be unregistered if you return 0; it will merely be
  31. * inactive.                                                                  
  32. *
  33. */
  34. typedef float (* XPLMFlightLoop_f)(
  35.                          float                inElapsedSinceLastCall,   
  36.                          float                inElapsedTimeSinceLastFlightLoop,   
  37.                          int                  inCounter,   
  38.                          void *               inRefcon);
复制代码
回调函数在XPluginStart中被注册,其中的参数1代表插件运行多久(秒)后注册该函数:
  1. XPLMRegisterFlightLoopCallback(CallBackDataReader, 1, NULL);
复制代码
小结
本文主要展示了获取XP内部DataRef的一般步骤以及如何将数据保存至自定义文件中,并且展示了如何从0开始建立一个的数据读取并记录的插件。关键步骤:程序中包含的几个必要函数,如何查找并保存DataRef,以及如何编写回调函数。












发表于 2023-5-25 10:40:11 | 显示全部楼层
支持!!!!!!!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表