Unity游戏Mod/插件制作教程07 - Harmony补丁实战:拦截、修改与状态传递 📅 发布时间:2026/7/14 22:20:28 👁️ 浏览次数: 1. Harmony补丁实战入门从拦截到修改如果你已经跟着前几期教程学会了基础插件开发现在应该能用C#代码实现一些简单功能了。但你可能发现了问题——很多功能需要主动触发比如按键操作而我们更希望Mod能自动运行。这时候就该Harmony登场了它能让我们在不修改游戏源码的情况下直接干预游戏方法的执行流程。上周我们学会了用[HarmonyPatch]标记补丁目标这次要深入两个实战场景方法拦截修改和状态传递。想象你要做个无限耐力Mod当角色奔跑时我们需要拦截游戏计算耐力消耗的方法修改传入的耐力值参数在不同补丁方法间传递自定义状态// 示例修改角色耐力值 [HarmonyPatch(typeof(PlayerController), UpdateStamina)] class InfiniteStaminaPatch { public static void Prefix(ref float staminaCost) { staminaCost 0; // 所有耐力消耗归零 } }2. Prefix补丁的四种拦截姿势2.1 基础参数修改最常见的场景是修改方法参数。比如游戏里有这样计算伤害的方法public float CalculateDamage(float baseDamage, float armorFactor) { return baseDamage * (1 - armorFactor); }我们可以用Prefix补丁强行修改伤害值[HarmonyPatch(typeof(DamageSystem), CalculateDamage)] class GodModePatch { public static void Prefix(ref float baseDamage) { if(Input.GetKey(KeyCode.F1)) baseDamage 9999; // 按下F1开启秒杀模式 } }2.2 完全拦截方法执行通过返回false可以阻止原方法运行这在实现无敌模式时特别有用[HarmonyPatch(typeof(PlayerHealth), TakeDamage)] class NoDamagePatch { public static bool Prefix() { return false; // 拦截所有伤害 } }2.3 条件拦截更聪明的做法是根据游戏状态决定是否拦截。比如只当生命值低于20%时触发自动回血[HarmonyPatch(typeof(PlayerHealth), Update)] class AutoHealPatch { public static bool Prefix(PlayerHealth __instance) { if(__instance.health __instance.maxHealth * 0.2f) { __instance.health Time.deltaTime * 5; return false; // 拦截原更新逻辑 } return true; } }2.4 修改返回值虽然Prefix拿不到方法返回值但我们可以直接给结果变量赋值[HarmonyPatch(typeof(Inventory), GetItemCount)] class InfiniteItemsPatch { public static bool Prefix(ref int __result) { __result 999; // 获取物品数量永远返回999 return false; } }3. 跨补丁的状态传递技巧3.1 __state参数的妙用有时候我们需要在Prefix和Postfix之间传递数据。比如记录方法执行前的状态[HarmonyPatch(typeof(EnemyAI), Update)] class EnemyAIPatch { public static void Prefix(out Vector3 __state) { __state EnemyAI.lastKnownPosition; // 保存原始值 } public static void Postfix(Vector3 __state) { if(Vector3.Distance(__state, EnemyAI.lastKnownPosition) 10) Debug.Log(敌人位置发生剧烈变化); } }3.2 复杂状态对象__state不仅限于基础类型可以传递任意对象class AttackState { public float originalDamage; public bool wasCritical; } [HarmonyPatch(typeof(Weapon), CalculateCriticalHit)] class CriticalHitPatch { public static void Prefix(ref AttackState __state, float baseDamage) { __state new AttackState { originalDamage baseDamage, wasCritical UnityEngine.Random.value 0.9f }; } public static void Postfix(AttackState __state, ref float __result) { if(__state.wasCritical) __result __state.originalDamage * 2.5f; } }3.3 状态生命周期管理注意__state的生命周期只在当前方法调用期间有效。如果需要持久化状态应该使用静态变量[HarmonyPatch(typeof(GameManager), SaveGame)] class SaveGamePatch { private static DateTime lastSaveTime; public static void Prefix() { lastSaveTime DateTime.Now; } public static void Postfix() { Debug.Log($存档耗时{(DateTime.Now - lastSaveTime).TotalMilliseconds}ms); } }4. 实战制作无限耐力Mod现在我们来完整实现一个经典案例。假设游戏中有个PlayerMovement类处理移动逻辑public class PlayerMovement : MonoBehaviour { public float stamina 100; public float maxStamina 100; void Update() { if(Input.GetKey(KeyCode.LeftShift) stamina 0) { stamina - Time.deltaTime * 10; MoveFaster(); } else if(stamina maxStamina) { stamina Time.deltaTime * 5; } } }4.1 方案一直接修改耐力值最简单的实现方式是在耐力减少时反向增加[HarmonyPatch(typeof(PlayerMovement), Update)] class InfiniteStaminaPatch1 { public static void Postfix(PlayerMovement __instance) { if(__instance.stamina __instance.maxStamina) __instance.stamina __instance.maxStamina; } }4.2 方案二拦截消耗逻辑更优雅的做法是拦截耐力消耗逻辑[HarmonyPatch(typeof(PlayerMovement), Update)] class InfiniteStaminaPatch2 { public static bool Prefix(PlayerMovement __instance, ref float __state) { __state __instance.stamina; // 保存当前值 // 如果按下冲刺键但耐力不足 if(Input.GetKey(KeyCode.LeftShift) __instance.stamina 0) { __instance.stamina 1; // 保持最小正值 return true; // 继续执行原方法 } return true; } public static void Postfix(PlayerMovement __instance, float __state) { // 如果耐力被消耗 if(__instance.stamina __state) { __instance.stamina __state; // 恢复原值 } } }4.3 方案三完全自定义逻辑最高级的实现是接管整个更新逻辑[HarmonyPatch(typeof(PlayerMovement), Update)] class InfiniteStaminaPatch3 { public static bool Prefix(PlayerMovement __instance) { // 完全重写Update逻辑 if(Input.GetKey(KeyCode.LeftShift)) { __instance.MoveFaster(); } return false; // 阻止原Update执行 } }5. 调试与常见问题排查5.1 补丁未生效检查清单确认Harmony初始化确保插件启动时调用了Harmony.CreateAndPatchAll检查方法签名参数类型和数量必须完全匹配验证访问权限非public方法需要正确设置BindingFlags查看Harmony日志在初始化代码后添加FileLog.Reset()开启日志5.2 典型错误案例案例一参数类型不匹配// 错误示范漏了ref关键字 [HarmonyPatch(typeof(Player), SetHealth)] class Patch1 { public static void Prefix(int health) {} // 应该用ref int } // 正确写法 [HarmonyPatch(typeof(Player), SetHealth)] class Patch2 { public static void Prefix(ref int health) {} }案例二混淆实例方法与静态方法// 错误示范静态方法误用__instance [HarmonyPatch(typeof(Utilities), StaticMethod)] class Patch3 { public static void Prefix(object __instance) {} // 静态方法没有实例 } // 正确写法 [HarmonyPatch(typeof(Utilities), StaticMethod)] class Patch4 { public static void Prefix() {} }5.3 性能优化建议减少补丁数量合并多个补丁到一个Patch类避免频繁分配内存特别是在Update方法中使用缓存对反射获取的MethodInfo进行缓存条件补丁通过[HarmonyBefore]、[HarmonyAfter]控制执行顺序// 优化示例缓存MethodInfo private static MethodInfo targetMethod; [HarmonyPatch] class OptimizedPatch { static MethodBase TargetMethod() { return targetMethod ?? AccessTools.Method( typeof(SomeClass), SomeMethod, new[] { typeof(int), typeof(string) } ); } }6. 高级技巧动态补丁有时候我们需要根据游戏状态动态启用/禁用补丁Harmony harmony; Patches staminaPatch; void Start() { harmony new Harmony(com.my.mod); staminaPatch new Patches(); TogglePatch(false); } void TogglePatch(bool enable) { if(enable) harmony.Patch( typeof(PlayerMovement).GetMethod(Update), prefix: new HarmonyMethod(staminaPatch.GetType().GetMethod(Prefix)) ); else harmony.Unpatch( typeof(PlayerMovement).GetMethod(Update), HarmonyPatchType.Prefix, com.my.mod ); } class Patches { public void Prefix() { ... } }7. 最佳实践与安全建议命名空间隔离为补丁类使用独立命名空间异常处理补丁方法内要有try-catch块版本兼容使用[HarmonyBefore]处理Mod冲突性能监控记录补丁执行时间配置化通过配置文件控制补丁行为[HarmonyPatch(typeof(PlayerMovement), Update)] class ConfigurablePatch { public static bool Prefix() { if(!ModSettings.EnableInfiniteStamina) return true; // 补丁逻辑... return false; } }记得在实际项目中过度使用Harmony补丁可能会导致游戏不稳定。我个人的经验法则是能用常规Unity事件系统实现的就不用Harmony必须修改游戏原有逻辑时才使用补丁技术。
RAG检索效果差?90%的人都忽略了这四层优化,你中招了吗? 搞技术的朋友们好,今天聊个扎心的话题。 你搭了一套 RAG 系统,向量库也选了,Embedding 模型也换了最新的,chunk 大小调了好几轮。结果呢?用户一问稍微复杂点的问题,返回的内容驴唇不对马嘴。你盯着检索结果… 2026/7/14 22:16:26
《Java 100 天进阶之路》第59篇:ThreadLocal原理与内存泄漏(2026版) 第59篇:ThreadLocal原理与内存泄漏(2026版) 📌 系列导航:《Java 100 天进阶之路》完整目录 | ⬅️ 上一篇:第58篇:原子类与CAS | ➡️ 下一篇:第60篇:并发编程面试压轴题… 2026/7/14 22:16:26
Bonferroni与BH校正选择指南:FWER和FDR的业务决策逻辑 1. 这不是“选一个公式”那么简单:为什么统计校正方法选错,整篇分析就废了一半 你刚跑完20个t检验,发现其中3个p值在0.04到0.048之间——看起来“显著”,但心里发虚:这到底是真实信号,还是纯属运气撞上的假… 2026/7/14 22:14:26
Python asyncio 深度并发:驾驭大模型调用的“车水马龙” 如果你正在用 FastAPI 构建一个 AI 应用,大概率会遇到这样的场景:用户发来一个问题,你需要同时调用 3 个大模型接口(比如让 GPT-4 写文案、让 Claude 做润色、让文心一言做翻译),最后把结果拼在一起。最笨的… 2026/7/15 0:27:09
Copilot团队管理功能落地全图谱(从权限配置到效能看板):微软内部PM未公开的7条黄金规则 更多请点击: https://codechina.net 第一章:Copilot团队管理功能全景概览 GitHub Copilot 的团队管理功能为组织级开发协作提供了统一策略配置、成员权限控制与使用洞察分析能力,覆盖从开发者准入到资源配额分配的全生命周期管理。该功能依托… 2026/7/15 0:23:09
为什么 AI 总是“一本正经地胡说八道”?大模型幻觉终于讲明白了 你问 AI:“这句话出自哪本书?”它给出书名、作者、出版社,甚至精确到页码。你满心欢喜地去查,却发现书里根本没有这句话。更令人困惑的是,它的语气没有一丝犹豫。这不是 AI 在故意撒谎,而是大模型最典型、也… 2026/7/15 0:21:08
这个GitHub工具太狠了,一键把网站搬回家,断网照样用! 你有没有遇到过这种情况——在飞机上、地铁里,信号全无,想翻之前收藏的一个网页,死活打不开。这时候你就想,要是能把整个网站”搬”到本地该多好。还真有人做了这么个工具。叫 kage,一个越南老哥用 Go 写的。原理说起来… 2026/7/15 0:17:07
2026年商城网站建设公司排名,综合实力谁更强 今天给大家带来2026年商城网站建设公司排名,综合实力谁更强。据行业白皮书数据,截至2026年第一季度,微信小程序日活已达9.2亿,同比增幅18.7%;全网商城小程序数量突破380万,零售行业商家渗透率高达74.3%&… 2026/7/15 0:15:07
ArkCompiler 5.0:方舟编译器新一代编译优化技术解析(160) ArkCompiler(方舟编译器)作为 HarmonyOS 的核心底层技术,在 5.0 版本中迎来了深度的架构演进与性能突破。其核心目标是通过技术创新,解决跨语言调用开销大、内存占用高以及复杂场景渲染卡顿等行业痛点。一、 核心架构突破… 2026/7/15 0:13:06
行星减速机的工作原理是什么?从齿轮运动关系到减速比计算 一、行星齿轮机构的组成 标准行星齿轮机构主要包括: 太阳轮; 行星轮; 内齿圈; 行星架。 太阳轮位于机构中心。 多个行星轮围绕太阳轮均匀布置,行星轮内侧与太阳轮外啮合,外侧与内齿圈内啮合。 行星轮通过轴… 2026/7/15 0:03:00
阅读Java开源框架源码的心得分享! 前几日闲来无事有幸看到了一位博主分享自己阅读开源框架源码的心得,看了之后也引发了我的一些深度思考。我们为什么要看源码?我们该怎么样去看源码? 其中前者那位博主描述的我觉得很全了(如下图所示),就不做… 2026/7/15 0:03:00
【LINUX】驱动 【LINUX驱动】【字符设备】【中断】【Platform】【网课 设备树】【GPIO】【PINCTRL】【INPUT】【IIC】【SPI】【网络驱动】【屏幕驱动】【一 设备树】【二 内核模块编译】【三 基本驱动框架】【四 Platform总线设备驱动框架】【五 驱动子系统】【六 综合】 2026/7/15 0:07:01
Git reset 与 revert 深度对比:5个关键差异与 3 种典型应用场景 Git Reset 与 Revert 深度对比:5个关键差异与3种典型应用场景在团队协作开发中,代码版本管理如同行走钢丝——一步失误可能导致整个项目陷入混乱。作为Git进阶用户,你是否曾在深夜面对错误的提交束手无策?是否在强制推送后收到同事… 2026/7/13 8:31:55
GitHub 学生包申请避坑:5个常见失败原因与开发者工具调试方案 GitHub 学生包申请技术排障指南:5个高频失败场景与开发者工具实战方案第一次尝试申请GitHub学生包时,我盯着屏幕上那个不断转圈的加载动画整整15分钟,最终只等来了一行冰冷的错误提示。这可能是许多开发者共同的经历——明明按照教程操作&… 2026/7/14 18:25:04
冒烟测试用例设计规范:5%-10%覆盖率下的3类核心场景与执行标准 冒烟测试用例设计的黄金法则:5%-10%覆盖率下的精准筛选策略在快节奏的敏捷开发环境中,冒烟测试作为质量保障的第一道防线,其重要性不言而喻。当测试资源有限而时间紧迫时,如何从海量测试用例中精准筛选出那关键的5%-10%࿰… 2026/7/14 5:09:41