Notifications
Article
Unity项目运行流程分析
Updated a year ago
1.6 K
0
Unity项目运行流程分析
很普通的,但是好像有很实用的一种架构,不管它好不好吧,学习了研究了,我就想记录一下。想想好像也有论年没有写博客了吧,也不能让它废在这里吧!废话,不讲了,开始我的分析报告了。
首先是一个场景,一个SceneMain挂载一个启动脚本,框架就是围绕着它进行,还有一个PreGameMain物体,存放一些挂载点,比如UI的Root啊等,到时候动态加载的东西会放到某些指定的挂载点下面。
讲讲那个挂载在SceneMain上面的启动脚本SceneMain.cs了,同样的它肯定是一个继承自MonoBehaviour的脚本组件。所以有着Start()、Update()、OnGUI()等方法,主要围绕着Start()和Update()方法,运行起整个框架。
先说一下Start方法的类容和运行的情况,说完Start方法后其实Update的大概大家都可以想得到了。只用一个Start方法,那么肯定是用来生成各种管理器单例并初始化的了。
void Start () { Application.targetFrameRate = 45; Instance = this; try { //全局的初始化 RuntimeInfo.GlobalInit();//一些数据目录的创建 SceneObjMgr.Instance.GlobalInit();//获取场景的挂载点 ResManager.Instance.GlobalInit();//版本信息的获取 GlobalUpdate.GlobalInit();//一些需要Update的管理器的初始化 NetCmdMapping.GlobalInit();//网络命令和结构体的映射初始化 LogicManager.Instance.GlobalInit();//激活第一个逻辑 } catch (System.Exception e) { ReportException.Instance.AddException(e.ToString()); } }
先讲讲单例,可以看出Instance的都是单例的管理器,他们类的声明是继承一个单例的生产机器。
//public class LogicManager:Singleton<LogicManager> 单例类的声明方式 public class Singleton<T> where T : new() { protected static readonly T ms_Instance = new T();//Activator.CreateInstance<T>(); public static T Instance { get { return ms_Instance;//只读的,外界环境不能修改 } } protected Singleton() { } }
这个挺简单的,从理论上讲,每一个Singleton<T> T不同就是一个不同的类型,所以每个管理器都有一个属于它自己的基类,从而产出自己的单个自读的实例。

后面将关注的类容聚焦到GlobalUpdate和LogicManager地方上来,先说GlobalUpdate,因为它比较简单且好说。其他的管理器像ResManager等的类容就不说了,我只是想记录下框架启动然后运行起来的流程,把这些管理器写出来是想告诉你这里可以有这么些东西。好了,进入正题,看看GlobalUpdate类有些什么东西。
//全局更新,只有必要更新放此
public class GlobalUpdate { public static void GlobalInit() { FTPClient.Instance.GlobalInit(); NetServices.Instance.GlobalInit(); GlobalEffectMgr.Instance.GlobalInit(); GlobalAudioMgr.Instance.GlobalInit(); GlobalLoading.Instance.GlobalInit(); PlayerRole.Instance.OnInit(); } public static void PreUpdate(float delta) { } public static void LateUpdate(float delta) { NetServices.Instance.Update(delta); GlobalEffectMgr.Instance.Update(delta); GlobalAudioMgr.Instance.Update(delta); GlobalHallUIMgr.Instance.Update(delta); GlobalLoading.Instance.Update(delta); SceneObjMgr.Instance.Update(delta); } }
GlobalInit,PreUpdate,LateUpdate,这些看方法名就知道他们的意思了PreUpdate何LateUpdate很明显的会在Logic的Update之前和之后调用,在SceneMain的Update方法里面。GlobalInit其实可以放在SceneMain的Start方法里的,不过不影响,主要的是PreUpdate和LateUpdate方法给需要一帧帧运行的管理器使用的。
最后先说LogicManager之前,先把SceneMain的Update方法说一下,其实也可以猜到大概是什么样子的了,看代码吧。
uint m_FrameTick; void Update () { uint curTick = Utility.GetTickCount(); m_Delta = Utility.TickToFloat(curTick - m_FrameTick); m_FrameTick = curTick; if (BlockLogic.Instance.Update(m_Delta)) { //如果当前没有阻塞 LogicManager.Instance.Update(m_Delta); } }
异常简单的Update方法,计算帧率的时间,GetTickCount()返回的是一个Stopwatch实例计时的毫秒值,计算前一个时间和当前的差值作为帧时间。虽然再判断无阻塞的情况下,调用LogicManager的Update方法。BlockLogic里面维护了一个列表,为阻塞的类容,如果有就阻塞。好了,到这里,全都转移到LogicManager上了。

在Start方法中,剩下了最后一个LogicManager.GlobalInit()没有讲,现在这个也是LogicManager.Update方法,后面就看看LogicManager是怎么推动程序的运行的吧,可以把所有的关注点都放在LogicManager中来了。
//LogicManager的初始化,在SceneMain的Start方法调用的那个 public void GlobalInit() { LogicRuntimeData ld = LogicFlowManager.GetLogic(LogicType.LOGIC_UPDATE); ActiveLogic(ld); }
看着代码的意思,好像就是说获取的一个叫做Update的逻辑,然后激活它,跟随脚步,看看LogicFlowManager是什么东西先。
public enum LogicType {//逻辑类型枚举 LOGIC_UPDATE = 0, LOGIC_INIT, LOGIC_LOGON, LOGIC_HALL, LOGIC_SCENE, LOGIC_MAX } public interface ILogic { //初始化 bool Init(ILogicUI logicUI, object obj); //每帧更新 void Update(float delta); //释放资源 void Shutdown(); } //逻辑表示界面 public interface ILogicUI { //初始化 bool Init(ILogic logic, object obj); //GUI调用 void OnGUI(float delta); //每帧调用 void Update(float delta); //资源释放 void Shutdown(); } //LogicFlowManager using UnityEngine; public class LogicRuntimeData { public ILogic LogicObj; public ILogicUI LogicUIObj; public ResType LogicRes; public LogicType Type; } public class LogicFlowManager { interface ILogicFactoryBase { ILogic CreateLogic(); } interface ILogicUIFactoryBase { ILogicUI CreateLogicUI(); } struct LogicMapping { //保存一个逻辑,对应的资源类型,逻辑工厂,逻辑UI工厂 public ResType LogicResType; public ILogicFactoryBase LogicInterface; public ILogicUIFactoryBase LogicUIInterface; } class LogicFactory<T> : ILogicFactoryBase where T : ILogic, new() { public ILogic CreateLogic() { return new T(); } } class LogicUIFactory<T> : ILogicUIFactoryBase where T : ILogicUI, new() { public ILogicUI CreateLogicUI() { return new T(); } } static LogicMapping NewMapping<LOGIC, UI>(LogicType logicType, ResType res) where LOGIC :ILogic, new() where UI :ILogicUI, new() { LogicMapping lm = new LogicMapping(); lm.LogicResType = res; lm.LogicInterface = new LogicFactory<LOGIC>(); lm.LogicUIInterface = new LogicUIFactory<UI>(); return lm; } static LogicMapping[] ms_LogicMapping = new LogicMapping[(int)LogicType.LOGIC_MAX] { //按枚举顺序,在此注册逻辑 NewMapping<UpdateLogic, UpdateLogicUI>(LogicType.LOGIC_UPDATE, ResType.MAX), NewMapping<InitLogic, InitLogicUI>(LogicType.LOGIC_INIT, ResType.GlobalRes), NewMapping<LogonLogic, LogonLogicUI>(LogicType.LOGIC_LOGON, ResType.LogonRes), NewMapping<HallLogic, HallLogicUI>(LogicType.LOGIC_HALL, ResType.HallRes), NewMapping<SceneLogic, SceneLogicUI>(LogicType.LOGIC_SCENE, ResType.SceneRes), }; //获取上一个逻辑 public static LogicRuntimeData GetBackLogic(LogicType lt) { if(lt > LogicType.LOGIC_LOGON) { return GetLogic(--lt); } else { Debug.Log("GetBackLogic failed."); return null; } } //获取下一个逻辑 public static LogicRuntimeData GetForwardLogic(LogicType lt) { if (lt < LogicType.LOGIC_MAX - 1) { return GetLogic(++lt); } else { Debug.Log("GetForwardLogic failed."); return null; } } //根据Type获取指定逻辑,通过LogicRuntimeData包装一下 public static LogicRuntimeData GetLogic(LogicType lt) { LogicRuntimeData ld = new LogicRuntimeData(); LogicMapping lm = ms_LogicMapping[(int)lt]; //工厂创建Logic和LogicUI的实例 ld.LogicObj = lm.LogicInterface.CreateLogic(); ld.LogicUIObj = lm.LogicUIInterface.CreateLogicUI(); ld.LogicRes = lm.LogicResType; ld.Type = lt; return ld; } }
这里有很多接口类,ILogic,ILogicUI,ILogicFactoryBase,ILogicUIFactoryBase等,在LogicFlowManager中定义了一个数组保存了所有的逻辑,顺序和枚举对应上,每一个逻辑用NewMaping创建LogicMapping,LogicMapping保存了资源的类型和Logic工厂、LogicUI工厂,当获取的时候就根据这两个工厂创建相应的实例。
好了,现在已经事先在LogicFlowManager注册好了Logic的相关信息,就可以在外面获取得到相应的Logic了,例如Update逻辑判断资源的更新,Init逻辑加载公共的资源,Logon逻辑用来登录,Hall逻辑用来大厅等等,通过提供好的向前向后接口推动游戏的进程。就下来回到LogicManager.GlobalInit(),这里先获取到了Update的逻辑信息,然后调用了ActiveLogic方法,下面看看怎么激活这个Logic。
//LogicManager void ActiveLogic(LogicRuntimeData ld) { CloseLogic();//先关闭 m_LogicData = ld; if (!ResManager.Instance.BeginLoadRes(m_LogicData.LogicRes, true)) { NativeInterface.ShowMsgAndExit("res_load_err", 203); return; } bool bRet = m_LogicData.LogicObj.Init(m_LogicData.LogicUIObj, m_Params); bRet &= m_LogicData.LogicUIObj.Init(m_LogicData.LogicObj, m_Params); m_Params = null; if (!bRet) { //逻辑和UI初始化失败 NativeInterface.ShowMsgAndExit("res_load_err", 202); return; } ResManager.Instance.EndLoadRes(); } void CloseLogic() { if(m_LogicData != null) { m_LogicData.LogicUIObj.Shutdown(); m_LogicData.LogicObj.Shutdown(); //释放资源 ResManager.Instance.UnloadManagerObjects(); m_LogicData = null; } }
先CloseLogic关闭原来的逻辑,调用的时Shutdown方法和释放资源。随后将获取的新的逻辑信息赋值给全局的LogicRuntimeData,后面的Update时指定调用。ResManager加载本地需要的资源(ResType枚举,名字就是AssetBundle的资源名字),Init方法就已经去到具体的逻辑了,例如UpdateLogic,就是检查更新从远程加载指定的资源到RuntimeInfo保存的资源目录,加载完毕之后就将逻辑向前推进......到了InitLogic了.....

好了,最后到LogicManager.Update方法了,其实已经很简单了。
//LogicManager public void Update(float delta) { GlobalUpdate.PreUpdate(delta);//管理器逻辑前Update if(m_LogicData != null) { //逻辑帧更新 m_LogicData.LogicObj.Update(delta); m_LogicData.LogicUIObj.Update(delta); } GlobalUpdate.LateUpdate(delta);//管理器逻辑后Update if(m_LogicData != null) CheckAction();//检查逻辑的变更 }
主要看的时CheckAction方法,他是检查是否切换逻辑。
//LogicManager enum Action { ACTION_NONE, ACTION_SHUTDOWN, ACTION_FORWARD, ACTION_BACK, } void CheckAction() { if (LogicAction == Action.ACTION_NONE) return; if (LogicAction == Action.ACTION_FORWARD) { LogicRuntimeData ld = LogicFlowManager.GetForwardLogic(m_LogicData.Type); if (ld == null) { Debug.Log("Logic forward failed."); } ActiveLogic(ld); } else if (LogicAction == Action.ACTION_BACK) { LogicRuntimeData ld = LogicFlowManager.GetBackLogic(m_LogicData.Type); if (ld == null) { Debug.Log("Logic back failed."); } ActiveLogic(ld); } else if (LogicAction == Action.ACTION_SHUTDOWN) { SceneMain.Instance.Shutdown(); } LogicAction = Action.ACTION_NONE; } public void Forward(object obj) { m_Params = obj; LogicAction = Action.ACTION_FORWARD; } public void Back(object obj) { if(IsPlaying) NetServices.Instance.ClearCmdAndHandler(); m_Params = obj; LogicAction = Action.ACTION_BACK; }
LogicManage提供方法先前向后,更换LogicAction的状态,然后CheckAction方法就会第用用LogicFlowManager的指定方法获取指定的逻辑然后激活它。
好了,差不多说完了,整个流程就这样跑起来了,很多具体的类容是没有说到了,比如资源怎么更新加载、Logic和LogicUI的具体界面和逻辑的分离等等。这种架构方式用来构建关卡型的游戏其实非常好的,前面三个Logic固定,一个更新下载资源,一个初始化全局资源,一个登陆,其他为关卡Logic,当要调节、删除,添加插入关卡时,只需要移动删除LogicMapping数组和Logic枚举的顺序就行了。
Tags:
吃丨菜
www.chicai.group - Programmer
1
Comments