using System; using Malee.List; using System.Linq; using UnityEngine; using System.Reflection; using ILRuntime.CLR.Utils; using ILRuntime.Reflection; using JEngine.Core.DO_NOT_USE; using ILRuntime.CLR.TypeSystem; using UnityEngine.Serialization; using ILRuntime.Runtime.Enviorment; using ILRuntime.Runtime.Intepreter; using AppDomain = ILRuntime.Runtime.Enviorment.AppDomain; namespace JEngine.Core { [HelpURL("https://docs.xgamedev.net/zh/documents/0.8/classbind.html")] public class ClassBind : MonoBehaviour { [FormerlySerializedAs("ScriptsToBind")] public ClassData[] scriptsToBind = new ClassData[1]; /// /// Bind itself, call it after when instantiating a prefab with ClassBind in main solution /// 激活ClassBind,在主工程Instantiate带有ClassBind的prefab后调用 /// public void BindSelf() { ClassBindMgr.DoBind(this); } private static readonly Type MonoType = typeof(MonoBehaviour); private static readonly Type AdapterInterfaceType = typeof(CrossBindingAdaptorType); private static readonly Type MonoAdapterType = typeof(MonoBehaviourAdapter.Adaptor); private static readonly Type ILTypeInstanceType = typeof(ILTypeInstance); private static readonly Type AppdomainType = typeof(AppDomain); private const BindingFlags AllBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy | BindingFlags.Default; /// /// Add class /// /// /// public object AddClass(ClassData classData) { //添加脚本 string classType = $"{(string.IsNullOrEmpty(classData.classNamespace) ? String.Empty : $"{classData.classNamespace}.")}{classData.className}"; if (!InitJEngine.Appdomain.LoadedTypes.TryGetValue(classType, out var type)) { Log.PrintError($"自动绑定{name}出错:{classType}不存在,已跳过"); return null; } Type t = type.ReflectionType; //获取实际属性 classData.ClassType = t; Type baseType = t.BaseType is ILRuntimeWrapperType wrapperType ? wrapperType.RealType : t.BaseType; //这个地方太坑了 你一旦热更工程代码写的骚 就会导致ILWrapperType这个问题出现 一般人还真不容易发现这个坑 //JBehaviour需自动赋值一个值 bool isMono = t.IsSubclassOf(MonoType) || (baseType?.IsSubclassOf(MonoType)).GetValueOrDefault(); bool needAdapter = baseType != null && baseType.GetInterfaces().Contains(AdapterInterfaceType); ILTypeInstance instance = isMono ? new ILTypeInstance(type as ILType, false) : InitJEngine.Appdomain.Instantiate(classType); instance.CLRInstance = instance; /* * 这里是ClassBind的灵魂,我都佩服我自己这么写,所以别乱改这块 * 非mono的跨域继承用特殊的,就是用JEngine提供的一个mono脚本,来显示字段,里面存ILTypeInstance * 总之JEngine牛逼 * ClassBind只支持挂以下2种热更类型:纯热更类型,继承了Mono的类型(无论是主工程多重继承后跨域还是跨域后热更工程多重继承都可以) * 主工程多重继承后再跨域多重继承的应该还不支持 */ //主工程多重继承后跨域继承的生成适配器后用这个 if (needAdapter && isMono && baseType != MonoAdapterType) { Type adapterType = Type.GetType(baseType.FullName ?? string.Empty); if (adapterType == null) { Log.PrintError($"{t.FullName}, need to generate adapter"); return null; } //直接反射赋值一波了 var clrInstance = gameObject.AddComponent(adapterType) as MonoBehaviour; var clrILInstance = t.GetFields(AllBindingFlags) .First(f => f.Name == "instance" && f.FieldType == ILTypeInstanceType); var clrAppDomain = t.GetFields(AllBindingFlags) .First(f => f.Name == "appdomain" && f.FieldType == AppdomainType); if (!(clrInstance is null)) { clrInstance.enabled = false; clrILInstance.SetValue(clrInstance, instance); clrAppDomain.SetValue(clrInstance, InitJEngine.Appdomain); instance.CLRInstance = clrInstance; classData.ClrInstance = (CrossBindingAdaptorType)clrInstance; } } //直接继承Mono的,热更工程多层继承mono的,非继承mono的,或不需要继承的,用这个 else { //挂个适配器到编辑器(直接继承mono) if (isMono) { var clrInstance = gameObject.AddComponent(); clrInstance.enabled = false; clrInstance.ILInstance = instance; clrInstance.AppDomain = InitJEngine.Appdomain; classData.ClrInstance = clrInstance; //是MonoBehaviour继承,需要指定CLRInstance instance.CLRInstance = clrInstance; //判断类型 classData.Added = true; } //非继承mono,无需继承,都可以用这个 else { var clrInstance = gameObject.AddComponent(); clrInstance.enabled = false; clrInstance.ILInstance = instance; clrInstance.isJBehaviour = Tools.IsJBehaviourType(classType); clrInstance.AppDomain = InitJEngine.Appdomain; classData.ClrInstance = clrInstance; classData.Added = true; //JBehaviour额外处理 if (clrInstance.isJBehaviour) { var go = t.GetField("_gameObject", AllBindingFlags); go?.SetValue(clrInstance.ILInstance, gameObject); } } } if (isMono) { var m = type.GetConstructor(Extensions.EmptyParamList); if (m != null) { InitJEngine.Appdomain.Invoke(m, instance, null); } } return instance; } /// /// Set value /// /// public void SetVal(ClassData classData) { string classType = $"{(string.IsNullOrEmpty(classData.classNamespace) ? String.Empty : $"{classData.classNamespace}.")}{classData.className}"; Type t = classData.ClassType; //获取实际属性 var clrInstance = classData.ClrInstance; //绑定数据 classData.BoundData = false; var fields = classData.fields; void BindVal(ClassField field, object obj) { try { var fi = t.GetField(field.fieldName, AllBindingFlags); var type = t; while (fi == null && type.BaseType != null) { fi = type.BaseType.GetField(field.fieldName, AllBindingFlags); type = type.BaseType; } if (fi != null) { fi.SetValue(clrInstance.ILInstance, obj); } else { var pi = t.GetProperty(field.fieldName, AllBindingFlags); type = t; while (pi == null && type.BaseType != null) { pi = type.BaseType.GetProperty(field.fieldName, AllBindingFlags); type = type.BaseType; } if (pi != null) pi.SetValue(clrInstance.ILInstance, obj); } } catch (Exception e) { Log.PrintError( $"自动绑定{name}出错:{classType}.{field.fieldName}赋值出错:{e.Message},已跳过"); } } foreach (ClassField field in fields) { if (field.fieldType == ClassField.FieldType.NotSupported) continue; object obj = null; try { if (field.fieldType == ClassField.FieldType.Number) { var fieldType = t.GetField(field.fieldName, AllBindingFlags)?.FieldType ?? (t.BaseType?.GetField(field.fieldName, AllBindingFlags)?.FieldType ?? (t.GetProperty(field.fieldName, AllBindingFlags)?.PropertyType ?? t.BaseType?.GetProperty(field.fieldName, AllBindingFlags)?.PropertyType)); fieldType = fieldType is ILRuntimeWrapperType wrapperType ? wrapperType.RealType : fieldType; if (fieldType == typeof(SByte)) { obj = SByte.Parse(field.value); classData.BoundData = true; } else if (fieldType == typeof(Byte)) { obj = Byte.Parse(field.value); classData.BoundData = true; } else if (fieldType == typeof(Int16)) { obj = Int16.Parse(field.value); classData.BoundData = true; } else if (fieldType == typeof(UInt16)) { obj = UInt16.Parse(field.value); classData.BoundData = true; } else if (fieldType == typeof(Int32)) { obj = Int32.Parse(field.value); classData.BoundData = true; } else if (fieldType == typeof(UInt32)) { obj = UInt32.Parse(field.value); classData.BoundData = true; } else if (fieldType == typeof(Int64)) { obj = Int64.Parse(field.value); classData.BoundData = true; } else if (fieldType == typeof(UInt64)) { obj = UInt64.Parse(field.value); classData.BoundData = true; } else if (fieldType == typeof(Single)) { obj = Single.Parse(field.value); classData.BoundData = true; } else if (fieldType == typeof(Decimal)) { obj = Decimal.Parse(field.value); classData.BoundData = true; } else if (fieldType == typeof(Double)) { obj = Double.Parse(field.value); classData.BoundData = true; } } else if (field.fieldType == ClassField.FieldType.String) { obj = field.value; classData.BoundData = true; } else if (field.fieldType == ClassField.FieldType.Bool) { field.value = field.value.ToLower(); obj = field.value == "true"; classData.BoundData = true; } if (field.fieldType == ClassField.FieldType.GameObject) { GameObject go = field.gameObject; if (go == null) { try { go = field.value == "${this}" ? gameObject : GameObject.Find(field.value); if (go == null) //找父物体 { go = FindSubGameObject(field); if (go == null) //如果父物体还不存在 { continue; } } } catch (Exception) //找父物体(如果抛出空异常) { go = FindSubGameObject(field); if (go == null) //如果父物体还不存在 { continue; } } } obj = go; classData.BoundData = true; } else if (field.fieldType == ClassField.FieldType.UnityComponent) { GameObject go = field.gameObject; if (go == null) { try { if (field.value.Contains(".")) { field.value = field.value.Remove(field.value.IndexOf(".", StringComparison.Ordinal)); } go = field.value == "${this}" ? gameObject : GameObject.Find(field.value); if (go == null) //找父物体 { go = FindSubGameObject(field); if (go == null) //如果父物体还不存在 { continue; } } } catch (Exception) //找父物体(如果抛出空异常) { go = FindSubGameObject(field); if (go == null) //如果父物体还不存在 { continue; } } } void SetField(Type fieldType) { fieldType = fieldType is ILRuntimeWrapperType wrapperType ? wrapperType.RealType : fieldType; if (fieldType is ILRuntimeType ilType) //如果在热更中 { var components = go.GetComponents(); foreach (var c in components) { if (c.ILInstance.Type.CanAssignTo(ilType.ILType)) { obj = c.ILInstance; classData.BoundData = true; break; } } } else { var component = go.GetComponent(fieldType); if (component != null) { obj = component; classData.BoundData = true; } } } var tp = t.GetField(field.fieldName, AllBindingFlags); if (tp == null) tp = t.BaseType?.GetField(field.fieldName, AllBindingFlags); if (tp != null) { SetField(tp.FieldType); } else { var pi = t.GetProperty(field.fieldName, AllBindingFlags); if (pi == null) pi = t.BaseType?.GetProperty(field.fieldName, AllBindingFlags); if (pi != null) { SetField(pi.PropertyType); } else { Log.PrintError( $"自动绑定{name}出错:{classType}.{field.fieldName}赋值出错:{field.fieldName}不存在"); } } } else if (field.fieldType == ClassField.FieldType.HotUpdateResource) { LifeCycleMgr.Instance.AddTask(async () => { //Unity 编辑器下AssetDatabase读取图片会变texture2d导致无法给sprite赋值 var fieldType = t.GetField(field.fieldName, AllBindingFlags)?.FieldType ?? (t.BaseType?.GetField(field.fieldName, AllBindingFlags)?.FieldType ?? (t.GetProperty(field.fieldName, AllBindingFlags)?.PropertyType ?? t.BaseType?.GetProperty(field.fieldName, AllBindingFlags)?.PropertyType)); fieldType = fieldType is ILRuntimeWrapperType wrapperType ? wrapperType.RealType : fieldType; var o = await AssetMgr.LoadAsync(field.value, fieldType); if (fieldType == typeof(Sprite) && o is Texture2D tx) { o = Sprite.Create(tx, new Rect(0, 0, tx.width, tx.height), new Vector2(0.5f, 0.5f), 100.0f); } obj = o; BindVal(field, obj); }); classData.BoundData = true; continue; } } catch (Exception except) { Log.PrintError( $"自动绑定{name}出错:{classType}.{field.fieldName}获取值{field.value}出错:{except.Message},已跳过,{except.StackTrace}"); } //如果有数据再绑定 if (classData.BoundData) { BindVal(field, obj); } } } /// /// Active /// /// public void Active(ClassData classData) { string classType = $"{(string.IsNullOrEmpty(classData.classNamespace) ? String.Empty : $"{classData.classNamespace}.")}{classData.className}"; Type t = classData.ClassType; //获取实际属性 var clrInstance = classData.ClrInstance; //是否激活 if (classData.activeAfter) { if (classData.BoundData == false && classData.fields != null && classData.fields.Count > 0) { Log.PrintError($"自动绑定{name}出错:{classType}没有成功绑定数据,自动激活成功,但可能会抛出空异常!"); } if (classData.ClrInstance is MonoBehaviourAdapter.Adaptor mb) { mb.Awake(); } else if (classData.ClrInstance is ClassBindNonMonoBehaviourAdapter.Adaptor mb2) { mb2.Awake(); } else { //不管是啥类型,直接invoke这个awake方法 var flags = BindingFlags.Default | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Static; var awakeMethod = clrInstance.GetType().GetMethod("Awake", flags); if (awakeMethod == null) { awakeMethod = t.GetMethod("Awake", flags); } else { awakeMethod.Invoke(clrInstance, null); classData.Activated = true; } if (awakeMethod == null) { Log.PrintError($"{t.FullName}不包含Awake方法,无法激活,已跳过"); } else if (!classData.Activated) { awakeMethod.Invoke(clrInstance, null); } } LifeCycleMgr.Instance.AddTask(() => { ((MonoBehaviour)clrInstance).enabled = true; classData.Activated = true; }); } } private GameObject FindSubGameObject(ClassField field) { if (field.value.Contains("/")) //如果有父级 { try { var parent = GameObject.Find(field.value.Substring(0, field.value.IndexOf('/'))); //寻找父物体 var go = parent.transform .Find(field.value.Substring(field.value.IndexOf('/') + 1)) .gameObject; return go; } catch { Log.PrintError($"自动绑定{name}出错:{field.value}对象被隐藏或不存在,无法获取,已跳过"); } } else { Log.PrintError($"自动绑定{name}出错:{field.value}对象被隐藏或不存在,无法获取,已跳过"); } return null; } } [Serializable] public class ClassData { [FormerlySerializedAs("Namespace")] public string classNamespace = "HotUpdateScripts"; [FormerlySerializedAs("Class")] public string className = ""; [FormerlySerializedAs("ActiveAfter")] public bool activeAfter = true; [FormerlySerializedAs("Fields")] [Reorderable(elementNameProperty = "fieldName")] public FieldList fields = new FieldList(); public bool BoundData { get; set; } public bool Added { get; set; } public bool Activated { get; set; } public CrossBindingAdaptorType ClrInstance { get; set; } public Type ClassType { get; set; } } [Serializable] public class ClassField { public enum FieldType { Number, String, Bool, GameObject, UnityComponent, HotUpdateResource, NotSupported } public FieldType fieldType; [Tooltip("需要赋值的键的名字")] public string fieldName; [Header("For Normal Value and Hot Update Resource Field Type")] [Tooltip("非GameObject和UnityComponent的fieldType的值")] public string value; [Header("For GameObject or Unity Component Field Type")] [Tooltip("如果fieldType是GameObject或UnityComponent,此处可填,否则无效")] public GameObject gameObject; } [Serializable] public class FieldList : ReorderableArray { } /// /// Ignore the following field/property while matching fields in the editor /// 在编辑器下进行自动匹配时忽略该字段/属性 /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] public class ClassBindIgnoreAttribute : Attribute { } }