Unity脚本执行顺序自研框架
1 Unity脚本执行排序的说明
在Unity中,要控制Unity的脚本执行顺序,Unity引擎本身已经有了一个脚本排序。这个排序在编辑器中可以编辑并设置。
它里面带有默认的,根据优先级来排定执行顺序。若没有在排序的均在default time排序的间隙随机执行。也就是说,在在default time 以上列表中的优先级总是高于其他排序的。
这对于引擎本身来说,已经做的很好了。最起码把你的代码给排序了呢。
这里官方脚本执行网址:https://docs.unity3d.com/Manual/class-ScriptExecution.html
这个排序的已经可以满足基本的要求。比方说要对游戏配置进行初始化就把它排在所有其他代码的最前面就好了。问题就解决了。
2 脚本执行顺序在那里呢?
脚本的执行顺序,我之前总是以为它会全部都存在游戏的projectSettings中的DynamicManager.asset中,或在其他的TagManager.asset中,但是其实并不然。
看到每个脚本有个Meta文件,meta文件包含了脚本的执行顺序。
fileFormatVersion: 2 guid: 5715403d8bbda7e4d905d57906a392da timeCreated: 1470808531 licenseType: Free MonoImporter: serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant:
executionOrder: 0 这个就是执行顺序。越小执行的优先级月高。默认的情况下就是0。若想让脚本最先执行,可以把它设置为负值,比方说:-1200等这样数字,当然还是在脚本执行顺序中,确认一下。
1 入口
在Unity中,因为在代码中各自为战的处理,Awake,Start脚本,虽然可以处理上述所说的问题。
但是在大多数游戏中,还是需要把游戏的流程给全部梳理,让所有的start和Awake,甚至Update都给控制起来。
这样的框架中,必然有个main.cs最为游戏的入口,其他的大部分的脚本就都是从这里初始化和使用的。
mai.cs的基本结构:
using UnityEngine; public class Main : MonoBehaviour { void Awake() { } void Start() { GameWorld.Instance.Init(this); } void Update() { GameWorld.Instance.Update(); } void OnDestroy() { GameWorld.Instance.Destroy(); } void OnApplicationQuit() { } void OnApplicationPause(bool pause) { } }
那个GameWorld是个嘛玩意呢?
这个就是所说的单例模式了。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class GameWorld { public static GameWorld Instance { get { return SingletonProvider<GameWorld>.Instance; } } private List<Action> frameActionList = new List<Action>(); public GameObject MainObj { get; private set; } public Main Main { get; private set; } public void Init(Main main) { this.Main = main; this.MainObj = main.gameObject; NotificationManager.Instance.Init(); TimerManager.Instance.Init(); NetProcessManager.Instance.Init(); } public void Update() { NotificationManager.Instance.Update(); TimerManager.Instance.Update(); DelayManager.Instance.Update(); NetProcessManager.Instance.Update(); //主循环 for (int i = 0; i < frameActionList.Count; i++) { Action action = frameActionList[i]; if (action != null) { action(); } } } public void Destroy() { NotificationManager.Instance.Destroy(); TimerManager.Instance.Destroy(); } public void AddFrameAction(Action action) { if (!frameActionList.Contains(action)) { frameActionList.Add(action); } } public void RemoveFrameAction(Action action) { if (frameActionList.Contains(action)) { frameActionList.Remove(action); } } public Coroutine StartCoroutine(IEnumerator routine) { return this.Main.StartCoroutine(routine); } public void StopCoroutine(IEnumerator routine) { this.Main.StopCoroutine(routine); } }
这样其他的都需要在这里出发了。
其他的单例就在这初始化,更新Update,销毁Destroy等等。
简单的比方:
using System; using System.Collections.Generic; using UnityEngine; public class XXManager : BaseManager { public static XXManager Instance { get { return SingletonProvider<XXManager>.Instance; } } public override void Init() { } public override void Update() { } public override void Reset() { } public override void Destroy() { } public void Remove(string clockID) { } public void Remove(int clockID) { } }
基本就是这样的了。
然而这些都不是我想要说的。
这些单例模式优点还是蛮多的。代码容易管理、整理,在有bug的时候可以容易定位bug等等。
代码简单,明了,结构化和比较容易满足单一职责原则等等。再说,单例本身就是个很好的设计模式。
但是,我想使用另外一个尝试下。
1 什么是属性
在C#中,有个Attribute这样个的属性,打算利用这个性质来把所有相关的函数进行收集统一处理。
这样有很好处呢。
先说一点,这个只是个人的一点想法。当然已经基本实现完毕。好不好呢,这个以后再说,至于有点,好歹说是轮子也是自己制作啊。
况且,还是有优点的。
首先,属性的定义:
[System.AttributeUsage(AttributeTargets.All)] internal class SFHCall : Attribute { }
这个就是个属性继承定义。无它。
2 收集所对应属性的函数
这个当然需要一个字典来存储了。
直接代码吧:
using UnityEngine; using System.Collections; using System.Collections.Generic; using System; using System.Reflection; public class AttributeUtils { public static Dictionary<MonoBehaviour, List<MethodInfo>> monoRPCMethodsCache = new Dictionary<MonoBehaviour, List<MethodInfo>>(); public static void GetAllDestByProperties<T>(object []mono) { int length = mono.Length; for (int i = 0; i < length; i++) { GetDescByProperties<T>(mono[i]); } } private static void GetDescByProperties<T>(object p) { MonoBehaviour mo = (MonoBehaviour)p; if (!monoRPCMethodsCache.ContainsKey(mo)) { Cache<T>(mo); } return; } private static void Cache<T>(MonoBehaviour p) { List<MethodInfo> cachedCustomMethods = new List<MethodInfo>(); monoRPCMethodsCache.Add(p, cachedCustomMethods); var type = p.GetType(); // 不会重复调用父类的方法了。 var fields2 = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); foreach (var field in fields2) { var objs = field.GetCustomAttributes(typeof(T), false); if (objs.Length > 0) { cachedCustomMethods.Add(field); } } } }
这里需要稍微说下:
// 不会重复调用父类的方法了。 var fields2 = type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
- 1
- 2
最后一个只查找当然文件中的符合要求的函数,并不会扩展到父类中。要是到父类中查找,这样就会造成大量的重复函数,这样在后面的执行中,就会浪费多倍的效率。
代码虽然没有太多注释, 但是基本都是自明其意的。所以,不再过多的阐述了。
3 函数的执行过程
函数都收集起来的。在不同的List中保存,这样就根据你自己爱好来执行先后顺序吧!!
怎么执行呢?关键在于Invoke.
using UnityEngine; using System.Collections; using System; using System.Collections.Generic; using UnityEditor; class SFHListener { static SFHListener() { InitialStart(); } public static void callme() { Console.WriteLine("call me"); } private static void InitialStart() { MonoBehaviour[] testMono = GetScriptAssetsOfType<MonoBehaviour>(); AttributeUtils.GetAllDestByProperties<SFHCall>(testMono); int AttributeFunctionCount = AttributeUtils.monoRPCMethodsCache.Count; if (AttributeFunctionCount < 0) { return; } foreach (var item in AttributeUtils.monoRPCMethodsCache) { MonoBehaviour monob = (MonoBehaviour)item.Key; for (int iMethod = 0; iMethod < item.Value.Count; iMethod++) { object result = item.Value[iMethod].Invoke((object)monob, new object[] { }); if (item.Value[iMethod].ReturnType == typeof(IEnumerator)) { monob.StartCoroutine((IEnumerator)result); } } } } private static MonoBehaviour[] GetScriptAssetsOfType<T>() { // current scripts in current scene; MonoBehaviour[] Monoscripts = (MonoBehaviour[])GameObject.FindObjectsOfType<MonoBehaviour>(); // get all monobehaviours contains current scene and also all Prefabs //MonoBehaviour[] Monoscripts = (MonoBehaviour[])Resources.FindObjectsOfTypeAll<MonoBehaviour>(); return Monoscripts; } }
需要注意的是,最后一个函数中。
注释掉的代码,会查找到所有场景中和预制体的代码,这样也会有特大量的超载和重复造成的浪费。
4 最后就是调用
调用,这个超级简单。
当然也可以根据你的需要来调整。放在Awake或start中都是随你意愿啊!!
using UnityEngine; using System.Collections; using System.Collections.Generic; using UnityEditor; using System.Reflection; public class StartFromHere : MonoBehaviour { // Use this for initialization void Start() { SFHListener.callme(); } }
这样基本就说明完毕。
Github 地址:https://github.com/cartzhang/StartFromHere
当然,也非常欢迎你来提交代码。
现在代码中并乜有说可以带参数。这个估计看看,若有需要再尝试啊!!