本文仅作学习笔记与交流,不作任何商业用途,作者能力有限,如有不足还请斧正
有一些插件就是利用本篇的方法做"自动"处理的
目录
1.情景:
2.介绍与举例:
自定义特性API与使用
反射搜索自定义API
3.优化
4.处理带有自定义特性的类
1.情景:
你肯定见过这个东西,序列化私有字段的特性 将其变量可以显示在编辑器
[SerializeField]
private int hp;
他是怎么实现的呢?F12进去看 其继承了C#之中的特性 [Atttibute]
结合以前的知识,特性本质上就是一个标记,肯定还有其他什么代码去处理这个标记
所以就涉及Unity的序列化系统
-
序列化流程:
- 收集可序列化字段:Unity在导入脚本时,通过反射分析所有字段,检查是否标记了
[SerializeField]
或是否为公共字段。 - 生成序列化数据:将符合条件的字段(公共字段或标记了
[SerializeField]
的私有字段)的信息(名称、类型、值)保存到场景或预制体文件中。 - 反序列化:加载场景或预制体时,根据字段信息通过反射设置对应字段的值。
- 收集可序列化字段:Unity在导入脚本时,通过反射分析所有字段,检查是否标记了
-
私有字段访问:
- Unity使用反射的
BindingFlags.NonPublic
标志访问私有字段:FieldInfo field = type.GetField("_health", BindingFlags.NonPublic | BindingFlags
- Unity使用反射的
其中有一个关键字叫做反射,所以今天就来看看怎么去自己实现[特性----反射处理]这个机制
2.介绍与举例:
自定义特性API与使用
//参数一: 限定属性的作用对象, 这里是类
//参数二: 是否允许多个属性, 这里是false
//AttributeUsage 不做任何处理, 仅仅作为标记使用,此时Att就是一个特性了
[AttributeUsage(AttributeTargets.Class,AllowMultiple =false)]
public class Att : Attribute
{}
[Att]
public class Test { }
反射搜索自定义API
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;public class Rel : MonoBehaviour
{private void Awake(){GetAtt();}private List<Type> attList = new List<Type>();private void GetAtt() {//从当前应用程序域中加载所有程序集Assembly[] assArray = AppDomain.CurrentDomain.GetAssemblies();foreach (var ass in assArray){//获取程序集中所有类型Type[] typeArray = ass.GetTypes();foreach (var type in typeArray){Att att = type.GetCustomAttribute<Att>();if (att != null){// 将应用了 Att 特性的类型添加到列表中attList.Add(type);}} }Debug.Log(attList.Count);}
}
当前程序域下的所有程序集:
一个程序集可以包含一个或多个类型(类、接口等)的定义
这些是 Unity 引擎提供的程序集,每个模块对应了 Unity 引擎的不同功能部分,比如
UnityEngine.AIModule
可能包含了与人工智能相关的功能,UnityEngine.AnimationModule
包含了动画相关的功能。它们为开发者在 Unity 中创建游戏和互动内容提供了丰富的 API
然后是单个程序集中的所有内容(因为是foreach遍历)
最后再foreach一遍直到在所有类型中找到想要的那个打了Att特性的类
3.优化
你也看出来了,几千上万个甚至十几万个类型不断foreach 是很消耗性能的,所以可以用UnityEngine提供的一个方法进行优化
程序集范围限定
只处理Assembly-CSharp
,避免无关程序集的反射遍历利用
TypeCache
Unity 内部缓存机制直接获取标记了特性的类型,无需手动反射条件编译兼容旧版本
通过#if UNITY_2020_1_OR_NEWER
确保代码在旧版 Unity 中仍可运行
using System.Collections.Generic;
using UnityEngine;
using System.Reflection;
using System;#if UNITY_2020_1_OR_NEWER
using UnityEditor; // 需要 Unity 2020.1+ 的 TypeCache
#endifpublic class Rel : MonoBehaviour
{private List<Type> attList = new List<Type>();private void Awake(){GetAtt();}private void GetAtt(){// 优化1:限定在 Assembly-CSharp 程序集Assembly targetAssembly = null;foreach (var ass in AppDomain.CurrentDomain.GetAssemblies()){if (ass.GetName().Name == "Assembly-CSharp"){targetAssembly = ass;break;}}if (targetAssembly == null){Debug.LogError("Assembly-CSharp not found!");return;}// 优化2:使用 TypeCache 替代手动遍历(仅限 Unity 2020.1+)
#if UNITY_2020_1_OR_NEWERvar types = TypeCache.GetTypesWithAttribute<Att>();foreach (var type in types){// 优化3:进一步过滤确保类型属于 Assembly-CSharpif (type.Assembly == targetAssembly){attList.Add(type);}}
#else// 回退方案:手动遍历目标程序集中的所有类型foreach (var type in targetAssembly.GetTypes()){if (type.GetCustomAttribute<Att>() != null){attList.Add(type);}}
#endifDebug.Log(attList.Count);}
}
你还可以将其作为编辑器下运行的方法 从开发阶段就可以运行获取该特性的类 然后做处理
[InitializeOnLoadMethod]
4.处理带有自定义特性的类
只是举一个简单的例子,该例子没有任何实用性
但是可以突出我们获取到了带有自定义特性的类这一点
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(Rel))]
public class RelEditor : Editor
{public override void OnInspectorGUI(){base.OnInspectorGUI();Rel rel = (Rel)target;// 显示所有标记 [Att] 的类型GUILayout.Label("Att-marked Types:");foreach (var type in rel.attList){GUILayout.Label(type.Name);}}
}
#endif