9
HowToUseWeaklyScripts
李若辰 edited this page 2026-03-10 16:44:00 +08:00

XericLibrary.Runtime.Type

虚弱脚本 WeaklyMonoBase

虚弱脚本不允许使用不明确的运算符进行判断,为了语法规范,提高性能,请使用 is 关键字进行判断。

虚弱脚本可以用作隐藏较深的管理器,管理器上可以重定向如何获取各组件的用法。

超级单例 SingleMonoBase

超级单例可以像正常单例一样使用,但超级单例也允许你通过各种方式实现单例模式,并在此基础上进行了大幅扩展。

  1. 你可以基于超级单例声名脚本,但不必在场景中挂载; 超级单例会根据用法自动在便于管理的位置生成该单例,而您只需要专注于用法,生成过程和生命周期顺序皆自由可控,且对过程隐式。

  2. 你可以不必在意单例是否源自自身,超级单例允许使用其他已有的类型作为泛型参数: 使用self限定获取自己,反之获取泛型中指定的类型脚本。
    比如希望使用另一个类中的功能,但又不不希望自己重写另一个类中的生命周期和方法,此举可以快速在多个类型实例中进行切换,而完全不用在场景中进行操作和挂载,切换时对过程隐式。

  3. 临时希望场景中存在多个单例?使用超级单例进行声名的对象,对自身来说最多允许同时管理两个实例,最多四种类型:1.自身实例,2.基于自身类型的代理实例,3.源于自身的实例, 4.基于源于自身的实例类型的代理实例。
    其中自身与源于自身的类型用于区别访问自己类和继承的单例类,两个类是一个实例上的同一个对象结构,但是可以通过超级单例来区别访问。

  4. 允许隐式声名
    超级单例不强制要求继承,使用相同的单例语法,可以自动创建一个完全对过程隐式的单例对象,对任何基于组件的脚本有效。
    场景中已经存在了这个脚本?不用担心,超级单例会复用这个对象,不会出现单例溢出。

  5. 允许放在全局不销毁世界中 使用Global限定,创建的单例将自动移动到不销毁世界中。

  6. 支持更多语法(喜欢烧脑的感觉吗?) 单例对象允许进行嵌套,配合第二点,单例可以直接套娃式递增,配合第四点,单例将不仅限于隐式生成单个对象。
    超级单例支持动态的切换单例对象,并将这个新的单例呈现在全局;比如希望创建一个唯一的对话框,并立刻将这个对话框作为前台提交到全局,使用完毕后再回到默认单例上。

隐式提升为单例(隐式声名)

你可以直接在代码中通过超级单例访问实例,而不用在乎这个类是否实现过单例。

// 超级单例只能作用在组件脚本上,比如随便声名一个脚本
public class TestScript: MonoBehaviour
{
    // 一些其他与单例实现无关的逻辑
}

// 接下来直接通过超级单例访问:
TestScript inst =  SingleMonoBase<TestScript>.Instance; 
// 1. 如果这个类还没有在世界场景中生成,那么这一步会立刻创建一个实例在世界中。
// 2. 如果这个类已经存在,但是还没有通过超级单例访问,那么超级单例会首先尝试在场景中查找,然后根据你调用的作用域将他转移到对应的位置。

// 这里使用另一种全局作用域,这会将单例创建在dontdestroy世界中
TestScript globalInst = SingleMonoBase<TestScript>.GlobalInstance;
// 如果对象已经被超级单例注册过了,那这个对象就会保持当前的作用域,调用新的作用域也不会使其转移,所以请在一开始就确定单例的作用范围。

隔离绝对自身

超级单例允许你使用并非传统单例的模式进行声名,谁说自己是单例的同时必须单例是自己了?

public class MyClassB: SingleMonoBase<MyClassA> 
{  
    // 如果想访问自己可以这么做
    public static MyClassB myInstb => InstanceSelf as MyClassB;
    
    // 否则其他inst方法获取的是 MyClassA的单例
    public static MyClassA myinsta => MyClassB.Instance;
}

// 顺带一提,可以将两个脚本互为单例,编译器不会报错,就是不知道有什么用。
public class MyClassA : SingleMonoBase<MyClassB> {    }
public class MyClassB : SingleMonoBase<MyClassA> {    }

MyClassB中可以与MyClassB完全隔离的用法,不会继承MyClassA的任何内容。 但又可以继续使用MyClassA的属性和方法,和普通的单例几乎一样。

解释一下为什么这么做,你有一个很好用的基类,里面定义了一些你想用的方法,但可惜这些方法都是实例状态下调用的,而且它不能用在你当前的环境下。
比如这个基类是设计给3d场景的,而你的脚本用于ui环境下,基类挂不上,或者不能挂上去。那么可以声名成这样的形式,你的单例仍然是继承自MonoBehaviour,而不是那个基类。
而那个基类则会自动创建,在使用过程中直接调用即可。

代理单例

超级单例如果遇到又多个其他同类实例希望访问时,可以使用代理类,前提是类型都是一样的。


// inst允许输入一个对象
MyClassA.Inst = bbb;
// 后续访问时将优先访问这里设置的对象
bbb = MyClassA.Inst;

如果代理单例被销毁,获取设置为null,那么之后还是访问到原来的单例。

软接口 SoftInterface

软接口利用反射完成对目标字段,属性,方法的识别。
如果这些对象的名称都几乎一致,但由于各种原因,导致没有或无法使用同一个基类来控制这些字段;比较常见的是一些繁琐,数据量,接口数量庞大的项目中,如果反复用继承去控制这些相似的字段,那么代码复杂度反而会上升,使用接口有可能会导致某些情况下字段被分隔开。
简单来说就是常规用法下,继承和接口都会有颗粒度控制不够精细的问题。 而使用反射,特别是一个用法简单的反射是比较好的。

  1. 访问字段或属性 在希望访问的接口实例上使用 GetSoftInterface 来访问特定名称的成员,如果没有这个成员,那么会返回空,如果有,那么方法的 out object value 会返回这个值。你也可以使用 GetSoftInterface<T> 来访问,这样返回的值会先尝试转为 T 类型,如果转换失败,也会返回false,表示返回类型限定错误。
  2. 批量访问字段或属性 当接口实例是一个数据集合时,可以使用 GetSoftInterfaces 来访问它们,这会直接将里面特定名称的成员输出为新的枚举器集合。
  3. 访问方法 在被调用方法的实例上使用 InvokeSoftMethod 来调用指定名称的方法,或使用 MacroReflection.InvokeSoftMethod<T> 来调用到类上的静态方法。 当用方法作为接口时,需要明确输入参数,输出参数,和方法名称。在软接口中,带有ref标记的参数会同时被认为是输入参数和输出参数,而out标记的参数则只被认为是输出参数,在使用类型限定时,首先需要指定函数返回值,如果是void,则直接指定后续的out参数类型即可。 参数类型可以是目标方法上指定类型的基类,但如果同时有多个方法的参数与指定名称匹配,则会返回多个匹配对象,但默认只会调用找到的第一个方法。 注意方法除非调用发生错误,否则不会产生太多可感知的错误,这可能会在一定程度上增加调试困难程度,另外方法调用应尽量保证查找的唯一性,尽量使用完整的输入输出参数进行限定。

反射的性能的确不高,但实际上,除非你在每帧上重复调用数百次,否则反射带来的性能开销都不会很大,至少应该是可以接收的程度。 0.5.4之后的版本中已经对软接口进行了结果烘焙,这应该可以一定程度上加速查找。

菜单特性