前言
通过之前的教程,我们已经知道如何编写基本的插件,如果你有C#和Unity的基础,这个时候已经可以做出一些功能了,比如通过按键修改游戏数据之类的。但是,这有很大的局限性,因为通常情况下,我们并不想通过按键来调用我们的功能,我们想让大多数的功能都是加载之后就不需要管了,或者想做一些普通情况下比较难以操作的事情。这个时候,通过Harmony进行补丁可以解决我们绝大多数的需求。

Harmony的github链接 https://github.com/pardeike/Harmony 详细信息可以在github查看。

Harmony中使用最频繁的两个地方就是前置补丁和后置补丁,也是最简单的,本篇文章主要就讲这两种。一些特殊的需求需要修改函数本身也是可以的,Harmony支持修改函数的IL码,不过这个就不在基础的范畴了,以后有机会的话会放在进阶篇来讲。

HarmonyPatch特性
要对游戏中的方法进行补丁,首先我们需要确定一个目标,这里我准备了一个类,我们就以这个类为例子,对它进行补丁。

public class People
{
    public string Name{ get; set; };
    public int Age{ get; set; };

public People(string name, int age)
    {
        Name = name;
        Age = age;
    }

public void Sleep()
    {
        Console.WriteLine("睡觉");
    }

public void Say()
    {
        Console.WriteLine("Hello");
    }

public void Say(string content)
    {
        Console.WriteLine(content);
    }

public void Say(int content)
    {
        Console.WriteLine(content);
    }
}
这是一个简单的People类,有两个属性,分别是姓名和年龄,一个构造函数,一个Sleep方法,还有3个说话的方法,使用了3种重载。

以Sleep方法为例,我们写一个最简单的补丁。

[HarmonyPatch(typeof(People), "Sleep")]
class PeopleSleepPatch
{
    public static void Postfix(People __instance)
    {
        Console.WriteLine(__instance.Name + "睡觉了");
    }
}
这里的HarmonyPatch特性就用于确定补丁目标,这个特性的参数可以写在一排也可以分成几排写。例子中的两个参数分别是要补丁的类型,还有要补丁的方法的名字。这是最简单的情况,实际上我们还经常会遇到其他几种情况。比如,Say方法有3个重载,如何确定要补丁哪一个?属性要怎么补丁?我们再来看几个例子。

[HarmonyPatch(typeof(People), "Say", new Type[] { })]
[HarmonyPatch(typeof(People), "Say", new Type[] { typeof(string) })]
[HarmonyPatch(typeof(People), "Say", new Type[] { typeof(int) })]
如此,面对有重载的情况,我们只需要在添加一个参数,这个参数是一个Type数组,我们按顺序将参数类型填入即可。

[HarmonyPatch(typeof(People), "Name", MethodType.Getter]
[HarmonyPatch(typeof(People), "Age", MethodType.Setter]
[HarmonyPatch(typeof(People), MethodType.Constructor]
面对属性和构造函数,我们可以使用MethodType枚举来当作参数。需要注意的是,补丁构造函数时,函数名不能写dnSpy中看到的.ctor,而是应该直接省略不写函数名。

补丁方法
既然已经可以确定补丁目标了,接下来让我们了解一下最基础最常用的两种补丁方法,Prefix(前置补丁)、Postfix(后置补丁)。

先说后置补丁,这是最简单的,它在补丁目标运行结束之后运行,上面示例中的就是后置补丁,可以使用__result参数接收目标的返回值。

然后是前置补丁,顾名思义,它是在补丁目标运行之前运行的,这个相对复杂一点。因为我们可以选择是否执行原方法。我们来看两个例子。

[HarmonyPatch(typeof(People), "Say",  new Type[] { typeof(string) })]
class PeopleSayPatch
{
    public static bool Prefix(ref string content)
    {
        content = "要说的内容已被修改";
        return true;
    }
}

[HarmonyPatch(typeof(People), "Name",  MethodType.Getter)]
class PeopleNamePatch
{
    public static bool Prefix(ref string __result)
    {
        __result = "张三";
        return false; //拦截原方法,直接使用我们给出的结果
    }
}
第一个例子,我们将content的值修改为了我们自己想要的值,然后返回true表示让原函数继续执行。第二个例子,我们直接将最终结果修改,然后返回false,表示阻止原函数执行。如果你搞不明白IL代码,不知道如何修改函数本体,也可以通过前置补丁的方式自己计算结果然后修改。

补丁参数
在上面的例子中,我们有时候使用了__instance,有时候使用了__result,想必读者还留有疑问,为什么要这么写。其实,这是Harmony作者为我们定好的获取方法信息的方式。

大概情况如下:

补丁方法必须是静态方法

Prefix需要返回void或者bool类型(void即不拦截)

Postfix需要返回void类型,或者返回的类型要与第一个参数一致(直通模式)

如果原方法不是静态方法,则可以使用名为__instance(两个下划线)的参数来访问对象实例

可以使用名为__result(两个下划线)的参数来访问方法的返回值,如果是Prefix,则得到返回值的默认值

可以使用名为__state(两个下划线)的参数在Prefix补丁中存储任意类型的值,然后在Postfix中使用它,你有责任在Prefix中初始化它的值

可以使用与原方法中同名的参数来访问对应的参数,如果你要写入非引用类型,记得使用ref关键字

补丁使用的参数必须严格对应类型(或者使用object类型)和名字

我们的补丁只需要定义我们需要用到的参数,不用把所有参数都写上

要允许补丁重用,可以使用名为__originalMethod(两个下划线)的参数注入原始方法

Transpilers还有一些可选参数,我们这里不做探讨,想了解可以访问Harmony的wiki。

自动补丁
补丁的情况我们大体介绍完了,但是我们现在只是写了补丁,还没有对游戏进行补丁,其实很简单,我们只要在插件加载的时候,加上一句代码就好。

new Harmony("me.xiaoye97.plugin.Tutorial").PatchAll(); //以作者输入的字符串作为ID,对程序集中所有找到的补丁方法进行补丁。
或者

Harmony.CreateAndPatchAll(typeof(PluginTutorial)); //Harmony以类名为ID进行补丁,并且只补丁此类下的方法。
除了自动补丁之外,还可以进行手动补丁,可以更加细微的控制,就不在基础教程中说了,读者可以通过GitHub继续了解,以后的教程如果遇到需要手动补丁的情况我再继续讲解。

Unity游戏Mod/插件制作教程06 - Harmony补丁基础相关推荐

  1. Unity游戏Mod/插件制作教程05 - 插件实例2: 简单功能实现

    这一次的教程进行一个小小的功能实现,完整的制作一个插件.以Mirror这个游戏为例,插件的目标是当玩家按下空格时,有一定概率为玩家增加金钱,或者扣除玩家金钱.概率.增加的金钱.扣除的金钱都由配置文件决 ...

  2. Unity游戏Mod/插件制作教程01 - BepInEx的安装和使用

    前言 本章节为没有使用过BepInEx的同学进行BepInEx的安装和使用方面的介绍,如果你之前已经使用过并了解如何使用,可以直接跳过本章节. BepInEx下载 BepInEx的Github链接 h ...

  3. Unity游戏Mod/插件制作教程03 - 插件实例1: HelloWorld

    准备工作 作为编程类的教程,果然第一个需要来一个传统项目--HelloWolrd. 在开始之前,我先贴一个链接,这是BepInex官方的开发手册 https://bepinex.github.io/b ...

  4. Unity游戏Mod/插件制作教程02 - 开发环境准备

    前言 虽然本教程的目标读者是有C#基础的玩家,但是作为流程,基础的开发软件部分我还是要记录一下. 安装VisualStudio VisualStudio是我们开发插件最重要的工具,也许你习惯其他开发. ...

  5. WordPress插件制作教程概述

    接下来的一段时间里,开始为大家讲解WordPress插件制作系列教程,这篇主要是对WordPress插件的一些介绍和说明,还有一些我们需要注意的地方,以及需要掌握的知识. WordPress插件允许你 ...

  6. Unity游戏开发官方入门教程:飞机大战(六)——创建子弹

    Unity版本:Unity 2018.2.14f1 原视频链接:https://unity3d.com/cn/learn/tutorials/s/space-shooter-tutorial 教程目录 ...

  7. Unity游戏开发官方入门教程:飞机大战(二)——创建飞船对象

    Unity版本:Unity 2018.2.14f1 原视频链接:https://unity3d.com/cn/learn/tutorials/s/space-shooter-tutorial 教程目录 ...

  8. Unity游戏开发官方入门教程:飞机大战(五)——实现飞船控制脚本

    Unity版本:Unity 2018.2.14f1 原视频链接:https://unity3d.com/cn/learn/tutorials/s/space-shooter-tutorial 教程目录 ...

  9. Unity游戏开发官方入门教程:飞机大战(十)——敌人的爆炸和移动

    Unity版本:Unity 2018.2.14f1 原视频链接:https://unity3d.com/cn/learn/tutorials/s/space-shooter-tutorial 教程目录 ...

最新文章

  1. Dataset:数据集集合(综合性)——机器学习、深度学习算法中常用数据集大集合(建议收藏,持续更新)
  2. box-sizing:border-box
  3. 赵鹏计算机专业,安徽大学计算机科学与技术学院导师介绍:赵鹏
  4. luogu P1330 封锁阳光大学
  5. linux系统中/etc/syslog.conf文件解读
  6. linux server添加硬盘,新增硬盘扩充linux ftp server空间
  7. linux修改grub权限,linux下肿么修改grub.cfg
  8. 怎么监听linux防火墙,linux怎么查看防火墙是否开启并清除防火墙规则?
  9. 【Python】Python列表基本操作
  10. 下列有关计算机图形的叙述中错误的是,计算机图形学(9-16)-中国大学mooc-题库零氪...
  11. 网狐荣耀6701/6801服务端 子游戏编译 部署
  12. 如何理解c和c ++的复杂类型声明
  13. OpenCV x64 vs2010 下打开摄像头录制视频写成avi(代码为转载)
  14. 微信小程序chooseMedia应用
  15. 腾讯企业邮箱开发(非官方开发文档方式
  16. ThinkPHP拼团拼购h5单商户商城[可对接公众号]非常棒的一款h5拼团商城源码
  17. 如此可爱,焉能不爱?—我与在线作业不得不说的故事
  18. Python:实现字符串Z 函数或 Z 算法(附完整源码)
  19. 北邮博导孙松林:5G 新物种开启新时代
  20. R语言矩阵新增一列并提前作为第一列

热门文章

  1. linux7安装mysql
  2. CGCTF maze
  3. ftp邮箱里的文件无法连接服务器地址,匿名访问ftp服务器 电子邮件地址
  4. 数据库设计方法、规范与技巧(推荐)
  5. this的本质(详解与代码)
  6. 专业摄影-水的拍摄技巧
  7. 使用安卓模拟器抢小米手机
  8. Unity EasyTouch(一)
  9. Scratch编程与游戏之坦克炮弹V3!
  10. [深入理解Android卷一全文-第五章]深入理解常见类