多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

OpenMW Lua 脚本中的 YAML 数据处理:openmw.markup 包完整实战指南

OpenMW Lua 脚本中的 YAML 数据处理:openmw.markup 包完整实战指南 游戏开发图形学3D渲染【免费下载链接】openmwOpenMW is an open-source open-world RPG game engine that supports playing Morrowind. Main repo and issue tracker can be found here: https://gitlab.com/OpenMW/openmw/项目地址https://gitcode.com/gh_mirrors/op/openmw点击查看免费下载导读OpenMW 为 Mod 作者提供了完整的 Lua 脚本 API而 openmw.markup 是其中专门负责标记语言数据读写的基础包它让脚本可以在**全局global、菜单menu、本地local、玩家player与加载load**五种上下文中把 YAML 字符串或 VFS 中的 YAML 文件转换成 Lua 对象。读完本文你将掌握markup.decodeYaml与markup.loadYaml两个函数的调用方式、YAML 1.2 Core Schema 下标量类型推导的全部规则与边界限制以及 OpenMW 在 C 层markupbindings.cpp 与 yamlloader.cpp如何实现这套解析从而在制作 Mod 时安全、高效地用 YAML 承载配置数据。一、openmw.markup 包是什么openmw.markup是 OpenMW 内置 Lua API 中一个轻量级的数据互操作包官方 API 文档openmw_markup.rst将其定位为Allows to work with markup languages处理标记语言。它当前聚焦于一种数据格式——YAML提供两个入口函数作用数据来源decodeYaml(inputData)把 YAML 字符串解码为 Lua 对象内存中的字符串loadYaml(fileName)从 VFS 加载 YAML 文件并解码VFS 中的文件路径在引擎内部该包被注册在通用包集合中。查看 luabindings.cpp 中的initCommonPackages可以看到openmw.markup与openmw.async、openmw.util、openmw.vfs一同通过initMarkupPackage(context)初始化。包最终由 markupbindings.cpp 实现它从引擎的资源系统中取得 VFS 管理器把loadYaml绑定到经 VFS 规范化路径读取文件流的操作把decodeYaml绑定到直接解析传入字符串的操作两者最终都汇聚到LuaUtil::loadYaml并由LuaUtil::makeReadOnly把返回的 API 表设为只读防止脚本篡改接口本身。使用前提需要在脚本头部引入local markup require(openmw.markup)。该包可用的上下文为global | menu | local | player | load即全局脚本、菜单界面、本地脚本、玩家脚本和加载界面脚本中均可调用。返回的 Lua 对象可能是表table或标量值取决于 YAML 根节点的类型。二、decodeYaml把 YAML 字符串转为 Lua 对象markup.decodeYaml(inputData)接受一个字符串参数返回对应的 Lua 对象。官方文档给出的最小示例local markup require(openmw.markup) local result markup.decodeYaml({ x: 1 }) print(result[x]) -- 输出 1解析走的是 yamlloader.cpp 中的LuaUtil::loadYaml(const std::string, sol::state_view)先调用YAML::LoadAll把输入解析为 YAML 节点树再递归转换成 Lua 对象。多文档支持decodeYaml使用YAML::LoadAll而非Load这意味着它支持一个字符串中包含多个 YAML 文档以---分隔。行为如下见 yamlloader.cpp若没有根节点返回nil若只有一个根节点直接返回该节点对应的对象若存在多个根节点返回一个顺序数组表依次存放每个文档解码后的对象。例如local multi markup.decodeYaml(a: 1\n---\nb: 2\n---\n3) -- multi 是一个表{ {a1}, {b2}, 3 } print(multi[1].a) -- 1 print(multi[2].b) -- 2 print(multi[3]) -- 3三、loadYaml从 VFS 加载 YAML 文件markup.loadYaml(fileName)从游戏数据目录VFS即 Virtual File System中读取指定文件并解码转换规则与decodeYaml完全一致。官方示例local markup require(openmw.markup) -- 假设 test.yaml 内容为 { x: 1 } local result markup.loadYaml(test.yaml) print(result[x]) -- 输出 1其底层实现位于 markupbindings.cppapi[loadYaml] lua, vfs { Files::IStreamPtr file vfs-get(VFS::Path::Normalized(fileName)); return LuaUtil::loadYaml(*file, lua); };关键点文件路径会经过VFS::Path::Normalized规范化处理因此传入路径时应使用 VFS 的标准格式相对于 data 目录、/分隔。若文件不存在或无法读取会由 VFS 层抛出异常脚本需要自行用pcall包裹以优雅降级。该函数是 yamlloader.cpp 中loadYaml(std::istream, sol::state_view)重载的入口与字符串版共享同一套节点遍历与标量转换逻辑。典型用法——把 Mod 的配置集中放在一个 YAML 文件里local markup require(openmw.markup) local cfg markup.loadYaml(my_mod/config.yaml) local enabled cfg.enabled -- 布尔值 local damage cfg.damage -- 数字 local name cfg.displayName -- 字符串四、YAML 标量类型推导规则Core Schema解析器没有采用一切皆字符串的粗暴策略而是严格按照YAML 1.2 Core Schema对未加引号的标量做类型推断。类型识别逻辑集中在 yamlloader.cpp 的getScalarType判定顺序与正则如下标量写法示例识别为正则依据Core Schematrue/True/TRUE/false/False/FALSE布尔true|True|TRUE|false|False|FALSE42、-7、10十进制整数整数int[-]?[0-9]3.14、.5、1e3、1.2E-2浮点浮点数double[-]?([.][0-9]|[0-9]([.][0-9]*)?)([eE][-]?[0-9])?0x1F十六进制整数0x[0-9a-fA-F]0o17八进制整数0o[0-7].inf/.Inf/.INF、-.inf正/负无穷[-]?([.]inf|[.]Inf|[.]INF).nan/.NaN/.NANnan浮点数 NaN[.]nan|[.]NaN|[.]NANnull/Null/NULL/~/ 空值nilnull|Null|NULL|~其余含所有带引号字符串字符串——以源码实现为准可以确认以下几个重要细节引号决定类型带引号的标量一律按字符串处理对应文档限制第 4 条。42得到字符串42而未加引号的42得到数字。整数溢出限制十进制、十六进制、八进制整数最终都通过std::from_chars解析到int见 yamlloader.cpp因此数值范围受 Cint限制。需要更大数值时请在 YAML 中使用浮点写法如1e10或直接写成带引号的字符串。浮点数用double承载浮点标量通过YAML::convertdouble::decode转换。大小写不敏感判定布尔值的真伪判断使用Misc::StringUtils::ciEqual做大小写不敏感比较因此TRUE、True、true均视为真但正则本身只接受上述三种全大写/全小写/首字母大写形式。显式 YAML 标签被忽略解析器对非默认标签tag ! ?且tag ! !会直接抛出错误。!标签即强制字符串被支持并映射为字符串其余如!!bool、!!str这类显式标签在当前实现中会被拒绝见 yamlloader.cpp 的注释说明——在 Lua 中这些转换意义不大字符串可以用引号表达因此被有意忽略。映射Map与键的限制YAML 映射会转换为 Lua 表但键必须是标量字符串、布尔、数字。源码 yamlloader.cpp 明确禁止键为映射map抛错 Only scalar nodes can be used as keys, encountered map instead键为序列array抛错 Only scalar nodes can be used as keys, encountered array instead键为 null抛错 Only scalar nodes can be used as keys, encountered null instead键解析为 NaN 数字抛错 Only scalar nodes can be used as keys, encountered nan instead因为 NaN 无法作为 Lua 表键。其余标量键含字符串、布尔、数字均可用例如local data markup.decodeYaml([[ true: 1 42: forty-two name: OpenMW ]]) print(data[true]) -- 1 print(data[42]) -- forty-two print(data[name]) -- OpenMW序列与嵌套YAML 序列转换为 Lua 的数组表从1开始索引支持任意深度的嵌套local nested markup.decodeYaml([[ items: - name: Sword damage: 10 - name: Shield armor: 5 ]]) print(nested.items[1].name) -- Sword print(nested.items[2].armor) -- 5深度与循环引用保护解析器内置了递归深度上限maxDepth 250见 yamlloader.cpp。当嵌套深度达到上限时抛出运行时错误 Maximum layers depth exceeded, probably caused by a circular reference这同时防御了 YAML 节点间的循环引用——因为 YAML 文档本身不允许循环依赖对应文档限制第 5 条。五、错误信息与定位所有解码错误都会带上源码中的行列位置便于 Mod 作者定位问题。nodeError见 yamlloader.cpp利用 yaml-cpp 的node.Mark()生成形如at line1 column2 position3的报错行、列、偏移量均为从 1 计数。因此脚本中建议用pcall捕获并打印错误local ok, res pcall(markup.decodeYaml, x: [1, 2) if not ok then print(YAML 解析失败 .. tostring(res)) end六、官方限制清单务必遵守综合 API 文档与源码实现openmw.markup的 YAML 支持有以下明确边界使用 YAML 1.2 规范对应 yaml.org 1.2.2。映射键必须是标量字符串、布尔、数字不支持复杂键。不支持 YAML 标签体系!!xxx显式标签会被拒绝。带引号的标量一律视为字符串未加引号时按 Core Schema 推导类型。不允许节点间的循环依赖同时有 250 层深度上限兜底。Lua 5.1 没有整数类型OpenMW 使用的 Lua 中数字统一是浮点#number。实现上整数标量以int读取后转为 Lua number因此超出int范围的大整数必须用浮点记法如1e10或字符串表示。整数标量受int范围限制超大数值请使用浮点写法。七、源码级调用链一览从 Lua 脚本到最终解析结果完整链路如下Lua 脚本调用markup.decodeYaml(str)或markup.loadYaml(path)进入 C 绑定 markupbindings.cpploadYaml经vfs-get打开文件流两者都调用LuaUtil::loadYaml在 yamlloader.cpp 中由YAML::LoadAll解析为节点树loadAll处理单文档/多文档分支getNode按节点类型分发到getMap/getArray/getScalargetScalarType按 Core Schema 正则推断标量类型getScalar将其转为对应的 Lua 值nil/ 字符串 / 布尔 /int/double整个 API 表经LuaUtil::makeReadOnly设为只读后交付给 Lua 层。这也是理解为什么openmw.markup返回的是只读 API、为什么错误信息带行列号的钥匙。八、最佳实践小结配置优先用loadYaml把 Mod 的可调参数集中到data目录下的 YAML 文件避免硬编码在.lua脚本中改配置无需重编译、便于玩家自定义。大整数用浮点或字符串牢记int范围限制超过2^31-1的数值在 YAML 里写1e10或加引号。复杂键用字符串表达不要试图在 YAML 里用嵌套结构作键。用pcall保护解析文件缺失、语法错误、非法标签都会抛异常捕获后给出友好提示。遵循 YAML 1.2避免依赖旧版 YAML 1.1 的特殊标量写法如y/n表示布尔、on/off等这些在 Core Schema 下会被识别为字符串。上下文选择openmw.markup在global / menu / local / player / load上下文中均可require但要注意把只读 API 与可变配置数据区分开——返回的对象本身是可读写的普通 Lua 表可放心存入openmw.storage或直接使用。赞分享游戏开发图形学3D渲染【免费下载链接】openmwOpenMW is an open-source open-world RPG game engine that supports playing Morrowind. Main repo and issue tracker can be found here: https://gitlab.com/OpenMW/openmw/项目地址https://gitcode.com/gh_mirrors/op/openmw点击查看免费下载相关推荐OpenMW Lua 脚本指南openmw.ambient 环境音效包完整参考OpenMW Lua 脚本指南openmw.ambient 环境音效包完整参考 openmw.ambient 是 OpenMW 为 Lua 脚本提供的环境音效游戏开发图形学3D渲染OpenMW Lua 脚本实战用 Combat AI 包驱动 NPC 攻击行为OpenMW Lua 脚本实战用 Combat AI 包驱动 NPC 攻击行为 本文是 OpenMW Lua 脚本系统中 AI 包AI Package机制游戏开发图形学3D渲染OpenMW Lua 脚本 AI 指南Escort 护送 AI 包的参数详解与底层实现OpenMW Lua 脚本 AI 指南Escort 护送 AI 包的参数详解与底层实现 本文围绕 OpenMW 官方 Lua 脚本参考文档 Escort AI游戏开发图形学3D渲染上一篇一个号码交叉验证六个公共数据源k-skill biz-health-check 的 사업자 실사 复合查询设计下一篇Scarab模组管理器空洞骑士模组安装的终极解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表