优点:
1.无需拆分工程,不需对工程进行重构, 但大规模新增功能受限
2.全部代码都以原生方式运行,有更新的话,仅仅待更新那少数几个函数切换到解析执行
3.DLC更新较小
集成IFix
- 编译
打开源码包的SourceVSProjbuild_for_unity.bat,UNITY_HOME变量的值修改为指向本机unity安装目录 @set UNITY_HOME=G:WinEditor
运行build_for_unity.bat
将IFixToolKit拷贝到Unity项目的Assets同级目录
IFixToolKit拷贝
将IFix.Core.dll拷贝到Unity项目的Assets/Plugins
升级IFix步骤也是如此
使用说明
- 加载补丁
本地测试
var patchPath = "./Assets/IFix/Resources/Assembly-CSharp.ill.bytes";
if (File.Exists(patchPath))
{
PatchManager.Load(new FileStream(patchPath, FileMode.Open));
}
线上更新,从AB包中加载,如果二进制流内容不为空,则
PatchManager.Load(binaryObject.m_stream);
- 配置
热补丁的实现依赖于提前做些静态代码插入,需要配置对可能热更类预处理,配置后才能被修复。
[Configure]
public class InterpertConfig {
[IFix]
static IEnumerable<Type> HotfixInject
{
get
{
var t1 = from type in Assembly.Load("要加载的dll").GetTypes() select type;
或者 var t1 = Assembly.Load("Assembly-CSharp(加载的程序集)").GetTypes()
var canFixtypes = (from type in t1
where (IsNeedInject(type)
)
select type);
return canFixtypes;
}
}
}
private static List<string> removeInjectNamespacesFix = new List<string>
{
"XLua",
};
private static bool IsTypeNeedInject(Type type){
if (!string.IsNullOrEmpty(type.Namespace)){
if (removeInjectNamespacesFix .Contains(type.Namespace))
{
return false;
}
foreach (string removeInjectNamespaceFix in removeInjectNamespacesFix )
{
if (type.Namespace.StartsWith(removeInjectNamespaceFix ))
{
return false;
}
}
}
return true;
}
注意:
· 配置类打上Configure标签
· 配置的属性打上IFix标签,而且必须是 static 类型
· 这配置必须放在Editor目录下
动态配置可以批量处理,后续该名字空间下增删类,都不需要更改配置。
配置好后,打包手机版本会自动预处理,如果希望自动化打包,也可以手动调用IFix.Editor.IFixEditor.InjectAllAssemblys函数。
- 流程说明
有两个步骤:Inject,Fix。
Inject只需在发包时做一次,这个步骤主要是对代码做一定的预处理,只有做了预处理的代码后续才能正常加载补丁。
执行"InjectFix/Fix"菜单。
补丁制作成功后会放到工程指定目录下,文件名为“{Dll Name}.patch.bytes”(比如:“Assembly-CSharp.patch.bytes”),编辑器测试可直接加载,加载就能看到效果,线上热更打将patch.bytes文件打进热更包即可。
Fix的过程是根据修改后的代码编译后的dll,生成补丁。
修改代码和Fix之间无需执行Inject,否则iFix会认为这是个线上版本,拒绝生成补丁。鉴于这个限制,编辑器下体验流程上做一定的调整:先修改代码为正确逻辑,生成patch。然后回退代码,执行Inject模拟线上有问题的版本。
- IFix常用说明
[IFix.Patch]
修复某个函数,该标签只能用在方法上,直接在方法上面标注一下[IFix.Patch]即可
[IFix.Patch]
private int Add(int a,int b)
{
return a * b;
}
[IFix.Interpret]
新增个函数或者类,在属性,方法,类型上,直接在要新增的代码上面标注一下这个标签即可。
[IFix.Patch]和[IFix.Interpret]属于比较常用的修复Bug
[IFix.CustomBridge]
在注入阶段使用; 把一个虚拟机的类适配到原生interface或者把一个虚拟机的函数适配到原生delegate。
· 修复代码赋值一个闭包到一个delegate变量;
· 修复代码的Unity协程用了yield return;
· 新增一个函数,赋值到一个delegate变量;
· 新增一个类,赋值到一个原生interface变量;
· 新增函数,用了yield return;
该标签只能用在类上,写上一个静态类,里面有一个静态字段,值就是interface和delegate的类型集合,该配置类不能放到Editor目录,且不能内嵌到另外一个类里头。
例如:
[IFix.CustomBridge]public static class AdditionalBridge
{
static List<Type> bridge = new List<Type>()
{
typeof(ISubSystem),
typeof(IEnumerator), //如果之前使用过协程,可不做处理
typeof(Test.MyDelegate)
};
}
[Filter]
在注入阶段使用,过滤某些方法。在注入阶段,凡是在[IFix]标签下的属性里面的值,都会被注入适配代码,但如果不想对某个函数进行注入,可以用该标签进行过滤。该标签只能用在方法上,Configure类中的一个静态方法。
[Filter]
static bool Filter(System.Reflection.MethodInfo methodInfo)
{
return methodInfo.DeclaringType.FullName == "Test"
&& (methodInfo.Name == "Test2" || methodInfo.Name == "Test1");
}
用法
该标签只能用在方法上,Configure类中的一个静态方法。
标签 | 使用阶段 | 用途 | 用法 |
[IFix.Patch] | 补丁 | 修复函数 | 只能放在函数上 |
[IFix.Interpret] | 补丁 | 新增属性,函数,类型 | 放在属性,函数,类型上 |
[IFix.CustomBridge] | 注入 | interface和delegate桥接 | 只能放在单独写一个静态类上,存储虚拟机的类适配到原生interface或者虚拟机的函数适配到原生delegate,该类不能放Editor目录 |
[Configure] | 注入 | 配置类 | 只能放在单独写一个存放在Editor目录下的类上 |
[IFix] | 注入 | 可能需要修复函数的类的集合 | 只能放在[Configure]类的一个静态属性上 |
[Filter] | 注入 | 不想发生注入的函数 | 只能放在[Configure]类的一个静态函数上 |
测试用例:
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
namespace Assets.Scripts.GameSystem
{
[IFix.CustomBridge]
public static class AdditionalBridge
{
static System.Collections.Generic.List<Type> bridge = new System.Collections.Generic.List<Type>()
{
typeof(ISubSystem),
typeof(IEnumerator), //如果之前使用过协程,可不做处理
typeof(IFixShowUIView.MyDelegate)
};
}
class IFixShowUIView : MonoBehaviour
{
#region widget
public Text2 ifixInfo;
public Button patchBtn;
public Toggle interpretTgl;
public Button attributeBtn;
public Button mthBtn;
public Button classBtn;
public GameObject interpretGroup;
public Toggle customBridgeTgl;
public Button interfaceBtn;
public Button iEnumeratorBtn;
public Button delegateBtn;
public GameObject customBridgeGroup;
private string iFixName;//这个name字段是原生的
public string IFixName
{
[IFix.Interpret]
set
{
iFixName = value;
}
[IFix.Interpret]
get
{
return iFixName;
}
}
private void Awake()
{
InitWidget();
}
private void OnDestroy()
{
Clear();
}
#endregion
public void Clear()
{
patchBtn.onClick.RemoveListener(PatchBtnClick);
interpretTgl.onValueChanged.RemoveListener(InterpretClick);
attributeBtn.onClick.RemoveListener(AttributeBtnClick);
mthBtn.onClick.RemoveListener(MthBtnClick);
classBtn.onClick.RemoveListener(ClassBtnClick);
customBridgeTgl.onValueChanged.RemoveListener(CustomBridgeClick);
interfaceBtn.onClick.RemoveListener(InterfaceTest);
iEnumeratorBtn.onClick.RemoveListener(IEnumeratorTest);
delegateBtn.onClick.RemoveListener(DelegateTest);
}
private void InitWidget()
{
patchBtn.onClick.AddListener(PatchBtnClick);
interpretTgl.onValueChanged.AddListener(InterpretClick);
attributeBtn.onClick.AddListener(AttributeBtnClick);
mthBtn.onClick.AddListener(MthBtnClick);
classBtn.onClick.AddListener(ClassBtnClick);
customBridgeTgl.onValueChanged.AddListener(CustomBridgeClick);
interfaceBtn.onClick.AddListener(InterfaceTest);
iEnumeratorBtn.onClick.AddListener(IEnumeratorTest);
delegateBtn.onClick.AddListener(DelegateTest);
}
private void PatchBtnClick()
{
Debug.Log("修复函数");
ifixInfo.text = string.Format("Add函数由*修改为+ 结果为:{0}", Add(10, 9));
}
[IFix.Patch]
private int Add(int a,int b)
{
return a * b;
}
private void InterpretClick(bool isOn)
{
interpretGroup.SetActive(isOn);
}
private void CustomBridgeClick(bool isOn)
{
customBridgeGroup.SetActive(isOn);
}
[IFix.Patch]
private void AttributeBtnClick()
{
ifixInfo.text = "测试IFix";
Debug.Log("新增属性");
IFixName = "新增属性IFixName";
ifixInfo.text = iFixName;
}
[IFix.Patch]
private void MthBtnClick()
{
ifixInfo.text = "测试IFix";
Debug.Log("新增方法");
Sub();
}
[IFix.Patch]
private void ClassBtnClick()
{
ifixInfo.text = "测试IFix";
Debug.Log("新增类");
ifixInfo.text = InterpretNewClass.GetNewClass();
}
[IFix.Patch]
private void InterfaceTest()
{
ifixInfo.text = "测试IFix";
DebugHelper.Log("测试Interface");
ISubSystem subSystem = new SubSystem();
ifixInfo.text = subSystem.Print();
}
[IFix.Patch]
private void IEnumeratorTest()
{
ifixInfo.text = "测试IFix";
DebugHelper.Log("测试IEnumerator");
StartCoroutine(IFixIEnumerator());
}
[IFix.Patch]
private void DelegateTest()
{
ifixInfo.text = "测试IFix";
DebugHelper.Log("测试Delegate");
MyDelegate myDelegate = new MyDelegate(TestDelegate());
ifixInfo.text = string.Format("测试Delegate解果----:{0}", myDelegate(100, 200));
}
[IFix.Interpret]
public IEnumerator IFixIEnumerator()
{
yield return new WaitForSeconds(1);
ifixInfo.text = "测试--------IFixIEnumerator";
}
public delegate int MyDelegate(int a, int b);
[IFix.Interpret]
public MyDelegate TestDelegate()
{
return (a, b) => a + b;
}
[IFix.Interpret]
public void Sub()
{
ifixInfo.text = "新增函数Sub";
}
}
[IFix.Interpret]
public static class InterpretNewClass
{
public static string GetNewClass()
{
return "新增类InterpretNewClass";
}
}
public interface ISubSystem
{
bool running { get; }
string Print();
}
[IFix.Interpret]
public class SubSystem : ISubSystem
{
public bool running { get { return true; } }
public string Print()
{
return "SubSystem1.Print";
}
}
}