RExtender

创作者: Fouru4U
已更新:2026-06-05 10:53:52
1.0MB

关于此 MOD

不再手动缓存!实现类型与单例的永久全局缓存、共享记忆化、面向游戏逻辑的简易计时器、跨脚本事件、部分ImGui工具等功能。适用于任意
要是我们有RE10和REXtender就好了,那会轻松很多。
扩展器
这是一个模组制作者工具框架,主要目的是提升性能。 REFrameworkLua脚本,其次是添加实用功能。
如果你不是模组制作者,直接点击下载即可,或许能小幅提升性能。
如果你 是 如果你是一名模组作者,下面列出了一些你可能会感兴趣的功能,以及我的Discord邀请链接,欢迎加入。


重要: 这只是框架的初始上传,未来如有任何更新版本,将仅上传至 这个 GitHub 页面

  • 全局自动 缓存中: 每个 Lua 脚本最终都会发现,它们需要一个缓存系统来管理单例、类型定义、方法等。这本身非常好,但这些脚本却都在重复实现着完全相同的系统——其他脚本也在做同样的事。这意味着,如果 10 个模组都想缓存某个内容……角色管理器每个都在本地执行!结果还是十个完全相同的实例引用! [标题卡] 一次搞定,人人受益,全自动运行,最重要的是——你几乎不用学任何新东西!真的!只要装上它,你的脚本就能确保自动使用全局缓存!
  • 全局 记忆化 “缓存”这个词听起来挺高级的,但我实在没法不强调:它比上面那个姐妹技术要强大得多。通常你只能缓存单例、类型定义之类的东西——这之所以行得通,是因为它们在初始化之后是静态的。而记忆化呢,它能让你缓存以下内容的结果—— 调用它们的方法 例如,备忘“玩家上下文从CharacterManager:快速获取玩家上下文()"你需要且只能用一个,最多也就一个。" 二 充分发挥功能,尽享其益!这是该框架的重中之重特性,请务必阅读下方更详细的章节!如果这还不够吸引你,那看看这个:它能将 一百万 呼叫 每帧 直接把你弹回桌面……帧率掉到大约20?当然,这得看设备配置,不过我这边就是这样。 远 说实话,高端算不上,顶多算还行。
  • 跨脚本事件: 一个遵循“虚空呐喊”原则的简单系统:模组A使用唯一字符串标识注册事件拥有者,模组B针对该标识注册事件监听器及事件触发时要执行的函数。仅此而已!随后模组A可执行“event_owner:触发(...)“在他们代码中的任何位置,传入希望监听器接收的任何参数。紧接着,他们就会执行对应的函数!最棒的是,模组B无需事先检查模组A是否存在就能创建监听器——这一切都是自动处理的,你只需直接调用订阅接口即可。” [标题卡] 而且它会为你包办一切。没有错误,没有崩溃,完全不影响性能!另外,Mod A 还能订阅自身事件,如果你想方便其他 Mod 作者调用它的执行逻辑,随时都能派上用场——一石二鸟!
  • 游戏友好计时: 有时在代码中,你会遇到一些想延后处理的事情。 [TITLE CARD]只需调用一个函数即可。这些计时器采用C++编写,以实现最高性能和最小内存占用。 极其 即便你疯狂连续使用,它们依然轻便且易于上手。接下来我要解释它们为何"玩法友好"。撇开轻松临时暂停与强制永久暂停不谈,它们有两大突出特性:游戏暂停时自动暂停,以及在其计时周期的三个阶段调用功能(检测计时条件、执行计时本身、剩余时间为零时触发计时完成)。遗憾的是,前者存在一些"意外陷阱",但其他部分运作流畅,详见下方详细说明!
  • **剩余次要新增内容:** (当前文本属于技术性描述,需根据游戏引擎特性处理) 初始功能包含简易富文本(如彩色文字、自动换行、居中对齐、字号调节等),基于ImGui框架实现,后续可能扩展更多特性。最亮眼的功能当属富文本系统:可将字符串"[FF0000]红十六进制 [00FF00]蓝十六进制 [0000FF]绿十六进制"自动渲染为对应颜色文本。红色六边形 蓝色六边形 绿色六边形就这么简单,居中、换行、字号调整都能用!
详细栏目
既然走到这一步,想必你对此颇感兴趣。那么有必要提一下,该模组文件名为"000_RExtender",因为它需要优先初始化,才能确保其模块准备就绪并先于其他内容启动。如果你在编写Lua脚本,完全无需担心这一点——RExtender是DLL文件,其加载顺序在所有脚本之前。只有当你在编写插件,且插件功能与这里提到的任何内容相关时,才需要注意。
而且呢,虽然这些内容看起来像大段文字,但其实主要就是我在讲整体情况。读起来很轻松,也不难懂,所以先别叹气啦。


第一部分:全局自动缓存
相信各位对这些功能已经很熟悉了:
**sdk.get_managed_singleton(名称: 字符串)**
sdk.get_native_singleton(name: string)
sdk.find_type_definition(type_name: string)
> sdk.typeof(type_name: string) -- 这个可能不太常见
如果滥用这些功能,很容易导致帧率跌至危险水平。据我观察,单次调用需要经历 Lua → REFramework(C/C++)→ RE引擎 → REFramework → Lua 的完整路径。自然需要想办法优化,最直接的办法就是缓存。虽然实现起来不难,但终究是相当繁琐的工作,最麻烦的是各位还得各自搭建本地系统。

有了RExtender,你只需调用上述4个函数中的任意一个即可。

很矛盾对吧?不过这正是它的精妙之处——就像我承诺过的,你几乎不需要学任何新东西!RExtender 会自动捕获对这些函数的调用,并检查之前是否已存储过目标对象。如果已存储,每次都会返回完全相同的实例;如果未存储,则会将调用转发给原始函数并存储结果。然而,由于某些限制,它无法捕获或存储“object:get_type_definition() (对象:获取类型定义)“,所以尽量使用”SDK一、另外还附带了这两项新增功能:
sdk.find_method_definition(type_name: string, method_name: string)
sdk.find_field_definition(type_name: string, field_name: string)
他们完美利用了缓存系统。

这部分内容就到这里。很简单,对吧?只要装上RExtender,你就能省下不少功夫,还能确保脚本不会意外拖垮性能。另外还有一点想提的是,它们不会缓存“无"数值,怕你不知道。

想看性能表现?设备参数不便透露,但请相信我的设备最多也就中高端水平。即便信不过我,跑分结果可不会骗人。以下是我的压力测试流程:
re.on_frame(function())
for i = 1, 100000, 1 do -- 十万次
sdk.get_managed_singleton("app.CharacterManager")
结束
结束)
没有RExtender时,它几乎直接卡死,我根本不敢测试百万数量级。而有了RExtender,我测试百万数量时,它稳稳维持在REFramework窗口的分析视图约50~60毫秒,相当于约20帧;十万数量时,延迟甚至几乎没超过9毫秒。

第二部分:全局记忆化
依我看,这是RExtender最棒的部分,坐稳了。虽然看起来内容不少,但用起来真的很简单。至少通读一遍吧。
与默认缓存不同,记忆化不会存储单例或类型定义——它存储的是调用这些对象上的函数所得到的结果,包括“nil“重要的是这些数值。我指的是这些:”
> local cm = sdk.get_managed_singleton("app.CharacterManager")
local pc = cm:get_PlayerContextFast() -- 此方式、:call() 替代方式及方法定义替代方式
这些调用成本极高(相比之下,不调用它们自然没成本,哈哈),而且不像静态单例那样可以缓存。每帧更新时它们都可能变为空值,因此即便你缓存了它们,也必须在每帧丢弃缓存并重新缓存。这种做法在你的脚本本地运行或许可行,但考虑到其他脚本可能也在用完全相同的函数调用完全相同的单例并以完全相同的方式缓存它们,想必你也明白其中还有很大的优化空间。

这就是RExtender登场的时候了——它能在全球范围内搞定这一切。

每次调用我们即将提到的两个函数时,都会缓存结果。其他脚本调用它们时,也会直接获取相同结果,而无需每秒向REFramework请求60次。由于下一帧结果可能失效,它会在REFramework允许的最早时机清空缓存——早于任何其他脚本运行,也早于所有在RExtender之后加载的插件(这便是文件命名的原因),以此确保所有调用始终获得最新引用。

在进入正题之前先说明一点:虽然理论上你可以将这些函数用于任意类型和方法,但它们仅适用于返回托管对象的方法,例如"app.角色上下文,app.PlayerUpdater,app.PlayerInputDriver,app.相机控制器等等,以及 不 对于返回“系统.布尔值,系统.整型", 某些易变或特殊对象(如数组、列表、集合等)的行为调用方式为: 未测试 与 强烈不建议 尽管可行,但为了维持我们极力追求的性能表现,该设计仍保持了精简。

如果我让你误会了——不,它们并不是只能用于单例模式,下面的例子会让你明白。

  • 雷克斯.已记录(实例: REManagedObject 方法名称: 您好,请提供您需要翻译的文本内容。, ...: 任何)
论点直白明了:
- 调用该方法的实例
- 要调用的方法名称
传递给该方法的参数(若有)

示例:
local gms = sdk.get_managed_singleton
local memoized = rext.memoized → 本地已缓存 = rext.memoized
-- 强烈建议像对待所有全局Lua函数一样处理,以避免查找性能开销

> local cm = gms("app.CharacterManager")
-- 同时也完全支持重载方法
-- pc 是 "app.PlayerContext"
完成。在该帧的剩余时间里,任何调用脚本的 雷克斯特.已记忆 " 关于那种方法角色管理器“将以惊人的速度和低廉的价格,收到先前已处理完毕的精确物品。唯一需要注意的是,”获取玩家上下文(快速)“与”快速获取玩家上下文()(括号)被视为不同的方法,但完成的整体优化远超这个问题,将其彻底碾压并抛之脑后。

该功能可动态推断类型。 副本 赋予它之后,通常最适合用于类型为动态、未知或不可预测的情况。另一个用途是当你尝试调用某个方法,而该方法标有醒目的红色提示“占位符写在一旁,不包含重复。
示例之一见于《生化危机:安魂曲》:app.PlayerContext:get_Updater()显示它返回app.PlayerUpdaterBase 玩家更新器基础在对象资源管理器中,它实际上会在运行时返回不同类型的角色专属更新器:Cp_A000更新器“给里昂的”Cp_A100更新器“献给格蕾丝。”

也就是说,如果你换这么做的话……
好的,已为您处理。以下是本地化后的中文版本:

```lua
local gms = sdk.get_managed_singleton
```

这段文本是游戏引擎脚本中的代码行,非自然语言内容,因此无需翻译。
local memoized = rext.memoized
local strict = rext.memoized_strict -- 你马上就会明白
-- 强烈建议如所有全局 Lua 函数一样处理,以避免查找性能开销

local cm = gms("app.CharacterManager")
local pc = memoized(cm, "get_PlayerContextFast") -- 同样完全支持重载方法
-- pc 是 "app.PlayerContext"

local upd = strict(pc, "app.PlayerContext", "get_Updater")
local camera = strict(upd, "app.PlayerUpdaterBase", "getPlayerCameraController") -- 标记为“占位”,无“重复”
...会报错。

这就是该功能存在的原因——它能自动获取类型及其名称。当然,这意味着它需要额外做一些工作才能返回结果。不过别担心,完全可以放心调用这个功能。在性能方面,即便是在高负载压力测试中,它也只比我们即将介绍的严格版本慢了大约10毫秒。
经过极致优化,采用对象指针(炫酷术语)实现每帧仅查询一次,因此完全无需担忧性能问题——除非你像我测试时那样运行百万次循环。即便在这种情况下,整体耗时差异也仅有约10毫秒(指百万次总量,而非单次调用)。

  • 雷克斯特.记忆化严格(实例: RE管理对象, 副本类型名称: 您好!请提供您需要翻译的文本内容。我会根据游戏本地化规范进行专业翻译。, 方法名称: 您好!请提供您需要翻译的文本内容,我会按照要求进行专业的游戏本地化翻译。, ...: 任意)
这本质上与上述功能如出一辙,但更为严苛。它不会动态获取类型,而是要求你自行提供正确的类型(即 副本注意是属性的类型,而不是方法的返回类型(因为很容易搞混)。如果没注意的话,可能会直接报错。根据我的经验,这种情况下只有带“占位符“非功能性函数”重复,且这些道具非常稀有,会在游戏中明确标注。

例如,若已知模组仅在玩家角色为里昂或格蕾丝时生效,则完全可以改用以下写法:
```yaml
local gms = sdk.get_managed_singleton
```

翻译:

```yaml
本地化 gms = SDK.获取托管单例
```
> local strict = rext.memoized_strict
-- 强烈建议像对待所有全局Lua函数一样处理,以避免查找性能损耗

```plaintext local cm = gms("app.CharacterManager") ```
local pc = strict(cm, "app.CharacterManager", "get_PlayerContextFast") -- 也完全支持重载方法
local upd = strict(pc, "app.PlayerContext", "get_Updater")

local is_leon = ... -- 某些判定逻辑
local type = is_leon 且 "app.Cp_A000Updater" 或 "app.Cp_A100Updater"

local camera = strict(upd, type, "getPlayerCameraController") -- 这个写法没问题,不像普通缓存示例那样
说实话,这两个你随便用都绝对安全。严格模式最快,普通模式只慢那么一丁点,你自己选吧。只要别……STUB“你的脚趾。”

性能表现与缓存接近:
`local cm = sdk.get_managed_singleton("app.CharacterManager")`

re.on_frame(function()
for i = 1, 800000, 1 do -- 八十万
cm:get_PlayerContextFast()
结束
结束)
直接这样访问,让我的电脑从稳定约70帧掉到了卡成幻灯片。
与 雷克斯特.已记忆反而仅将帧时间增加至约60毫秒,以及约50毫秒。 瑞克斯.严格记忆约相当于20帧/秒。

第三部分:跨脚本事件
这个系统也很直观。你应该已经发现了——我尽力让一切都简单易用。

设想一个场景:你和你的模组作者朋友希望让两个模组之间产生联动,但又不希望互相产生依赖。你希望玩家可以单独安装任意一个模组,也能同时安装两者来体验联动内容。为此,你必须在两个模组里编写一堆古怪的代码,用来检测对方模组是否已安装之类的逻辑。

以下是该文本的中文翻译: 用RExtender操作的方法如下:
local explosion_event = rext.create_event("hot_potato:exploded")
> local obtained_event = rext.create_event("hot_potato:obtained") -- 建议拆分为“模组ID:实际ID”以避免与全局名称产生冲突
-- ……你还需要什么其他东西尽管说
标识符长度不得超过64个字符,建议将事件集中存储,例如在"events.lua"文件中创建一个表格,以便在脚本中便捷调用。最理想的做法是在该文件中同时包含事件ID,这样其他人可以直接引用变量订阅事件,无需复制粘贴字符串。

而倾听的一方则会这样做,地点随他们挑:
rext.subscribe_event("hot_potato:exploded", function(power) ... end)
> rext.subscribe_event("hot_potato:obtained", function(new_owner, old_owner) ... end)
-- 其他必要事件
现在你的设置已经准备就绪!RExtender 会把监听器加入队列,如果10秒内找不到目标标识符,就会自动将其丢弃。无需手动检查任何内容。没错,在事件实际注册前订阅也是安全的。这一切都已考虑周全,你完全不必担心。如有需要,你还可以订阅自己的事件。

之后,活动主理人便可轻松触发活动:
...某个处理棘手任务的函数,“events”是你创建的一个表,用于方便访问事件。
local power = calculate_power(...)
events.exploded:trigger(power) -- 监听器等待的“能量”参数
谨慎提醒:通常情况下,Lua 会暂停脚本执行,直到所有监听器完成其函数。这是完全正常的行为,你无需担心性能或其他问题,只需确保此处是你想要触发事件的位置即可。若实际出现性能下降,那百分百是某人的事件监听器在进行高级运算。

传入“触发(...)“除非是表格这类可变对象,否则数据是不可变的,且该函数本身不返回任何内容。” 

第四部分:便于游戏操作的计时器
我想我不用再重复了,不过要是你直接跳到这部分的话——它们用起来简单又方便,跟其他东西一样。

首先需要明确一点:支持「暂停时计时器停止」的功能。虽然我很想把这个系统做成开箱即用、适配所有游戏,但目前还无法实现。每款游戏的GUI和玩法管理器不同,且相关方法分散在整个TDB中。目前,该功能仅在《RE2》(非RT版未测试)、《RE3》(同上)、《RE4》和《RE9》中受支持。即便如此,仍可能存在某些窗口期——即便我测试时,游戏也未必被判定为“暂停”。例如,《RE9》在启动阶段、尚未进入标题画面时,仍被视为活跃状态。此外,如果游戏…… 不 暂停时,我会把所有那些检测都扔给引擎,导致后台性能被大量消耗。我花了很多时间尝试在这些游戏中尽可能接近这个效果,但这已经是我能做到的极限了。说实话,对于其他游戏,我不会这么做。那些游戏不属于我,我也不可能为了一个计时功能就去买下来。
不过,如果你想帮忙的话,直接发消息给我就行。 Discord 在你的 Lua 代码中。通常,我的目标是让结果变成"正确当满足以下任一条件时:
- 玩家处于某种暂停菜单中(如背包、设置等)
玩家正在加载界面中
玩家已死亡
- 玩家视角锁定目标,无法操控角色(类似“调查”时,周围世界暂停)。
- 玩家HUD已关闭(通常用于过场动画、影片等场景)
- 如有必要,显式检测正在播放的过场动画和影片
可能需要增加一些额外的校验来确保一切正常,不过你大概明白意思了,也希望能理解为什么我无法做出一个完美适配暂停的系统。
我很乐意和你反复沟通,确保你提供的代码能按预期正常运行。毕竟又不是每周都有新的RE引擎游戏发布。再说了,这类游戏总共也没几款,我们只需要找几个愿意配合的人就能搞定。

话说回来,还有件事你得知道,以防万一:计时器与帧率挂钩,这是无法避免的。帧率越高,计时器更新频率就越高,但每次更新的间隔时间越短;反之,帧率越低,更新频率就越低,但更新间隔时间越长。 然而每次更新时,它们会受到硬性限制,每次最多减少50毫秒,从而避免延迟波动等问题。需要明确的是,在任何情况下它们都不会单次减少超过50毫秒,但如果游戏运行流畅,它们也可以按更小的数值进行递减。
接续前文:超时问题。假设你的计时器设置为1秒并处于循环状态。如果游戏在最糟的瞬间卡顿,计时器剩余时间最多会降至-0.05。此处负数值完全正常且符合预期。因此循环时,系统会将完整时长加回剩余时间,确保计时与游戏进程同步——即计时器将从0.95而非1秒重新开始。
问题在于计时器设为过短值时会出现。若你设置了总时长0.05秒的永久循环计时器,
当剩余时间降到零秒或更少时,若将该负向超调值与总时长相加,其剩余时长会正好归零,并在下一次更新时强制触发一次完整的完成循环。为防止此情况,我设置了另一个硬性上限:若剩余时长加上总时长的结果小于或等于零,则将剩余时长完全重置为总时长。

闲聊了这么多,终于该聊聊实际功能了。
我得长话短说,才能把整段说明塞进这一页。反正用起来应该挺直观的。

  • 雷克斯特.创建计时器(持续时间: 数字, 循环播放: 布尔值?, 需要活跃的游戏进程: 布尔值?)
持续时间可任意设定,其余两个参数默认为"**错误**“若未赠与,则其名称已说明用途。”

当你创建一个计时器时,RExtender 会返回一个 手柄 使用实时计时器对象本身会带来风险。因为当计时器到期并被删除后,如果您的Lua脚本仍保留着对该对象的引用,直接访问它可能会导致游戏瞬间崩溃或破坏其他脚本的运行。而句柄作为一种安全的中介机制——每次您通过它调用方法时,它都会在内部执行一个"解决“找到真正的计时器。”

如果在单个代码块中对同一计时器进行多次链式操作,通过句柄调用这些函数时,系统需要反复查找该计时器。 每一行所以改为:

  • 手柄:resolve()
这将让你直接访问计时器对象。执行此操作时,请仅在该局部作用域内立即使用已解析的计时器。 从未 全局存储会导致其失去句柄的安全保护!因此,你应该存储的是句柄本身。这样既不会崩溃,即便计时器以某种方式过期,也只会解析失败而已。

手柄/计时器具有以下功能:
  • 已更新(fn: 功能) - 计时器运行时,每 tick 触发一次,需满足所有条件,函数将接收已解析的计时器对象作为唯一参数
  • 完成时设置(fn: 功能) - 倒计时结束时触发一次(若循环播放则在每次循环结束时触发),该函数接收已完成的倒计时对象作为唯一参数
  • 设置条件测试(fn: 功能) - 每 tick 发射一次,返回 "假"跳过tick,你的函数将接收已解析的定时器对象作为唯一参数"
  • 剩余时间() / 剩余时长(数量) - 当前周期还剩多少时间
  • 获取剩余暂停时长() / set_remaining_pause_duration(数字) - 类似于强制暂停,但这是临时的,且会自动倒计时。在所有其他条件(包括你设置的条件)通过后触发。
  • 获取持续时间() / 设置时长(数字) - 计时器总时长
  • 需要活跃的游戏玩法() / 需要活跃玩法(布尔值) - 游戏暂停时计时器是否暂停
  • 是否循环() / 循环播放(布尔值) - 完成计时后是否重新开始
  • 强制暂停() / 设置强制暂停(布尔值) - 随时手动冻结或解冻计时器
  • 已销毁() - 布尔值, 只读,若计时器已清理,则为真
  • 摧毁( ) - 立即结束计时器(仅限句柄使用;计时器对象将在内部自动清理)
  • 重置() - 将剩余时间重置为总时长
已解决的计时器大多使用属性而非函数,因此你可以直接读取和设置它们,无需调用任何内容:
  • 剩余时间 - 数字,当前周期还剩多少时间
  • 剩余暂停时长 - 数字剩余暂停时间还有多少
  • 时长 - 数字计时器总时长
  • 需要活跃玩法 - 布尔值计时器是否在游戏暂停时暂停
  • is_looping(循环播放) - 布尔值计时器完成后是否重新开始
  • 强制暂停 - 布尔值手动暂停与恢复计时器
  • 已摧毁 - 布尔值只读,计时器已清除时为真
功能 重置 以及回调 已更新, 已完成设置 与 设置条件测试 也有同款腕带可用,功能与手柄款完全一致。

计时器用于检测暂停的变量可通过以下方式获取。 雷克斯.游戏已暂停(若游戏不支持,则始终返回“假"它本身不会进行任何计算,所以你可以把它放在任何你喜欢的地方。

第五部分:其他内容
  • 简单说明一下:这个框架应支持所有REFramework所支持的游戏,但如果出现不支持的情况,请告知我。
  • 关于 ImGui 函数的快速提示:由于它们使用了大量字符串切片等操作,性能开销较大。不过鉴于通常只在配置菜单中使用,这点影响应该不大。
  • "types.zip"压缩包内包含元文件,可放入你的VSCode项目或任意编辑器中使用,从而获得REFramework和RExtender的完整智能提示功能。为制作这些文件我投入了大量时间,即便你对其他内容不感兴趣,至少也请用上这个。虽然前者部分注释不一定处处周全,但我已尽力说明了一些未来可能用到的功能点。
至于其他嘛……你得亲自去看看才能知道。 GitHub抱歉,不能把所有内容都写在页面上。
该框架的所有功能均有独立的文档文件,请选择最新分支并查阅所需内容。


另外,我不太了解VR的运行原理,所以不确定RExtender在VR上能不能用。应该可以吧?
 
如有疑问、建议等,请加入Discord服务器: 点击

使用 Xmod 解锁 生化危机:安魂曲 的全部潜力 — 今天就探索这些顶级 MOD!