本文章由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————————–



若有问题,随时联系!!

非常感谢! !!!

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

  1. unity脚本执行顺序

    Awake ->OnEable-> Start ->-> FixedUpdate-> Update  -> LateUpdate ->OnGUI ->R ...

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

     通过Edit->Project Settings->Script Execution Order打开MonoManager面板  或者选择任意脚本在Inspector视图中点击Execu ...

  3. 浅谈Unity的脚本执行顺序

    一.添加脚本的顺序 这是一张官方的脚本顺序图 一般,当我们把脚本绑定在游戏对象上时,或者点击绑定好的脚本的reset按钮时,会调用Reset() 当我们初始化一个对象时,会先调用Awake()在调用O ...

  4. MonoBehaviour Lifecycle(生命周期/脚本执行顺序)

    脚本执行顺序 前言 搭建一个示例来验证Unity脚本的执行顺序,大概测试以下部分: 物理方面(Physics) 渲染(Scene rendering) 输入事件(InputEvent) 流程图 Uni ...

  5. [转]Script标签和脚本执行顺序

    Script标签和脚本执行顺序 这里详细聊聊和script标签相关的脚本执行顺序. Script标签的默认行为 几个首要特性: script标签(不带defer或async属性)的会阻止文档渲染.相关 ...

  6. Unity 继承MonoBehaviour脚本 执行顺序 详解

    先看结果 Awake ->OnEnable-> Start ->-> FixedUpdate-> Update  -> LateUpdate ->OnGUI ...

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

    unity脚本自带函数执行顺序例如以下:将以下脚本挂在随意物体执行就可以得到 Awake ->OnEable-> Start ->-> FixedUpdate-> Upd ...

  8. html/css基础篇——link和@inport详解以及脚本执行顺序探讨

    先说一说两者之间的异同 两者都可以引用外部CSS的方式,现在主流浏览器两者都支持(ps:@import是CSS2.1提出的),但是存在一定的区别: 1.link是XHTML标签,除了加载CSS外,还可 ...

  9. unity 脚本编译顺序

    根据官方的解释,它们的编译顺序如下: (1)所有在Standard Assets.Pro Standard Assets或者Plugins文件夹中的脚本会产生一个Assembly-CSharp-fil ...

随机推荐

  1. RabbitMQ 消息的可靠投递

    mq 提供了两种方式确认消息的可靠投递 confirmCallback 确认模式 returnCallback 未投递到 queue 退回模式 在使用 RabbitMQ 的时候,作为消息发送方希望杜绝 ...

  2. 开发原则&设计模式

    1.关于软件开发中的开发原则和设计模式: 1.1.开发原则 1.1.1.什么是开发原则? 开发原则就是开发的依据,只要依照这些原则进行开发,将来开发的软件具有很强的扩展力,很低的耦合度. 开发原则不属 ...

  3. JFrame 文本打印

    package tools; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import jav ...

  4. juypter-notebook安装配置

    juypter-notebook安装配置 Table of Contents 1. jupyter notebook概述 2. jupyter notebook安装 3. 在jupyter noteb ...

  5. Unity中的输入

    目录 移动平台的输入 触摸 触摸相关的函数 触摸的一个示例 重力加速器 在Unity中访问重力加速器的信息 重力加速器示例 虚拟键盘 其他输入 传统的输入 鼠标,键盘,控制杆,手柄 虚拟控制轴(Vir ...

  6. /usr/local/sbin/arpspoof

    /usr/local/sbin/arpspoof arpspoof -t 攻击者ip地址 网关ip地址 稍等系,被攻击者机器的arp的缓存就已经变了.

  7. python3基础11(正则表达式及re模块)

    #生成re对象 compile# 之后再期调用 match search 返回匹配到的字符串# findall 返回匹配结果的列表#如果要对匹配的结果进行分组,可加(),并可通过\数字 去应用

  8. 本人常用的Phpstorm快捷键

    我设置的是eclipse的按键风格(按键习惯),不是phpstorm的风格 1.添加TODO(这个不是快捷键)://TODO 后面是说明,换行写实现代码 2.选择相同单词做一次性修改:Alt+J+鼠标 ...

  9. HDU3954 线段树(区间更新 + 点更新)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3954 , 一道比较好的线段树题,值得做. 题目是NotOnlySuccess大神出的,借此题来膜拜一下 ...

  10. vue登录权限

    登录:当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个token,拿到token之后(我会将这个token存贮到cookie中,保证刷新页面后能记住用户登录状态),前端会根 ...