RimWorld Mod开发:使用Harmony库实现C#代码动态补丁与游戏逻辑修改 1. 项目概述为什么要在Rimworld Mod开发中引入Harmony如果你和我一样是个在Rimworld社区里摸爬滚打多年的Mod开发者那你一定经历过这样的痛苦时刻面对游戏核心的C#代码想要修改一个不起眼的小逻辑却发现目标方法被标记为private或者sealed甚至整个类都是internal的。直接修改源代码不现实。继承重写目标方法可能不是virtual的。这时候一种更底层、更强大的技术就成了必需品——动态补丁Patching。而Harmony库正是实现这一目标的瑞士军刀。简单来说这个项目就是教你如何利用Harmony这个强大的库在Rimworld的Mod开发中对游戏原有的C#代码进行“外科手术式”的修改。它不是简单地替换文件而是在游戏运行时动态地将你自己的代码逻辑“注入”到游戏原有的方法执行流程中。你可以前置执行在原有方法前干点啥、后置执行在原有方法后干点啥甚至完全替换掉原有方法的逻辑。这为我们实现那些看似“不可能”的功能打开了大门比如修改小人Pawn的特定行为逻辑、调整物品的交互规则或者给游戏界面添加新的响应事件。对于Rimworld Modder而言掌握Harmony意味着从“功能组装者”进阶为“规则修改者”。你不再完全受限于游戏暴露出的有限API而是能够深入到游戏运行的毛细血管中实现真正独特和深度的游戏体验改造。接下来我将以一个实战案例为主线拆解从环境准备、原理理解到代码实现、调试排错的全过程分享我这几年踩过的坑和积累的经验。2. 核心原理与Harmony库深度解析在动手写代码之前我们必须搞清楚Harmony到底是怎么工作的。知其然更要知其所以然这能帮助你在遇到诡异Bug时快速定位问题根源。2.1 动态补丁的本质IL代码操作与运行时织入C#代码最终会被编译为中间语言IL代码。Harmony的核心原理就是在目标程序集比如RimWorld的Assembly-CSharp.dll被加载到内存后游戏运行前通过.NET提供的Mono.Cecil或System.Reflection等机制直接读取和修改这些IL指令。它并不是真的去修改硬盘上的DLL文件而是在内存中为目标方法创建一份“修改版”。当游戏调用这个方法时实际执行的是被我们“打过补丁”的版本。这个过程被称为“织入”Weaving。Harmony帮我们封装了所有复杂的IL操作让我们能够用相对简单的C#语法特性标签和标准方法来描述补丁逻辑。2.2 Harmony的核心概念前缀、后缀、置换与终结器Harmony提供了几种主要的补丁类型对应不同的修改意图前缀补丁Prefix在目标方法执行之前运行。通常用于修改传入的参数。执行一些前置检查如权限验证。如果返回false可以跳过原始方法及后续所有补丁的执行非常强大需谨慎使用。后缀补丁Postfix在目标方法执行之后运行。无论原始方法正常返回还是抛出异常后缀补丁都会执行除非被前缀阻止。通常用于修改原始方法的返回值。读取或修改原始方法执行后的状态。执行一些清理或日志记录工作。置换补丁Transpiler这是最强大也是最复杂的一种。它不直接添加逻辑而是修改目标方法的IL指令流。你可以把它想象成直接编辑方法的汇编代码。通常用于修改方法内部的特定逻辑判断如把某个常数100改成200。移除或替换某些指令调用。实现一些前缀后缀无法完成的、极其精细的操控。终结器补丁Finalizer在目标方法执行后运行即使方法抛出异常也会执行。主要用于异常处理和资源清理确保状态安全。在Rimworld Mod开发中前缀和后缀满足了95%以上的需求是我们重点学习和使用的对象。置换补丁虽然强大但需要对IL有一定了解且调试困难除非万不得已否则不建议新手直接上手。2.3 Harmony在Rimworld中的工作流程理解Harmony在Rimworld Mod加载过程中的时机至关重要Mod加载Rimworld启动读取所有Mod的About.xml和Assemblies。Harmony实例化在你的Mod主类继承自Mod的构造函数中你会创建Harmony实例。public class MyMod : Mod { public static Harmony harmonyInstance; public MyMod(ModContentPack content) : base(content) { harmonyInstance new Harmony(com.yourname.modname); harmonyInstance.PatchAll(); // 自动搜索并应用所有补丁 } }补丁扫描与应用调用PatchAll()后Harmony会扫描当前程序集你的Mod DLL寻找所有标记了[HarmonyPatch]特性的类并根据其中的信息找到游戏程序集中的目标方法将补丁逻辑织入。游戏运行此后游戏中任何代码调用被修补的方法时都会先执行你的前缀如果有然后执行原始方法如果前缀没跳过最后执行你的后缀。注意PatchAll()虽然方便但会扫描整个程序集。对于大型Mod或希望精确控制补丁加载顺序的情况更推荐使用harmonyInstance.Patch(...)方法手动指定每个补丁。3. 开发环境搭建与基础项目结构工欲善其事必先利其器。一个清晰稳定的开发环境能极大提升效率减少不必要的麻烦。3.1 开发环境配置清单集成开发环境IDEVisual Studio 2022社区版是首选。它对C#和.NET的支持最完善NuGet包管理方便。Rider也是一个非常强大的替代品。.NET FrameworkRimworld目前截至1.4版本运行在.NET Framework 4.7.2上。确保你的VS项目目标框架为此版本。Rimworld Mod开发环境在Steam库中右键Rimworld - 属性 - 测试版 - 选择1.4 - unstable或当前最新的开发分支。这能让你访问到包含调试符号的Assembly-CSharp.dll对理解游戏代码至关重要。在游戏安装目录的RimWorldWin64_Data/Managed/文件夹下找到核心程序集如Assembly-CSharp.dll,UnityEngine.CoreModule.dll等。我们将引用它们。Harmony库通过NuGet包管理器安装Lib.Harmony。重要Rimworld 1.3之后自带了Harmony库0Harmony.dll但为了开发便利和版本控制我仍然建议在项目中通过NuGet安装。在发布Mod时你需要在About.xml中声明对Harmony的依赖packageIdbrrainz.harmony/packageId这样玩家就不需要单独下载Harmony了。3.2 创建Mod项目与基础结构新建类库项目在VS中创建新的“类库(.NET Framework)”项目命名为你的Mod名如MyAwesomeMod目标框架选.NET Framework 4.7.2。引用必要的DLL添加对游戏目录下Assembly-CSharp.dll的引用这是游戏主逻辑。添加对UnityEngine.CoreModule.dll等你可能需要的Unity引擎模块的引用。通过NuGet添加Lib.Harmony。设置生成路径为了让编译的DLL直接输出到Rimworld的Mod文件夹方便测试。在项目属性 - 生成事件 - 后期生成事件命令行中可以添加类似以下的命令路径请替换为你自己的copy /Y $(TargetPath) D:\Games\Steam\steamapps\common\RimWorld\Mods\YourModName\Assemblies\创建必要的Mod文件About/About.xml: Mod的元数据名称、作者、描述、依赖等。Assemblies/文件夹用于存放你编译出的YourModName.dll。Defs/,Textures/等根据你的Mod内容添加。一个典型的项目解决方案资源管理器视图应该类似这样MyAwesomeMod.csproj ├── References │ ├── Assembly-CSharp │ ├── UnityEngine.CoreModule │ └── Lib.Harmony ├── Properties/ ├── About/ │ └── About.xml ├── Patches/ (存放所有Harmony补丁类的文件夹) │ ├── Pawn_Patch.cs │ └── JobDriver_Patch.cs └── MyMod.cs (继承自Verse.Mod的主类)3.3 编写主Mod入口类这是Mod的起点也是初始化Harmony的地方。using HarmonyLib; using Verse; namespace MyAwesomeMod { [StaticConstructorOnStartup] // 确保在游戏静态构造时运行所有Defs已加载 public class MyMod : Mod { public static Harmony harmonyInstance; static MyMod() { harmonyInstance new Harmony(com.YourName.MyAwesomeMod); Log.Message([MyAwesomeMod] Applying Harmony patches...); try { harmonyInstance.PatchAll(); // 自动打补丁 // 或者手动打补丁harmonyInstance.Patch(...); Log.Message([MyAwesomeMod] Harmony patches applied successfully.); } catch (System.Exception ex) { Log.Error($[MyAwesomeMod] Failed to apply Harmony patches: {ex}); // 补丁失败不应导致游戏崩溃但需记录错误 } } public MyMod(ModContentPack content) : base(content) { // 构造函数可以在这里初始化Mod设置等 } } }实操心得务必用try-catch包裹PatchAll()或手动补丁调用。一个失败的补丁可能导致游戏在启动时静默崩溃而日志是唯一的救命稻草。给日志信息加上你的Mod名前缀方便在游戏日志中过滤查找。4. 实战案例为“治疗”工作添加自定义逻辑让我们通过一个具体的、有实际意义的例子来贯穿整个流程。假设我们想实现一个功能当殖民者医生治疗病人时如果医生身上携带着某种我们自定义的“高级医疗包”物品则治疗效果提升20%。4.1 目标分析与方法定位首先我们需要找到Rimworld中负责计算治疗效果的代码位置。这需要一些“侦查”工作。使用反编译工具dnSpy或ILSpy是必备工具。打开游戏目录下的Assembly-CSharp.dll。关键词搜索在反编译器中搜索“Heal”或“治疗”。通过浏览我们可能会找到Verse.AI.JobDriver_TendPatient治疗工作的驱动器或RimWorld.HealthUtility健康工具类。深入分析经过分析我们发现RimWorld.HealthUtility.HealInjury方法可能是执行治疗计算的核心。但进一步查看治疗的具体数值可能是在JobDriver_TendPatient的某个流程中决定的。最终定位实际上治疗量的计算逻辑更可能封装在RimWorld.TendUtility.DoTend这个静态方法中。它接收医生Pawn、病人Pawn、治疗物品Thing等参数。这个方法内部会计算基础治疗量并应用医生技能、物品质量等因素。这里就是我们理想的切入点。通过dnSpy查看TendUtility.DoTend的方法签名public static void DoTend(Pawn doctor, Pawn patient, Thing medicine, ...)我们的目标是在这个方法执行之后根据医生是否携带“高级医疗包”来额外增加治疗量。因此我们选择使用后缀补丁Postfix。4.2 创建后缀补丁类在项目的Patches文件夹下新建一个C#类文件例如Patch_TendUtility.cs。using HarmonyLib; using RimWorld; using Verse; namespace MyAwesomeMod.Patches { [HarmonyPatch(typeof(RimWorld.TendUtility))] // 指定要修补的类 [HarmonyPatch(nameof(RimWorld.TendUtility.DoTend))] // 指定要修补的方法名 // 如果需要可以用[HarmonyPatch(DoTend, new Type[] { typeof(Pawn), typeof(Pawn), typeof(Thing), ...})]来精确重载 public static class Patch_TendUtility_DoTend { // 后缀补丁方法必须是静态的 [HarmonyPostfix] public static void Postfix(Pawn doctor, Pawn patient, Thing medicine) { // 我们的补丁逻辑写在这里 } } }代码解析[HarmonyPatch(typeof(ClassName))]告诉Harmony这个补丁类针对哪个类。[HarmonyPatch(nameof(ClassName.MethodName))]告诉Harmony针对该类下的哪个方法。使用nameof操作符是安全的可以避免拼写错误。[HarmonyPostfix]标记下面的静态方法是一个后缀补丁。补丁方法的命名可以是任意的这里叫Postfix是惯例但其参数和返回值有特定规则。4.3 补丁方法参数与返回值规则这是Harmony使用的核心技巧必须理解参数你的补丁方法可以访问原始方法的参数。只需在补丁方法的参数列表中使用与原始方法相同的参数名和类型。Harmony会自动匹配并传入对应的值。你也可以使用__instance对于实例方法访问原对象、__result访问原始方法的返回值对于void方法则是ref类型的结果等特殊参数。返回值对于后缀补丁如果你的方法返回void则不影响原始流程。如果你需要修改原始方法的返回值可以将返回值类型改为与原始方法一致并通过ref参数或修改__result来影响最终结果。在我们的例子中DoTend是void方法没有返回值。我们的目标是修改治疗过程中的某个“状态”。但仔细看DoTend内部似乎直接操作了Hediff健康差异即伤口的Severity严重度。我们无法直接修改这个已经发生的过程。我们需要调整策略也许更好的切入点是计算“治疗质量系数”的地方。经过再次搜索我们发现RimWorld.HealthUtility.TendQuality方法它根据医生、病人、药物等计算一个0到1的质量系数。修改这个系数就能全局影响治疗量。4.4 调整目标修补HealthUtility.TendQuality让我们重新定位到RimWorld.HealthUtility.TendQuality方法。它的签名可能是public static float TendQuality(Pawn doctor, Pawn patient, Thing medicine, ...)它返回一个float即治疗质量系数。我们的后缀补丁需要读取这个返回值并在医生携带高级医疗包时将其提升20%。using HarmonyLib; using RimWorld; using Verse; namespace MyAwesomeMod.Patches { [HarmonyPatch(typeof(RimWorld.HealthUtility))] [HarmonyPatch(nameof(RimWorld.HealthUtility.TendQuality))] public static class Patch_HealthUtility_TendQuality { [HarmonyPostfix] // __result 是特殊参数代表原始方法的返回值。使用 ref float 表示我们要修改它。 public static void Postfix(ref float __result, Pawn doctor, Pawn patient, Thing medicine) { // 1. 安全检查医生必须存在且不是机器人等 if (doctor null || !doctor.IsColonistPlayerControlled) { return; } // 2. 检查医生是否携带了“高级医疗包” // 假设我们通过另一个Mod模块或Def定义了一种名为“AdvancedMedkit”的物品 bool hasAdvancedMedkit false; // 检查医生装备栏如果装备了 if (doctor.equipment ! null) { foreach (ThingWithComps thing in doctor.equipment.AllEquipmentListForReading) { if (thing.def.defName AdvancedMedkit) { hasAdvancedMedkit true; break; } } } // 检查医生物品栏 if (!hasAdvancedMedkit doctor.inventory ! null) { hasAdvancedMedkit doctor.inventory.innerContainer.Any(t t.def.defName AdvancedMedkit); } // 3. 如果携带提升治疗质量系数20% if (hasAdvancedMedkit) { float bonusMultiplier 1.20f; // 提升20% __result * bonusMultiplier; // 修改原始返回值 // 可选记录日志或显示一个浮动消息调试用 // Log.Message(${doctor.NameShortColored}使用高级医疗包治疗质量提升至{__result:P0}.); } } } }4.5 定义“高级医疗包”物品为了让上面的代码生效我们需要在Mod的Defs中定义这个物品。在Defs/ThingDefs/文件夹下创建Items_Medical.xml?xml version1.0 encodingutf-8? Defs ThingDef ParentNameMedicineBase defNameAdvancedMedkit/defName label高级医疗包/label description采用先进技术制造的医疗包能显著提升治疗效率。/description graphicData texPathThings/Item/Resource/Medkit/texPath graphicClassGraphic_Single/graphicClass /graphicData statBases MarketValue85/MarketValue Mass0.5/Mass DeteriorationRate0.5/DeteriorationRate /statBases ingestible drugCategoryMedical/drugCategory /ingestible !-- 可以添加自定义属性比如在补丁中检查 -- modExtensions li ClassMyAwesomeMod.CompProperties_AdvancedMedkit treatmentBonusFactor1.2/treatmentBonusFactor /li /modExtensions /ThingDef /Defs同时可以创建一个对应的CompProperties类来更优雅地处理这个加成系数using Verse; namespace MyAwesomeMod { public class CompProperties_AdvancedMedkit : CompProperties { public float treatmentBonusFactor 1.0f; public CompProperties_AdvancedMedkit() { compClass typeof(CompAdvancedMedkit); } } public class CompAdvancedMedkit : ThingComp { public float TreatmentBonusFactor ((CompProperties_AdvancedMedkit)props).treatmentBonusFactor; // 这个组件本身可能没有逻辑只是数据的持有者 } }然后补丁代码中检查的部分可以优化为bool hasAdvancedMedkit false; float bonusFactor 1.0f; // 检查物品栏和装备栏寻找带有我们自定义组件的物品 if (doctor.inventory ! null) { var medkit doctor.inventory.innerContainer.FirstOrDefault(t t.TryGetCompCompAdvancedMedkit() ! null); if (medkit ! null) { hasAdvancedMedkit true; bonusFactor medkit.TryGetCompCompAdvancedMedkit().TreatmentBonusFactor; } } // ... 装备栏检查类似 if (hasAdvancedMedkit) { __result * bonusFactor; }5. 调试、测试与常见问题排查写完了补丁编译并放入Mod文件夹启动Rimworld测试却发现游戏崩溃或者功能没生效别急这是常态。下面是我总结的排查流程和常见坑点。5.1 调试与日志输出Rimworld的Verse.Log类是你的好朋友。在补丁的关键位置添加日志输出是定位问题最直接的方法。[HarmonyPostfix] public static void Postfix(ref float __result, Pawn doctor, Pawn patient, Thing medicine) { Log.Message($[MyAwesomeMod] Postfix called. Doctor: {doctor?.LabelShort}, Patient: {patient?.LabelShort}, Base Result: {__result}); // ... 你的逻辑 if (hasAdvancedMedkit) { Log.Message($[MyAwesomeMod] Advanced medkit found. Bonus applied. New result: {__result}); } }启动游戏后打开开发者模式Options - Development Mode点击“Open Log Window”查看实时日志。过滤[MyAwesomeMod]可以快速找到你的输出。5.2 常见问题与解决方案速查表问题现象可能原因排查步骤与解决方案游戏启动时崩溃1. 补丁目标方法签名错误。2. 补丁类或方法不是static。3. Harmony库版本冲突。1. 检查[HarmonyPatch]中的类名和方法名是否完全正确包括命名空间。使用dnSpy反复确认。2. 确保补丁类和补丁方法Prefix,Postfix都是static。3. 确保游戏内置Harmony版本与你引用的版本兼容。在About.xml中正确声明依赖。补丁没有生效1. 补丁方法没有被正确识别特性标记错误。2. 补丁逻辑条件判断有误提前return了。3. 目标方法有多个重载补丁应用到了错误的重载上。1. 检查[HarmonyPostfix]等特性是否拼写正确是否应用到了静态方法上。2. 在补丁方法第一行加日志确认方法是否被调用。逐步检查条件判断。3. 使用[HarmonyPatch(typeof(Class), typeof(Param1Type), typeof(Param2Type), ...)]来指定精确的参数类型数组以锁定特定重载。补丁生效但导致游戏逻辑错误1. 修改了不该修改的变量或状态。2. 前缀补丁返回false不当阻止了原始方法执行。3. 对__result或ref参数的操作有误。1. 仔细分析原始方法的逻辑确保你的修改符合游戏规则。避免修改private字段除非你知道后果。2. 前缀补丁返回false需极其谨慎这会导致原始方法和所有后续补丁被跳过。确保你的条件判断绝对正确。3. 对于值类型如float,int的__result使用ref关键字。对于引用类型直接修改其属性即可。性能问题1. 补丁方法逻辑过于复杂或在高频方法上运行。2. 在补丁中进行了昂贵的查找如Find.MapPawns。1. 优化补丁逻辑避免不必要的计算。对于每帧都调用的方法如Tick,Update补丁要格外轻量。2. 缓存查找结果。例如可以将“是否携带高级医疗包”的状态缓存在Pawn的某个自定义数据中只在物品增减时更新缓存。与其他Mod冲突多个Mod修补了同一个方法执行顺序或逻辑冲突。1. 使用Harmony的优先级特性[HarmonyPriority(Priority.High)]或[HarmonyPriority(Priority.Low)]。但需谨慎过度依赖优先级可能导致不可预知的依赖关系。2. 尽量使你的补丁逻辑独立不依赖其他补丁的执行结果。使用__result时要意识到它可能已经被其他Mod修改过。3. 在Mod描述中写明潜在的兼容性问题。5.3 使用Harmony Debug模式在开发阶段可以在创建Harmony实例后启用调试日志harmonyInstance new Harmony(com.YourName.MyAwesomeMod); Harmony.DEBUG true; // 启用调试输出 FileLog.LogFilePath D:\RimWorldHarmonyLog.txt; // 可选指定日志文件 harmonyInstance.PatchAll();这会在日志中输出详细的补丁应用信息包括找到了哪些方法、应用了哪些补丁等对于复杂补丁非常有用。5.4 实战排查案例补丁未生效假设我们的高级医疗包补丁没有生效。排查步骤检查日志查看游戏启动日志确认[MyAwesomeMod] Harmony patches applied successfully.这条信息是否出现。如果没有说明PatchAll()可能抛出了异常被我们的try-catch捕获了查看错误日志。确认补丁加载在补丁类的静态构造函数或补丁方法第一行加日志看是否执行。确认目标方法在dnSpy中再次确认HealthUtility.TendQuality的方法签名确保我们的参数列表(ref float __result, Pawn doctor, Pawn patient, Thing medicine)与之匹配。注意参数顺序和可选参数。检查条件逻辑在hasAdvancedMedkit判断前后加日志输出医生的名字和库存物品列表确认检查逻辑是否正确。检查物品定义确认AdvancedMedkit的defName在游戏中确实被加载并且没有拼写错误。可以在游戏内用开发者模式生成该物品测试。6. 进阶技巧与最佳实践当你掌握了基础的前后缀补丁后下面这些技巧能让你的Mod更健壮、更高效。6.1 使用__instance访问成员字段和方法当你修补的是一个实例方法非static时你可以在补丁方法中使用__instance参数来访问调用该方法的原始对象实例。例如假设我们想修改Pawn的Tick方法每帧调用[HarmonyPatch(typeof(Pawn))] [HarmonyPatch(Tick)] public static class Patch_Pawn_Tick { [HarmonyPostfix] public static void Postfix(Pawn __instance) { // __instance 就是当前正在“Tick”的这个Pawn对象 if (__instance.IsColonistPlayerControlled) { // 可以访问和修改 __instance 的字段或属性 // 例如if (__instance.needs.mood ! null) { ... } } } }6.2 使用__state在前后缀间传递信息有时你需要在前缀中计算一些值然后在后缀中使用。Harmony提供了__state参数来实现这个目的。你需要定义一个特殊类型通常用object或一个自定义结构体作为__state。[HarmonyPatch(typeof(SomeClass))] [HarmonyPatch(SomeMethod)] public static class Patch_SomeClass_SomeMethod { // 前缀可以接收一个 ref __state 参数并为其赋值 [HarmonyPrefix] public static bool Prefix(out DateTime __state) { __state DateTime.Now; // 记录方法开始时间 return true; // 继续执行原始方法 } // 后缀可以接收同一个 __state 参数读取前缀设置的值 [HarmonyPostfix] public static void Postfix(DateTime __state) { TimeSpan duration DateTime.Now - __state; if (duration.TotalMilliseconds 100) { Log.Warning($SomeMethod took too long: {duration.TotalMilliseconds}ms); } } }6.3 处理泛型方法修补泛型方法需要一些技巧。你需要指定泛型参数的类型或者使用[HarmonyPatch]的特性来匹配。// 假设要修补 ListT.Add 方法 [HarmonyPatch(typeof(List))] // 注意是 typeof(List) [HarmonyPatch(Add)] public static class Patch_List_Add { [HarmonyPrefix] public static bool Prefix(object __instance, object item) { // __instance 是 ListTitem 是 T Log.Message($Adding {item} to a list.); return true; } }对于更复杂的泛型可能需要使用[HarmonyPatch(typeof(GenericClass,), ...)]等方式。6.4 性能优化缓存与条件执行避免重复计算在补丁中尤其是高频方法如Tick,Update,GUI绘制方法的补丁中避免进行复杂的查找或计算。例如我们的“高级医疗包”检查可以在Pawn身上缓存一个布尔值只在Pawn物品栏发生变化时更新这个缓存。使用快速路径在补丁开始时进行快速条件判断如果条件不满足尽早return。例如如果补丁只针对人类殖民者那么第一行就检查!pawn.RaceProps.Humanlike然后返回。谨慎使用反射在补丁内部尽量避免使用C#反射GetField,Invoke等这非常慢。如果必须访问私有成员考虑使用Harmony的AccessTools辅助类它会对反射操作进行缓存。6.5 兼容性与版本控制使用AccessToolsHarmonyLib.AccessTools类提供了安全访问私有成员的方法如FieldRefAccessT, V它比直接反射快并且更兼容未来的游戏版本如果字段名没变。防御性编程总是对补丁方法中的对象进行空值检查if (__instance null) return;。版本检查如果你的Mod只支持特定版本的Rimworld可以在补丁应用前检查游戏版本。if (VersionControl.CurrentVersion.Major ! 1 || VersionControl.CurrentVersion.Minor 4) { Log.Error(MyAwesomeMod requires RimWorld 1.4 or later.); return; } harmonyInstance.PatchAll();提供补丁开关考虑在Mod设置中添加选项允许玩家禁用某些可能不稳定的补丁功能。7. 从动态补丁到完整Mod的整合Harmony补丁是你Mod的“引擎”但一个完整的Mod还需要用户界面UI、游戏数据Defs和稳定的架构。7.1 与Mod设置集成让我们为“高级医疗包”添加一个可配置的加成系数。首先创建一个Mod设置类using Verse; namespace MyAwesomeMod { public class MyModSettings : ModSettings { public float advancedMedkitBonusFactor 1.20f; // 默认20%提升 public override void ExposeData() { base.ExposeData(); Scribe_Values.Look(ref advancedMedkitBonusFactor, advancedMedkitBonusFactor, 1.20f); } } }然后在主Mod类中加载设置并提供一个设置窗口public class MyMod : Mod { public static MyModSettings Settings; public static Harmony harmonyInstance; public MyMod(ModContentPack content) : base(content) { Settings GetSettingsMyModSettings(); } public override void DoSettingsWindowContents(Rect inRect) { base.DoSettingsWindowContents(inRect); Listing_Standard listing new Listing_Standard(); listing.Begin(inRect); listing.Label($高级医疗包治疗效果加成: {Settings.advancedMedkitBonusFactor:P0}); Settings.advancedMedkitBonusFactor listing.Slider(Settings.advancedMedkitBonusFactor, 1.0f, 2.0f); // 从100%到200% listing.End(); } public override string SettingsCategory() 我的超赞Mod; }最后修改我们的Harmony补丁从设置中读取加成系数[HarmonyPostfix] public static void Postfix(ref float __result, Pawn doctor, Pawn patient, Thing medicine) { if (doctor null || !doctor.IsColonistPlayerControlled) return; bool hasAdvancedMedkit ...; // 检查逻辑 if (hasAdvancedMedkit) { // 从Mod设置中读取加成系数 float bonus MyMod.Settings?.advancedMedkitBonusFactor ?? 1.20f; __result * bonus; } }7.2 处理游戏存档与加载Harmony补丁修改的是运行时的代码逻辑其本身不需要直接序列化。但是如果你的补丁逻辑依赖于某些需要保存的状态例如我们缓存了某个Pawn携带高级医疗包的状态你需要将这些状态通过游戏现有的存档系统如Pawn的hediffs、comps或自定义GameComponent来保存。一个常见的模式是给Pawn添加一个自定义的Hediff或ThingComp来存储Mod相关状态。这些类会自动被游戏的存档系统序列化。7.3 发布前的最终检查清单功能测试在纯净游戏环境和与其他流行Mod共存的环境下全面测试。性能测试使用开发者模式的“性能分析器”观察添加Mod后关键方法如Tick的耗时是否有显著增加。日志检查在加载和游玩过程中检查游戏日志是否有任何错误黄色或警告红色信息特别是来自你的Mod的。清理调试代码移除或注释掉所有用于调试的Log.Message语句只保留关键的Log.Warning或Log.Error。版本与依赖声明确保About.xml中的targetVersion正确并声明了对Harmony的依赖packageIdbrrainz.harmony/packageId。文档与兼容性说明在Mod发布页面清晰说明Mod的功能、已知问题以及与其他Mod的兼容性情况。掌握Harmony进行动态补丁开发就像获得了修改Rimworld底层规则的钥匙。它要求你对游戏代码结构有更深入的理解同时也带来了无与伦比的灵活性。从简单的数值调整到复杂的行为重写可能性只受限于你的想象力和对C#及IL的理解深度。记住能力越大责任越大。在修改核心游戏逻辑时始终要以兼容性和稳定性为优先考虑并享受创造独特游戏体验的乐趣。

本月热点