Unity-笔记-ILRuntime接入

文档

 从零开始 — ILRuntime

委托注册函数

Unity热更新之ILRuntime_暗夜__的博客-CSDN博客_ilruntime

任务

1.接入ILruntime

2.接入Ilriuntime的Protobuf

代码

首先定义好自己的热更布局,(比如我喜欢把热更脚本正常放在Unity里面,设置好你的程序集的引用就完全没问题)

比如:

编辑器playersetting别忘记改了,

 

 在 Unity 中使用 .NET 4.x | Microsoft Docs 

简单了解一下:简单说就是4.xAPI更多,但是个别api不是所有平台都支持的

 

定义在热更的初始脚本:

using System;
using UnityEngine;
namespace HotFix
{
    public class HotFixStart
    {
        public static void Start()
        {
            Debug.Log("Start---------------------------------");
        }
    }
}

外部调用热更的管理脚本:

找到你自己的热更dll(随便你怎么弄放在哪最终把这个dll拿到我们的项目就行),把dll改成后缀名txt或者是bytes的都可以方便我们读取该dll(这里放在StreamingAssets下为了方便测试,想要动态热更的话用你自己的资源加载工具加载即可比如Addressable)

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;

public class Startup : MonoBehaviour
{
    private ILRuntime.Runtime.Enviorment.AppDomain appdomain;

    void Start()
    {
        StartCoroutine(LoadILRuntime());
    }

    IEnumerator LoadILRuntime()
    {
        appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();

        UnityWebRequest webRequest = UnityWebRequest.Get(StreamingAssetsPath("HotFix.dll.txt"));
        yield return webRequest.SendWebRequest();
        if (webRequest.result != UnityWebRequest.Result.Success)
        {
            yield break;
        }
        byte[] dll = webRequest.downloadHandler.data;
        webRequest.Dispose();

        webRequest = UnityWebRequest.Get(StreamingAssetsPath("HotFix.pdb.txt"));
        yield return webRequest.SendWebRequest();
        if (webRequest.result != UnityWebRequest.Result.Success)
        {
            yield break;
        }
        byte[] pdb = webRequest.downloadHandler.data;
        webRequest.Dispose();

        appdomain.LoadAssembly(new MemoryStream(dll), new MemoryStream(pdb), new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());

        OnILRuntimeInitialized();
    }

    void OnILRuntimeInitialized()
    {
        appdomain.Invoke("HotFix.AppMain", "Start", null, null);
    }

    public string StreamingAssetsPath(string fileName)
    {
        string path = Application.streamingAssetsPath + "/" + fileName;
        return path;
    }

}

理论上来说其实已经可以了只要你能跑起来

跨域继承脚本代理:

using ILRuntime.CLR.TypeSystem;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

/// <summary>
/// 在GameObject上挂载的真正的脚本对象MonoProxy
/// 运行时,会把逻辑执行转交给绑定的对应热更脚本对象ScriptObject
/// </summary>
public class MonoProxy : MonoBehaviour
{
    /// <summary>
    /// 当前这个MonoProxy对象映射的热更脚本的类型字符串
    /// </summary>
    public string ScriptName;

    /// <summary>
    /// 映射的热更脚本的类型的对象
    /// </summary>
    public object ScriptObject;

    /// <summary>
    /// 将本MonoProxy对象和一个热更脚本绑定在一起
    /// </summary>
    /// <param name="scriptName"></param>
    public void Bind(string scriptName)
    {
        ScriptName = "HotFix." + scriptName;

        ScriptObject = Startup.appdomain.Instantiate(ScriptName);

        IType scriptIType = Startup.appdomain.LoadedTypes[ScriptName];
        FieldInfo goField = scriptIType.ReflectionType.GetField("gameObject");
        goField.SetValue(ScriptObject, gameObject);

        Startup.appdomain.Invoke(ScriptName, "Awake", ScriptObject, null);
    }
    
    void Start()
    {
        Startup.appdomain.Invoke(ScriptName, "Start", ScriptObject, null);
    }

    void Update()
    {
        Startup.appdomain.Invoke(ScriptName, "Update", ScriptObject, null);
    }

    private void OnDestroy()
    {
        Startup.appdomain.Invoke(ScriptName, "OnDestroy", ScriptObject, null);
    }
}

热更项目中继承的伪Mono脚本:

using System.Collections;
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

namespace HotFix
{
	public class MonoBehaviourEX
	{
		public GameObject gameObject;
		public Transform transform => gameObject.transform;

		public virtual void Awake() { }

		public virtual void Start() { }

		public virtual void Update() { }

		public virtual void OnDestroy() { }

		public void StartCoroutineEx(IEnumerator coroutine)
		{
			gameObject.GetComponent<MonoProxy>().StartCoroutine(coroutine);
		}

	}
}

热更中扩展辅助脚本挂载:(为了符合自己的习惯喜欢用AddComponent罢了)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace HotFix
{
	public static class GameObjectEx
	{
		/// <summary>
		/// 给GameObject类型,扩展一个新函数Bind,就是给GameObject绑定一个热更的脚本对象
		/// </summary>
		/// <param name="gameObject"></param>
		/// <param name="scriptName"></param>
		public static void Bind(this GameObject gameObject, string scriptName)
		{
			MonoProxy monoProxy = gameObject.AddComponent<MonoProxy>();
			monoProxy.Bind(scriptName);
		}
		public static T Bind<T>(this GameObject gameObject)
		{
			MonoProxy monoProxy = gameObject.AddComponent<MonoProxy>();
			string scriptName = typeof(T).Name;

			Debug.Log(scriptName);
			return (T)monoProxy.Bind(scriptName);
		}
		public static T AddComponent<T>(this GameObject gameObject) where T : MonoBehaviourEX
		{
			MonoProxy monoProxy = gameObject.AddComponent<MonoProxy>();
			string scriptName = typeof(T).Name;

			Debug.Log(scriptName);
			return (T)monoProxy.Bind(scriptName);
		}
	}
}

协程适配器:

using ILRuntime.CLR.Method;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.Runtime.Intepreter;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CoroutineAdapter : CrossBindingAdaptor
{
    public override Type BaseCLRType
    {
        get
        {
            return null;
        }
    }

    public override Type[] BaseCLRTypes
    {
        get
        {
            //跨域继承只能有1个Adapter,因此应该尽量避免一个类同时实现多个外部接口,对于coroutine来说是IEnumerator<object>,IEnumerator和IDisposable,
            //ILRuntime虽然支持,但是一定要小心这种用法,使用不当很容易造成不可预期的问题
            //日常开发如果需要实现多个DLL外部接口,请在Unity这边先做一个基类实现那些个接口,然后继承那个基类
            return new Type[] { typeof(IEnumerator<object>), typeof(IEnumerator), typeof(IDisposable) };
        }
    }

    public override Type AdaptorType
    {
        get
        {
            return typeof(Adaptor);
        }
    }

    public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
    {
        return new Adaptor(appdomain, instance);
    }

    /// <summary>
    /// Coroutine生成的类实现了IEnumerator<System.Object>, IEnumerator, IDisposable 所以都要实现
    /// 这个可以通过reflector之类的IL反编译软件得知
    /// </summary>
    internal class Adaptor : IEnumerator<System.Object>, IEnumerator, IDisposable, CrossBindingAdaptorType
    {
        ILTypeInstance instance;
        ILRuntime.Runtime.Enviorment.AppDomain appdomain;

        public Adaptor()
        {

        }

        public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
        {
            this.appdomain = appdomain;
            this.instance = instance;
        }

        public ILTypeInstance ILInstance { get { return instance; } }

        IMethod mCurrentMethod;
        bool mCurrentMethodGot;
        public object Current
        {
            get
            {
                if (!mCurrentMethodGot)
                {
                    mCurrentMethod = instance.Type.GetMethod("get_Current", 0);
                    if (mCurrentMethod == null)
                    {
                        //这里写System.Collections.IEnumerator.get_Current而不是直接get_Current是因为coroutine生成的类是显式实现这个接口的,通过Reflector等反编译软件可得知
                        //为了兼容其他只实现了单一Current属性的,所以上面先直接取了get_Current
                        mCurrentMethod = instance.Type.GetMethod("System.Collections.IEnumerator.get_Current", 0);
                    }
                    mCurrentMethodGot = true;
                }

                if (mCurrentMethod != null)
                {
                    object res = appdomain.Invoke(mCurrentMethod, instance, null);
                    return res;
                }
                else
                {
                    return null;
                }
            }
        }

        IMethod mDisposeMethod;
        bool mDisposeMethodGot;
        public void Dispose()
        {
            if (!mDisposeMethodGot)
            {
                mDisposeMethod = instance.Type.GetMethod("Dispose", 0);
                if (mDisposeMethod == null)
                {
                    mDisposeMethod = instance.Type.GetMethod("System.IDisposable.Dispose", 0);
                }
                mDisposeMethodGot = true;
            }

            if (mDisposeMethod != null)
            {
                appdomain.Invoke(mDisposeMethod, instance, null);
            }
        }

        IMethod mMoveNextMethod;
        bool mMoveNextMethodGot;
        public bool MoveNext()
        {
            if (!mMoveNextMethodGot)
            {
                mMoveNextMethod = instance.Type.GetMethod("MoveNext", 0);
                mMoveNextMethodGot = true;
            }

            if (mMoveNextMethod != null)
            {
                return (bool)appdomain.Invoke(mMoveNextMethod, instance, null);
            }
            else
            {
                return false;
            }
        }

        IMethod mResetMethod;
        bool mResetMethodGot;
        public void Reset()
        {
            if (!mResetMethodGot)
            {
                mResetMethod = instance.Type.GetMethod("Reset", 0);
                mResetMethodGot = true;
            }

            if (mResetMethod != null)
            {
                appdomain.Invoke(mResetMethod, instance, null);
            }
        }

        public override string ToString()
        {
            IMethod m = appdomain.ObjectType.GetMethod("ToString", 0);
            m = instance.Type.GetVirtualMethod(m);
            if (m == null || m is ILMethod)
            {
                return instance.ToString();
            }
            else
                return instance.Type.FullName;
        }
    }
}

在热更新中加入Protobuf

protobuf-net: Protobuf-net for ILRuntime 可以使用repeat 支持proto2 & proto3 只支持unity2017 .net 4.6

导入之后找到Editor文件夹 修改你自己的参数

#if UNITY_2018_1_OR_NEWER
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using ProtoBuf;
using Google.Protobuf.Reflection;
using ProtoBuf.Reflection;
using System.IO;
public class Protogen
{
	private static string ProtoPath = Application.dataPath + "/../../ProtoFiles";
	private static string ExePath = Application.dataPath + "/../../Protoc/protoc-3.5.1-win32/bin/";
	/// <summary>
	/// 忽略这里,这是给我自己服务器配的地址
	/// </summary>
	private static string outPath = Application.dataPath + "/../../Server/proto/";
	[MenuItem("CustomTools/Generate Protocs")]
	public static void GenerateProtobufCS()
	{
		Generate(ProtoPath, new string[] { "NetMessage.proto" },
		//这个是你的输出地址
		Application.dataPath + "/ILRunimeFrameWork/Hotfix/ProtoCode");
	}
	[MenuItem("CustomTools/Generate ServerProtocs")]
	public static void Generateser()
	{
		RunExeByProcess(outPath);
	}
	static void Generate(string inpath, string[] inprotos, string outpath)
	{

		var set = new FileDescriptorSet();

		set.AddImportPath(inpath);
		foreach (var inproto in inprotos)
		{
			set.Add(inproto, true);
		}

		set.Process();
		var errors = set.GetErrors();
		CSharpCodeGenerator.ClearTypeNames();
		var files = CSharpCodeGenerator.Default.Generate(set);

		int idx = 1;
		foreach (var file in files)
		{
			EditorUtility.DisplayProgressBar("Generate", file.Name, idx / (1.0f * inprotos.Length));
			var path = Path.Combine(outpath, file.Name);
			File.WriteAllText(path, file.Text);

			Debug.Log($"generated: {path}");
		}
		EditorUtility.ClearProgressBar();

		AssetDatabase.Refresh();

	}
	//private string savePath = System.Environment.CurrentDirectory + @"\文件夹名";

	public static void RunExeByProcess(string arguments)
	{
		//开启新线程
		System.Diagnostics.Process process = new System.Diagnostics.Process();
		//调用的exe名称
		process.StartInfo.FileName = "GenForServer.bat";
		process.StartInfo.WorkingDirectory = ExePath;
		//传递参数
		process.StartInfo.Arguments = arguments;
		process.Start();

		process.WaitForExit();//停顿,当外部程序退出后才能继续执行

		if (process.ExitCode == 0)//程序退出
		{
			Debug.Log("执行成功");
		}
	}
}
#endif

这时候生成的代码还是不能用的:一个是没在Ilruntime的环境里注册,另一个是还需要一个辅助类(他文档里写了看看就会了):

ilruntime注册:

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using SimpleJSON;
using System;

public class AppStart : MonoBehaviour
{
	public static ILRuntime.Runtime.Enviorment.AppDomain appdomain;

	void Start()
	{
		StartCoroutine(LoadILRuntime());
	}

	IEnumerator LoadILRuntime()
	{
		appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();

		/*UnityWebRequest webRequest = UnityWebRequest.Get(StreamingAssetsPath("Hotfix.dll.txt"));
		yield return webRequest.SendWebRequest();

		byte[] dll = webRequest.downloadHandler.data;
		webRequest.Dispose();

		webRequest = UnityWebRequest.Get(StreamingAssetsPath("Hotfix.pdb.txt"));
		yield return webRequest.SendWebRequest();

		byte[] pdb = webRequest.downloadHandler.data;
		webRequest.Dispose();*/

		byte[] dll = AssetLoader.Instance.CreateAsset<TextAsset>("Launch", "Assets/GAssets/Launch/Scriptdll/Hotfix.dll.bytes", gameObject).bytes;
		byte[] pdb = AssetLoader.Instance.CreateAsset<TextAsset>("Launch", "Assets/GAssets/Launch/Scriptdll/Hotfix.pdb.bytes", gameObject).bytes;

		yield return null;
		appdomain.LoadAssembly(new MemoryStream(dll), new MemoryStream(pdb), new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());

		appdomain.RegisterCrossBindingAdaptor(new CoroutineAdapter());
		appdomain.DelegateManager.RegisterFunctionDelegate<System.String, JSONNode>();
		appdomain.DelegateManager.RegisterMethodDelegate<global::NetPacket>();
		appdomain.DelegateManager.RegisterMethodDelegate<System.Int32>();
		appdomain.DelegateManager.RegisterMethodDelegate<cfg.BagItem>();

		appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction>((act) =>
		{
			return new UnityEngine.Events.UnityAction(() =>
			{
				((Action)act)();
			});
		});
		// 注册Protobuf相关
		ProtoBuf.PType.RegisterFunctionCreateInstance(PType_CreateInstance);
		ProtoBuf.PType.RegisterFunctionGetRealType(PType_GetRealType);
		//appdomain.RegisterCLRMethodRedirection(0, new Bright.Config.BeanBase.GetTypeId());
		OnILRuntimeInitialized();

	}
	public static object PType_CreateInstance(string typeName)
	{
		return appdomain.Instantiate(typeName);
	}

	public static Type PType_GetRealType(object o)
	{
		Type type = o.GetType();
		if (type.FullName == "ILRuntime.Runtime.Intepreter.ILTypeInstance")
		{
			var ilo = o as ILRuntime.Runtime.Intepreter.ILTypeInstance;
			type = ProtoBuf.PType.FindType(ilo.Type.FullName);
		}
		return type;
	}
	void OnILRuntimeInitialized()
	{
		appdomain.Invoke("HotFix.HotfixMain", "Start", null, null);
	}

	public string StreamingAssetsPath(string fileName)
	{
		string path = Application.streamingAssetsPath + "/" + fileName;
		return path;
	}

}

hotfix程序集加一个辅助类

using ProtoBuf;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Helper
{
    public sealed class ProtoHelper
    {
        public static byte[] EncodeWithName(object p)
        {
            var type = p.GetType();
            var name = type.FullName;
            using (var ms = new MemoryStream())
            {
                Serializer.Serialize(ms, p);
                //var cos = new Google.Protobuf.CodedOutputStream(ms);
                //((IMessage)p).Encode(cos); //非反射方式
                //cos.Flush(); 
                var nbs = Encoding.UTF8.GetBytes(name);
                int nblen = nbs.Length;
                if (nblen > 255)
                {
                    throw new Exception("PB:name->" + name + " is To Long " + nblen + " > 255");
                }
                var buffer = new byte[ms.Length + nbs.Length + 1];
                buffer[0] = (byte)((nblen >> 0) & 0xFF);
                Buffer.BlockCopy(nbs, 0, buffer, 1, nblen);
                ms.Position = 0;
                ms.Read(buffer, 1 + nblen, (int)ms.Length);
                return buffer;
            }
        }

		public static object DecodeWithName(byte[] b, out string name)
		{
    		var bytesLen = b[0];
    		name = Encoding.UTF8.GetString(b, 1, bytesLen);
    		using (var ms = new MemoryStream(b, 1 + bytesLen, b.Length - 1 - bytesLen))
    		{
        		Type T = Type.GetType(name);
        		if (name.Contains(".")) { name = name.Substring(name.LastIndexOf('.') + 1); }
        		return Serializer.Deserialize(T, ms);
        		//var o = Activator.CreateInstance(T);
        		//((IMessage)o).Decode(new Google.Protobuf.CodedInputStream(ms));  //非反射方式
        		//return o;
    		}
		}
	}

	public interface IProtoRecv
	{
    	void OnRecv(string name, object o);
	}

	public interface IMessage
	{
    	void Encode(Google.Protobuf.CodedOutputStream writer);
    	void Decode(Google.Protobuf.CodedInputStream reader);
	}
}

最终在你热更入口调用该函数比如(这个函数就是你生成的protobuf文件里面自动有个函数):

 

基本完成:该有的功能都有,和你正常在Unity开发应该差不多。。。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值