☰
RE-UE4SS FindAllOf 全局函数详解:遍历 UE4/5 全局对象数组检索实例的实战指南
2026/10/4 1:58:43 网站建设 项目流程
  • 游戏开发
  • 逆向工程

【免费下载链接】RE-UE4SS

Injectable LUA scripting system, SDK generator, live property editor and other dumping utilities for UE4/5 games

项目地址:https://gitcode.com/gh_mirrors/re/RE-UE4SS
点击查看免费下载

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 参数

#类型说明
1string要查找实例的类的短名称(Short Name)

所谓短名称即不含路径前缀的类名,例如"Actor"、"Light"、"PlayerController",而非/Script/Engine.Actor这种完整路径。传入完整路径名会导致匹配失败。

2.2 返回值

#类型子类型说明
1nil 或 tableUObject、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(); }

三个关键点:

  1. 预分配 40 个槽位:由于FindAllOf大概率会命中大量对象,绑定层预先reserve(40)以减少扩容开销;
  2. 返回 1 基索引表:源码注释明确说明++count是为了适配 Lua 从 1 开始的索引约定;
  3. 空结果返回 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 逻辑)中应尽量避免。

若确实要用,官方给出以下缓解建议:

  1. 用#运算符先看命中数量,例如print(#AllActors)——#对数值表的长度取整是 O(1) 操作;
  2. 命中数量大时,不要在遍历循环体内做重活(如字符串格式化、反复调用GetFullName);
  3. 优先手动遍历 outer 链、class 链、super 链,对链上的每个元素调用UObject::GetFName,而不是调用UObject::GetFullName再去解析字符串——因为字符串拼接与解析远贵于 FName 比较;
  4. 把 FNames 缓存到循环外再参与比较;
  5. 或者干脆调用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):

#类型说明
1string要查找实例的类的短名称

返回单个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

项目地址:https://gitcode.com/gh_mirrors/re/RE-UE4SS
点击查看免费下载
上一篇:CANN/metadef获取输出偏移量
下一篇:CANN/metadef获取必选输出格式

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询