本章收集可以组合使用的接入技巧,后续案例会继续作为独立页面补充。第一篇用“游戏定义扩展基类,Mod 创建子类,加载后由游戏统一发现和调用”的方式,把各个 Mod 自带的可玩地图加入选图界面。同样的结构也适合物品、角色或任务目录。
分工:EasyMod 已提供派生类查询、状态和生命周期作用域;本例中的扩展基类、地图配置和管理器由游戏自己实现。无需修改 Mod Editor,也无需编辑隐藏的托管入口;游戏的公开接口变化后,仍需重新导出 SDK。
整体结构
Game
|
+-- BP_ModExtension (game-defined base class)
| +-- Initialize(Context, ModId)
| +-- GetPlayableMaps()
| +-- Shutdown()
|
+-- BP_ModExtensionManager
+-- Find Derived Mod Classes
+-- Construct instances and call Initialize
+-- Map registry: ModId + MapId
Mod A Mod B
+-- BP_IslandExtension +-- BP_ArenaExtension
+-- DA_IslandMaps +-- DA_ArenaMaps
+-- Island.umap +-- Arena.umap
每个 Mod 可以有一个或多个扩展子类。游戏查询到实际的 Class 后创建对象,调用基类约定的方法;作者重写的方法由正常的 Blueprint 多态分派执行。仅创建子类不会让它自动执行,下面的管理器负责这一步。
1. 在游戏中准备扩展接口
创建可继承的 UObject Blueprint 基类 BP_ModExtension。示例接口如下;这些名称都是游戏自己的教学接口,不是插件新增节点。
| 接口 | 约定 |
Initialize(Context, ModId) | 保存管理器引用和所属 Mod ID,需要时执行初始化。通过 Context 访问游戏功能,不依赖普通 UObject 自动获得世界上下文。 |
GetPlayableMaps() | 返回地图配置中的数组;可以在基类实现默认读取,也可由作者重写。 |
Shutdown() | 撤销本实例创建的游戏注册、监听与运行逻辑;允许重复清理而不产生副作用。 |
再创建当前世界使用的 Actor 管理器 BP_ModExtensionManager,在游戏启动流程中放置或生成一个。由它保存扩展对象引用、调用接口并维护地图列表。不要在 Class Default Object 上调用带运行逻辑的初始化。
2. 用 DataAsset 决定哪些地图进入列表
创建游戏结构体 ST_ModMapInfo 和配置类 PDA_ModMapList(PrimaryDataAsset)。配置类保存地图数组,扩展基类保存该配置的引用。
| 字段 | 类型 | 用途 |
MapId | Name | 同一个 Mod 内稳定且唯一的地图标识。 |
DisplayName | Text | 选图界面的显示名称。 |
Map | World Soft Object Reference | 实际地图的软引用,避免打开列表时加载所有世界。 |
Mod 作者在自己的目录创建配置实例,例如 DA_IslandMaps,只填入想向玩家开放的地图,再赋给自己的扩展子类。测试地图不放入数组即可;需要更多分类时,再增加游戏自己的标签或分类字段。
每个 Mod 提交自己的配置:让所有 Mod 修改同一份游戏 DataAsset 不会自动合并数组。这里由游戏读取不同路径的配置,再汇总到运行时列表。游戏定义的 RegisterMap 使用 ModId + MapId 作为键;重复键需要拒绝并报告,不能悄悄覆盖其他记录。
3. 在 SDK 中开放必要内容
- 在 Mod settings 的类权限中公开扩展基类,允许继承、Mod 后代、派生查询,以及该 UObject 创建流程需要的实例化权限。
- 开放结构体、配置类型及地图数组等必要字段,允许作者创建和填写自己的配置资源;原游戏配置仍可保留只读。
- 在成员权限中批准所需的函数调用、函数重写和默认值编辑。公开父类不会自动开放所有成员。
- 如果作者的初始化需要调用管理器,将管理器类型作为可引用类型公开,只开放需要的函数。按本例内容启用 Blueprint、数据和地图创作能力,然后导出更新后的 SDK。
作者在现有 Mod Editor 中创建扩展子类、独立配置和地图。扩展基类的默认读取逻辑足够时,只配置默认值即可;需要自定义行为时再重写方法。这不涉及替换 EasyMod 管理的入口。
4. 加载后查询子类并执行
管理器先绑定 On Ready 和 On Changed,然后立刻刷新一次,兼容管理器创建前 Mod 已准备好的情况。两种通知都用于触发重新检查;实际按 Get Installed Mods 返回的启用标志和状态筛选。
BP_ModExtensionManager: BeginPlay
-> Get EasyMod Subsystem
-> Bind On Ready + On Changed
-> RefreshExtensions()
RefreshExtensions() // game-defined, guarded against re-entry
-> Get Installed Mods
-> For each enabled Mod with State == Active:
Scope = Get Mod Execution Scope(ModId)
Classes = Find Derived Mod Classes(
BP_ModExtension, ModId, Recursive=true, IncludeAbstract=false)
For each Class:
Key = (ModId, ClassPath)
If Key is not registered and Scope is valid:
Construct Object from Class (Outer = this manager)
Cast to BP_ModExtension
Store instance; register with Scope
Initialize(Context = this manager, ModId)
GetPlayableMaps() -> RegisterMap(ModId, MapInfo)
-> Remove stale instances and their map records
Scope.OnCancelled / Manager.EndPlay
-> Shutdown() once per instance
-> Remove map records; release references; unbind delegates
Find Derived Mod Classes 只返回当前已挂载、通过目录及继承检查的 Mod 类,还要求游戏基类允许派生查询。按每个 Mod ID 查询可保留来源信息;递归查询支持多层继承,排除抽象类可避免无法实例化的结果。
准备完成与运行初始化分开:On Ready 在原生入口激活之前广播。若只是读取地图配置,可在已启用 Mod 的 Mounted 阶段处理;本例包含运行初始化,统一等到 Active,并取得当前世界的有效 Get Mod Execution Scope。不能只收到准备通知就假定所有 Mod 已激活。
用 ModId + ClassPath 记录已创建的扩展对象并保留强引用。再次通知时只补充缺失实例,移除不再有效的实例;刷新期间出现新通知时合并为一次后续刷新,避免重复初始化和回调重入。管理器读取 GetPlayableMaps() 的结果后调用 RegisterMap 汇总。
5. 配对处理停用和切换地图
通过当前 Mod 的作用域调用 Register Owned Object,登记新建的扩展实例;登记失败就放弃本次初始化。作用域的 On Cancelled 通知和管理器的 EndPlay 都进入同一套幂等清理流程:先调用 Shutdown(),再移除所属地图记录、解除监听并释放引用。
作用域取消时会先发出取消通知,再清理所拥有的对象;它不会自动知道游戏自定义的地图列表。管理器必须自行撤销这些记录。切换世界后重新创建管理器和扩展实例;GameInstance 可以保存软地图信息,不要长期保存上一世界的扩展对象。
6. 接到选图界面并验证发布结果
UI 读取汇总后的地图列表,保留每一项的 Mod ID 和地图标识。单机选中后可将软地图引用交给 Open Level (by Object Reference);联机由服务器决定切图,并结合自动 Mod 同步确保参与者准备好同一套内容。
地图仍按现有地图发布和打包流程进入 Mod。软引用用于配置和加载,并不等于已验证 Cook 完成;用导出的实际运行包检查地图及依赖是否包含在内。
验收这个技巧:- 安装两个 Mod,各自配置一张可玩地图和一张未登记的测试地图;列表只出现两张可玩地图。
- 两个 Mod 使用相同的局部地图标识仍能共存;同一 Mod 内的重复键会被明确报告。
- 重复状态通知不会重复添加;停用、重启和切换地图后没有重复条目或旧世界引用。
- 使用实际发布包进入每张登记地图;需要联机时再验证服务端切图与客户端准备流程。
后续可复用这一机制,增加物品、角色、任务等使用技巧:先由游戏定义稳定的接口和数据,再让各个 Mod 独立提供内容,由游戏控制发现、执行、汇总与清理。