1

一、主工程调用Hotfix代码

假设Hotfix工程里有一个Test类,该如何调用该类的方法呢?

namespace Hotfix {

    public class Test {

        // 实例方法
        public string GetName() {
            return "test";
        }

        // 静态方法
        public static float Sum(float a, float b) {
            return a + b;
        }

    }

}

1.调用静态方法

// 获取类型
IType type = appdomain.LoadedTypes["Hotfix.Test"];
// 获取方法
IMethod method = type.GetMethod("Sum", 2);
// 调用方法
object returnValue = appdomain.Invoke(method, null, 1, 2);
// 输出返回值
print("静态方法返回值:" + returnValue);

2.调用实例方法

// 获取类型
IType type = appdomain.LoadedTypes["Hotfix.Test"];
// 创建实例
object instance = (type as ILType).Instantiate();
// 获取方法
IMethod method = type.GetMethod("GetName", 0);
// 调用方法
object returnValue = appdomain.Invoke(method, instance);
// 输出返回值
print("静态方法返回值:" + returnValue);

二、Hotfix调用主工程代码

Hotfix调用主工程代码直接调用即可,无需特别步骤。

namespace Hotfix {

    using UnityEngine;

    public class Test {

        // 调用主工程方法
        private void CallUnity() {
            Debug.Log(Application.streamingAssetsPath);
        }

    }

}

三、Hotfix响应MonoBehaviour事件

Hotfix响应MonoBehaviour中的事件,可以用代理方法实现。

1.在Unity中新建一个Mono脚本,实现自己需要的接口,在这些接口被调用时,调用代理事件

namespace GameUtils.Triggers {

    using System;
    using UnityEngine;

    /// <summary>
    /// <para>MonoBehaviour 基本事件触发器 触发以下事件</para>
    /// OnEnable、Start、OnDisable、OnDestroy
    /// </summary>
    public class MonoBehaviourEventTrigger : MonoBehaviour {

        public Action onEnable;
        public Action start;
        public Action update
        public Action onDisable;
        public Action onDestroy;

        private void OnEnable() {
            if (onEnable != null) onEnable.Invoke();
        }

        private void Start() {
            if (start != null) start.Invoke();
        }

        private void OnDisable() {
            if (onDisable != null) onDisable.Invoke();
        }
        
        private void Update() {
            if (update != null) update.Invoke();
        }

        private void OnDestroy() {
            if (onDestroy != null) onDestroy.Invoke();
        }

    }

}

2.在Hotfix工程中这样调用:

namespace Hotfix {

    using UnityEngine;
    using GameUtils.Triggers;

    public class Test {

        private void MonoTest() {
            GameObject obj = new GameObject("Test");
            MonoBehaviourEventTrigger monoTrigger = obj.AddComponent<MonoBehaviourEventTrigger>();
            monoTrigger.start = Start;
            monoTrigger.update = Update;
            monoTrigger.onDestroy = OnDestroy;
        }

        private void Start() {
            Debug.Log("Start");
        }
        
        private void Update() {
            // 鼠标按下时,做射线碰撞检测
            if (Input.GetMouseButtonDown(0)) {
                Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit, 1000)) {
                    Debug.Log(hit.point);
                }
            }
        }

        private void OnDestroy() {
            Debug.Log("OnDestroy");
        }

    }

}

注:由于Mono接口是主工程中的,不能热更,所以要提前写好所有接口以供热更代码调用。Mono中的接口众多,如果全部用一个类实现的话太过冗余,我这里做了一下优化,把不同功能的接口分组到不同的脚本中,由一个统一的Mono根据调用情况动态添加,在set里实现调用时的动态添加脚本功能,使用时还是一样方便,代码太长就不贴了。

3.带参数的Action

注意,带参数的Action在跨域调用前要在主工程里注册参数。
否则调用会报错。

private void RegistDelegate() {
        DelegateManager manager = appdomain.DelegateManager;
        manager.RegisterMethodDelegate<bool>();
        manager.RegisterMethodDelegate<byte>();
        manager.RegisterMethodDelegate<sbyte>();
        manager.RegisterMethodDelegate<char>();
        manager.RegisterMethodDelegate<short>();
        manager.RegisterMethodDelegate<ushort>();
        manager.RegisterMethodDelegate<int>();
        manager.RegisterMethodDelegate<uint>();
        manager.RegisterMethodDelegate<long>();
        manager.RegisterMethodDelegate<ulong>();
        manager.RegisterMethodDelegate<float>();
        manager.RegisterMethodDelegate<double>();
        manager.RegisterMethodDelegate<string>();
        manager.RegisterMethodDelegate<object>();
        manager.RegisterMethodDelegate<Collider>();
        manager.RegisterMethodDelegate<Collision>();
        manager.RegisterMethodDelegate<BaseEventData>();
        manager.RegisterMethodDelegate<PointerEventData>();
        manager.RegisterMethodDelegate<Object>();
        manager.RegisterMethodDelegate<GameObject>();
        
        appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction>((action) => {
            return new UnityEngine.Events.UnityAction(() => {
                ((System.Action)action)();
            });
        });

    }

四、跨域调用性能优化

跨域调用的性能是很差的。性能优化的方式如下:

1.主工程调用Hotfix时,多用type.GetMethod()去调用。

// 1.用string调用方法:
appdomain.Invoke("Hotfix.Test", "Sum", null, null);

// 2.用Method调用方法性能更好
IType type = appdomain.LoadedTypes["Hotfix.Test"];
IMethod method = type.GetMethod("Sum", 2);
appdomain.Invoke(method, instance, 1, 2);

2.Hotfix调用主工程代码时,可以用CLRBinding优化。

官方提供了一个工具,可以实现Hotfix调用的优化,该工具会自动分析热更dll中调用的方法,自动生成CLRBinding类。

使用方式是:在unity顶部菜单中选择ILRuntime > Generate CLR Binding Code by Analysis

CLRBinding.png

当然,使用前要配置你热更dll的路径和生成文件的路径。
在Unity查找ILRuntimeCLRBinding这个类,修改其中路径。

#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using System;
using System.Text;
using System.Collections.Generic;
using ILRuntimeDemo;
[System.Reflection.Obfuscation(Exclude = true)]
public class ILRuntimeCLRBinding
{
   [MenuItem("ILRuntime/Generate CLR Binding Code by Analysis")]
    static void GenerateCLRBindingByAnalysis()
    {
        //用新的分析热更dll调用引用来生成绑定代码
        ILRuntime.Runtime.Enviorment.AppDomain domain = new ILRuntime.Runtime.Enviorment.AppDomain();
        
        using (System.IO.FileStream fs = new System.IO.FileStream("Assets/StreamingAssets/Hotfix.dll", System.IO.FileMode.Open, System.IO.FileAccess.Read))
        {
            domain.LoadAssembly(fs);

            //Crossbind Adapter is needed to generate the correct binding code
            InitILRuntime(domain);
            ILRuntime.Runtime.CLRBinding.BindingCodeGenerator.GenerateBindingCode(domain, "Assets/Game/ILRuntime/Generated");
        }

        AssetDatabase.Refresh();
    }

    static void InitILRuntime(ILRuntime.Runtime.Enviorment.AppDomain domain)
    {
        //这里需要注册所有热更DLL中用到的跨域继承Adapter,否则无法正确抓取引用
        domain.RegisterCrossBindingAdaptor(new MonoBehaviourAdapter());
        domain.RegisterCrossBindingAdaptor(new CoroutineAdapter());
        domain.RegisterCrossBindingAdaptor(new TestClassBaseAdapter());
        domain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
    }
}
#endif

总结:
主工程调用Hotfix代码时比较麻烦,要用类似反射的形式。
Hotfix调用主工程代码很容易,正常怎么写就怎么写。
Hotfix调用MonoBehaviour的接口可以用代理的方式。
调用时要注意性能问题,可以进行优化。


冰封百度
233 声望43 粉丝

Unity游戏程序员一枚。生命不息,学习不止。