游戏架构源码-Unity3D游戏框架设计

Http请求返回的数据格式(和前端协议),包括错误码、数据、消息等。

TCP客户端:

网络通信模块基于System.Net.Sockets.TcpClient,以异步阻塞方式封装了Tcp客户端(具体子类BaseTcpClient),提供了Tcp客户端常用技能的默认实现,包括连接、关闭连接、自动脉冲、发送消息、包顺序排列、粘包发送处理、断线重连、断线弹跳、收到消息弹跳等功能。 在通用构造方法中,您需要提供IP地址/域名和端口来创建Tcp客户端实例。 通常,泛型类型应该重新绘制断开连接的弹跳方法和接收消息弹跳的技术,并定义默认的通信数据类型(前后客户端通信数据契约默认使用json)以支持Tcp客户端的实现。

每个Tcp客户端实例内部维护两个线程,一个线程用于定时发送自定义脉冲包,另一个线程用于阻塞等待接收消息包。 Tcp客户端的通用类型不是从MonoBehaviour继承的。 这样设计的初衷是为了让Tcp客户端作为一个更加封闭、独立的模块存在于游戏进程之外。 Tcp客户端的生命周期管理方式也和UnityGameObject的生命周期不同。 所以不需要继承MonoBehaviour。 实例化和连接成功后,Tcp客户端的作用就是加快游戏进程。 业务层应该订阅Tcp客户端感兴趣的事件。 当Tcp客户端收到消息包时,会将消息包的处理方法分发到主线程消息队列,订阅storm的方法在主线程中(因为大多数UnityApi只能在主线程中调用)没有继承MonoBehaviour,所以在设计脉冲包功能时,没有选择使用解释器在游戏循环中定时发送,而是单独开一个线程。

在处理粘包发送问题时,网络通信模块采用自定义的包格式(牡丹江+包体)来处理。 前后端通信的每个数据包在发送前都会在原始数据前添加4个字节的玉林来表示数据包的大小。 客户端通过估计玉林来获取各个包体,将不完整的包暂存在缓冲区中。

基本 TCP 客户端:

Tcp客户端通用,实现了常用方法,包括ConnectAsync(异步连接)、Close(关闭连接)、Reconnect(断线重连)、Send(发送消息)、OnReceived(接收消息反弹)、OnLoseConnect(断线反弹)等。

TcpRecv数据:

默认实现的接收到的消息包的数据类型(和前端协议),包括包序列、类型、数据(对象、业务层分析)、错误码、消息。

TCP发送数据:

默认实现的发送消息包的数据类型(和前端协议),包括用户ID、类型、数据(对象、业务层分析)。

不足与反思:

A。 下载文件需要断点续传,这部分功能需要优化,尤其是更新资源包体积较大时。 如果因为网络波动导致下载进度丢失,将会导致玩家的游戏体验变差。 经验。

b. 很多大鳄都在建议使用Protobuf进行结构化数据存储,框架整体的数据存储和通信使用json。 Protobuf 性能更好,占用显存也更少,所以整个框架中的 json 数据存储方案替换为 Protobuf 存储方案,会提升一些性能。

C。 UDP客户端尚未实现,并且当前的网络通信模块还没有实现UDP客户端通用。 不排除该项目未来可能会使用UDP合约通信。

d. 取消异步方法。 整个框架中大部分异步方法都不支持取消,尤其是网络和I/O部分。 有大量的异步方法。 对于玩家来说,等待时间过长、难以取消的体验是极其不友好的。 参考网上的解决方案,socket可以实现一些类型来支持取消操作。

e. 关于手动发送脉冲包和业务层发送消息导致的线程同步问题,这个问题目前还不是很清楚。 由于业务层在主线程中发送消息,并在线程池中手动发送脉冲包游戏架构源码,因此可能同时进行两者发送消息的操作,但是网络流NetworkStream似乎不是线程安全的(不确定) )。 实践中,前端已经响应了问题,将所有脉冲操作派发到主线程执行,违背了设计初衷(封闭、独立),增加了这部分模块的可扩展性(不能在其他地方使用)。

3. 界面框架

UI框架在UGUI的基础上简单封装了UI表单的各种属性和技巧,同时提供UI管理器作为调用业务层的socket。

游戏中的所有 2D 视觉元素都可以被视为一种形式或形式的一部分。 例如,设置界面、消息确认界面、松饼通知都可以体现为表单。 而在Unity中设计这样的界面需要大量的游戏对象组合,所以为了方便管理,表单本身的显示元素都放在一个空的游戏对象StaticLayout中。 之后,将介绍父窗体、子窗体和兄弟窗体的概念。 考虑到一个表单在交互过程中可能会形成属于该表单的其他表单,如果生成的表单的生命周期应该长于 等于原表单的生命周期,那么它们就有逻辑上的兄弟关系。 此时,生成的表单是原始表单的子表单,而原始表单是生成的表单的父表单,它们具有相同的父表单,彼此的表单都是兄弟表单,每个子表单都是单独取出时也是一个独立的形式。 为了方便管理子表单,所有子表单根据显示类型放置在StackNode或NormalNode下。 这三个节点(StaticLayout、StakcNode、NormalNode)统一挂在同一个游戏对象下,并将Form组件添加到游戏对象中。 此时,游戏对象在逻辑上就相当于一个表单。 将游戏对象作为预制件资源,可以方便地配置表单的属性(如显示类型、预制件名称和预制件路径)和特性(如支持拖拽、模态显示、固定显示、全局唯一性等)。 )在 Inspector 面板中,以及 window 实例化表单时需要指定表单名称。 右图是表单层次结构示意图:

根据这种层次结构,还引入了一个概念“虚拟层次路径”,它是一个字符串,通过串联根表单和本表单之间的各级表单名称,作为索引来唯一标记表单。 创建表单时,有两种显示模式:堆栈模式和普通模式。 任何时候,堆栈模式下只有一个子窗体获得焦点(可操作),其他子窗体的窗口都处于冻结状态,任何时候只有栈顶的窗口具有焦点堆栈模式下的时间。 当单击任何窗口或创建新的子窗口时,相应的窗口将移动到堆栈的顶部。 正常模式下的子窗体都是可以操作的。

UI基础表单:

它是所有自定义表单的通用类型,实现了一些表单功能和技能,并提供了默认实现。 泛型可以重绘InitContent(初始化表单)、Show(显示表单)、Freezed(冻结表单)、Destroy(销毁表​​单)、Recovery(从冻结状态恢复)等方法,方便更新其界面当 UI 表单的状态发生变化时。

用户界面管理器:

UI管理器内部有多个数据结构来维护所有现有表单的兄弟/兄弟关系,并公开诸如CreateForm(创建表单)和CloseForm(销毁表单)等方法。

不足与反思:

A。 分层路径允许使用字符串直接获取对表单的引用,尽管这种设计可以提供更灵活的表单来获取对表单的引用。 事实上,父窗体直接拥有其所有子窗体的引用和控制权限,而不必通过虚拟层次路径获取它们。 做同样的事情有不止一种方法,这在一定程度上符合设计原则。 本来这个UI框架中就没有虚拟的层次路径,个人能力有限。 在实际开发过程中,发现业务层不容易获取UI表单的引用,因此引入虚拟层次路径的概念,具有一定的意义。 妥协。

b. 界面的显示逻辑和交互逻辑(即View层和Controller层)并没有分离,因为虽然现在的UI框架只是比较简单的封装,但是继承自BaseUIForm的自定义表单类中包含了这两个代码处理交互,还有处理显示的代码,处理输入的部分和处理显示的部分是高度耦合的。 重新阅读代码,认为这部分可以优化如下:

l 任何继承自BaseUIForm的自定义表单类都需要实现相应的FormController类游戏架构源码,但它们都是作为组件挂在表单的顶层。 方便取消表单类的任何主动性,只接收交互逻辑和业务逻辑的通知来更新UI,使表单类更轻量级,只负责显示功能,以及处理交互的逻辑部分是在 FormController 类中实现的。

C。 关于UI表单的重用。 重复使用重复创建的 UI 表单可以提高性能。 虽然目前的UI框架支持使用对象池,并且与对象池系统松散集成,但是理想情况下业务层不应该知道创建UI表单的复用过程是的,并且目前每个自定义表单都需要实现定义的socket可以通过对象池来实现,但是在创建UI表单之前,自动判断对象池中是否有可复用的对象是非常复杂的,可以想办法让这部分集成到UI管理器内部。

d. 关于UI根节点。 UI框架的一个缺陷是必须有某个根节点,即所有UI表单的祖先节点都必须是这个根节点,甚至需要为根节点单独挂载一个特殊的脚本,该脚本存在于处理UI逻辑。 特殊情况会带来一定程度的复杂性。 切换场景后,必须重新初始化根节点。 根节点可以优化和取消,UI框架只需要在游戏加载阶段初始化一次。

4. 消息管理

为了增加模块之间的耦合性,需要使用一套消息通信机制。 消息管理模块基于观察者模式提供订阅、注销、发送消息等功能。 同时,它实现了一个线程安全类型来维护和分派到主线程的消息队列。

虽然Unity本身提供了消息机制,比如SendMessage、BroadcastMessage等,但了解后发现这些方法都有比较大的局限性:首先,这种方式发送消息严重依赖字符串,在编译时无法实现类型安全阶段。 它还可以调用私有方法来破坏Type封装,并且只能调用继承MonoBehaviour的类型。 这应该是这个机制内部使用了反射导致的问题。 所以需要自己实现一套消息管理机制。

消息管理模块的核心消息管理器(MessageCenter)是一个静态类型的基类。 类型参数是一个枚举类,用于定义不同模块的消息。 不同类型的参数有自己的消息队列来维护自己的内部消息。 处理。 例如,为了传输系统类型消息,设计一个SystemMessage枚举来枚举所有系统消息。 本设计主要考虑通过区分枚举类型来按照模块区分不同类型的消息,以便于维护和扩展。 不同的模块维护不同的消息队列。 添加模块只需要减少新的枚举类型即可。

留言中心:

消息管理器暴露了AddListener(订阅)、RemoveListener(注销)、Sendmessage(向窃听者发送消息)等方法,提供基本的消息通信机制。

主线程消息处理程序:

它内部维护了一个委托队列,用于缓存分派到主线程的待执行方法,并在主线程的Update周期中寻址以顺序执行该技术; RegisterAction(主线程消息派发)的方法对外公开。

不足与反思:

A。 虽然在实现消息管理器的时候有两种选择:单例或者静态类,但这部分的选择是非常郁闷的。 我认为单例的生命周期和初始化顺序不好控制,静态类不能继承,也不是面向对象的。 根据网上的相关资料,建议使用单例方案。 这种设计仍然存在缺陷。

b. 关于消息类型的泛型。 为了方便起见,消息管理模块使用的消息的子类是System.EventArgs,该类型几乎不提供任何功能。 目前框架需要封装一个基于EventArgs的通用消息类型来提供协作的消息管理器。 相关功能。

C。 发现消息管理器内部维护的数组类型是字典。 此时,不同类型参数维护的不同静态数组应该被忽略。 应该可以将其更改为Queue或直接更改为EventHandler。

d. 还有一个问题依然存在,没有解决,就是派发的消息抛出异常时,函数调用栈比较长。 如果在Unity编辑器中双击错误,会直接定位到消息管理器内部,只能查看异常内部。 通过调用堆栈查找错误代码需要花费时间和精力。 可以将消息管理器部分编译成动态链接库,以插件的形式引入到项目中,这样报错时就可以直接定位到抛出异常的代码。

5.场景管理

场景管理模块主要封装场景管理器和处理场景的通用泛型类型。

该模块的主要目的是提供唯一的场景入口和场景出口(包括游戏入口和游戏出口),并在入口处加载资源并初始化业务逻辑,在出口处释放资源并处理相关业务逻辑。 这样做有利于控制游戏流程的正确顺序,防止初始化逻辑碎片化。

每个罗店场景都需要一个实例来添加继承于Scene泛型的组件,并重绘场景进入和场景退出方法来管理场景。 同时提供一个全局的App组件,以统一的方式在App类中实现游戏的进入和游戏的退出,场景管理的流程如右图所示。

场景:

自定义场景类的子类提供虚方法SceneEntrance(场景进入)和SceneExport(场景退出)配合场景管理器进行场景之间的切换

场景管理器:

场景管理器封装了UnityEngine.SceneManagement.SceneManager的部分方法,并暴露SceneConvertAsync(异步切换场景)供业务层调用。

应用程序:

存在于整个游戏生命周期的组件,在其GameEntrance(游戏入口)方法中初始化全局框架和全局模块,并在其OnApplicationQuit(游戏退出)方法中执行相关操作。 它还为非继承自MonoBehaviour的类型(如Pause、Update、FixUpdate)的外部订阅提供了一些游戏周期风暴,这些由SystemMessage枚举。

不足与反思:

A。 场景切换的进度报告显示的问题目前只是一个简单的遮挡过程。 当游戏场景较大时,没有进度提醒,会导致玩家误解。

b. 关于“异步加载”的问题。 在该框架中,场景和资源使用Unity的解释器异步加载,而网络和I/O部分则使用多线程来实现异步请求。 在同一个方法中使用两者会导致只能等待其中一个的结果,这是非常棘手的。 由于一个方法要么返回IEnumertor,要么返回Task/Task/void,即await和yieldreturn不能同时存在于一个方法中,尽管两种实现异步的机制不同。 这种缺陷在场景切换时尤其被放大,因为场景切换时有时需要进行网络请求和文件读写。 目前还没有想到更好的解决方案,业务层只能尽量避免同时使用两者。

6. 数据分析与获取

该模块主要向业务层提供文件访问方式(包括异步和同步),并提供字符串加解密服务。 数据序列化和反序列化是通过调用第三方库Newtonsoft.Json来实现的。

文件访问部分将文件流操作方法封装在System.IO命名空间下,并以更加友好的形式公开; 加解密采用DES对称加密方案,并将相关类型封装在System.Security.Cryptography命名空间下。 业务层文件存储的过程通常是将相关对象序列化为字符串,加密后存储在本地可写路径中; 读取文件的过程就是从本地可读路径读取字节流并转换为字符串,解密后反序列化为对应的对象,供业务层使用。

文件助手:

提供文件访问的相关功能,并重载大量方法方便调用,包括Write(写入)、Read(读)、WriteAsync(异步写)、ReadAsync(异步读)、SelectFile(选中文件)等方法。

安全工厂:

提供基于DES对称加密方案的对称加密相关功能; 基于MD5加密方案提供不可逆加密相关功能,包括Encrypt(加密)和Decrypt(公开)。

不足与反思:

A。 对称加密的安全性仍然得不到保证。 为了简化加解密过程,在设计模块时将密钥直接放在代码中。 由于托管语言的特性,游戏逻辑编译后的托管库(Assembly-CSharp.dll)是可以反编译的,可以轻松获取秘钥。 考虑以下解决方案:使用工具进行代码混淆,增加破解难度; 使用IL2CPP方案将托管语言编译为本机语言以增强安全性; 将秘钥放在服务器上,游戏运行时在线获取。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 游戏源码 游戏架构源码-Unity3D游戏框架设计 https://www.wkzy.net/game/174748.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务