- 游戏开发
- 逆向工程
【免费下载链接】RE-UE4SS
Injectable LUA scripting system, SDK generator, live property editor and other dumping utilities for UE4/5 games
FindAllOf是 RE-UE4SS 注入式 Lua 脚本系统中用于检索 UObject 实例的全局函数:它接收一个类的短名称(Short Name),扫描整个游戏的全局对象数组(GUObjectArray),返回该类的所有非默认实例。本文将以 官方文档 为骨架,结合 Lua 绑定实现 与项目内置 Mod 的真实用法,完整讲解该函数的参数、返回值、性能陷阱,以及三种基于 SuperStruct 链做类型过滤的进阶写法,帮助你在 Mod 开发中安全、高效地定位任意对象。
一、函数定位与核心语义
1.1 它在 RE-UE4SS 中的位置
FindAllOf与FindFirstOf同属于 RE-UE4SS 向 Lua 暴露的全局函数(即不挂在任何表上、可直接调用的顶层函数)。在 Lua 引擎初始化阶段,二者在 UE4SS/src/Mod/LuaMod.cpp 中通过lua.register_function注册:
FindFirstOf对应源码中的lua.register_function("FindFirstOf", ...),返回单个对象;FindAllOf对应lua.register_function("FindAllOf", ...),返回对象表。
两者最终都汇入 C++ 层的Unreal::UObjectGlobals::FindAllOf / FindFirstOf,后者在项目内部被大量复用,例如:
- UE4SSProgram.cpp 中通过
UObjectGlobals::FindAllOf(STR("PlayerController"), player_controllers)获取玩家控制器列表; - BPMods.cpp 通过
UObjectGlobals::FindAllOf(mod_actor_name, mod_actors)枚举蓝图 ModActor 实例用于 GUI 渲染; - LiveView.cpp 将
FindFirstOf作为 Watch 的 AcquisitionMethod(对象重获取方式)之一,与StaticFindObject并列可选。
这说明FindAllOf / FindFirstOf不只是面向 Lua 脚本作者,也是 RE-UE4SS 自身 GUI(LiveView、BPMods、UFunctionCallerWidget 等)检索对象的底层基础设施。
1.2 官方定义
按 findallof.md 的描述:
FindAllOf函数将查找所给类名的全部非默认(non-default)实例。
该函数不能用于查找非实例对象,也不能查找默认实例(default instance,即 CDO——Class Default Object)。
也就是说,它遍历的是全局对象数组中的"活"对象:Actor、UObject、UClass 等运行时实例都在其检索范围内,但类默认对象(Class Default Object,如Engine.Engine的 CDO)以及纯类型定义本身不属于检索目标。
二、参数与返回值
2.1 参数
| # | 类型 | 说明 |
|---|---|---|
| 1 | string | 要查找实例的类的短名称(Short Name) |
所谓短名称即不含路径前缀的类名,例如"Actor"、"Light"、"PlayerController",而非/Script/Engine.Actor这种完整路径。传入完整路径名会导致匹配失败。
2.2 返回值
| # | 类型 | 子类型 | 说明 |
|---|---|---|---|
| 1 | nil 或 table | UObject、UClass 或 AActor | 若未找到任何实例则返回nil;否则返回一个从 1 开始索引的数值表,包含所有匹配实例 |
返回值的形态可以在 Lua 绑定源码中精确印证。看 LuaMod.cpp 中FindAllOf的实现:
constexpr int32_t elements_to_reserve = 40; std::vector<Unreal::UObject*> found_unreal_objects; // Reserving some space because FindAllOf is likely to find lots of objects found_unreal_objects.reserve(elements_to_reserve); Unreal::UObjectGlobals::FindAllOf(lua.get_string(), found_unreal_objects); if (!found_unreal_objects.empty()) { LuaMadeSimple::Lua::Table table = lua.prepare_new_table(elements_to_reserve); for (size_t count{}; const auto& unreal_object : found_unreal_objects) { // Increasing the count first, this is to accommodate the one-index based tables of Lua ++count; table.add_key(count); LuaType::auto_construct_object(lua, unreal_object); table.fuse_pair(); } table.make_local(); } else { lua.set_nil(); }三个关键点:
- 预分配 40 个槽位:由于
FindAllOf大概率会命中大量对象,绑定层预先reserve(40)以减少扩容开销; - 返回 1 基索引表:源码注释明确说明
++count是为了适配 Lua 从 1 开始的索引约定; - 空结果返回 nil:
found_unreal_objects.empty()时直接lua.set_nil(),所以脚本里必须先判空再遍历。
每个表元素通过LuaType::auto_construct_object自动构造为对应的 Lua 类型——按 LuaUObject.cpp 的分派逻辑,实际对象会被包装为UObject、UClass、UScriptStruct、UDataTable、UStruct等对应 Lua 类型,且该构造对nullptr安全(返回无效对象而非崩溃)。
三、基础用法示例
官方文档给出的最简示例——输出所有继承自Actor类的对象名称:
local ActorInstances = FindAllOf("Actor") if not ActorInstances then print("No instances of 'Actor' were found\n") else for Index, ActorInstance in pairs(ActorInstances) do print(string.format("[%d] %s\n", Index, ActorInstance:GetFullName())) end end要点拆解:
FindAllOf("Actor")会命中所有继承自Actor的实例(注意:是"继承自",不是仅精确匹配Actor类本身,详见下文第四节);- 结果可能为
nil,因此先if not ActorInstances判空再遍历是标准姿势; ActorInstance:GetFullName()返回带包路径的完整名称(如/Game/Maps/MyMap.MyMap:PersistentLevel.SomeLight_0);- 用
pairs遍历 1 基索引表在 Lua 5.x 下可行,但若要保证顺序,更推荐ipairs。
项目自带的 UEHelpers.lua 也展示了真实 Mod 中的常见用法——获取玩家控制器时同时回退到父类:
local Controllers = FindAllOf("PlayerController") or FindAllOf("Controller") ---@type AController[]?注意这里利用FindAllOf返回nil的特性,用or直接做了一次回退查找,非常简洁。
四、性能:为什么说它"非常慢"
官方文档对此毫不讳言:这个函数非常慢,因为它必须完整遍历一遍整个全局对象数组。
4.1 慢的根源
FindAllOf的检索范围是引擎持有的全部 UObject(包括所有 Actor、组件、资源、蓝图生成的对象等),数量动辄数万乃至数十万。每次调用都是 O(N) 全表扫描,且对每个对象还要做类型匹配(包括向上追溯继承链),因此在生产代码(如每帧执行的 Tick 逻辑)中应尽量避免。
若确实要用,官方给出以下缓解建议:
- 用
#运算符先看命中数量,例如print(#AllActors)——#对数值表的长度取整是 O(1) 操作; - 命中数量大时,不要在遍历循环体内做重活(如字符串格式化、反复调用
GetFullName); - 优先手动遍历 outer 链、class 链、super 链,对链上的每个元素调用
UObject::GetFName,而不是调用UObject::GetFullName再去解析字符串——因为字符串拼接与解析远贵于 FName 比较; - 把 FNames 缓存到循环外再参与比较;
- 或者干脆调用
UObject::IsA(这是最准确的类型判定,详见第五节)。
4.2 来自 Profilers 的佐证
RE-UE4SS 自带的 Profilers 标签页(Profilers.cpp)中就有专门的性能对比按钮,印证了文档的说法:
Test: FindAllOf<AActor>/Test: FindAllOf<UClass>——通过FindObjectSearcher<AActor, AnySuperStruct>().ForEach(...)走"快速路径"统计数量;Test: Fast Instances——调用UObjectGlobals::FindAllOf(STR("Actor"), Instances, true),显式走快速路径;Test: Slow Instances——调用UObjectGlobals::FindAllOf(STR("Actor"), Instances, false),强制走慢速路径。
注意 C++ 层的FindAllOf有一个额外的布尔参数控制使用快速路径(基于索引的FindObjectSearcher)还是慢速路径(逐对象扫描),而 Lua 绑定层只暴露了字符串短名称这一个参数。这也从实现角度解释了文档"非常慢"的结论:Lua 侧无法选择快速路径,只能承受全表扫描。
4.3 实用建议
- 一次性查询、缓存结果:把
FindAllOf的结果存到局部/全局变量,避免在循环或高频回调中反复调用; - 缩小范围:如果能确定目标继承自某个更具体的类,就直接用那个类的短名称(如用
"Light"而非"Actor"),减少后续过滤工作量; - 低频使用:仅在初始化、按键回调、菜单打开等低频时机使用。
五、类型过滤进阶:SuperStruct 链遍历
官方文档强调了一个容易被忽略的事实:FindAllOf("Actor")返回的是所有"继承自 Actor"的实例,而不是"精确是 Actor"的实例。因此当你想对某一具体子类(如Light)做逻辑时,要么直接用更具体的短名称调FindAllOf("Light"),要么自己写过滤。
5.1 手动遍历 SuperStruct 链
下面这段官方示例故意写得"复杂",目的是展示如何遍历类的 SuperStruct 链表(即 Unreal 的继承链)来判定对象类型:
local AllActors = FindAllOf("Actor") print(string.format("NumActors found: %s\n", #AllActors)) local LightName = UEHelpers.FindOrAddFName("Light") for _, Actor in ipairs(AllActors) do -- Filter on the type. -- The class represents the type. local ActorClass = Actor:GetClass() -- Check direct type. local bIsLight = false if ActorClass:GetFName() == LightName then -- Is directly a Light. bIsLight = true end -- Check inheritance. local Super = ActorClass:GetSuperStruct() while Super:IsValid() do if Super:GetFName() == LightName then -- Inherits from Light. bIsLight = true end Super = Super:GetSuperStruct() end if bIsLight then -- Do logic on Light. end end关键点:
Actor:GetClass()拿到该实例的 UClass,UClass 即对象的"类型";ActorClass:GetFName()是当前类的名字,与缓存的LightName比较——这就是文档所说"缓存 FNames 到循环外比较"的实践;ActorClass:GetSuperStruct()返回父类,然后不断Super = Super:GetSuperStruct()沿继承链向上走,直到Super:IsValid()为假(走到UObject或链表尽头);- 一旦发现链上任何一级的名字等于
LightName,说明该对象继承自Light。
文档还特别点出:这种过滤其实常常可以由FindAllOf自己完成——如果你直接把"Light"传给FindAllOf,上面的整个过滤块都可以省略:
-- 上面一整套 Actor + 手动过滤,等价于下面这一行: local AllLights = FindAllOf("Light")5.2 封装成可复用函数
若你需要在多个地方做类型判定,可以照抄官方的IsObjectOfType辅助函数。注意它接收的TypeName参数既可以传短名字符串,也可以传 FName(GetFName()返回的 FName 与字符串比较在 Lua 绑定中是等价的):
local function IsObjectOfType(Object, TypeName) -- Filter on the type. -- The class represents the type. local ObjectClass = Object:GetClass() -- Check direct type. if ObjectClass:GetFName() == TypeName then return true end -- Check inheritance. local Super = ObjectClass:GetSuperStruct() while Super:IsValid() do if Super:GetFName() == TypeName then return true end Super = Super:GetSuperStruct() end return false end官方用它实现了一个"按灯光类型分发逻辑"的完整示例——先FindAllOf("Light")拿到全部灯光,再对每个灯光判定它具体是PointLight、RectLight、SpotLight还是DirectionalLight,从而执行不同的分支:
local AllActors = FindAllOf("Light") print(string.format("NumActors found: %s\n", #AllActors)) local PointLightName = UEHelpers.FindOrAddFName("PointLight") local RectLightName = UEHelpers.FindOrAddFName("RectLight") local SpotLightName = UEHelpers.FindOrAddFName("SpotLight") local DirectionalLightName = UEHelpers.FindOrAddFName("DirectionalLight") for _, Light in ipairs(AllActors) do if IsObjectOfType(Light, PointLightName) then -- Do logic on PointLight. end if IsObjectOfType(Light, RectLightName) then -- Do logic on RectLight. end if IsObjectOfType(Light, SpotLightName) then -- Do logic on SpotLight. end if IsObjectOfType(Light, DirectionalLightName) then -- Do logic on DirectionalLightName. end end注意这里通过UEHelpers.FindOrAddFName("PointLight")一次性把四个类型名转成 FName 并缓存在循环外,循环内只做 FName 比较——正是文档"缓存 FNames"建议的直接体现。UEHelpers定义于 assets/Mods/shared/UEHelpers/UEHelpers.lua,它同时提供了GetEngine(内部FindFirstOf("Engine"))、GetGameInstance(内部FindFirstOf("GameInstance"))等基于本函数家族的高频辅助函数,值得在写 Mod 时复用。
六、最准确的方式:IsA + StaticFindObject
SuperStruct 名字比较在多数场景够用,但官方文档明确指出它并非最准。更准确的做法是用UObject::IsA:
你也可以使用辅助函数
IsA,如果你不想自己写过滤器的话。IsA做的是完整的类型检查,并且使用类对象而不是名字——这就是为什么你需要自己缓存并传入类对象。它是验证类型最准确的方式。
用法差异在于:IsA需要的是UClass 对象而非名字,所以要先通过StaticFindObject按完整路径把类对象查出来并缓存:
local AllActors = FindAllOf("Light") print(string.format("NumActors found: %s\n", #AllActors)) local PointLightClass = StaticFindObject("/Script/Engine.PointLight") local RectLightClass = StaticFindObject("/Script/Engine.RectLight") local SpotLightClass = StaticFindObject("/Script/Engine.SpotLight") local DirectionalLightClass = StaticFindObject("/Script/Engine.DirectionalLight") for _, Actor in ipairs(AllActors) do if Actor:IsA(PointLightClass) then -- Do logic on PointLight. print(string.format("PL: '%s'\n", Actor:GetFullName())) end if Actor:IsA(RectLightClass) then -- Do logic on RectLight. print(string.format("RL: '%s'\n", Actor:GetFullName())) end if Actor:IsA(SpotLightClass) then -- Do logic on SpotLight. print(string.format("SL: '%s'\n", Actor:GetFullName())) end if Actor:IsA(DirectionalLightClass) then -- Do logic on DirectionalLightName. print(string.format("DL: '%s'\n", Actor:GetFullName())) end end要点:
StaticFindObject("/Script/Engine.PointLight")需要完整路径(/Script/前缀 + 类全名),这与FindAllOf只要短名称形成鲜明对比,二者参数形态不要搞混;IsA内部由 C++ 完成继承链判定,比手写 SuperStruct 循环更可靠(例如能正确处理接口、中间类型等边界情况);IsA是最准确的类型验证手段,但代价是必须预先取得 UClass——通常在游戏启动后类对象已经加载,用StaticFindObject一次取到后缓存即可。
七、姊妹函数 FindFirstOf 速览
与FindAllOf同源的FindFirstOf只返回第一个非默认实例,参数与返回值对应关系如下(详见 findfirstof.md):
| # | 类型 | 说明 |
|---|---|---|
| 1 | string | 要查找实例的类的短名称 |
返回单个UObject / UClass / AActor,仅在找到实例时有效;未找到时返回的对象是无效对象(不是nil),所以官方示例用:IsValid()判空:
local CharacterInstance = FindFirstOf("Character") if not CharacterInstance:IsValid() then print("No instance of class 'Character' was found.") end注意与FindAllOf的差异:后者未命中返回nil,前者未命中返回"无效对象",判空方式不同。FindFirstOf同样源自Unreal::UObjectGlobals::FindFirstOf,LuaMod.cpp 中的绑定通过LuaType::auto_construct_object(lua, object)构造结果——注释明确说明该构造是nullptr 安全的,找不到时也会产出一个IsValid()为假的占位对象。
八、实战决策清单
最后把本文要点整理成一份可直接照做的决策清单:
| 场景 | 推荐做法 |
|---|---|
| 需要全部实例,且是低频操作(初始化、按钮回调) | FindAllOf("短名称"),先判nil再遍历 |
| 需要第一个实例(Engine、GameInstance 等单例) | FindFirstOf("短名称")+:IsValid()判空 |
| 只关心某个具体子类 | 直接把子类短名称传给FindAllOf,例如FindAllOf("Light") |
| 需要最精确的类型判定 | StaticFindObject缓存 UClass 后调用:IsA(Class) |
| 高频/每帧调用 | 不要用FindAllOf;改为缓存结果、缓存 FName、遍历时只做轻量比较 |
| 命中数量很大 | 用#查看数量,循环体内避免GetFullName等字符串开销 |
FindAllOf是 RE-UE4SS 脚本体系中"无差别全量检索"的利器,理解其"全表扫描、返回 1 基索引表、空则 nil"的语义,配合 SuperStruct 链遍历或IsA精确过滤,你就能在游戏运行时稳定地定位并操作任意对象。相关 API 的完整索引可继续查阅 lua-api.md 与 SUMMARY.md。
- 游戏开发
- 逆向工程
【免费下载链接】RE-UE4SS
Injectable LUA scripting system, SDK generator, live property editor and other dumping utilities for UE4/5 games
相关推荐
UE4SS 全局函数 NotifyOnNewObject 详解:监听 UE4/UE5 对象构造与回调注销
UE4SS 全局函数 NotifyOnNewObject 详解:监听 UE4/UE5 对象构造与回调注销 NotifyOnNewObject 是 UE4SS 注
游戏开发逆向工程UE4SS 的 DumpAllObjects 全局函数:全量对象与属性转储指南(UE4SS_ObjectDump.txt 生成详解)
UE4SS 的 DumpAllObjects 全局函数:全量对象与属性转储指南(UE4SS_ObjectDump.txt 生成详解) DumpAllObject
游戏开发逆向工程UE4SS LoadAsset 全局函数详解:在游戏线程中按路径加载 UE4/5 资产
UE4SS LoadAsset 全局函数详解:在游戏线程中按路径加载 UE4/5 资产 LoadAsset 是 UE4SS 提供的 Lua 全局函数,用于在游戏
游戏开发逆向工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考