首页 / 资讯中心 / 文章详情

REPENTOGON 实体存档状态向量(EntitiesSaveStateVector)Lua API 详解

REPENTOGON 实体存档状态向量(EntitiesSaveStateVector)Lua API 详解 ★ FEATURED ARTICLE
游戏开发插件系统【免费下载链接】REPENTOGONScript extender for The Binding of Isaac: Repentance项目地址https://gitcode.com/gh_mirrors/re/REPENTOGON点击查看免费下载EntitiesSaveStateVector是 REPENTOGON 为《以撒的结合忏悔》脚本扩展器提供的一类只读代理容器对象它直接包装游戏内部的std::vectorEntitySaveState用于访问房间、玩家与关卡中保存的实体存档状态。本文将基于官方文档与仓库源码完整讲解该类的Get、GetByType、Clear与__len四个接口的签名、语义、返回值与底层实现并延伸介绍如何通过RoomDescriptor、Level、EntityPlayer获取此类对象以及它与EntitySaveState、EntitySaveStateManager之间的协作关系帮助 Mod 作者在存档管理场景中安全地读写实体状态数据。一、背景什么是 EntitySaveState 与存档状态向量在《以撒》的存档机制中游戏会把房间内的实体以“存档状态Save State”的形式记录下来以便在切换房间、回溯楼层、使用回溯道具如时之沙漏、妈妈的盒子时恢复实体现场。REPENTOGON 在 SaveStateManagement/EntitySaveStateManagement.cpp 中实现了一套完整的存档状态扩展系统下称 ESSM为每个处于活动状态的存档结构分配唯一 ID并将 Mod 自定义数据与游戏原生状态关联起来。EntitiesSaveStateVector正是这套系统向 Lua 侧暴露的容器视图。根据 docs/docs/EntitiesSaveStateVector.md该类的全部公开接口只有四个接口签名说明GetEntitiesSaveState Get(int Index)按下标取得一个存档状态对象GetByTypeEntitiesSaveState GetByType(EntityType Type, int Variant 0, int SubType 0)按类型过滤返回匹配状态的 Lua 表Clearvoid Clear()清空向量中所有已登记的存档状态__lenint __len()返回向量长度支持#vec语法需要强调的是该向量是游戏内部真实容器std::vectorEntitySaveState*的直接代理而非副本。因此通过Get拿到的EntitiesSaveState对象对字段的修改会直接写回游戏内存这一点与常见的“快照式”返回值有本质区别。二、如何获取 EntitiesSaveStateVector 对象该类型自身没有构造函数只能通过其他对象的方法间接获取。仓库源码中共有以下四个入口对应四个不同的内部容器1. RoomDescriptor房间实体与装饰存档在 repentogon/LuaInterfaces/Room/LuaRoomDescriptor.cpp 中Lua_GetEntitiesSaveState与Lua_GetDecoSaveState分别将descriptor-SavedEntities和descriptor-SavedEffects包装为EntitiesSaveStateVector返回对应文档 docs/docs/RoomDescriptor.md 中的两个方法local roomDesc room:GetRoomDescriptor() local entityStates roomDesc:GetEntitiesSaveState() -- 房间内实体存档 local decoStates roomDesc:GetDecoSaveState() -- 房间内装饰类存档SavedEffects2. Level我的勿忘我Myosotis拾取物在 repentogon/LuaInterfaces/LuaLevel.cpp 中Lua_GetMyosotisPickups包装了level-_myosotisPickupslocal myosotisStates level:GetMyosotisPickups()对应文档 docs/docs/Level.md 中的GetMyosotisPickups()。3. EntityPlayer妈妈的盒子Moving Box内容在 repentogon/LuaInterfaces/Entities/LuaEntityPlayer.cpp 中Lua_PlayerGetMovingBoxContents包装了player-GetMovingBoxContents()local movingBoxStates player:GetMovingBoxContents()对应文档 docs/docs/EntityPlayer.md 中的GetMovingBoxContents()。三、四个核心方法逐一详解以下实现细节均可在 repentogon/LuaInterfaces/LuaEntitySaveState.cpp 的Lua_EntitiesSaveStateVectorAPI中找到对应函数。3.1 Get(Index)按下标访问local state vec:Get(0) -- 取第一个存档状态索引从 0 开始计数与 Lua 的 1 起始数组下标不同注意区分。内部实现为Lua_Get先通过luaL_checkinteger读取索引再进行边界检查——当index 0或index >-- 取所有类型为 EntityType.ENTITY_PICKUP 的存档状态 local pickups vec:GetByType(EntityType.ENTITY_PICKUP) -- 指定 Variant例如只取红心Heart 的 variant 之一 local hearts vec:GetByType(EntityType.ENTITY_PICKUP, 10) -- 使用 -1 通配符匹配任意 Variant 和/或任意 SubType local any vec:GetByType(EntityType.ENTITY_PICKUP, -1, -1)这是官方文档中明确强调的要点你可以使用-1作为通配符匹配任意 Variant 和/或任意 SubType。参数的默认值分别为Variant 0、SubType 0。底层实现Lua_GetByType的语义值得注意它遍历容器中的每一个EntitySaveState按st.type type (variant -1 || st.variant variant) (subtype -1 || st.subtype subtype)进行过滤返回值不是单个对象而是一个以 1 为起始下标的 Lua table表中每一项都是命中条件的EntitiesSaveState用户数据保持容器内原始下标没有命中时返回空表不会报错。因此典型用法是配合ipairs或#遍历local pickupStates vec:GetByType(EntityType.ENTITY_PICKUP, -1, -1) for i, state in ipairs(pickupStates) do local t state:GetType() local v state:GetVariant() local s state:GetSubType() print(i, t, v, s) end3.3 Clear()批量清空vec:Clear()Clear并非简单调用vector-clear()而是先调用ESSM::EntitySaveState_ClearBatch(*vector)再清空容器。其关键作用见 repentogon/SaveStateManagement/EntitySaveStateManagement.cpp遍历向量中每个状态对处于“已劫持Hijacked”状态的存档执行Core::ClearState_HijackedOnly回收其 ESSM IDID 进入reusableIds复用池见IdManager::ClearId收集本次被清空的所有 ID 后通过s_luaCallbacks.ClearStates(clearedIds)通知 Lua 侧即EntitySaveStateManager的ClearStates回调使与之关联的 Mod 数据同步失效若同一批次内同时存在已劫持与未劫持的状态会输出错误日志[ERROR] [ESSM] - EntitySaveState_ClearBatch found both hijacked and unhijacked entities提示数据不一致。由此可以推断直接调用Clear()会影响整个 ESSM 关联系统它会把该容器内所有已登记存档的 Mod 数据一并清理。因此在业务中应谨慎使用避免在遍历期间清空导致迭代器失效。3.4 __len()长度运算符local count #vec -- 直接使用 # 取长度 local count2 vec:__len() -- 或显式调用__len元方法直接返回ud-data-size()。该接口对 Lua 的#运算符生效是遍历容器最常用的辅助手段local states roomDesc:GetEntitiesSaveState() for i 0, #states - 1 do local state states:Get(i) -- ... end四、返回的 EntitiesSaveState 对象能做什么Get/GetByType返回的EntitiesSaveState对象本身也是一个完整类其全部读写方法见 docs/docs/EntitiesSaveState.md实现位于 repentogon/LuaInterfaces/LuaEntitySaveState.cpp 的方法表中。按用途可归类如下身份标识只读与可写方法语义GetType()/SetType(t)、GetVariant()/SetVariant(v)、GetSubType()/SetSubType(s)实体类型 / 变体 / 子类型类型使用EntityType枚举通用存储槽整数方法字段语义依据官方文档注释GetI2()/SetI2(n)用于存储主动道具拾取物的充能值GetI3()/SetI3(n)用于存储拾取物的商店价格GetI4()/SetI4(n)用于存储拾取物的ShopItemIdGetI1/GetI5/GetI6/GetI7/GetI8及其 Set 版本通用整数存储槽特殊解释的存储槽来自源码Lua_GetI1/Lua_SetI1/Lua_GetU1/Lua_SetU1当GetType() 4NPC/敌人、33可拾取物拾取状态相关或292时I1槽位按float解释读写其余类型按整数解释U1在GetType() 5拾取物时按boolean解释读写其余类型按int8_t整数解释。其他字段方法语义GetPos()/SetPos(Vector)存档时的目标位置返回Vector对象GetInitSeed()、GetDropSeed()初始种子与掉落种子只读GetSpawnerType()、GetSpawnerVariant()生成者类型与变体只读GetGridSpawnIdx()网格出生索引只读源码中SetGridSpawnIdx已注释禁用GetB1()/SetB1(b)、GetB2()/SetB2(b)、GetF1()/SetF1(f)、GetF2()/SetF2(f)布尔槽位与浮点槽位这里需要再次强调“引用视图”的底层事实这些 Get/Set 方法全部通过(*ud-vec)[ud-index]解引用真实容器见 LuaEntitySaveState.h因此Set*的修改会立即作用到游戏内部存档状态。五、底层原理ESSM 的“劫持Hijack”机制要理解Clear()为什么需要走EntitySaveState_ClearBatch就必须了解 ESSM 的核心设计。根据 EntitySaveStateManagement.cpp 开头的系统说明为什么需要劫持游戏原生EntitySaveState结构中没有为 Mod 预留数据空间REPENTOGON 通过“劫持”结构中不常用的字段来存放 ESSM ID具体字段对EntitySaveState而言gridSpawnIdx被用作标记marker_intStorage7被用作 ID见EntityTraitsEntitySaveStateManagement.cpp标记取值HIJACK_MARKER 0x5247、READ_MARKER 0x5248、CLEARED_MARKER 0x5249、WRITTEN_MARKER 0x5246以及调试版专用的DEBUG_CLEARED_MARKER 0x5250原始值备份被劫持字段的原始内容会复制到s_systemData.hijackedStates[id]中在实体 Restore 前恢复从而不破坏游戏原有逻辑ID 复用IdManager::NewId优先从reusableIds复用已清空的 ID否则递增totalIds。这套机制也解释了为什么GetByType的过滤可以直接读取st.type、st.variant、st.subtype——这些字段没有被劫持始终保存真实值。六、相关容器GridEntitiesSaveStateVector 的差异EntitiesSaveStateVector还有一个姊妹类型GridEntitiesSaveStateVector二者容易混淆。后者的文档见 docs/docs/GridEntitiesSaveStateVector.md其包装的是std::vectorGridEntityDesc网格实体描述符通过roomDesc:GetGridEntitiesSaveState()获取见 LuaRoomDescriptor.cpp。两者接口几乎一致但存在三点关键差异见 LuaEntitySaveState.cpp差异点EntitiesSaveStateVectorGridEntitiesSaveStateVectorGet返回值EntitiesSaveState带视图引用GridEntityDesc直接 userdata 指针GetByType参数Type, Variant0, SubType0支持-1通配仅 Type无通配符Clear语义先EntitySaveState_ClearBatch再clear()真正清空并回收 ID仅将每个元素的_type置为 0不缩小向量长度可以看到GridEntitiesSaveStateVector:Clear()是“逻辑清空”保留容器大小、仅把类型标记为 0而EntitiesSaveStateVector:Clear()是“物理清空 ESSM ID 回收”两者不可互换。七、与 EntitySaveStateManager 的联动Mod 数据持久化EntitiesSaveStateVector中每个EntitiesSaveState都可以作为EntitySaveStateManager的第二个参数用于读写与该存档状态关联的 Mod 数据。相关文档见 docs/docs/EntitySaveStateManager.md实现位于 repentogon/resources/scripts/repentogon_extras/entity_save_state_manager.lua。注意EntitySaveStateManager是全局表其方法必须用.点号而不是:冒号调用local data, isNew EntitySaveStateManager.GetEntitySaveStateData(modRef, state) -- data与该存档状态关联的 Mod 数据表 -- isNew该表是新建的还是先前已存在的 local existing EntitySaveStateManager.TryGetEntitySaveStateData(modRef, state) -- 仅当表已存在时返回否则返回 nil底层链路是C 侧LuaFunctions::GetEntitySaveStateId读取状态的 ESSM ID未处于劫持状态时会报错Provided EntitiesSaveState is not supported by EntitySaveStateManager.见 EntitySaveStateManagement.cppLua 侧以modId → saveStateId → dataTable的三层映射维护数据见entity_save_state_manager.lua的s_saveStateData存储/恢复/复制/清空/序列化均通过 C 侧的LuaCallbacksStoreEntity、RestoreEntity、ClearStates、CopyStates、Serialize、PreDeserialize、Deserialize自动触发Mod 无需手动调用。这正是上文“Clear()会影响整个 ESSM 关联系统”的原因EntitySaveState_ClearBatch在回收 ID 的同时会触发ClearStates回调把对应 ID 的 Mod 数据一并删除保证两侧数据一致。八、注意事项与最佳实践索引从 0 开始Get(i)的合法范围是0到#vec - 1越界会直接抛 Lua 错误遍历时务必配合__len使用。引用而非副本通过Get获得的EntitiesSaveState是对容器元素的实时引用Set*修改会写回游戏内存切勿在遍历期间随意Clear。-1通配符只作用于 Variant/SubTypeGetByType的 Type 参数不支持通配必须给出具体EntityType。GetByType返回 table其返回结构与Get的单对象不同是 1 起始的数组表建议用ipairs遍历。区分两个 Vector 的Clear语义EntitiesSaveStateVector:Clear()会回收 ESSM ID 并触发 Lua 侧数据清理GridEntitiesSaveStateVector:Clear()仅将_type置零。数据自动持久化通过EntitySaveStateManager关联的 Mod 数据会随游戏存档自动保存/恢复序列化文件存放在 Mod 数据目录下的EntitySaveStateManagement/filename.dat见 EntitySaveStateManagement.cppMod 开发者无需自行管理生命周期。九、小结EntitiesSaveStateVector虽然只有四个方法却是 REPENTOGON 存档扩展体系中连接“游戏原生存档结构”与“Mod 自定义数据”的关键桥梁。理解其容器代理的本质、GetByType的通配过滤规则、Clear的 ESSM 联动副作用以及它与EntitiesSaveState、EntitySaveStateManager、GridEntitiesSaveStateVector的关系是撰写安全可靠的存档管理 Mod 的前提。本文所述全部接口签名与行为均以 docs/docs/EntitiesSaveStateVector.md 及 repentogon/LuaInterfaces/LuaEntitySaveState.cpp 的实现为准Mod 作者可直接对照源码进一步探索。赞分享游戏开发插件系统【免费下载链接】REPENTOGONScript extender for The Binding of Isaac: Repentance项目地址https://gitcode.com/gh_mirrors/re/REPENTOGON点击查看免费下载相关推荐REPENTOGON 扩展文档深度解读GridEntitiesSaveStateVector 网格实体存档状态向量完全指南REPENTOGON 扩展文档深度解读GridEntitiesSaveStateVector 网格实体存档状态向量完全指南 导读 GridEntitiesSa游戏开发插件系统REPENTOGON 状态效果枚举 StatusEffect 全解Lua 常量、底层标志与 Mod 回调实战REPENTOGON 状态效果枚举 StatusEffect 全解Lua 常量、底层标志与 Mod 回调实战 导读 StatusEffect 是 REPENT游戏开发插件系统REPENTOGON 实体类继承体系图解EntityClassDiagram 片段与 Lua 实体 API 深度解析REPENTOGON 实体类继承体系图解EntityClassDiagram 片段与 Lua 实体 API 深度解析 导读 本文以 REPENTOGON 官方游戏开发插件系统上一篇GrapesJS 快速入门从零搭建一个可视化页面构建器下一篇Repomix FAQ 实战指南私有仓库打包、Token 压缩、安全检测与 MCP 集成全解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站