Mod 构思
做东西前最好构思好,我这个 Mod 要做什么?需要用到些什么?
这里作为一个例子,我们做一个简单的 Mod ,功能就是当玩家吃到补给的时候,重置天赋的冷却时间和使用次数。这个 Mod 比较简单,没有 UI 界面,也没有参数需要跨会话保存。
可行性评估
如何获取吃补给的玩家对象
光有构思可不行,还要评估是否有可能将其实现。这是很重要的,异想天开的想法很有可能并不能实现。
考虑到我们 Mod 的功能,要知道是谁吃了补给,应该是需要用到游戏内资产 补给舱 的,我们先在 DUMP 文件里找一下。
这个东西就是游戏里的补给舱。随便一个文本编辑器打开看看:
从这个文件我们可以得到的信息, BP_SupplyPod_Ammo 父类是 RessuplyPod(鬼船谜之拼写错误),四份补给想必是用红色箭头标出的四个 SingleUsable 组件标记使用的。那么,RessuplyPod 和 SingleUsable 组件又是什么东西?
不用着急,我们大部分时候所需要查找的东西其实都在一个文件里面,就是 DUMP 文件里面的 FSD.hpp。
就是这个有将近三万行的巨大头文件。这个文件里面定义的类、结构、函数等都已经包含在 Template 模板里,可以直接调用。利用搜索功能查找,RessuplyPod 豁然在列。不过没有太多有价值的信息,在这里我们找不到关于谁吃了补给的信息。
那么换个思路,上面提到的 SingleUsable 组件是否提供了有价值的信息?在 FSD.hpp 里搜索 SingleUsableComponent ,果然发现端倪:
这些 F 开头的玩意是 UE4 一种叫委托的东西,当触发时会发出一个信号,可以在其他对象中接收这个信号并进行处理。在这里,我们可以理解成,当这个 SingleUsable 组件被使用完毕时它会发射一个 OnUsedBy 信号,包含了使用者 User 和 使用按键的指针。既然补给舱 BP_SupplyPod_Ammo 用了这个 SingleUsable 组件,那想必 OnUsedBy 就是玩家吃了补给时会发出的信号,正好这里就能取到吃补给的玩家的信息。
如何重置指定玩家的天赋
那么下一步,知道了谁吃了补给,那么怎么重置他的天赋使用情况呢?
老办法,浏览下 FSD.hpp 文件里和玩家有关的部分,通常情况位于 PlayerCharacter, PlayerController 等地方。最终,可以在 FSDPlayerController 这里发现可疑对象:
顺藤摸瓜,找到了我们想要的东西:
理论上,我们调用 Server_CheatReset() 方法,就可以重置玩家的天赋。
实现 Mod
回顾我们的思路,首先是要在游戏世界里找到 BP_SupplyPod_Ammo 这个对象。可惜的是,模板工程里没有具体的资产,我们需要自己新建一个资产来方便我们调用。
要建立资产,首先得知道资产的位置。找到之前解包的游戏资产文件,搜索资产名称,这里是 BP_SupplyPod_Ammo。
打开 Template 工程,在下面的 Content Browser 空白处右键,选 New Folder。依次新建文件夹 WeaponsNTools\SupplyPod\,必须和原有游戏资产目录一模一样。
在 SupplyPod 目录下空白处右键,选 BluePrint Class.弹出框中把下面 All Classes 展开,输入 RessuplyPod 并选择 RessuplyPod 作为父类,然后将其名称改为 BP_SupplyPod_Ammo。
此时应该类似这样:
右键点击我们新建的资产,选 Save。由于我们不需要进一步建立变量、方法、组件等,因此这样就可以了。
回到最顶层的 Content 文件夹。新建一个文件夹作为我们这个 Mod 的文件夹,名字随意,比如叫 MyMod_ResupplyMyPerks.
进入文件夹,在空白处右键,选择 Blueprint Class。
这里我们要建立执行 mod 功能的 Actor。按照 DRG 游戏的 Mod 载入原理,其会载入并刷出名称为 InitCave (如果在任务中)或 InitSpacerig (如果在太空钻台)的 Actor 。如果你有多个 Mod,相同的名称很容易会导致混淆。所以我们这里另外制作一个 Mod actor,取一个标志性的名称。
我们直接选择 Actor 作为父类,然后给它起名叫 _ResupplyMyPerks_InitBase.
双击在编辑器中打开这个 Actor。
切到 Event Graph 选项卡,可以看到默认自带了 3 个事件:
- Event BeginPlay 是 Actor 在 spawn,即实例化的时候会调用的事件;
- Event ActorBeginOverlap 是 Actor 与其他物体碰撞时触发的事件,我们这用不上;
- Event Tick 是每一个 Tick 都会触发的事件,即每一帧都会触发一次,我们这里也用不上。
考虑到我们的游戏逻辑,它需要在游戏世界中查找 BP_SupplyPod_Ammo 对象,且由于这个对象是随时可能生成的(叫一次补给就生成一个)而不是起初就存在于世界,决定采用定时器来定时查找。
从 Event BeginPlay 引脚拉出来一条线,键入 Set Timer,选择 Set Timer By Function Name
参数参考这样填
Function Name 是计时到了会执行的方法名称,Time 是计时时长,Looping 勾上表示每经过一个 Time 就触发一次,而不是只触发一次。
现在来实现这个方法。在左侧的 Function 处点 + 号,新建一个 Function,命名为刚才Function Name 给定的名称。
寻找特定 Actor 用 GetAllActorsOfClass ,然后用 ForEachLoop 对其单个进行加工:
注意,GetAllActorsOfClass 里,Class 选择 BP_SupplyPod_Ammo,GetComponentsByClass 里,Class 选 SingleUsableComponent。原因嘛,想必读到这里应该很清楚了。
最后,在 Bind Event 那里,从红色的 Event 节点拉出 CreateEvent, 并选择 [Create a matching function].
编辑器自动跳到新创建的 Function 处。我们在左边的 Function 列表处重命名这个 Function 为 ResetPerk.
这个 Function 逻辑也非常简单,依样画葫芦就可以了。
到这里 Mod Actor 已经做完了,我们编译保存它。这里建议把 Compile 按钮旁边三角点开,把总是保存勾选上。
然后单击 Compile,一切顺利的话,应该像这样.
但是!别高兴得太早,还记得之前说的只会加载 InitCave 或 InitSpacerig 吗?显然我们名字奇怪(_ResupplyMyPerks_InitBase)的 Actor 并不会被游戏加载。
但这并不是什么大问题。关闭 Actor 的编辑器,回到这个 Actor 所处的目录下,右键建立 Blueprint Class。但选择父类的时候,输入我们刚才的 Mod Actor的名字!
然后给它起名 InitCave。
就这么简单。现在游戏在加载洞穴时会加载 InitCave 这个 Actor ,又由于这个 Actor 是我们 InitBase 的子类,所以也就相当于加载了 InitBase 。
打包 Mod
虚幻引擎烘培
前往 打包设置
展开高级设置
找到 Directories to never cook,把 Content 文件夹下,除了我们 Mod 的文件夹以外其他文件夹全部加进去。因为其他文件要么是空的,要么无需我们烘培(想想我们之前新建的 BP_SupplyPod_Ammo,是一个空壳,打包进去游戏指定得崩溃)。
最后,点击打包,选一个目录就等打包完成吧。
首次打包耗时会比较久,可能需要两三分钟以上。以后再打包大概也就不到 30 秒了。
DRGPacker 打包
去刚才的打包文件夹,找到这两个东西
把它们复制到 DRGPacker 的 input 文件夹下。再把整个 input 文件夹拖到 _Repack.bat 上,静等命令行跑完。
这个 input_P.pak 就是我们的蓝图 Mod 本体了。
在游戏里测试
在游戏安装目录找到这个位置:\Deep Rock Galactic\FSD\Mods\,新建一个文件夹,再把我们上一步得到的 pak 文件放进去。最好把这个文件夹和 pak 文件重命名为一个相同且有代表性的名字,如下图:
然后就可以启动游戏去测试了。
记得勾选启用!
上传到 Mod.io
mod.io 网址
多的不用说,选文件的时候,将 pak 文件的上一级同名文件夹打包成一个 zip 压缩包上传就可以了。