C# Unity 面向对象补全计划 之 [反射]自动处理带有自定义[特性]的类
本文仅作学习笔记与交流,不作任何商业用途,作者能力有限,如有不足还请斧正
有一些插件就是利用本篇的方法做"自动"处理的
目录
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
#endif
public 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_NEWER
var types = TypeCache.GetTypesWithAttribute<Att>();
foreach (var type in types)
{
// 优化3:进一步过滤确保类型属于 Assembly-CSharp
if (type.Assembly == targetAssembly)
{
attList.Add(type);
}
}
#else
// 回退方案:手动遍历目标程序集中的所有类型
foreach (var type in targetAssembly.GetTypes())
{
if (type.GetCustomAttribute<Att>() != null)
{
attList.Add(type);
}
}
#endif
Debug.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