Skip to content

Unity3D 思考如何”填”资源打包的”坑”

Unity3D 思考如何”填”资源打包的”坑” published on Unity3D 思考如何”填”资源打包的”坑”有101条评论

unity47

最近一直在思考如何“填”Unity3D资源打包这个坑,这篇文章的目的是整理思路,所以正确与否就仁者见仁智者见智了。

首先说下资源打包的目的,我认为资源打包无非是为了以下几方面的目的:

  • 最重要的是使游戏资源可动态更新,而不用每次都走发布流程
  • 其次可快速解决一些因为资源带来BUG
  • 最后就是手动控制资源的使用,节约内存,提升游戏性能

我目前暂时想到这些,其他的目的暂时没想到,其他朋友想到的可以告知一下,我好添加进来。好了,基于打包的目的,我的思考出发点是Unity3D几个原生概念

  • Scene(场景)
  • Prefab(预制)
  • Asset(资源)

这几个东西其实是相辅相成的。

我对Scene(场景)的理解是用来保存相对静态的游戏对象,当然动态对象的实例也可以放里面,比如已经被做为Prefab(预制)的角色或模型的实例。我这里说的静态的游戏对象,其实主要是指场景中不动的、几乎不变化的物件,例如地形、房屋、树木、花草、山石、装饰物、常态粒子等等,其实Scene(场景)好比一个“房间”,里面可以放各种“家具”,场景就是用来保存这些“家具”的类型、组合方式、位置、旋转、缩放等这些基本信息的。

而Prefab(预制)呢,就是用来保存在场景中会动态创建的对象信息的,例如RPG游戏中的玩家角色、怪物、玩家的防具、武器等等。用类比的方式来讲,Prefab其实就是一个“模具”,根据同一个模具可以做出很多相同的“玩具”,这个“模具”保存了这个“玩具”是如何组合起来的,由哪些“零件”组成,也保存了这些“零件”的相对位置、相对旋转、缩放比例等信息等。总体来说我认为预制主要目的是用来保存需要在游戏中动态实例化对象的东东(当然静态实例化的对象也可以用这个来保存)。写着写着突然又想到一个更好的类比,程序猿一看就懂的:预制是你写的类,预制可以被实例化,就像你把类new出来一样。

Asset(资源)在Unity3D里面是一个很重要的概念,可以这样说构成游戏世界的80%的游戏对象都关联了一个资源。Unity3D中资源的类型很多例如:贴图、材质、模型、Shader、脚本、文本文件、声音、动画等,其实我们在Project面板下看到的所有东西都被称为资源。这些资源并不是一个一个独立的个体它们之间是可以存在依赖关系的,例如模型关联了贴图和材质可能还有动画。

插一句,在Unity3D中,选中一个目标资源,右键,便可以查看其依赖关系。dependencies

 

现在来思考打包方案,我先提出待解决的问题(要填的坑)

  • 同资源不能被重复打包或打包到多个AssetBundle里面
  • 打包后的资源可以独立更新
  • 加载要方便,不能而为了加载一个资源手写一堆预加载代码

然后是解决问题的思路(如何填坑)

  • 每个资源单独打包,这样做的目的是为了解决独立更新的问题
  • 按资源的依赖关系分级,从分级最低的开始打包,目的是为了避免资源被重复打包。比如,一个FBX模型,他依赖了材质、贴图等,其中材质又依赖贴图,而贴图不依赖任何东西,所以贴图这种资源的依赖分级就是最低的
  • 所有被打包的资源都需要被管理起来,包括资源的名称、打包后的名称、资源的依赖分级、打包时间、唯一标识、版本号等,保存一个列表文件中,方便读取和查找被打包的资源
  • 实现一个统一的资源管理器,目的是为了方便的更新资源、加载场景、实例化预制、将这些复杂的处理过程简化到极致。

大概的打包过程如下:

  • 预处理将要打包的目标,先处理要打包的场景,然后再处理场景中没有用到但游戏中要动态实例化的预制。需要说明的是,这里说的处理并不是指打包,而是将相关资源整理出来建立打包时需要的一些列信息。
  • 做场景预处理。先获取场景所依赖的所有资源,然后遍历这些资源,得到每个资源的依赖分级,因为有可能有同名资源,所以这时也要生成好每个资源被打包后的文件名,我想用“多级目录名_文件名”的方式应该可以保证其唯一性,例如static_texture_gun1_png.assetbundle。最后按依赖分级关系生成一个依赖文件,把之前整理出来的信息保存下来,这个文件不但会用于后面打包过程,也会用于运行期资源管理器加载场景时使用。需要注意的是,最后要把当前场景文件(*.unity)也要添加到依赖文件中,因为场景文件本身也需要打包的。正常来说对于每一个场景,都应该生成一个依赖文件。处理完一个场景后,要将这个场景的原始名和打包名称写到一个场景列表文件里,方便游戏运行期加载时读取。
  • 做需要被动态实例化的预制体预处理。这种预制体在开发的时候最好实现就放置在一个专用的文件夹下,以方便打包时的工作。和场景一样同样的,得到每个预制体的依赖资源,做好依赖分级处理,生成预制体的依赖文件。和场景一样,最后也要将这个预制体的名称写入到一个预制体列表文件里。对了,补充一下关于依赖文件最好也找个专用的文件夹保存,因为依赖文件最后也会打包成assetbundle文件发布。
  • 现在读取所有的依赖文件,在内存中生成一个待打包资源的列表,这个列表有清晰的分依赖分级关系,打包的时候就根据这个依赖分级来决定什么时候调用PushAssetDependencies()函数来建立依赖关系。为什么遍历所有依赖文件,目的是在于要在整理一次,剔除一下重复的资源,避免同一个资源被多次打包浪费时间。
  • 最后开始打包,根据上一步生成的待打包资源列表打包,打包文件存放也请指定好一个专用的文件夹。。
  • 打包完成后这里还需要生成资源更新文件,这个文件中保存了所有打包好的assetbundle文件的列表,以及这个assetbundle文件的唯一标识:比如MD5码、SHA码等、还有它的当前版本号。假如项目更新了,重头执行一遍打包过程,然后和上一次的这个文件做个比对,把变化的assetbundle文件升级版本号,然后放在服务端让客户端只下载最新的assetbundle文件。

 

原创文章,转载请注明: 转载自游戏无界·达秀的黑暗空间

本文链接地址: Unity3D 思考如何”填”资源打包的”坑”

本站作品除特殊申明外均为原创,采用知识共享署名-非商业性使用-禁止演绎 3.0 Unported许可协议进行许可。如果需转载请保持文章完整性和标明原文出处,禁止商业用途。

Unity3D 资源打包吐槽

Unity3D 资源打包吐槽 published on Unity3D 资源打包吐槽有84条评论

unity47

Unity3D的资源打包以及它的加载机制真的是一个坑,非常的XX(粗鲁了!-_-)。

先回顾下传统PC网络游戏的资源打包。

一般各个游戏开发厂商会自己定义一种资源包格式,然后将资源分门别类的放到这个大的资源包文件中,文件中可以在头部包含一个列表文件,表示什么资源在什么位置、资源的大小、资源的唯一标示等。也有的游戏转门维护一个资源列表文件,让它和打包文件分开,这样的好处是可以独立升级资源。例如:某个模型变了,只需要将这个模型通过网络方式下载到本地,添加或合并到一个最优先加载的资源打包文件中。游戏启动后,先加载优先级最高的资源打包文件的资源列表,再加载老旧的资源打包文件的资源列表,如果新旧资源列表都有同一个资源,那当然以最新的为准,同时打开所有的资源打包文件,得到文件句柄,注意这里仅仅是打开文件得到一个操作句柄,并不占用内存,换句话说没有把整个打包文件加载到内存中,等程序需要某个资源的时候,根据资源的名字或是唯一标识,通过之前获得的文件句柄直接移动文件指针到对应的文件位置去取资源,然后才加载进内存。在打包过程中,不会存在什么依赖不依赖的关系,都是单独的将每一个资源压缩,然后放在相应的位置,一切都是写代码控制,虽然麻烦,但是没有坑,机制一旦建立好,用起来非常简单,运行期也是要什么加载什么,不用了就释放,再清晰不过了。但是,到了Unity3D中,这一切都不一样了。

我们来看Unity3D的资源打包……(忍住不说脏话)

啪,首先给你来个概念,依赖关系。意思就是你打包的时候,要考虑依赖关系,举个例子,一个建筑,那就有模型、贴图、材质什么的,这些就叫这个建筑的依赖文件,打包的时候可以把这个建筑做成一个预制体来打包,如果不考虑依赖关系,打包出来的预制里面就带模型、贴图、材质了,你使用的时候直接实例化这个预制就可以用了,你是不是感觉很爽?别急马上你就不爽了。如果还有另一个建筑也用到了相同的贴图和材质,你又把他做成预制体,然后又打包,实例化后也可以正常使用,这里开始坑就出现了,你的2个打包文件中,把同一个材质打包了2次!当同时你加载这两个建筑的时候,Unity3D,会把这两个AssetBundle文件都加载,自然的相同的贴图、材质这些也就加载了两次,其实这还算好,毕竟加载完AssetBundle完成,你要实例化这个预制,实例化完成后就可以释放AssetBundle文件占用的内存了,但是就在你实例化两个建筑的时候,更深的坑接着出现了!这个相同的贴图和材质,有2份相同的拷贝。只要游戏中存在这两中预制类型的建筑(实例可能无数个),就始终会存在两份相同的贴图、材质的拷贝,如果你使用完AssetBundle不立即释放,那么就是四份相同的拷贝,这绝对是天坑吧。

其实人家Unity3D也考虑到了,所以刚才就告诉你了,咋们可以依赖关系打包啊,你为啥不用?OK,接着刚才的2建筑我们用依赖关系来打包…(省略几行代码)……OK打包完成,现在2个预制打包到一个AssetBundle里面了,加载看看,嗯,只有一份贴图和材质的实例,这一个AssetBundle也别刚才那两个合起来的大小要小,不错不错,节约了内存,好高兴啊。别高兴的太早,坑来了,我发现我的一个建筑在模型上有点瑕疵,要更新一下,肿么办,能单独更新这个模型么?对不起,不行!!!问题放大一点,几十个建筑,都依赖了相同的贴图和材质,当然也有各自专用的贴图和材质,我都是这样打包的,我只有一个AssetBundle,算小点吧,20MB,你要我更新整个AssetBundle,这不是害人么?PC上估计还好点,手机上的话,很多有性格、有个性的用户立马不爽了,槽,昨天还好好的,今天一开游戏就要用老子20MB的流量,哥不耍了还不行么!!

Unity3D又说了,你傻啊,你先push那个什么依赖关系把公共贴图和材质各自打包,然后再打包那个建筑文件,最后pop那个什么依赖关系,这样建筑文件里的AssetBundle里就不包含那个公共的贴图和材质了,你试试看?好吧,我试试。还是刚才那个例子,这样两个建筑打包出来就4个AssetBundle了,贴图一个,材质一个,2建筑模型各一个。好了,来跑程序吧,咦,怎么是个白的建筑?Unity3D又说了,你要先加载你的贴图AssetBundle,再加载你的材质AssetBundle,最后才能加载建筑那个AssetBundle。我槽,这又是一坑吧。我的共享纹理不止一个,有几个咋办,有几十个咋办?

敢不敢提供 一个通用一点的机制啊?要么你就不要提供这种半残的机制,完全让我们自己可以控制打包文件的格式以及内容啊!

吐槽总结:

  1. AssetBundle内存控制不友好,我还没读内容就先要把这个加载到内存
  2. AssetBundle用单一文件打包相关内容,使用非常方便,但是项目越大越耗费的内存越多
  3. AssetBundle用单一文件打包相关内容,打包的内容越多,更新量越大(整个包都要更新)
  4. AssetBundle用多文件依赖方式打包单个资源,使用起来非常复杂,手动写一个个的载入根本不现实,要自己实现一套AssetBundle和Asset的管理机制才行。

纯属个人吐槽,不喜勿喷

原创文章,转载请注明: 转载自游戏无界·达秀的黑暗空间

本文链接地址: Unity3D 资源打包吐槽

本站作品除特殊申明外均为原创,采用知识共享署名-非商业性使用-禁止演绎 3.0 Unported许可协议进行许可。如果需转载请保持文章完整性和标明原文出处,禁止商业用途。

乱谈游戏服务端分布式架构(二)

乱谈游戏服务端分布式架构(二) published on 乱谈游戏服务端分布式架构(二)有71条评论

architecture1

乱谈游戏服务端分布式架构(一),我们提取需求是以服务端在物理机在网络上的部署方式为出发点,这次我们将从另一个侧面来提取需求。

顺便说一下,要全面的描述一个系统的需要从多个不同的侧面来观察,从不同的方向和视角才能获得一个系统的全部需求。好比一个城市,在这个城市中有很多摄像机,每个摄像机都只能捕获这个城市的一个角落或者说一个局部信息,想了解这个城市的概貌,那么需要无数的摄像机。

……扯远了,回到正题上,这次我们立足的出发点为逻辑支撑情况,注意是对游戏逻辑的支撑情况,和物理机部署情况无关。先阐述一下我的期望:

  • 可以支持众多用户连接(>50K)
  • 用户连接本身和用户逻辑之间无强关联性,这样可以解决很多问题:例如用户中途掉线,随即又连接上来的情况,这样用户逻辑部分的数据就不用重读,用户状态也不用恢复,只需要替换一下和用户连接之间的关联性即可
  • 游戏逻辑端可以分类,如验证服务端、数据中心服务端、副本管理端、副本逻辑端、大厅服务端、比赛管理端、房间逻辑端等等
  • 逻辑端功能可以完全分离,比如副本管理端和副本逻辑端可以在同一进程下,也可以各自独立为一个进程运行,便于部署
  • 逻辑端采用多进程单线程模式,便于开发
  • 逻辑端之间会发生通讯,但又不希望众多的逻辑端之间互相连接
  • 数据包进入逻辑端需要有一种机制保证该连接的数据包合法
  • 相同的逻辑端可以启动多个实例,用于支撑更多的用户或者负载均衡。
  • 各个逻辑端可以自由扩展功能,这些扩展对原有逻辑无影响,如:对指定数据包加解密,数据包日志等

看着内容很多,其实不然,而且细细分析下发现,和之前的需求有很多共通之处,提取一下变为简要需求:

  • 一个独立的连接服务端,能支持>50K的用户连接
  • 该连接端同时能接受公网和私网的连接,来自公网的是客户端连接,来自私网的是逻辑端连接
  • 连接端需要管理用户连接和逻辑端连接,各种不同的连接需要分类管理
  • 连接端用于转发各种数据包,并且可以通过扩展模块来添加连接端功能
  • 连接端需管理Token,Token来自一个不需要Token的逻辑端,拥有Token的客户端连接发送的包才会被发往指定的逻辑端
  • 所有的逻辑端都连接到连接端,逻辑端之间的通讯由连接端负责转发。逻辑端连接不需要Token
  • 抽象逻辑端,目前将它分为2类,逻辑管理端和逻辑服务端
  • 逻辑端逻辑处理器实现为外部加载模块,可以随时替换
  • 连接端有OP管理机制,逻辑端负责注册自己能处理的OP,连接端根据OP来分发数据包

 

原创文章,转载请注明: 转载自游戏无界·达秀的黑暗空间

本文链接地址: 乱谈游戏服务端分布式架构(二)

本站作品除特殊申明外均为原创,采用知识共享署名-非商业性使用-禁止演绎 3.0 Unported许可协议进行许可。如果需转载请保持文章完整性和标明原文出处,禁止商业用途。

乱谈游戏服务端分布式架构(一)

乱谈游戏服务端分布式架构(一) published on 乱谈游戏服务端分布式架构(一)有105条评论

architecture1

首先说明一下分布式架构的含义,很多人将“分布式架构”和“分布式运算”两个词理解为同一个概念。这里提到的分布式架构是指服务端程序可以分开部署到多台物理机上(当然也可以部署到同一台物理机上),目的在于通过扩展物理机或硬件设备来提升服务端程序的能力。这里说的“服务端能力”是指服务端的逻辑处理能力、网络数据吞吐量、可承载的客户端连接量等等。

在开始后面的叙述之前我们先按物理机在网络上的部署不同把服务端分为两类,注意这里是按物理机在网络上的部署情况来分类。为何按“物理机在网络上”的部署情况来分类?接下来我们就细细道来。

对于网络游戏而言,一定会有客户端的接入,那么这台服务器一定是部署到公网环境下,如果服务端所有的程序都部署到公网环境,那可以说是灰常的不安全。试想如果负责逻辑处理的服务端程序在公网,黑客们就可以利用各种漏洞入侵了,可以做的事情如下:下载你的服务端程序、备份你的数据库内容等等。所以我们在设计之初就将负责连接的服务端程序和负责逻辑的服务端程序分开,这样至少可以在一定程度上避免这种问题,为什么是一定程度上呢,这样可以拦截掉一部分“新手”黑客。

按物理机在网络上的部署情况来分类,分为两类:网关服务器和逻辑服务器。

网关服务器部署在公网上

  • 接受连接,不管是来自公网的客户端连接,还是来自私网的逻辑服务器连接。反正接受连接就对了。
  • 接收来自公网客户端的数据包并按一定规则转发这些数据包到私网逻辑服务器。
  • 接受来自私网逻辑服务器的数据包并转发到目标公网客户端。
  • 转发所有逻辑服务器之间的内部通讯包。

逻辑服务器部署在私网上

  • 接收和处理来之网关服务器转发的客户端数据包。
  • 发送逻辑包到网关服务器。
  • 接受和处理来之其他逻辑服务器的数据包。

上面就是按物理机部署情况不同的分类,接下来的程序设计就要支持这种部署方式。

首先很明确的是网关服务器程序一定是个可以独立部署的程序,而逻辑服务器程序应该满足各种不同需要,比如支持不同游戏、支持各种逻辑端的管理、不同功能的逻辑端可多次部署、通过添加统一类型的逻辑端来支持更多数量的玩家等。

理解一下上面的文字,整理了一下简要需求,当然是非正式性质的:

  • 独立的网关服务端,独立的逻辑服务端
  • 网关服务端负责交换数据包,逻辑服务端主要负责处理数据包
  • 网关服务端要能控制包的流向
  • 每个端只是一个执行文件,框架而已,加载不同dll的实现不同的功能
  • 各dll要做的主要事情就是处理数据包
  • 网关服务端、逻辑服务端都可以加载多个不同的dll,各dll可以按顺序处理包
  • 各种dll来实现不同的逻辑,各dll比较独立,耦合性弱
  • 制订接口方案,开发人员只需要按接口规范写逻辑dll,能多个人同时开发
原创文章,转载请注明: 转载自游戏无界·达秀的黑暗空间

本文链接地址: 乱谈游戏服务端分布式架构(一)

本站作品除特殊申明外均为原创,采用知识共享署名-非商业性使用-禁止演绎 3.0 Unported许可协议进行许可。如果需转载请保持文章完整性和标明原文出处,禁止商业用途。