- A+
很普通的,但是好像有很实用的一种架构,不管它好不好吧,学习了研究了,我就想记录一下。想想好像也有论年没有写博客了吧,也不能让它废在这里吧!废话,不讲了,开始我的分析报告了。
首先是一个场景,一个SceneMain挂载一个启动脚本,框架就是围绕着它进行,还有一个PreGameMain物体,存放一些挂载点,比如UI的Root啊等,到时候动态加载的东西会放到某些指定的挂载点下面。
讲讲那个挂载在SceneMain上面的启动脚本SceneMain.cs了,同样的它肯定是一个继承自MonoBehaviour的脚本组件。所以有着Start()、Update()、OnGUI()等方法,主要围绕着Start()和Update()方法,运行起整个框架。
先说一下Start方法的类容和运行的情况,说完Start方法后其实Update的大概大家都可以想得到了。只用一个Start方法,那么肯定是用来生成各种管理器单例并初始化的了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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的都是单例的管理器,他们类的声明是继承一个单例的生产机器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//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类有些什么东西。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
//全局更新,只有必要更新放此 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方法说一下,其实也可以猜到大概是什么样子的了,看代码吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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中来了。
1 2 3 4 5 6 |
//LogicManager的初始化,在SceneMain的Start方法调用的那个 public void GlobalInit() { LogicRuntimeData ld = LogicFlowManager.GetLogic(LogicType.LOGIC_UPDATE); ActiveLogic(ld); } |
看着代码的意思,好像就是说获取的一个叫做Update的逻辑,然后激活它,跟随脚步,看看LogicFlowManager是什么东西先。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
//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方法了,其实已经很简单了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//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方法,他是检查是否切换逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
//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枚举的顺序就行了。