少年修仙传客户端基础资源
lwb
2021-01-26 f10c768723fda22f62f26833bdaa672baa3dc322
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using ILRuntime.CLR.TypeSystem;
using ILRuntime.CLR.Method;
using ILRuntime.CLR.Utils;
using ILRuntime.Runtime.Intepreter;
using ILRuntime.Runtime.Stack;
using ILRuntime.Runtime.Enviorment;
//下面这行为了取消使用WWW的警告,Unity2018以后推荐使用UnityWebRequest,处于兼容性考虑Demo依然使用WWW
#pragma warning disable CS0618
 
public class MonoBehaviourDemo : MonoBehaviour
{
    static MonoBehaviourDemo instance;
    System.IO.MemoryStream fs;
    System.IO.MemoryStream p;
    public static MonoBehaviourDemo Instance
    {
        get { return instance; }
    }
    //AppDomain是ILRuntime的入口,最好是在一个单例类中保存,整个游戏全局就一个,这里为了示例方便,每个例子里面都单独做了一个
    //大家在正式项目中请全局只创建一个AppDomain
    AppDomain appdomain;
 
    void Start()
    {
        instance = this;
        StartCoroutine(LoadHotFixAssembly());
    }
 
    IEnumerator LoadHotFixAssembly()
    {
        //首先实例化ILRuntime的AppDomain,AppDomain是一个应用程序域,每个AppDomain都是一个独立的沙盒
        appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();
        //正常项目中应该是自行从其他地方下载dll,或者打包在AssetBundle中读取,平时开发以及为了演示方便直接从StreammingAssets中读取,
        //正式发布的时候需要大家自行从其他地方读取dll
 
        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        //这个DLL文件是直接编译HotFix_Project.sln生成的,已经在项目中设置好输出目录为StreamingAssets,在VS里直接编译即可生成到对应目录,无需手动拷贝
#if UNITY_ANDROID
        WWW www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.dll");
#else
        WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");
#endif
        while (!www.isDone)
            yield return null;
        if (!string.IsNullOrEmpty(www.error))
            UnityEngine.Debug.LogError(www.error);
        byte[] dll = www.bytes;
        www.Dispose();
 
        //PDB文件是调试数据库,如需要在日志中显示报错的行号,则必须提供PDB文件,不过由于会额外耗用内存,正式发布时请将PDB去掉,下面LoadAssembly的时候pdb传null即可
#if UNITY_ANDROID
        www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.pdb");
#else
        www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.pdb");
#endif
        while (!www.isDone)
            yield return null;
        if (!string.IsNullOrEmpty(www.error))
            UnityEngine.Debug.LogError(www.error);
        byte[] pdb = www.bytes;
        fs = new MemoryStream(dll);
        p = new MemoryStream(pdb);
        try
        {
            appdomain.LoadAssembly(fs, p, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());
        }
        catch
        {
            Debug.LogError("加载热更DLL失败,请确保已经通过VS打开Assets/Samples/ILRuntime/1.6/Demo/HotFix_Project/HotFix_Project.sln编译过热更DLL");
        }
 
        InitializeILRuntime();
        OnHotFixLoaded();
    }
 
    private void OnDestroy()
    {
        fs.Close();
        p.Close();
    }
 
    unsafe void InitializeILRuntime()
    {
#if DEBUG && (UNITY_EDITOR || UNITY_ANDROID || UNITY_IPHONE)
        //由于Unity的Profiler接口只允许在主线程使用,为了避免出异常,需要告诉ILRuntime主线程的线程ID才能正确将函数运行耗时报告给Profiler
        appdomain.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
#endif
        //这里做一些ILRuntime的注册
        appdomain.RegisterCrossBindingAdaptor(new MonoBehaviourAdapter());
        appdomain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
        //ILRuntime.Runtime.Generated.CLRBindings.Initialize(appdomain);
    }
 
    unsafe void OnHotFixLoaded()
    {
        Debug.Log("在热更DLL里面使用MonoBehaviour是可以做到的,但是并不推荐这么做");
        Debug.Log("因为即便能做到使用,要完全支持MonoBehaviour的所有特性,会需要很多额外的工作量");
        Debug.Log("而且通过MonoBehaviour做游戏逻辑当项目规模大到一定程度之后会是个噩梦,因此应该尽量避免");
 
        Debug.Log("直接调用GameObject.AddComponent<T>会报错,这是因为这个方法是Unity实现的,他并不可能取到热更DLL内部的类型");
        Debug.Log("因此我们需要挟持AddComponent方法,然后自己实现");
        Debug.Log("我们先销毁掉之前创建的不合法的MonoBehaviour");
        SetupCLRRedirection();
        appdomain.Invoke("HotFix_Project.TestMonoBehaviour", "RunTest", null, gameObject);
 
        Debug.Log("可以看到已经成功了");
        Debug.Log("下面做另外一个实验");
        Debug.Log("GetComponent跟AddComponent类似,需要我们自己处理");
        SetupCLRRedirection2();
        appdomain.Invoke("HotFix_Project.TestMonoBehaviour", "RunTest2", null, gameObject);
        Debug.Log("成功了");
        Debug.Log("那我们怎么从Unity主工程获取热更DLL的MonoBehaviour呢?");
        Debug.Log("这需要我们自己实现一个GetComponent方法");
        var type = appdomain.LoadedTypes["HotFix_Project.SomeMonoBehaviour2"] as ILType;
        var smb = GetComponent(type);
        var m = type.GetMethod("Test2");
        Debug.Log("现在来试试调用");
        appdomain.Invoke(m, smb, null);
 
        Debug.Log("调用成功!");
        Debug.Log("我们点一下左边列表里的GameObject,查看一下我们刚刚挂的脚本");
        Debug.Log("默认情况下是无法显示DLL里面定义的public变量的值的");
        Debug.Log("这个Demo我们写了一个自定义Inspector来查看变量,同样只是抛砖引玉");
        Debug.Log("要完整实现MonoBehaviour所有功能得大家自己花功夫了,最好还是避免脚本里使用MonoBehaviour");
        Debug.Log("具体实现请看MonoBehaviourAdapterEditor");
        Debug.Log("特别注意,现在仅仅是运行时可以看到和编辑,由于没有处理序列化的问题,所以并不可能保存到Prefab当中,要想实现就得靠大家自己了");
    }
 
    unsafe void SetupCLRRedirection()
    {
        //这里面的通常应该写在InitializeILRuntime,这里为了演示写这里
        var arr = typeof(GameObject).GetMethods();
        foreach (var i in arr)
        {
            if (i.Name == "AddComponent" && i.GetGenericArguments().Length == 1)
            {
                appdomain.RegisterCLRMethodRedirection(i, AddComponent);
            }
        }
    }
 
    unsafe void SetupCLRRedirection2()
    {
        //这里面的通常应该写在InitializeILRuntime,这里为了演示写这里
        var arr = typeof(GameObject).GetMethods();
        foreach (var i in arr)
        {
            if (i.Name == "GetComponent" && i.GetGenericArguments().Length == 1)
            {
                appdomain.RegisterCLRMethodRedirection(i, GetComponent);
            }
        }
    }
 
    MonoBehaviourAdapter.Adaptor GetComponent(ILType type)
    {
        var arr = GetComponents<MonoBehaviourAdapter.Adaptor>();
        for(int i = 0; i < arr.Length; i++)
        {
            var instance = arr[i];
            if(instance.ILInstance != null && instance.ILInstance.Type == type)
            {
                return instance;
            } 
        }
        return null;
    }
 
    unsafe static StackObject* AddComponent(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj)
    {
        //CLR重定向的说明请看相关文档和教程,这里不多做解释
        ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
 
        var ptr = __esp - 1;
        //成员方法的第一个参数为this
        GameObject instance = StackObject.ToObject(ptr, __domain, __mStack) as GameObject;
        if (instance == null)
            throw new System.NullReferenceException();
        __intp.Free(ptr);
 
        var genericArgument = __method.GenericArguments;
        //AddComponent应该有且只有1个泛型参数
        if (genericArgument != null && genericArgument.Length == 1)
        {
            var type = genericArgument[0];
            object res;
            if(type is CLRType)
            {
                //Unity主工程的类不需要任何特殊处理,直接调用Unity接口
                res = instance.AddComponent(type.TypeForCLR);
            }
            else
            {
                //热更DLL内的类型比较麻烦。首先我们得自己手动创建实例
                var ilInstance = new ILTypeInstance(type as ILType, false);//手动创建实例是因为默认方式会new MonoBehaviour,这在Unity里不允许
                //接下来创建Adapter实例
                var clrInstance = instance.AddComponent<MonoBehaviourAdapter.Adaptor>();
                //unity创建的实例并没有热更DLL里面的实例,所以需要手动赋值
                clrInstance.ILInstance = ilInstance;
                clrInstance.AppDomain = __domain;
                //这个实例默认创建的CLRInstance不是通过AddComponent出来的有效实例,所以得手动替换
                ilInstance.CLRInstance = clrInstance;
 
                res = clrInstance.ILInstance;//交给ILRuntime的实例应该为ILInstance
 
                clrInstance.Awake();//因为Unity调用这个方法时还没准备好所以这里补调一次
            }
 
            return ILIntepreter.PushObject(ptr, __mStack, res);
        }
 
        return __esp;
    }
 
    unsafe static StackObject* GetComponent(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj)
    {
        //CLR重定向的说明请看相关文档和教程,这里不多做解释
        ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
 
        var ptr = __esp - 1;
        //成员方法的第一个参数为this
        GameObject instance = StackObject.ToObject(ptr, __domain, __mStack) as GameObject;
        if (instance == null)
            throw new System.NullReferenceException();
        __intp.Free(ptr);
 
        var genericArgument = __method.GenericArguments;
        //AddComponent应该有且只有1个泛型参数
        if (genericArgument != null && genericArgument.Length == 1)
        {
            var type = genericArgument[0];
            object res = null;
            if (type is CLRType)
            {
                //Unity主工程的类不需要任何特殊处理,直接调用Unity接口
                res = instance.GetComponent(type.TypeForCLR);
            }
            else
            {
                //因为所有DLL里面的MonoBehaviour实际都是这个Component,所以我们只能全取出来遍历查找
                var clrInstances = instance.GetComponents<MonoBehaviourAdapter.Adaptor>();
                for(int i = 0; i < clrInstances.Length; i++)
                {
                    var clrInstance = clrInstances[i];
                    if (clrInstance.ILInstance != null)//ILInstance为null, 表示是无效的MonoBehaviour,要略过
                    {
                        if (clrInstance.ILInstance.Type == type)
                        {
                            res = clrInstance.ILInstance;//交给ILRuntime的实例应该为ILInstance
                            break;
                        }
                    }
                }
            }
 
            return ILIntepreter.PushObject(ptr, __mStack, res);
        }
 
        return __esp;
    }
}