Unity脚本执行顺序自研框架

本文章由cartzhang编写,转载请注明出处。 所有权利保留。

文章链接:http://blog.csdn.net/cartzhang/article/details/52372611

作者:cartzhang

一、关于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)
    {

    }

}

2 单例模式脚本

那个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);

最后一个只查找当然文件中的符合要求的函数,并不会扩展到父类中。要是到父类中查找,这样就会造成大量的重复函数,这样在后面的执行中,就会浪费多倍的效率。

代码虽然没有太多注释, 但是基本都是自明其意的。所以,不再过多的阐述了。

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();
    }
}

这里举例只写在Start中。

这样基本就说明完毕。

四、当然是源码

Github 地址:https://github.com/cartzhang/StartFromHere

当然,也非常欢迎你来提交代码。

现在代码中并乜有说可以带参数。这个估计看看,若有需要再尝试啊!!

毕竟我还要回家睡觉啊!!

——————–THE———————–END————————–

若有问题,随时联系!!

非常感谢! !!!

时间: 2024-08-06 03:41:36

Unity脚本执行顺序自研框架的相关文章

unity脚本执行顺序详解

unity脚本自带函数执行顺序如下:将下面脚本挂在任意物体运行即可得到 Awake ->OnEable-> Start ->-> FixedUpdate-> Update  -> LateUpdate ->OnGUI ->Reset -> OnDisable ->OnDestroy using UnityEngine; using System.Collections; public class timetest : MonoBehaviour

unity脚本执行顺序

Awake ->OnEable-> Start ->-> FixedUpdate-> Update  -> LateUpdate ->OnGUI ->Reset -> OnDisable ->OnDestroy using UnityEngine; using System.Collections; public class timetest : MonoBehaviour { void LateUpdate() { print("La

unity 脚本执行顺序设置 Script Execution Order Settings

通过Edit->Project Settings->Script Execution Order打开MonoManager面板 或者选择任意脚本在Inspector视图中点击Execution Order..按钮 Default Time下方数值越小的排在越前面脚本将率先执行.

Unity3d脚本执行顺序详解

欢迎来到unity学习.unity培训.unity企业培训教育专区,这里有很多U3D资源.U3D培训视频.U3D教程.U3D常见问题.U3D项目源码,我们致力于打造业内unity3d培训.学习第一品牌. 在调用脚本的时候遇到下面报错情况: NullReferenceException: Object reference not set to an instance of an object 意思就是:未将对象引用设置到对象的实例脚本内部的 Start()函数并不能作为构造的形式,因为在同一个对象

关于Cocos Creator脚本执行顺序的几点补充

Cocos Creator开发远非初看起来那么容易,正早一些可视化开发语言,例如VB,入门很容易,但进入实战阶段,你很可能会发现问题一股脑儿出现.但是,不要怕,根本原因还在于你不熟悉它.其效率不必怀疑,而且官方的主打产品也正是它.开发中,深感官方关于Cocos Creator脚本执行顺序尚存在很多不详细或者透彻的地方:因此,本人结合最近一个时段的学习,作以下几点补充. 一,官方文档最重要 地址是:http://docs.cocos.com/creator/manual/zh/scripting/

脚本执行顺序

一般的,建议在Awake方法中创建游戏对象或Resources.Load(Prefab)对象,然后在Start方法中去获取游戏对象或者组件,因为事件函数的执行顺序是固定的,这样就可以确保万无一失了. 另外,Unity也提供了一个方法来设置脚本的执行顺序,在Edit -> Project Settings -> Script Execution Order菜单项中,可以在Inspector面板设置 点击右下角的"+"将弹出下拉窗口,包括游戏中的所有脚本.脚本添加完毕后,可以用

unity脚本运行顺序具体的解释

unity脚本自带函数执行顺序例如以下:将以下脚本挂在随意物体执行就可以得到 Awake ->OnEable-> Start ->-> FixedUpdate-> Update  -> LateUpdate ->OnGUI ->Reset -> OnDisable ->OnDestroy using UnityEngine; using System.Collections; public class timetest : MonoBehavio

Unity脚本启动顺序调整方法

我们都知道,Unity中,某个游戏物体上的脚本初始化顺序是先Awake,再Start,那么假如现在有这样一种情况: 有两个模块,它们都通过Awake或者Start来初始化,但其中一个会持有对另一个模块的引用,更具体一点,比如脚本1是单例的,脚本2中持有对脚本1的引用,而且脚本2会在初始化的时候将引用赋值为脚本1的单例. 此时万一脚本2的Awake和Start先执行,那初始化时由于脚本1还没有初始化,肯定会出错,怎么办呢? 一种解决方案是在Edit->Project Settings->Scri

html解析和渲染过程 与 Script标签和脚本执行顺序

几个首要特性: script标签(不带defer或async属性)的会阻止文档渲染.相关脚本会立即下载并执行. document.currentScript可以获得当前正在运行的脚本(Chrome 29+, FF4+) 脚本顺序再默认情况下和script标签出现的顺序一致 有defer或async属性(defer和async没有完全兼容所有浏览器) 仅有async属性,脚本会异步执行 仅有defer属性,脚本会在文档解析完毕后执行 两个属性都没有,脚本会被同步下载并执行(顺序下载,顺序执行),期