Unity管理器驱动怎么做?简单聊下静态类与解耦

10 篇文章 0 订阅
5 篇文章 0 订阅

        在前边的文章里提到了,我们希望做一个拦截Unity Log的管理类,并且初衷是希望,程序直接把这个脚本拖动到工程里,别的什么代码都不用动,管理类就能自动运行。然而当时测试的结果并不行。

        我们问题的关键点在于,不改动其它的代码。试想一下,如果一个类不被别的类初始化和调用,那它的代码怎么执行?所以我最开始的想法是把初始化直接放到类的静态构造函数里去。但是这样做并没有用。

        因为类的静态构造函数的执行时机是不确定的,它只能保证类在被引用前一定会执行完静态构造函数,我们看一下官方怎么说的

A static constructor is called automatically. It initializes the class before the first instance is created or any static members are referenced. A static constructor runs before an instance constructor. A type's static constructor is called when a static method assigned to an event or a delegate is invoked and not when it is assigned. If static field variable initializers are present in the class of the static constructor, they're executed in the textual order in which they appear in the class declaration. The initializers run immediately prior to the execution of the static constructor.

        所以如果一个静态类不被外部引用,它的静态构造函数就不会执行。所以这种外挂式的方法就行不通了。而我们项目里也没有提供这种独立挂载的机制,所以不得不在启动类里手动调用一下新写的类了。

        那么有什么方法可以方便做到“解耦”呢?那基本上是要用到反射,我们知道我们自己写的代码都是在一个程序集里的,但这也意味着,这种设计最开始就要在整个系统的架构中才行。

        我们先定义一个管理器接口

public interface IGameManager
{
    void Init();
    void Update(float deltaTime);
}

        我们希望做到的是,所有继承自这个接口的类,都会在游戏初始化时自动调用该类的初始化,在游戏帧循环时自动调用该类的帧循环。

        然后我们定义一个测试用的管理类LogManager

public class LogManager : IGameManager
{
    public void Init()
    {
        Debug.Log("Log Manager Init");
    }

    public void Update(float deltaTime)
    {
        Debug.Log("Log Manager Update");
    }
}

        代码非常简单,就是打印一下。接下来我们在场景里挂一个启动的脚本作为测试,我们解耦的思路是什么呢,就是在游戏管理器初始化的时候,通过反射获取到程序集里的所有类型,然后判断哪些类型是继承自IGameManager的,找到所有这样的类,通过反射创建实例化对象,并调用管理器的接口。  

public class ManagerLauncher : MonoBehaviour
{
    private List<IGameManager> _managers;
    void Start()
    {
         _managers = new List<IGameManager>();
        Type[] types = typeof(ManagerLauncher).Assembly.GetTypes();
        foreach (var type in types)
        {
            if (type.GetInterface("IGameManager") != null)
            {
                var manager = Activator.CreateInstance(type) as IGameManager;
                manager.Init();
                _managers.Add(manager);
            }
        }
    }
    
    void Update()
    {
        foreach (var manager in _managers)
        {
            manager.Update(Time.deltaTime);
        }
    }
}

        代码非常的简单清晰,足够说明问题了,就不再注释和解释了。

        运行游戏,代码如预期执行了

        这样就达到了解耦的目的,后续需要添加新的管理器,就不需要改动其它的代码,只要新的管理器继承自这个接口。就会被自动驱动。

        这是一种解耦的思路,其缺点是不够灵活和定制化。比如我们有两个管理器A和B,A有两个初始化方法,其中一个初始化要在B初始化之前执行,另一个要在B初始化之后执行。如果按照传统的做法,可以这么写

void GameInit()
{
    A.Init_Before_B();
    B.Init();
    A.Init_After_B();
}

        但是用自动初始化就需要一些额外的工作,不是不能做。比如给接口增加一个Priority的优先级属性,在通过反射获取到所有类以后,先排序,然后再初始化。对于上边那个接口,甚至可以在A的初始化里初始化B,然后调用A的剩余部分,B的初始化不执行任何功能。但总体来说还是应该尽量避免这样的设计,如果有顺序依赖,也应该把顺序更清晰一些,减少这种交叉的存在,优先考虑类的布局,而不是依赖强大的管理器驱动程序。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值