乐趣区

关于unity:Unity-C热更新方案-ILRuntime学习笔记二-代码跨域调用

一、主工程调用 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

当然,应用前要配置你热更 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 的接口能够用代理的形式。
调用时要留神性能问题,能够进行优化。

退出移动版