无聊的任天堂游戏机豆知识#我仿佛明白了什么.P 来自

503 Service Temporarily Unavailable
503 Service Temporarily Unavailable&p&咦,居然有人提《传奇》?这是中国网游无法绕过的一座碑。&/p&&p&可以说《传奇》领跑了中国网游的腾飞,是千万初代网游玩家的游戏启蒙。&/p&&p&从2001年开始,算到今天,《传奇》已经活过14年。&/p&&p&14年听起来并不长,但在互联网世界已足够沧海桑田。&/p&&p&那么,2001年是个什么年代?14年又是什么跨度?&/p&&br&&p&2001年那会,没有百兆光纤,上网不搜wifi。虽然号称上网是“冲浪”,可峰值网速也就56K。&/p&&p&那时,“呵呵”还是见面必用褒义词;“萌”依然解释为植物发芽;99%的国人不知道“囧”字怎么念。&/p&&p&那时,距离《魔兽世界》上线还有3年。2004年山口山开始北美公测时,盛大刚在纳斯达克上市。陈天桥凭着《传奇》坐上了中国首富,被比下去的叫丁磊。&/p&&br&&p&在的中国,《传奇》真的上演了一出传奇。&/p&&p&进入新千年的中国,网游市场方兴未艾。韩国的Actoz带着自家游戏四处兜售却无人问津。同期的盛大与中华网分道扬镳元气大伤。潦落刀客江左相逢,也谈不上多少惺惺相惜,反正陈天桥拿出最后的30万换来传奇的运营权。&/p&&p&囊中空空仅余一纸合同的陈天桥,带着这个品相不佳的游戏说服了育碧代理;又拿着育碧代理说服了浪潮和dell提供三个月的服务器免费试用;最后提着服务器说服了电信获得了带宽……是空手套白狼也好,供应链金融也罢
,总之,万事齐备。&/p&&p&《传奇》上线了,&/p&&p&带着开疆拓土的热望上线了,&/p&&p&带着不成功就死的决绝上线了。&/p&&br&&p&01年9月测试开启, 10天在线人数破万。&/p&&p&01年11月正式运营,各类销售榜全榜冠军。&/p&&p&02年4月,同时在线人数25万。&/p&&p&02年8月,同时在线人数50万,《传奇》成为当时世界第一规模的网游。&/p&&p&02年11月,同时在线人数65万,注册账号7000万。&/p&&p&这是什么概念?&/p&&blockquote&“CNNIC在03年发表数据称截止日,中国网民人数达5910万,超过日本。”&/blockquote&&p&平均下来,中国网民人手一个《传奇》号。&/p&&blockquote&“根据2002年中国游戏产业研究报告,中国网游用户800万。”&/blockquote&&p&假设这些人均匀在线,每天玩游戏3小时,那么每两个网游玩家里就有一人在玩《传奇》。&/p&&p&……&/p&&br&&p&因为有了《传奇》,盛大从不见经传到声名鹊起;但又缘于对《传奇》的路径依赖,盛大从网游领袖黯然退居二线。&/p&&p&《传奇》随着盛大的成长、兴盛与衰弱,一路走来,相伴14年。&/p&&p&14年里,不断有新人出生在新手村砍鸡,依然有老人在安全区喊话炫装备,依然有人被爆光装备删号,来去的都是故事。&/p&&p&14年里,万人攻城的沙巴克城从开启到消失,狂霸酷炫的屠龙刀从现世到无名,记录的全是历史。&/p&&p&14年里,通宵熬夜也要玩传奇的少年过了10点也会犯困,一起组队打怪PK的兄弟开始PK晒娃,溜走的都是时间。&/p&&p&14年里,《传奇》从一种潮流,变成标志,成为象征,风化为记忆。&/p&&br&&p&14年的时间,&/p&&p&《剑侠情缘OL》从1腐到了3,《魔兽世界》从九城换到了网易,史玉柱的《征途》从敛金走向了绿色。&/p&&p&《火影》从开始连载到完结,《柯南》从一年级升入了二年级,《猎人》神坑依旧。&/p&&p&盛大玩了游戏,玩了文学,玩了视频,玩了电子书机顶盒,玩了退市私有化。&/p&&p&《传奇》出了《传奇3》、《传奇外传》、《传奇归来》,如今又有了《热血传奇手机版》。&/p&&p&14年的时间,赋予了《传奇》品牌,也消磨了它的价值;有人看见它会困惑,同样也有人会唏嘘。&/p&&br&&p&题主问《传奇》是什么鬼?我说:&/p&&p&《传奇》是&b&曾经网络游戏的代名词&/b&。21世纪头几年是网吧里可以没游戏但不能不装《传奇》的年代,是玩家床头都贴着《传奇》女道士的年代,是网管开机带你打《传奇》的年代。&/p&&p&《传奇》是&b&中国网游市场的引爆点&/b&。它缔造的财富神话引得各路资本不再矜持纷纷涌入淘金,网游业界开始繁荣并呈现群雄逐鹿的乱象。&/p&
《传奇》是&b&中国网游模式的奠基石&/b&。《传奇》简单粗暴的“打怪升级PK”游戏模式,影响了身后无数的效仿者和挑战者,最终演变成中国网游不变的主题。&br&《传奇》是&b&网游时代的群体记忆&/b&。许多人的青春记事,是在网吧伴着键盘、烟灰还有传奇。
咦,居然有人提《传奇》?这是中国网游无法绕过的一座碑。可以说《传奇》领跑了中国网游的腾飞,是千万初代网游玩家的游戏启蒙。从2001年开始,算到今天,《传奇》已经活过14年。14年听起来并不长,但在互联网世界已足够沧海桑田。那么,2001年是个什么年代…
既然是上海地区的,以下就按片区说说几个呆过及朋友呆过的公司,大部分实情,不匿了:&br&——————---——上海最全游戏地图————————&br&浦东陆家嘴软件园片区:&br&1,上海完美时空 &br&即完美世界上海分公司。&br&旗下产品:神鬼传奇,神鬼幻想,降龙之剑,射雕英雄转Zero,神鬼幻想手游,射雕3d手游,最终兵器,Dota2运营,Arc游戏平台。&br&从一年多之前完美世界拆分成几家子公司,池宇峰离任去完美影视,到完美私有化进程加速,到重回A股,资本应该圈了不少钱,在纳斯达克那么多年,手里十几万期权的人到离职走都没拿到一分钱的红利。。。也真是苦了孩子没套着狼。&br&完美的现金流非常非常非常充裕,但拆分后,就一盘儿散沙了。所以高层动荡的破坏力真心惊人。&br&两年前上海研发实力最强,3D端游研发人才综合储备最好的公司,没有之一。&br&上海完美这两年离职了太多核心骨干和真正的技术牛人。这些人就像蒲公英的种子,撒播到上海的各个片区,一起谱写了上海游戏版图,所以放在第一个说。&br&(才一年多过去居然没落到这么多答案几乎没有被提及,也真是醉了。)&br&2,恺英网络&br&旗下产品:传奇盛世,蜀山传奇,全民奇迹MU&br&恺英每次快死了就会搞出来一个救命的牛逼产品赚得满盆金箔,fu~&br&反正有一年我记得年会他们楼下停了一二十辆豪车,说是年会奖品。。不管你信不信反正我是信了&br&3,Neowiz新游&br&母公司韩国大厂,《FIFA online》,《穿越火线》《qq音速》,不过陆家嘴软件园这家子公司好像并木优做什么听说过的游戏。&br&&br&浦东张江片区&br&1,盛大&br&《传奇》及系列作品,及最新的端游《传奇永恒》,《星辰变》《热血传奇》手游。代理的《龙之谷》《最终幻想14》《冒险岛》等,话说最近去他首页偶见vaultboy才惊异的发现,好吧《辐射庇护所》手游代理盛大也拿了。。&br&盛大这些年走了非常多优秀的运营,自研的端游还是没起来,刚好又赶上手游这一波靠热血传奇赚了不少倒是让走下坡路的盛大走的更缓和了一些。龙之谷的ip给了江游开发,回头打算复制热血传奇的运营思路,和腾讯联运。&br&在盛大,千万别随便喷一些看起来啥都不懂的人,说不定分分钟人家就用背景秒死你。。。&br&盛大的自研实力比较一般,但其实不乏一些行业技术大拿还留在那边养老。&br&2,欢乐互娱&br&即江游。《街机三国》赚了很多钱,完成了原始资本积累。不过其实欢乐互娱的老板三兄弟家底也是真的丰厚。江游这边主要是其中的两兄弟来搞。后来出的手游其实整体水准和画面也都不错。但可惜都没做成,慢慢的也快耗死了。&br&幸亏幸亏现在拿了《龙之谷》的ip做手游,上次内测有去玩,图标模型动作资源都直接从韩国搞来了和端游几乎一样,所以应该很省美术。开发团队是以前上海完美《射雕英雄传zero》端游的核心程序团队集体跳槽过去的,后来又陆陆续续去了不少美术。所以应该能hold住这种mmo大型手游研发。&br&但最近坊间有个趣闻……因为腾讯和盛大联运,所以霸气的腾讯爸爸要求江游这边把《龙之谷》的开发团队搞到深圳去封闭开发。结果呢……结果腾讯爸爸很体贴的给不远千里从上海背井离乡到深圳的开发团队找了一个很高大上的酒店来搞研发……但是,但是,最后需要江游自己出钱……&a href=&tel:&&&/a&&br&3,九城&br&自研基本不太行,主要是代理和运营。当年《魔兽世界》运营水准真心不错,但是这几年都在走下坡路,主要可能公司的重点也不在游戏上了,所以就不多说了。&br&4,NCsoft中国&br&韩国一线大厂,在华子公司,没啥可说的。&br&&br&浦东金桥片区&br&烛龙科技&br&《古剑奇谭》。古剑毕竟是烛龙的亲儿子,但是古剑以前是做单机的,现在想做MMO,但是文案能顶半边天的单机研发不会做啊!所以挖了之前上海完美《射雕英雄传zero》的制作人(以前西山居剑网2剑网3的策划大牛),又带了一两个数值,系统和关卡策划及客户端程序过去。所以古剑online会做成什么样呢?还是很期待ing&br&&br&浦东联洋片区&br&苏州叠纸网络科技&br&对,就是暖暖游戏。以女性为主的研发公司,奇迹暖暖,暖暖环游世界,也算是国内女性游戏方面做的最好的一家。现在B轮,也融了上亿的钱了。而且抱了腾讯爸爸的大腿。公司注册地在苏州,但实际招人和研发都在上海浦东。据说研发大部分都是妹子,而且不太加班:P&br&&br&&br&浦东陆家嘴片区&br&哔哩哔哩&br&好吧,传说中神出鬼没连官网都没写公司地址的B站,其实在浦东距离陆家嘴金融圈最近的位置。&br&b站不仅在其游戏中心可以看到一众2次元类手游运营产品,&br&其实b站私底下投了不少自研游戏团队,(其中也有上海完美之前的离职团队),基本上b站投游戏团队的风格应该属于自己比较喜欢做大股东的那一类,好处是也可以利用手里的2次元资源来帮助团队在这个领域获得比较好的合作,可歌可泣。&br&后续可以期待一下。&br&&br&徐汇漕河泾片区:&br&1,腾讯上海分公司&br&即腾讯上分。其实腾讯网,即通综合也都在上分,&br&游戏的话单说IEG:&br&自研产品:轩辕传奇,天涯明月刀,刀锋铁骑,天天酷跑,天魔幻想,即时通部门早期还有一两款不错的页游和手游;合作产品的NBA2kOnline,怪物猎人;发行线上剑灵,兽人必须死&br&不过北极光多少年了也没赚什么钱,现在天刀应该还可以,但宣传也没少花资源。合作产品的剑灵和怪猎也都坑了。。。估计刀锋也是堪忧。。。&br&主要收入还是天美早期的天天爱消除(爱消除后来搬到深圳去了),天天酷跑。年初的天魔幻想没接力上。KPI业绩压力应该灰常大。&br&另外小马哥之前不爽上海epic分厂于是低调的跑到美国直接买了美国epic多少股权?你们猜。&br&其实我倒是很看好这部分合作在未来可能给上分研发部带来的次世代技术积累以及VR游戏领域爆发的可能性。&br&腾讯上分的特点就是编制卡的严,内部竞争死。因为编制少,所以有大量的QA外聘,甚至连游戏系统和关卡也有外包的,国企既视感有木有??另一方面因为ieg的5星资源手游是门票制,所以很多手游还没轮到你出去,先在内部竞争就已经死了。。。现在是全上海加班最丧心病狂的游戏公司,没有之一。完爆传说中“加班狂魔”游族二十条街。&br&腾讯是我知道国内唯一一个不允许刷榜的游戏公司,不过网易可能也用不着刷榜。所以看到app上打腾讯logo的排名基本都是真实数据。&br&2,莉莉丝&br&靠《刀塔传奇》这一款现象级产品爆发的公司,早期的创始人三元老很讽刺的都出自腾讯上分北极光工作室。但北极光一直没做出什么爆款来,所以这事儿在腾讯内部是挺尴尬挺没面子的事儿。&br&莉莉丝所推崇的“原创精神”与腾讯“务实”的研发氛围是漕河泾上空两条背道而驰的彩虹。虽然刀塔传奇后来被告各种侵权被迫下架,但我想真正的业内人士应该还是会很肯定和佩服这款现象级产品内大量优秀的原创性设计的。&br&另有一款在研手游,类slg战棋游戏。也是原来刀塔传奇三巨头之一的主创亲自带队,具体做出来的品类不好说,毕竟莉莉丝的宗旨一直是要做一些独创性的东西。&br&但莉莉丝积累的年份有限,加上快速扩张增加人员,内部管理还保持着小作坊式的机制,可能在未来是一种隐患。据说莉莉丝策划是不写文档给程序的……真假等辟谣。&br&3,游族网络&br&《大将军》《大侠转》《大皇帝》……大人三部曲?《盗墓笔记》《女神联盟》,还有一众没太出名的手游。《三体》在研,对,你没看错,我说的不是电影《三体》。&br&游族的老板温州八零后,眼界好,会玩儿,这些年买的ip也都很有价值。本来几年前快要进入视野盲区的游族,这几年一些列动作,风生水起,上市后有钱开得出大价钱也招了不少有实力的研发人员。&br&4,米哈游&br&说起来米哈游可能知道的人不多,不过在二次元手游圈,米哈游称老二,没人敢称老大。&br&代表作品《崩坏学园》《崩坏学园2》&br&崩坏赚了钱之后,米哈游也是到处挖人,找了不少完美和腾讯的老程序去喝茶,至于最后去了几个不好说。&br&我只想说他们真的真的真的很二次元。。。。行政妹子也是年轻美。&br&5,淘米网&br&代表作《摩尔庄园》,主攻儿童益智类游戏。但是摩尔庄园之后没见出什么新的游戏。了解不多,难道?儿童游戏领域有别于成人领域,可以一个游戏被无数经过这个年龄段的儿童一茬接一茬的玩?很有可能很有可能!&br&和腾讯上分大楼紧挨着,都在漕河泾总部园。&br&6,Gerena&br&和腾讯关系比较好的一家专门运营东南亚市场的公司,新加坡资本。自己也做自研,但没什么好作品。基本上腾讯卖的很好的游戏,gerena都会拿到东南亚的代理权去运营。&br&环境优越,钱多事少不加班。&br&7,Vsensory工作室,就是之前用《黑盾》拿了htc vive大奖的工作室,据说人家已经把自己的估值做到5个亿了。但是,其实从了解到的情况来看,美术团队很强,但程序和设计是短板,所以VR游戏从纯画面后面要进阶到玩法交互向之后,其爆发力会受限于缺乏牛逼的核心技术和策划。&br&其实这公司的老板还有一个手游公司,在一个楼办公,理论上不缺钱,特别特别特别会炒作,此处不是贬义。&br&&br&更新两家公司,排名和上述公司不分先后&br&8,三七互娱&br&这两年起来的非常快而且尤为财大气粗的一家公司。去年cj的展台也是吓傻一大波人。不少业内人士甚至都不知道这是哪家公司啊,怎么突然就这么大台面了。靠页游起家,代表作《大天使之剑》应该有超多人曾经见过它铺天盖地的广告。15年全面上市之后真的是有钱了,16年开始疯狂甩金招人。就像14年的游族一样。&br&9,智宇科技&br&可能听说的人不多,办公地点在徐家汇港汇···听起来是不是相当豪气。所以其背后金主绝对不是小咖。是谁呢?&br&阿里巴巴···啊!&br&阿里文化已经全面进军影视音乐游戏行业,虽然马云爸爸曾经说过不做网游。&br&智宇科技现在应该是阿里收了一家上海游戏公司后在此基础上开始扩张招人,主要以高端游戏人才和成熟开发团队收编为主。以slg和moba类布局开局打算进军这两个细分手游市场。&br&怎么说呢,2017年的手游公司,都有slg发布布局,已知的莉莉丝,腾讯天美等,所以又是一场腥风血雨。&br&&br&长宁片区&br&1,心动游戏&br&页游很牛,两款现象级产品《神仙道》和《盛世三国》当年都是一炮走红,后续的也有神仙道续集123,盛2。也有好几款手游。我只想说,神仙道和盛世三国当年真的是赚了很多很多很多钱!&br&2,骏梦&br&小小忍者当年还是很成功的。最新的应该是三国战纪?手里还有新仙剑的ip,最近传说要和虚渊玄一起搞《东离剑游纪》手游。&br&老板是以前久游出来的,基本上劲乐团劲舞团最火的那几年在做运营,也是挺牛。&br&这些年看骏梦的游戏基本上都是想在ip上捣鼓出一些作为,但目前来看还没有特别成的作品,继续持观望态度。&br&3,仟游&br&可能知道的人不多,但是提到大名鼎鼎的nba2k应该没人不知道。&br&几经变迁,2kGames最后在华的公司就叫仟游。&br&在篮球游戏方面有很深厚的技术积累。&br&&br&杨浦片区&br&Dena&br&真正的IP之王。。。&br&《OnePiece》《变形金刚》《灌篮高手》《圣斗士星矢》《敢达》《死神》《妖精的尾巴》&br&凡人们……颤抖了么???&br&可惜问题就在于,不接地气。在中国这片大地上,不接地气死的游戏还少么?&br&基本上dena现在也是之前的上海完美党占了半壁河山,一山不容二虎,据说还挤走了另一波上海完美党……江湖传闻,随便看看就好。&br&&br&虹口片区&br&久游&br&《劲乐团》《劲舞团》感觉都是上个时代的游戏了,太多年过去,后面敢达,还有手游并没有成事。&br&14年重组,15年借壳,反正都是资本的事,作为游戏公司真心没啥好说的了。&br&&br&嘉定片区&br&幻萌网络&br&也是2次元,红极一时的《舰娘》,就是他们造的。可惜跟派趣扯皮最后还是把《战舰少女》给坑了。有点可惜。&br&&br&松江片区&br&巨人&br&差点把巨人给忘了,毕竟松江还是有点山高皇帝远了。征途,绿征之后鲜有大作。大主宰,艾尔之光算名气比较大的。&br&征途培养了一批不错的策划特别是数值策划,但绿征相对来说比较坑,因为是在征途的基础上改的,所以对于全新原创一款游戏的能力就比较薄弱,但名气大,容易导致一些主创人员出去之后升的太快,升的太快其实未必是好事。&br&&br&除此之外,还有,育碧上海,动视暴雪,Epic上海,EA中国,这些国际知名大厂太有名了,次世代美术云集。不用多说大家都知道。&br&-------------------------------------------------&br&在中国,资金充裕决定不了一个游戏公司的前途;未来有没有前途也很难由当下的成功产品决定;这就是中国游戏业!这也是这么多年美国纳斯达克一直对中国游戏公司的估值偏低的原因之一,不像A股,技术储备,人才储备都是p,动不动一个手游概念就是几十个涨停……看清了这个现实,盛大,完美这些数得着的游戏大厂这两年也相继完成了私有化……&br&中国游戏行业,一个产品的风生水起,就是一大波的资本洪流,一个产品失败了第二个产品青黄不接,后面可能就是死的悄无声息,今天还是大公司,明天沦落到不为人知无人提及。这些事儿在游戏行业里每天重演。但也值得中国游戏人反思。
既然是上海地区的,以下就按片区说说几个呆过及朋友呆过的公司,大部分实情,不匿了: ——————---——上海最全游戏地图———————— 浦东陆家嘴软件园片区: 1,上海完美时空 即完美世界上海分公司。 旗下产品:神鬼传奇,神鬼幻想,降龙之剑,射…
上海传统的端游大厂都至少比较稳,但有钱也不代表日子舒服,因为这么多年下来公司的资本、管理、研发都各有龃龉。&br&&br&九城的朱老板心思不在游戏上面,从魔兽被网易端掉之后就没什么出色的动作。直到几年前朱老板的主业都是足球,游戏顺带玩玩的。关于足球这个就不能细说了,我还要混这行的;&br&&br&巨人凭世纪游轮回A股大涨一把,目前在上海已上市的游戏公司里算市值最高的。然而巨人从征途之后的研发一直就是个悲剧,研发的氛围也更是呵呵。一句话,史老板了解那么一撮富豪玩家的需求,但是太不了解一般玩家尤其是新崛起的移动玩家的需求;&br&&br&腾讯上海。其实就说腾讯互娱好了。有钱,去年虽然被网易打脸,但是毕竟拿下了移动游戏市场半壁江山。原美娱的刘铭跳槽到企鹅之后力主促成了《传奇》和腾讯的合作,总算挽尊,但奈何苹果爸爸更爱网易,谁要你大企鹅自带分发属性呢。但大企鹅的自研也是够北上,亮点全在代理产品上;&br&&br&盛大游戏,曾经的游戏市场NO.1,被陈天桥带到前年,终于卖掉了,从此和盛大集团分道扬镳。但多年沉疴,内部纷争不断,还好传奇手游一口暴击奶上来,勉强挽尊。然后两个大股东继续PK,两年前的回归A股,打到今天都还没搞定,一个大写的服;&br&&br&久游,劲舞团大家记得吧。几次三番上市都被人明枪暗箭打下来,允悲。原老板王子杰一气之下出走,后来据说参与搞了塞纳河48。这转身幅度……不愧是日系大厂出身;&br&&br&&br&页游大厂:&br&&br&恺英,靠全民奇迹来了个大翻身,不然今天也扎在一堆页游公司里没有出头之日了。上市前因侵权还被盛大一顿举报,差点憋死,最后赔钱了事;&br&&br&游族,上市前以加班闻名,后来上市后收敛了很多。页游大厂能及早意识到转身的少之又少,游族算最快的一个,而且转的是不相干的电影行业。但是这个转身的华丽程度嘛就哈哈哈;&br&&br&骏梦,曾和游族号称上海滩加班双雄。老板特别热衷于搞IP改编,然而最后都没搞大;&br&&br&——————————————————————————————————————————&br&&br&其实上海日子最好过的都是些中型游戏公司。其实未必是小,而是他们不上市。摊子没那么大,反而没有资本的压力,更有做事的气氛,老板也往往更加人性化,福利待遇都还不错。比如心动和莉莉丝。&br&&br&另外,上海有一家业外不怎么知名的公司海湃,这可是最早在全球市场掘到真金的移动游戏公司。他们赚移动游戏钱的时候其他大公司还在头破血流搞页游。但是后来消息就很少了,也许是低调。&br&&br&近期比较有意思的一家公司是派趣,融资情况不错,应该不缺钱,而且笼络了不少业内资源。不过肯定是要谋求上市的。说到底,上海游戏公司的老板绝大多数都不是产品出身,很容易就变成资本玩家。这种类型的游戏公司,某些节点上会有资本助力相当生猛,但是研发积累上都会有各种各样的问题导致后劲不足。&br&&br&暂时就先写这么多。一个个写太TM长了。
上海传统的端游大厂都至少比较稳,但有钱也不代表日子舒服,因为这么多年下来公司的资本、管理、研发都各有龃龉。 九城的朱老板心思不在游戏上面,从魔兽被网易端掉之后就没什么出色的动作。直到几年前朱老板的主业都是足球,游戏顺带玩玩的。关于足球这个…
&p&中国的游戏开发技术有多落后?纵观整个信息产业核心技术国内都是落后的。
为什么中国的编程语言那么落后?易语言?
为什么中国没有操作系统?红旗Linux?
为什么中国的CPU,GPU那么落后?龙芯?AMD在Inter面前都苟延残喘,您就别拿出来了好么?
为什么中国的搜索引擎那么落后?百度?能不要拿这家二流网站侮辱Google吗?
为什么中国的XX那么落后?在信息产业核心技术里都是可以通用的。这里有历史的原因,也有国情的原因。国内一直走的是copy to china 模式,现在也是。除了360的杀毒软件技术实力, OPPO的蓝光DVD,其它很少有能在世界拿得出手的核心技术。&/p&&p&关于游戏制作技术:&/p&&p&其实电子游戏从诞生到现在都是被日本的几家公司(任天堂、SEGA、SONY、 KONAMI、
CAPCOM 、SE )和美国几家公司(雅达利、后来的动视暴雪、微软、EA、
Valve 、 Bethesda Softwork 、ROCKSTAR和2K)所主导的,游戏开发技术一直随着平台走,后来随着平台的演化游戏开发出现了更重量级的角色:游戏引擎,几乎每家游戏开发巨头都有自己的游戏引擎,EA的The Dead engine,EA DICE的寒霜引擎(Frostbite Engine),CAPCOM的Mt Framework引擎,Hero公司的Hero Engine引擎,Valve的Source Engine(起源引擎),Crytek公司的Cry Engine引擎,Infinity Ward工作室的IW引擎,EPIC的Uneral虚幻引擎。&/p&&p&看了这么多国外鼎鼎大名的游戏引擎,反观国内呢?国内当然也有人搞引擎,搜狐畅游的黑火引擎,完美世界的Athena引擎,赵德贤先生的幻影游戏引擎,以及最近很火的HTML5白鹭Egret Engine,但这么多开发出了什么游戏我不知道。&/p&&p&但是,So what?这些丝毫不影响国内游戏公司捞钱!论财大气粗,什么EA暴雪任天堂在腾讯爸爸面前都弱爆了,腾讯持有暴雪24%股份,投资Epic,收购了拳头公司、买了 Supercell 。&/p&&figure&&img src=&/b1cddffa294ba8_b.jpg& data-rawwidth=&460& data-rawheight=&399& class=&origin_image zh-lightbox-thumb& width=&460& data-original=&/b1cddffa294ba8_r.jpg&&&/figure&
中国的游戏开发技术有多落后?纵观整个信息产业核心技术国内都是落后的。
为什么中国的编程语言那么落后?易语言?
为什么中国没有操作系统?红旗Linux?
为什么中国的CPU,GPU那么落后?龙芯?AMD在Inter面前都苟延残喘,您就别拿出来了好么?
为什么中国…
&figure&&img src=&/5d419ed54b09bada8dce1bc_b.jpg& data-rawwidth=&650& data-rawheight=&380& class=&origin_image zh-lightbox-thumb& width=&650& data-original=&/5d419ed54b09bada8dce1bc_r.jpg&&&/figure&&h2&1.概览&/h2&&p&Unity3D 5.0版本之后的AssetBundle机制和之前的4.x版本已经发生了很大的变化,一些曾经常用的流程已经不再使用,甚至一些老的API已经被新的API所取代。&br&
因此,本文的主要内容就是分析5.X版本的AssetBundle机制(包括&b&创建资源包、压缩资源包、加载资源包和从资源包中加载/卸载资源&/b&等几个方面)及其关键的API使用方式并总结一些对项目的建议(例如根据不同的情景,选择不同的包体加载方案等等)。&/p&&br&&h2&2.AssetBundle系统的新功能&/h2&&p&本小节包括:&/p&&ul&&li&AssetBundle系统的新功能&/li&&li&新的AssetBundle系统的优势&/li&&/ul&&h4&2.1.AssetBundle系统的新功能&/h4&&p&在新的AssetBundle系统中,出现了以下的新功能:&/p&&ul&&li&通过Editor中的UI即可方便的为AssetBundle标记资源。而且一个资源和对应的AssetBundle的映射将会在资源数据库(AssetDatabase)中被创建。&figure&&img data-rawheight=&291& data-rawwidth=&428& src=&/bf0a9772fab628dffc884e8ba283c204_b.jpg& class=&origin_image zh-lightbox-thumb& width=&428& data-original=&/bf0a9772fab628dffc884e8ba283c204_r.jpg&&&/figure&在箭头处即可指定该资源所述的AssetBundle,第一个选项为AssetBundle的名字,而后一个选项则是为AssetBundle创建变体,
例如一些素材需要区分为高清或普通存放在不同的AssetBundle中,那么第二选项就可以以hd和normal来区分。&/li&&li&提供了新的API用来设置资源所属的AssetBundle:&/li&&li&设置&b&AssetImporter.assetBundleName&/b&的值,即可为该资源指定它所属的AssetBundle。上文中在UI中设置的AssetBundle的名字便是为该值赋值,在资源有了assetBundleName之后,实际上它的信息就已经存在于AssetDataBase里面了。&/li&&li&新版本中,创建AssetBundle文件的API变得十分简单了:&/li&&li&&b&BuildPipeline.BuildAssetBundles()&/b&:我们只需要提供一个输出AssetBundle的地址即可。引擎将自动根据资源的assetbundleName属性(即在上文中UI中设置的值)批量打包,自动建立Bundle以及资源之间的依赖关系。&/li&&li&新增了一些打包策略/选项,且一些4.x中的旧有策略被默认开启。&/li&&li&&b&CompleteAssets&/b& ,用于保证资源的完备性,默认开启;&/li&&li&&b&CollectDependencies&/b&,用于收集资源的依赖项,默认开启;&/li&&li&&b&DeterministicAssetBundle&/b&,用于为资源维护固定ID,默认开启;&/li&&li&ForceRebuildAssetBundle,用于强制重打所有AssetBundle文件,新增;&/li&&li&IgnoreTypeTreeChanges,用于判断AssetBundle更新时,是否忽略TypeTree的变化,新增;&/li&&li&AppendHashToAssetBundleName,用于将Hash值添加在AssetBundle文件名之后,开启这个选项可以直接通过
文件名来判断哪些Bundle的内容进行了更新(4.x下普遍需要通过比较二进制等方法来判断,但在某些情况下即使内容不变重新打包,Bundle的二进
制也会变化),新增。&/li&&li&ChunkBasedCompression,用于使用&b&LZ4格式&/b&进行压缩,5.3新增。&/li&&li&Manifest文件。在4.x版本中,我们通常需要自行维护配置文件,以记录AssetBundle之间的依赖关系,并供运行时使用。而在5.x版本中,使用Manifest文件可以免去4.x版本中的这一过程。而Manifest文件分为两种:&/li&&li&单个bundle的Manifest文件,一旦一个新的AssetBundle文件被创建导出,便会对应生成一个.manifest文件,其中包含了校验、依赖文件等信息。所以可以用来做增量更新。&/li&&li&实际上在打包的时候,在输出的bundle所在的文件夹内还会生成一个总的manifest文件,叫做[文件夹名].manifest。它包含了
该文件夹内所有的bundle的信息,以及它们之间互相依赖的信息。所以在我们加载bundle的时候,需要先把总的manifest文件加载进来,以确
认各个bundle之间的依赖关系。&/li&&li&一些在运行时动态加载AssetBundle的API被新的API代替。&/li&&li&4.x版本中的AssetBundle.&b&CreateFromFile&/b&方法,在5.x版本中变成了AssetBundle.&b&LoadFromFile&/b&方法。&/li&&li&4.x版本中的AssetBundle.&b&CreateFromMemory&/b&方法,在5.x版本中变成了&b&LoadFromMemoryAsync&/b&方法。&/li&&li&4.x版本中的AssetBundle.&b&CreateFromMemoryImmediate&/b&方法,在5.x版本中变成了&b&LoadFromMemory&/b&方法。&/li&&/ul&&h4&2.2.新的AssetBundle系统的优势&/h4&&p&由于引擎提供的这些新功能,我们就不再需要像4.x时代那么复杂的用来打包的脚本了。&br&
同时,资源之间的互相依赖关系不再需要开发者手动维护了,曾经由于不当使用PushAssetDependencies/PopAssetDependencies而可能会造成依赖出现的问题,现在Unity3D已经为我们解决了。&br&
而且由于引入了清单文件manifest,因此我们可以实现增量更新,即只需要更新有变化的部分,而没有变化的则不必更新。&br&
举一个例子:&br&
假设我们有一个cube,它的material有一个材质,我们分别将cube和material打包成cubeBundle和
materialBundle,之后我们修改material上的材质。在过去,我们需要分别重新为cube和material打包,而现在只需要对
material重新打包即可,cube不受影响。&/p&&h2&3.AssetBundle文件的创建&/h2&&p&本小节包括:&/p&&ul&&li&旧有创建AssetBundle文件的API&/li&&li&新的创建AssetBundle文件的API&/li&&li&针对项目的建议&/li&&/ul&&h4&3.1.旧有创建AssetBundle文件的API&/h4&&p&在4.x时代,最常用的AssetBundle打包方法主要包括以下两个:&/p&&ul&&li&&b&BuildPipeline.BuildAssetBundle&/b&&br&
对除Scene以外的资源打包,支持单个和多个资源,需要在方法的参数中指明需要被打入AssetBundle的资源;&/li&&li&&b&BuildPipeline.BuildStreamedSceneAssetBundle&/b&&br&
对Scene文件打包,也支持单个和多个。&/li&&/ul&&p&且在4.x时代,打包还需要注意资源之间互相依赖的问题。为了避免资源冗余,同时提高资源加载和卸载的灵活性,因此依赖性打包的重要性不言而喻。老版本中,我们可以使用以下两个方法来实现这种依赖性:&/p&&ul&&li&&b&BuildPipeline.PushAssetDependencies&/b&&/li&&li&&b&BuildPipeline.PopAssetDependencies&/b&&/li&&/ul&&p&这种机制并不难理解,简单的说PushAssetDependencies是将资源进栈,PopAssetDependencies是让资源出栈,
每打一个包,引擎都会检查当前栈中所有的依赖项,查看是否有相同资源已经在栈中。如有,则与其相关的AssetBundle建立依赖关系。&/p&&h4&3.2.新的创建AssetBundle文件的API&/h4&&p&在新版本中,Unity3D为我们提供了唯一的API用来打AssetBundle包。即:&/p&&ul&&li&&b&BuildPipeline.BuildAssetBundles&/b&&/li&&/ul&&p&在脚本中调用BuildPipeline.BuildAssetBundles,U3D将自动根据资源的assetbundleName属性批量打包,自动建立Bundle和资源之间的依赖关系。&br&
在资源的Inpector界面最下方可设置该资源的assetbundleName,每个assetbundleName对应一个Bundle,即assetbundleName相同的资源会打在一个Bundle中。&br&
如果所依赖的资源设置了不同的assetbundleName,则会自动与之建立依赖关系,避免出现冗余,从而减小Bundle包的大小。&br&
当然,除了可以指定assetbundleName,我们还可以在Inpector中设置另一个名字,即variant。在打包时,variant会作为
后缀添加在assetbundleName之后。相同assetbundleName,不同variant的Bundle是可以相互替换的。&figure&&img data-rawheight=&299& data-rawwidth=&498& src=&/ea143ed0ef0f5f5fadbd_b.jpg& class=&origin_image zh-lightbox-thumb& width=&498& data-original=&/ea143ed0ef0f5f5fadbd_r.jpg&&&/figure&设置好之后,我们只需要创建一个新的脚本,通过编辑器拓展调用BuildPipeline.BuildAssetBundles方法即可:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&using UnityE
public class CreateAssetBundles
[MenuItem (&Assets/Build AssetBundles&)]
static void BuildAllAssetBundles ()
BuildPipeline.BuildAssetBundles (&Assets/AssetBundles&);
&/code&&/pre&&/div&&p&BuildPipeline.BuildAssetBundles方法的参数为bundle的导出目录。当然它有很多重载的版本,可以提供额外的参数来定制符合自己需求的AssetBundle。&/p&&h4&3.3.针对项目的建议&/h4&&p&虽然新的AssetBundle简化了打包和处理资源依赖的过程,但是却引入了一个新的复杂度,即需要设置资源的assetbundleName以实现打包的功能。&br&
因此我们可能需要做的是:&/p&&ol&&li&提供脚本批量对资源设置assetbundleName&/li&&li&规划好assetBundle所对应的资源类型,规划好assetBundle的数量&/li&&/ol&&br&&h2&4.AssetBundle的压缩&/h2&&p&本小节包括:&/p&&ul&&li&AssetBundle的压缩类型&/li&&li&针对项目的建议&/li&&/ul&&h4&4.1.AssetBundle的压缩类型&/h4&&p&Unity3D引擎为我们提供了三种压缩策略来处理AssetBundle的压缩,即:&/p&&ul&&li&LZMA格式&/li&&li&LZ4格式&/li&&li&不压缩&/li&&/ul&&p&&b&LZMA格式:&/b&&br&
在默认情况下,打包生成的AssetBundle都会被压缩。在U3D中,AssetBundle的标准压缩格式便是LZMA(LZMA是一种序列化流文
件),因此在默认情况下,打出的AssetBundle包处于LZMA格式的压缩状态,在使用AssetBundle前需要先解压缩。&br&
使用LZMA格式压缩的AssetBundle的包体积最小(高压缩比),但是相应的会增加解压缩时的时间。&br&&b&LZ4格式:&/b&&br&
Unity 5.3之后的版本增加了LZ4格式压缩,由于LZ4的压缩比一般,因此经过压缩后的AssetBundle包体的体积较大(该算法基于chunk)。但是,使用LZ4格式的好处在于解压缩的时间相对要短。&br&
若要使用LZ4格式压缩,只需要在打包的时候开启BuildAssetBundleOptions.ChunkBasedCompression即可。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath,
BuildAssetBundleOptions.ChunkBasedCompression);
&/code&&/pre&&/div&&p&&b&不压缩:&/b&&br&
当然,我们也可以不对AssetBundle进行压缩。没有经过压缩的包体积最大,但是访问速度最快。&br&
若要使用不压缩的策略,只需要在打包的时候开启BuildAssetBundleOptions.UncompressedAssetBundle即可。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&BuildPipeline.BuildAssetBundles(Application.streamingAssetsPath,
BuildAssetBundleOptions.UncompressedAssetBundle);
&/code&&/pre&&/div&&h4&4.2.针对项目的建议&/h4&&p&AssetBundle的压缩策略不仅仅和包体的大小、包体的解压速度相关,而且还会关系到AssetBundle在运行时动态加载的API使用。因此,针对不同类型资源的AssetBundle要指定出符合其使用特点的压缩策略。&/p&&br&&br&&h2&5.AssetBundle的加载和卸载&/h2&&p&本小节主要包括:&/p&&ul&&li&新版API&/li&&li&动态加载方式对比&/li&&li&针对项目的建议&/li&&/ul&&h4&5.1 新版API&/h4&&p&在5.x版本中的新AssetBundle系统中,旧有的一些动态加载API已经被新的API所取代,具体内容如下:&/p&&ul&&li&4.x版本中的AssetBundle.&b&CreateFromFile&/b&方法,在5.x版本中变成了AssetBundle.&b&LoadFromFile&/b&方法。&/li&&li&4.x版本中的AssetBundle.&b&CreateFromMemory&/b&方法,在5.x版本中变成了&b&LoadFromMemoryAsync&/b&方法。&/li&&li&4.x版本中的AssetBundle.&b&CreateFromMemoryImmediate&/b&方法,在5.x版本中变成了&b&LoadFromMemory&/b&方法。&/li&&/ul&&p&因此,本小节之后的内容将使用新版API。&/p&&h4&5.2.动态加载方式对比&/h4&&p&使用AssetBundle动态加载资源首先要获取AssetBundle对象,第二步才是从AssetBundle中加载目标资源。因此本小节将主要关注如何在运行时获取AssetBundle的对象,关于如何从AssetBundle中加载资源将在下一小节中分析。&br&
要在运行时加载AssetBundle对象主要可以分为两大类途径:&/p&&ul&&li&先获取WWW对象,再通过&a href=&/?target=http%3A//WWW.assetBundle& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&WWW.assetBundle&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&获取AssetBundle对象&/li&&li&直接获取AssetBundle&/li&&/ul&&p&下面我们就具体分析一下这两种途径:&/p&&p&&b&先获取WWW对象,再通过&a href=&/?target=http%3A//WWW.assetBundle& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&WWW.assetBundle&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&加载AssetBundle对象:&/b&&br&
在先获取WWW对象,在获取AssetBundle的这种方式中,我们可以使用以下两个API来实现这个功能。&/p&&ul&&li&&b&public WWW(string url)&/b&,直接调用WWW类的构造函数,目标AssetBundle所
在的路径作为其参数,构造WWW对象的过程中会加载Bundle文件并返回一个WWW对象,完成后会在内存中创建较大的WebStream(解压后的内
容,通常为原Bundle文件的4~5倍大小,纹理资源比例可能更大),因此后续的AssetBundle.LoadAsset可以直接在内存中进行。&/li&&li&&b&public static WWW LoadFromCacheOrDownload(string url, int version, uint crc = 0)&/b&,WWW
类的一个静态方法,调用该方法同样会加载Bundle文件同时返回一个WWW对象,和上一个直接调用WWW的构造函数的区别在于该方法会将解压形式的
Bundle内容存入磁盘中作为缓存(如果该Bundle已在缓存中,则省去这一步),完成后只会在内存中创建较小的SerializedFile,而后
续的AssetBundle.LoadAsset需要通过IO从磁盘中的缓存获取。&/li&&/ul&&p&&b&直接加载AssetBundle对象:&/b&&br&
在4.x时代,我们可以通过CreateFromFile或CreateFromMemory方法将磁盘上的文件或内存中的流构造成我们需要的
AssetBundle对象。但是在5.x版本中,曾经的这两个方法已经被新的LoadFromFile、LoadFromMemory方法所代替(这两
个方法还有异步的版本),且机制上也有了一些区别。&/p&&ul&&li&&b&public static AssetBundle LoadFromFile(string path, uint crc = 0):&/b&新
的从文件创建加载AssetBundle方法和4.x中的CreateFromFile方法在机制上有了一些分别,旧的CreateFromFile必须
使用未压缩的Bundle文件才能在运行时动态创建AssetBundle对象。而新的LoadFromFile方法则没有这个要求,它支持上一节中提到
的几个压缩格式,针对LZ压缩格式和未压缩的磁盘上的bundle文件,该方法会直接加载。针对使用默认的LZMA压缩格式压缩的bundle文件,该方
法会在幕后先将bundle文件解压后再加载。这是最快的加载AssetBundle的方式。该方法是同步版本,还有异步版
本:LoadFromFileAsync。&/li&&li&&b&public static AssetBundle LoadFromMemory(byte[] binary, uint crc = 0):&/b&从
内存中获取Bundle的二进制数据,同步地创建AssetBundle对象。该方法一般用在经过加密的数据上,经过加密的流数据经过解密之后我们可以调
用该方法动态的创建AssetBundle对象。该方法是同步版本,还有异步版本:LoadFromMemoryAsync。&/li&&/ul&&p&以上便是在运行时动态加载AssetBundle对象的方法。下面,我们再从加载过程中内存消耗的角度来对比一下这几种加载AssetBundle对象的方法,下表是Unity3D官方的一个中文版总结。&br&&figure&&img data-rawheight=&523& data-rawwidth=&1143& src=&/3865b6bcc745da09bf73514cfa313882_b.jpg& class=&origin_image zh-lightbox-thumb& width=&1143& data-original=&/3865b6bcc745da09bf73514cfa313882_r.jpg&&&/figure&注???:当使用WWW来下载一个bundle时,WebRequest还会有一个8*64KB的缓存区用来存储来自socket的数据。&/p&&h4&5.3.针对项目的建议&/h4&&p&由于以上分析的几种加载手段各有各的使用情景和特点。因此建议在我们的项目中按照以下情景使用这些方法:&/p&&ul&&li&随游戏一同发布的AssetBundle(一般位于StreamingAssets文件夹中):&/li&&li&在打AssetBundle包时,使用LZ4压缩格式进行打包(开启BuildAssetBundleOptions.ChunkBasedCompression即可)。&/li&&li&在运行时需要加载AssetBundle对象时,使用LoadFromFile方法进行加载。&/li&&li&这样做的好处是:&b&即可以将AssetBundle文件压缩,又可以兼顾加载速度,且节约内存。&/b&&/li&&li&作为更新包,需要从服务端下载的AssetBundle:&/li&&li&在打AssetBundle包时,使用默认的LZMA格式压缩。&/li&&li&使用&a href=&/?target=http%3A//WWW.LoadFromCacheOrDownload& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&WWW.LoadFromCacheOrDownload&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&方法下载并缓存AssetBundle包文件。&/li&&li&这样做的好处是:&b&获得了最大的压缩率,在下载过程中可以减少数据传输量。同时,在本地磁盘创建缓存之后,又可以兼顾之后的加载速度,且节约内存。&/b&&/li&&li&我们自己进行加密的AssetBundle:&/li&&li&在打AssetBundle包时,使用LZ4压缩格式进行打包(开启BuildAssetBundleOptions.ChunkBasedCompression即可)。&/li&&li&在运行时需要加载AssetBundle对象时,使用LoadFromMemory方法进行加载。(这也是从内存中使用流数据加载AssetBundle对象的仅有的使用场景。)&/li&&li&我们自己压缩的AssetBundle:&/li&&li&我们自己也可以使用第三方库或工具对生成的AssetBundle包文件进行压缩,如果需要这样做,则我们最好不要再使用Unity3D对
AssetBundle进行压缩,因此在打包时选择开启
BuildAssetBundleOptions.UncompressedAssetBundle。&/li&&li&在运行时需要加载AssetBundle对象时,使用LoadFromFileAsync方法进行异步加载。&/li&&/ul&&br&&h2&6.资源的加载和卸载&/h2&&p&本小节包括:&/p&&ul&&li&从AssetBundle对象中加载资源&/li&&li&资源的卸载&/li&&/ul&&h4&6.1.从AssetBundle对象中加载资源&/h4&&p&新旧版的加载和卸载资源的API名称发生了一些变化,但是机制变化不大。&br&
在旧有的4.X版本中,从AssetBundle对象中加载资源所使用的API主要包括以下几个:&/p&&ul&&li&Load:从资源包中加载指定的资源&/li&&li&LoadAll:加载当前资源包中所有的资源&/li&&li&LoadAsync:从资源包中异步加载资源&/li&&/ul&&p&而在新版的AssetBundle中,加载资源的API已经变成了以下的几个:&/p&&ul&&li&LoadAsset:从资源包中加载指定的资源&/li&&li&LoadAllAsset:加载当前资源包中所有的资源&/li&&li&LoadAssetAsync:从资源包中异步加载资源&/li&&/ul&&h4&6.2.资源卸载&/h4&&p&资源卸载部分的变化不大,使用的仍然是Unload方法。&/p&&ul&&li&&b&Unload&/b&&/li&&/ul&&p&该方法会卸载运行时内存中包含在bundle中的所有资源。&br&
当传入的参数为true,则不仅仅内存中的AssetBundle对象包含的资源会被销毁。根据这些资源实例化而来的游戏内的对象也会销毁。&br&
当传入的参数为false,则仅仅销毁内存中的AssetBundle对象包含的资源。&/p&
1.概览Unity3D 5.0版本之后的AssetBundle机制和之前的4.x版本已经发生了很大的变化,一些曾经常用的流程已经不再使用,甚至一些老的API已经被新的API所取代。
因此,本文的主要内容就是分析5.X版本的AssetBundle机制(包括创建资源包、压缩资源包、加载资…
&figure&&img src=&/a169e21c4da1e873a20a4ea6f585de10_b.jpg& data-rawwidth=&809& data-rawheight=&473& class=&origin_image zh-lightbox-thumb& width=&809& data-original=&/a169e21c4da1e873a20a4ea6f585de10_r.jpg&&&/figure&&h2&0x00 前言&/h2&&p&刚开始写这篇文章的时候选了一个很土的题目。。。《Unity3D优化全解析》。因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而
给自己“文(dou)学(bi)”加工留下的余地就少了很多。但又觉得这块是不得不提的一个地方,平时见到很多人对此处也给予了忽略了事,需要时才去网上
扒一些只言片语的资料。也恰逢年前,寻思着周末认真写点东西遇到节假日没准也没什么人读,所以索性就写了这篇临时的文章。题目很土,因为用了指向性很明确
的“Unity3D”,让人少了遐(瞎)想的空间,同时用了“高大全”这样的构词法,也让匹夫有成为众矢之的的可能。。。所以最后还是改成了现在各位看到
的题目。话不多说,下面就开始正文~正所谓“草蛇灰线,伏脉千里”。那咱们首先~~~~~~&/p&&br&&br&&h2&0x01 看看优化需要从哪里着手?&/h2&&p&匹夫印象里遇到的童靴,提Unity3D项目优化则必提DrawCall,这自然没错,但也有很不好影响。因为这会给人一个错误的认识:&strong&&em&所谓的优化就是把DrawCall弄的比较低就对了。&/em&&/strong&&/p&&p&对优化有这种第一印象的人不在少数,drawcall的确是一个很重要的指标,但绝非全部。为了让各位和匹夫能达成尽可能多的共识,匹夫首先介绍一下本文可能会涉及到的几个概念,之后会提出优化所涉及的三大方面:&/p&&ul&&li&drawcall是啥?其实就是对底层图形程序(比如:OpenGL ES)接口的调用,以在屏幕上画出东西。所以,是谁去调用这些接口呢?CPU。&/li&&li&fragment是啥?经常有人说vf啥的,vertex我们都知道是顶点,那fragment是啥呢?说它之前需要先说一下像素,像素各位应该
都知道吧?像素是构成数码影像的基本单元呀。那fragment呢?是有可能成为像素的东西。啥叫有可能?就是最终会不会被画出来不一定,是潜在的像素。
这会涉及到谁呢?GPU。&/li&&li&batching是啥?都知道批处理是干嘛的吧?没错,将批处理之前需要很多次调用(drawcall)的物体合并,之后只需要调用一次底层图形程序的接口就行。听上去这简直就是优化的终极方案啊!但是,理想是美好的,世界是残酷的,一些不足之后我们再细聊。&/li&&li&内存的分配:记住,除了Unity3D自己的内存损耗。我们可是还带着Mono呢啊,还有托管的那一套东西呢。更别说你一激动,又引入了自己的几个dll。这些都是内存开销上需要考虑到的。&/li&&/ul&&p&好啦,文中的几个概念提前讲清楚了,其实各位也能看的出来匹夫接下来要说的匹夫关注的优化时需要注意的方面:&/p&&ul&&li&&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23cpu& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&CPU方面&i class=&icon-external&&&/i&&/a&&/li&&li&&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23GPU& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GPU方面&i class=&icon-external&&&/i&&/a&&/li&&li&&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23memory& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&内存方面&i class=&icon-external&&&/i&&/a&&/li&&/ul&&p&所以,这篇文章也会按照CPU----&GPU----&内存的顺序进行。&/p&&br&&br&&h2&0x02 CPU的方面的优化&/h2&&p&上文中说了,drawcall影响的是CPU的效率,而且也是最知名的一个优化点。但是除了drawcall之外,还有哪些因素也会影响到CPU的效率呢?让我们一一列出暂时能想得到的:&/p&&ul&&li&&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23drawcall& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&DrawCalls&i class=&icon-external&&&/i&&/a&&/li&&li&&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23wuli& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&物理组件(Physics)&i class=&icon-external&&&/i&&/a&&/li&&li&&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23GC& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GC&i class=&icon-external&&&/i&&/a&(什么?GC不是处理内存问题的嘛?匹夫你不要骗我啊!不过,匹夫也要提醒一句,GC是用来处理内存的,但是是谁使用GC去处理内存的呢?)&/li&&li&当然,还有&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23code& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&代码质量&i class=&icon-external&&&/i&&/a&&/li&&/ul&&h4&DrawCalls:&/h4&&p&前面说过了,DrawCall是CPU调用底层图形接口。比如有上千个物体,每一个的渲染都需要去调用一次底层接口,而每一次的调用CPU都需要做
很多工作,那么CPU必然不堪重负。但是对于GPU来说,图形处理的工作量是一样的。所以对DrawCall的优化,主要就是为了尽量解放CPU在调用图
形接口上的开销。所以针对drawcall我们主要的思路就是每个物体尽量减少渲染次数,多个物体最好一起渲染。所以,按照这个思路就有了以下几个方案:&/p&&ol&&li&使用Draw Call Batching,也就是描绘调用批处理。Unity在运行时可以将一些物体进行合并,从而用一个描绘调用来渲染他们。具体下面会介绍。&/li&&li&通过把纹理打包成图集来尽量减少材质的使用。&/li&&li&尽量少的使用反光啦,阴影啦之类的,因为那会使物体多次渲染。&/li&&/ol&&h4&Draw Call Batching&/h4&&p&首先我们要先理解为何2个没有使用相同材质的物体即使使用批处理,也无法实现Draw Call数量的下降和性能上的提升。&/p&&p&因为被“批处理”的2个物体的网格模型需要使用相同材质的目的,在于其纹理是相同的,这样才可以实现同时渲染的目的。因而保证材质相同,是为了保证被渲染的纹理相同。&/p&&p&因此,为了将2个纹理不同的材质合二为一,我们就需要进行上面列出的第二步,将纹理打包成图集。具体到合二为一这种情况,就是将2个纹理合成一个纹理。这样我们就可以只用一个材质来代替之前的2个材质了。&/p&&p&而Draw Call Batching本身,也还会细分为2种。&/p&&h4&Static Batching 静态批处理&/h4&&p&看名字,猜使用的情景。&/p&&p&静态?那就是不动的咯。还有呢?额,听上去状态也不会改变,没有“生命”,比如山山石石,楼房校舍啥的。那和什么比较类似呢?嗯,聪明的各位一定觉得和场景的属性很像吧!所以我们的场景似乎就可以采用这种方式来减少draw call了。&/p&&p&那么写个定义:只要这些物体不移动,并且拥有相同的材质,静态批处理就允许引擎对任意大小的几何物体进行批处理操作来降低描绘调用。&/p&&p&那要如何使用静态批来减少Draw Call呢?你只需要明确指出哪些物体是静止的,并且在游戏中永远不会移动、旋转和缩放。想完成这一步,你只需要在检测器(Inspector)中将Static复选框打勾即可,如下图所示:&/p&&p&&figure&&img data-rawheight=&320& data-rawwidth=&288& src=&/cc180d1f8eaefb8ac4cab2f6_b.png& class=&content_image& width=&288&&&/figure&至于效果如何呢?&/p&&p&举个例子:新建4个物体,分别是Cube,Sphere, Capsule, Cylinder,它们有不同的网格模型,但是也有相同的材质(Default-Diffuse)。&/p&&p&首先,我们不指定它们是static的。Draw Call的次数是4次,如图:&/p&&p&&figure&&img data-rawheight=&280& data-rawwidth=&378& src=&/f71dbb8ccdbcb9d9ab5c59_b.png& class=&content_image& width=&378&&&/figure&我们现在将它们4个物体都设为static,在来运行一下:
&/p&&p&&br&&figure&&img data-rawheight=&297& data-rawwidth=&378& src=&/d75d23f4ecd06fb986ede_b.png& class=&content_image& width=&378&&&/figure&如图,Draw Call的次数变成了1,而Saved by batching的次数变成了3。
&/p&&p&如图,Draw Call的次数变成了1,而Saved by batching的次数变成了3。&/p&&p&静态批处理的好处很多,其中之一就是与下面要说的动态批处理相比,约束要少很多。所以一般推荐的是draw call的静态批处理来减少draw call的次数。那么接下来,我们就继续聊聊draw call的动态批处理。&/p&&h4&Dynamic Batching 动态批处理&/h4&&p&有阴就有阳,有静就有动,所以聊完了静态批处理,肯定跟着就要说说动态批处理了。首先要明确一点,Unity3D的draw
call动态批处理机制是引擎自动进行的,无需像静态批处理那样手动设置static。我们举一个动态实例化prefab的例子,如果动态物体共享相同的
材质,则引擎会自动对draw call优化,也就是使用批处理。首先,我们将一个cube做成prefab,然后再实例化500次,看看draw
call的数量。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&for(int i = 0; i & 500; i++)
cube = GameObject.Instantiate(prefab) as GameO
&/code&&/pre&&/div&&p&draw call的数量:&/p&&p&&figure&&img data-rawheight=&313& data-rawwidth=&305& src=&/e9b473c0ffded069e0ea1eaa87e5577d_b.png& class=&content_image& width=&305&&&/figure&可以看到draw call的数量为1,而 saved by batching的数量是499。而这个过程中,我们除了实例化创建物体之外什么都没做。不错,unity3d引擎为我们自动处理了这种情况。&/p&&p&但是有很多童靴也遇到这种情况,就是我也是从prefab实例化创建的物体,为何我的draw call依然很高呢?这就是匹夫上文说的,draw
call的动态批处理存在着很多约束。下面匹夫就演示一下,针对cube这样一个简单的物体的创建,如果稍有不慎就会造成draw
call飞涨的情况吧。&/p&&p&我们同样是创建500个物体,不同的是其中的100个物体,每个物体的大小都不同,也就是Scale不同。&/p&&br&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&for(int i = 0; i & 500; i++)
cube = GameObject.Instantiate(prefab) as GameO
if(i / 100 == 0)
cube.transform.localScale = new Vector3(2 + i, 2 + i, 2 + i);
&/code&&/pre&&/div&&br&&p&draw call的数量:&/p&&p&&figure&&img data-rawheight=&323& data-rawwidth=&310& src=&/b4cf9a4e00bc23ba7ef4b324aa58dd59_b.png& class=&content_image& width=&310&&&/figure&我们看到draw call的数量上升到了101次,而saved by
batching的数量也下降到了399。各位看官可以看到,仅仅是一个简单的cube的创建,如果scale不同,竟然也不会去做批处理优化。这仅仅是
动态批处理机制的一种约束,那我们总结一下动态批处理的约束,各位也许也能从中找到为何动态批处理在自己的项目中不起作用的原因:&/p&&ol&&li&批处理动态物体需要在每个顶点上进行一定的开销,所以动态批处理仅支持小于900顶点的网格物体。&/li&&li&如果你的着色器使用顶点位置,法线和UV值三种属性,那么你只能批处理300顶点以下的物体;如果你的着色器需要使用顶点位置,法线,UV0,UV1和切向量,那你只能批处理180顶点以下的物体。 &/li&&li&不要使用缩放。分别拥有缩放大小(1,1,1) 和(2,2,2)的两个物体将不会进行批处理。 &/li&&li&统一缩放的物体不会与非统一缩放的物体进行批处理。&/li&&li&使用缩放尺度(1,1,1) 和 (1,2,1)的两个物体将不会进行批处理,但是使用缩放尺度(1,2,1) 和(1,3,1)的两个物体将可以进行批处理。 &/li&&li&使用不同材质的实例化物体(instance)将会导致批处理失败。 &/li&&li&拥有lightmap的物体含有额外(隐藏)的材质属性,比如:lightmap的偏移和缩放系数等。所以,拥有lightmap的物体将不会进行批处理(除非他们指向lightmap的同一部分)。 &/li&&li&多通道的shader会妨碍批处理操作。比如,几乎unity中所有的着色器在前向渲染中都支持多个光源,并为它们有效地开辟多个通道。 &/li&&li&预设体的实例会自动地使用相同的网格模型和材质。&/li&&/ol&&p&所以,尽量使用静态的批处理。&/p&&h4&物理组件&/h4&&p&曾几何时,匹夫在做一个策略类游戏的时候需要在单元格上排兵布阵,而要侦测到哪个兵站在哪个格子匹夫选择使用了射线,由于士兵单位很多,而且为了精确每一帧都会执行检测,那时候CPU的负担叫一个惨不忍睹。后来匹夫果断放弃了这种做法,并且对物理组件产生了心理的阴影。&/p&&p&这里匹夫只提2点匹夫感觉比较重要的优化措施:&/p&&p&1.设置一个合适的Fixed Timestep。设置的位置如图:&/p&&p&&figure&&img data-rawheight=&658& data-rawwidth=&1003& src=&/1fb448a3b46cf_b.png& class=&origin_image zh-lightbox-thumb& width=&1003& data-original=&/1fb448a3b46cf_r.png&&&/figure&那何谓“合适”呢?首先我们要搞明白Fixed
Timestep和物理组件的关系。物理组件,或者说游戏中模拟各种物理效果的组件,最重要的是什么呢?计算啊。对,需要通过计算才能将真实的物理效果展
现在虚拟的游戏中。那么Fixed
Timestep这货就是和物理计算有关的啦。所以,若计算的频率太高,自然会影响到CPU的开销。同时,若计算频率达不到游戏设计时的要求,有会影响到
功能的实现,所以如何抉择需要各位具体分析,选择一个合适的值。
&/p&&p&2.就是不要使用网格碰撞器(mesh
collider):为啥?因为实在是太复杂了。网格碰撞器利用一个网格资源并在其上构建碰撞器。对于复杂网状模型上的碰撞检测,它要比应用原型碰撞器精
确的多。标记为凸起的(Convex )的网格碰撞器才能够和其他网格碰撞器发生碰撞。各位上网搜一下mesh
collider的图片,自然就会明白了。我们的手机游戏自然无需这种性价比不高的东西。&/p&&p&当然,从性能优化的角度考虑,物理组件能少用还是少用为好。&/p&&h4&处理内存,却让CPU受伤的GC&/h4&&p&在CPU的部分聊GC,感觉是不是怪怪的?其实小匹夫不这么觉得,虽然GC是用来处理内存的,但的确增加的是CPU的开销。因此它的确能达到释放内存的效果,但代价更加沉重,会加重CPU的负担,因此对于GC的优化目标就是尽量少的触发GC。&/p&&p&首先我们要明确所谓的GC是Mono运行时的机制,而非Unity3D游戏引擎的机制,所以GC也主要是针对Mono的对象来说的,而它管理的也是
Mono的托管堆。
搞清楚这一点,你也就明白了GC不是用来处理引擎的assets(纹理啦,音效啦等等)的内存释放的,因为U3D引擎也有自己的内存堆而不是和Mono一
起使用所谓的托管堆。&/p&&p&其次我们要搞清楚什么东西会被分配到托管堆上?不错咯,就是引用类型咯。比如类的实例,字符串,数组等等。而作为int,float,包括结构体struct其实都是值类型,它们会被分配在堆栈上而非堆上。所以我们关注的对象无外乎就是类实例,字符串,数组这些了。&/p&&p&那么GC什么时候会触发呢?两种情况:&/p&&ol&&li&首先当然是我们的堆的内存不足时,会自动调用GC。&/li&&li&其次呢,作为编程人员,我们自己也可以手动的调用GC。&/li&&/ol&&p&所以为了达到优化CPU的目的,我们就不能频繁的触发GC。而上文也说了GC处理的是托管堆,而不是Unity3D引擎的那些资源,所以GC的优化说白了也就是代码的优化。那么匹夫觉得有以下几点是需要注意的:&/p&&ol&&li&字符串连接的处理。因为将两个字符串连接的过程,其实是生成一个新的字符串的过程。而之前的旧的字符串自然而然就成为了垃圾。而作为引用类型的字符串,其空间是在堆上分配的,被弃置的旧的字符串的空间会被GC当做垃圾回收。&/li&&li&尽量不要使用foreach,而是使用for。foreach其实会涉及到迭代器的使用,而据传说每一次循环所产生的迭代器会带来24 Bytes的垃圾。那么循环10次就是240Bytes。&/li&&li&不要直接访问gameobject的tag属性。比如if (go.tag == “human”)最好换成if (go.CompareTag (“human”))。因为访问物体的tag属性会在堆上额外的分配空间。如果在循环中这么处理,留下的垃圾就可想而知了。&/li&&li&使用“池”,以实现空间的重复利用。&/li&&li&最好不用LINQ的命令,因为它们会分配临时的空间,同样也是GC收集的目标。而且我很讨厌LINQ的一点就是它有可能在某些情况下无法很好的进
行AOT编译。比如“OrderBy”会生成内部的泛型类“OrderedEnumerable”。这在AOT编译时是无法进行的,因为它只是在
OrderBy的方法中才使用。所以如果你使用了OrderBy,那么在IOS平台上也许会报错。&/li&&/ol&&h4&代码?脚本?&/h4&&p&聊到代码这个话题,也许有人会觉得匹夫多此一举。因为代码质量因人而异,很难像上面提到的几点,有一个明确的评判标准。也是,公写公有理,婆写婆有
理。但是匹夫这里要提到的所谓代码质量是基于一个前提的:Unity3D是用C++写的,而我们的代码是用C#作为脚本来写的,那么问题就来了~脚本和底
层的交互开销是否需要考虑呢?也就是说,我们用Unity3D写游戏的“游戏脚本语言”,也就是C#是由mono运行时托管的。而功能是底层引擎的C++
实现的,“游戏脚本”中的功能实现都离不开对底层代码的调用。那么这部分的开销,我们应该如何优化呢?&/p&&ol&&li&以物体的Transform组件为例,我们应该只访问一次,之后就将它的引用保留,而非每次使用都去访问。这里有人做过一个小实验,就是对比通过
方法GetComponent&Transform&()获取Transform组件,
通过MonoBehavor的transform属性去取,以及保留引用之后再去访问所需要的时间:
&ul&&li&GetComponent = 619ms&/li&&li&Monobehaviour = 60ms&/li&&li&CachedMB = 8ms&/li&&li&Manual Cache = 3ms&/li&&/ul&&/li&&/ol&&p&   2.如上所述,最好不要频繁使用GetComponent,尤其是在循环中。&/p&&p&   3.善于使用OnBecameVisible()和OnBecameVisible(),来控制物体的update()函数的执行以减少开销。&/p&&p&   4.使用内建的数组,比如用Vector3.zero而不是new Vector(0, 0, 0);&/p&&p&   5.对于方法的参数的优化:善于使用ref关键字。值类型的参数,是通过将实参的值&strong&复制&/strong&到形参,来实现按值传递到方法,也就是我们通常说的按值传递。复制嘛,总会让人感觉很笨重。比如Matrix4x4这样比较复杂的值类型,如果直接复制一份新的,反而不如将值类型的引用传递给方法作为参数。&/p&&p&好啦,CPU的部分匹夫觉得到此就介绍的差不多了。下面就简单聊聊其实匹夫并不是十分熟悉的部分,GPU的优化。&/p&&br&&br&&h2&GPU的优化&/h2&&p&GPU与CPU不同,所以侧重点自然也不一样。GPU的瓶颈主要存在在如下的方面:&/p&&ol&&li&填充率,可以简单的理解为图形处理单元每秒渲染的像素数量。&/li&&li&像素的复杂度,比如动态阴影,光照,复杂的shader等等&/li&&li&几何体的复杂度(顶点数量)&/li&&li&当然还有GPU的显存带宽&/li&&/ol&&p&那么针对以上4点,其实仔细分析我们就可以发现,影响的GPU性能的无非就是2大方面,一方面是顶点数量过多,像素计算过于复杂。另一方面就是GPU的显存带宽。那么针锋相对的两方面举措也就十分明显了。&/p&&ol&&li&&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23jianshao& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&减少顶点数量&i class=&icon-external&&&/i&&/a&,简化计算复杂度。&/li&&li&&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23daikuan& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&压缩图片&i class=&icon-external&&&/i&&/a&,以适应显存带宽。&/li&&/ol&&h4&减少绘制的数目&/h4&&p&那么第一个方面的优化也就是减少顶点数量,简化复杂度,具体的举措就总结如下了:&/p&&ul&&li&保持材质的数目尽可能少。这使得Unity更容易进行批处理。&/li&&li&使用纹理图集(一张大贴图里包含了很多子贴图)来代替一系列单独的小贴图。它们可以更快地被加载,具有很少的状态转换,而且批处理更友好。&/li&&li&如果使用了纹理图集和共享材质,使用Renderer.sharedMaterial 来代替Renderer.material 。 &/li&&li&使用光照纹理(lightmap)而非实时灯光。&/li&&li&使用LOD,好处就是对那些离得远,看不清的物体的细节可以忽略。&/li&&li&遮挡剔除(Occlusion culling)&/li&&li&使用mobile版的shader。因为简单。&/li&&/ul&&h4&优化显存带宽&/h4&&p&第二个方向呢?压缩图片,减小显存带宽的压力。&/p&&ul&&li&OpenGL ES 2.0使用ETC1格式压缩等等,在打包设置那里都有。&/li&&li&使用mipmap。&/li&&/ul&&h4&MipMap&/h4&&p&这里匹夫要着重介绍一下MipMap到底是啥。因为有人说过MipMap会占用内存呀,但为何又会优化显存带宽呢?那就不得不从MipMap是什么开始聊起。一张图其实就能解决这个疑问。&/p&&p&&figure&&img data-rawheight=&256& data-rawwidth=&384& src=&/ee1bf6293d39daf6df5ad7f_b.jpg& class=&content_image& width=&384&&&/figure&&em&上面是一个mipmap 如何储存的例子,左边的主图伴有一系列逐层缩小的备份小图&/em&&/p&&p&是不是很一目了然呢?Mipmap中每一个层级的小图都是主图的一个特定比例的缩
小细节的复制品。因为存了主图和它的那些缩小的复制品,所以内存占用会比之前大。但是为何又优化了显存带宽呢?因为可以根据实际情况,选择适合的小图来渲
染。所以,虽然会消耗一些内存,但是为了图片渲染的质量(比压缩要好),这种方式也是推荐的。&br&&/p&&br&&br&&h2&内存的优化&/h2&&p&既然要聊Unity3D运行时候的内存优化,那我们自然首先要知道Unity3D游戏引擎是如何分配内存的。大概可以分成三大部分:&/p&&ol&&li&Unity3D内部的内存&/li&&li&Mono的托管内存&/li&&li&若干我们自己引入的DLL或者第三方DLL所需要的内存。&/li&&/ol&&p&第3类不是我们关注的重点,所以接下来我们会分别来看一下&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23unity3d& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Unity3D内部内存&i class=&icon-external&&&/i&&/a&和&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23mono& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Mono托管内存&i class=&icon-external&&&/i&&/a&,最后还将分析一个官网上&a href=&/?target=http%3A///murongxiaopifu/p/4284988.html%23ab& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Assetbundle的案例&i class=&icon-external&&&/i&&/a&来说明内存的管理。&/p&&h4&Unity3D内部内存&/h4&&p&Unity3D的内部内存都会存放一些什么呢?各位想一想,除了用代码来驱动逻辑,一个游戏还需要什么呢?对,各种资源。所以简单总结一下Unity3D内部内存存放的东西吧:&/p&&ul&&li&资源:纹理、网格、音频等等&/li&&li&GameObject和各种组件。&/li&&li&引擎内部逻辑需要的内存:渲染器,物理系统,粒子系统等等&/li&&/ul&&h4&Mono托管内存&/h4&&p&因为我们的游戏脚本是用C#写的,同时还要跨平台,所以带着一个Mono的托管环境显然必须的。那么Mono的托管内存自然就不得不放到内存的优化
范畴中进行考虑。那么我们所说的Mono托管内存中存放的东西和Unity3D内部内存中存放的东西究竟有何不同呢?其实Mono的内存分配就是很传统的
运行时内存的分配了:&/p&&ul&&li&值类型:int型啦,float型啦,结构体struct啦,bool啦之类的。它们都存放在堆栈上(注意额,不是堆所以不涉及GC)。&/li&&li&引用类型:其实可以狭义的理解为各种类的实例。比如游戏脚本中对游戏引擎各种控件的封装。其实很好理解,C#中肯定要有对应的类去对应游戏引擎中的控件。那么这部分就是C#中的封装。由于是在堆上分配,所以会涉及到GC。&/li&&/ul&&p&而Mono托管堆中的那些封装的对象,除了在在Mono托管堆上分配封装类实例化之后所需要的内存之外,还会牵扯到其背后对应的游戏引擎内部控件在Unity3D内部内存上的分配。&/p&&p&举一个例子:&/p&&p&一个在.cs脚本中声明的WWW类型的对象www,Mono会在Mono托管堆上为www分配它所需要的内存。同时,这个实例对象背后的所代表的引擎资源所需要的内存也需要被分配。&/p&&p&一个WWW实例背后的资源:&/p&&ul&&li&压缩的文件&/li&&li&解压缩所需的缓存&/li&&li&解压缩之后的文件&/li&&/ul&&p&如图:&/p&&figure&&img data-rawheight=&218& data-rawwidth=&548& src=&/a977a65a7d5cc1b88f6dec_b.png& class=&origin_image zh-lightbox-thumb& width=&548& data-original=&/a977a65a7d5cc1b88f6dec_r.png&&&/figure&那么下面就举一个AssetBundle的例子:
&h4&Assetbundle的内存处理&/h4&&p&以下载Assetbundle为例子,聊一下内存的分配。匹夫从官网的手册上找到了一个使用Assetbundle的情景如下:&/p&&br&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&IEnumerator DownloadAndCache (){
// Wait for the Caching system to be ready
while (!Caching.ready)
// Load the AssetBundle file from Cache if it exists with the same version or download and store it in the cache
using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
//WWW是第1部分
if (www.error != null)
throw new Exception(&WWW download had an error:& + www.error);
AssetBundle bundle = www.assetB//AssetBundle是第2部分
if (AssetName == &&)
Instantiate(bundle.mainAsset);//实例化是第3部分
Instantiate(bundle.Load(AssetName));
// Unload the AssetBundles compressed contents to conserve memory
bundle.Unload(false);
} // memory is freed from the web stream (www.Dispose() gets called implicitly)
&/code&&/pre&&/div&&br&&p&内存分配的三个部分匹夫已经在代码中标识了出来:&/p&&ol&&li&&em&&strong&Web Stream&/strong&&/em&:包括了压缩的文件,解压所需的缓存,以及解压后的文件。&/li&&li&&em&&strong&AssetBundle&/strong&&/em&:Web Stream中的文件的映射,或者说引用。&/li&&li&&strong&&em&实例化之后的对象&/em&:&/strong&就是引擎的各种资源文件了,会在内存中创建出来。&/li&&/ol&&p&那就分别解析一下:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)
&/code&&/pre&&/div&&ol&&li&将压缩的文件读入内存中&/li&&li&创建解压所需的缓存&/li&&li&将文件解压,解压后的文件进入内存&/li&&li&关闭掉为解压创建的缓存&/li&&/ol&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&AssetBundle bundle = www.assetB
&/code&&/pre&&/div&&ol&&li&AssetBundle此时相当于一个桥梁,从Web Stream解压后的文件到最后实例化创建的对象之间的桥梁。&/li&&li&所以AssetBundle实质上是Web Stream解压后的文件中各个对象的映射。而非真实的对象。&/li&&li&实际的资源还存在Web Stream中,所以此时要保留Web Stream。&/li&&/ol&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&Instantiate(bundle.mainAsset);
&/code&&/pre&&/div&&ol&&li&通过AssetBundle获取资源,实例化对象&/li&&/ol&&p&最后各位可能看到了官网中的这个例子使用了:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&using(WWW www = WWW.LoadFromCacheOrDownload (BundleURL, version)){
&/code&&/pre&&/div&&p&这种using的用法。这种用法其实就是为了在使用完Web Stream之后,将内存释放掉的。因为WWW也继承了idispose的接口,所以可以使用using的这种用法。其实相当于最后执行了:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&//删除Web Stream
www.Dispose();
&/code&&/pre&&/div&&p&OK,Web Stream被删除掉了。那还有谁呢?对Assetbundle。那么使用&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&//删除AssetBundle
bundle.Unload(false);
&/code&&/pre&&/div&&p&ok,写到这里就先打住啦。写的有点超了。有点赶也有点临时,日后在补充编辑。&/p&&br&&br&&h2&使用Unity Profiler工具检测内存&/h2&&p&这篇文章当时写的时候略显仓促,因此并没有特别介绍Unity Profiler工具,也更谈不上用Unity Profiler工具来监测内存的使用状态了。但是使用Unity Profiler工具来监测还是十分必要的,下面就简单补充一下这方面的知识。&/p&&p&&figure&&img data-rawheight=&222& data-rawwidth=&550& src=&/3ab206cc4ffd8ed9f302c259a46a2828_b.png& class=&origin_image zh-lightbox-thumb& width=&550& data-original=&/3ab206cc4ffd8ed9f302c259a46a2828_r.png&&&/figure&在Profiler工具中提供了两种模式供我们监测内存的使用情况,即简易模式和详细模式。在简易模式中,我们可以看到总的内存(total)列出了两列,即&strong&Used Total(使用总内存)&/strong&和&strong&Reserved Total(预定总内存)&/strong&。Used Total和Reserved 均是物理内存,其中Reserved是unity向系统申请的总内存,Unity底层为了不经常向系统申请开辟内存,开启了较大一块内存作为&strong&缓存&/strong&,即所谓的&strong&Reserved内存&/strong&,
而运行时,unity所使用的内存首先是向Reserved中来申请内存,当不使用时也是先向Reserved中释放内存,从而来保证游戏运行的流畅性。
一般来说,Used Total越大,则Reserved Total越大,而当Used Total降下去后,Reserved
Total也是会随之下降的(但并不一定与Used Total同步)。&/p&&p&Unity3D的内存从大体上可以分为以下几个部分:&/p&&ol&&li&Unity:位Unity3D的底层代码所分配的内存。&/li&&li&Mono:即托管堆。Mono运行时在运行游戏脚本时所需要的内存,换句话说托管堆的大小与我们的GameObject数量、资源量无关,仅是脚本代码造成的。这部分内存是有&strong&垃圾回收机制&/strong&的。&/li&&li&GfxDriver:可以理解为&strong&GPU显存开销&/strong&,主要由Texture,Vertex buffer以及index buffer组成。所以尽可能地减少或释放Texture和mesh等资源,即可降低GfxDriver内存。&/li&&li&FMOD:音频的内存开销。&/li&&li&Profiler&/li&&/ol&&p&而在简易模式下的监视器最下方,则列出了常见的一些资源以及它们所消耗的内存。&/p&&ol&&li&纹理&/li&&li&网格&/li&&li&材质&/li&&li&动作&/li&&li&音频&/li&&li&游戏对象的数量&/li&&/ol&&p&而详细模式则需要点击“Take Sample”按钮来捕获详细的内存使用情况。需要注意的是,由于获得数据需要花费一定的时间,因此我们无法获得实时的详细内存的使用情况。在详细模式中,我们可以观察每个具体资源和游戏对象的内存使用情况。&/p&
0x00 前言刚开始写这篇文章的时候选了一个很土的题目。。。《Unity3D优化全解析》。因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而
给自己“文(dou)学(bi)”加工留下的余地就少了很多。但又觉得这块是不得不提的一个地方,平时见到很…
&figure&&img src=&/v2-eb24dbad282d6f1fda047_b.jpg& data-rawwidth=&850& data-rawheight=&512& class=&origin_image zh-lightbox-thumb& width=&850& data-original=&/v2-eb24dbad282d6f1fda047_r.jpg&&&/figure&&h2&0x00 前言&/h2&&p&在很长一段时间里,Unity项目的开发者的优化指南上基本都会有一条关于使用GetComponent方法获取组件的条目(例如14年我的这篇博客&a href=&/?target=http%3A///84323/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《深入浅出聊Unity3D项目优化:从Draw Calls到GC》&i class=&icon-external&&&/i&&/a&)。有时候还会发展为连一些Unity内部对象的属性访问器都要小心使用的注意事项,记得曾经有一段时间我们的项目组也会严格要求把例如transform、gameobject之类的属性访问器进行缓存使用。这其中的确有一些措施是有道理的,但很多朋友却也是知其然而不知其所以然,朦胧之间似乎有一个印象,进而成为习惯。那么本文就来聊聊Unity优化这个题目中偶尔会被误解的内容吧。&/p&&br&&br&&h2&0x01 来自官方的建议&/h2&&p&本文主要是关于Unity脚本优化的,而脚本和引擎打交道的一个常见情景便是使用GetComponent之类的方法, 接触过Unity的朋友大都知道要将GetComponent的结果进行缓存使用。不过很多人的理由是:&/p&&blockquote&&p&使用GetComponent会造成GC,从而影响效率。&/p&&/blockquote&&p&所以从Unity官方的手册来寻找关于GetCompnent的线索是最好的途径。的确,2011年的3.5.3版本的官方手册就已经建议减少使用GetCompnent方法来获取组件了,同时建议我们使用变量缓存获取的组件。&/p&&blockquote&&p&Reduce GetComponent Calls&br& Using GetComponent or built-in component accessors can have a noticeable overhead. You can avoid this by getting a reference to the component once and assigning it to a variable (sometimes referred to as &caching& the reference).&/p&&/blockquote&&p&但是,我们可以发现手册上只说了频繁的调用GetComponent会导致CPU的开销增加,但是并没有提到GC的问题。所以,为了验证GetComponent到底会导致哪些性能上的问题,我们可以做几个小测试。&/p&&br&&br&&h2&0x02 和GC无关的性能优化&/h2&&p&众所周知,GetComponent有三个重载版本,分别是:&/p&&ul&&li&GetComponent()&/li&&li&GetComponent(typeof(T))&/li&&li&GetComponent(string)&/li&&/ul&&p&所以,测试的第一步就是先确定一个效率最高的重载版本,之后再去检查它们各自引起的堆内存分配。&/p&&h4&“效率之王”&/h4&&p&为此,我们在&strong&5.X版本的Unity&/strong&中准备一个空白的场景并实现一个简单的计时器,之后就可以开始测试了。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&using S
using System.D
/// &summary&
/// 简易的计时类
/// &/summary&
public class YiWatch : IDisposable
#region 字段
private string testN
private int testC
#endregion
#region 构造函数
public YiWatch(string name, int count)
this.testName =
this.testCount = count & 0 ? count : 1;
this.watch = Stopwatch.StartNew();
#endregion
#region 方法
public void Dispose()
this.watch.Stop();
float totalTime = this.watch.ElapsedM
UnityEngine.Debug.Log(string.Format(&测试名称:{0}
总耗时:{1}
单次耗时:{2}
测试数量:{3}&,
this.testName, totalTime, totalTime / this.testCount, this.testCount));
#endregion
&/code&&/pre&&/div&&p&自定义的组件TestComp,以及我们的测试代码,每一个方法会被调用1000000次以便于观察测试结果:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
int testCount = 1000000;//定义测试的次数
using (new YiWatch(&GetComponent&&&, testCount))
for(int i = 0; i & testC i++)
GetComponent&TestComp&();
using (new YiWatch(&GetComponent(typeof(T))&, testCount))
for(int i = 0; i & testC i++)
GetComponent(typeof(TestComp));
using (new YiWatch(&GetComponent(string)&, testCount))
for(int i = 0; i & testC i++)
GetComponent(&TestComp&);
&/code&&/pre&&/div&&p&运行的结果如图(单位ms):&figure&&img src=&/v2-f52a9bed8debc3598cc4_b.jpg& data-rawheight=&253& data-rawwidth=&1232& class=&origin_image zh-lightbox-thumb& width=&1232& data-original=&/v2-f52a9bed8debc3598cc4_r.jpg&&&/figure&&br&我们可以发现在Unity 5.x版本中,泛型版本的GetComponent&&的性能最好,而GetComponent(string)的性能最差。&/p&&p&做成柱状图可能更加直观:&/p&&figure&&img src=&/v2-58a69a15cebcaf66c8abb_b.jpg& data-rawheight=&1107& data-rawwidth=&1141& class=&origin_image zh-lightbox-thumb& width=&1141& data-original=&/v2-58a69a15cebcaf66c8abb_r.jpg&&&/figure&&p&接下来,我们来测试一下我们感兴趣的堆内存分配吧。为了更好的观察,我们把测试代码放在Update中执行。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&void Update()
for(int i = 0; i & testC i++)
GetComponent&TestComp&();
&/code&&/pre&&/div&&p&同样每帧执行1000000次的GetComponent方法。打开profiler来观察一下堆内存分配吧:&/p&&p&&figure&&img src=&/v2-44a9c9a5f37a2cd3f02eb_b.jpg& data-rawheight=&1092& data-rawwidth=&1987& class=&origin_image zh-lightbox-thumb& width=&1987& data-original=&/v2-44a9c9a5f37a2cd3f02eb_r.jpg&&&/figure&&br&我们可以发现,虽然&strong&频繁调用GetComponent时会造成CPU的开销很大,但是堆内存分配却是0B&/strong&。&/p&&p&但是,我和朋友聊天偶尔聊到这个话题时,朋友说有时候会发现每次调用GetComponent时,在profiler中都会增加0.5kb的堆内存分配。不知道各位读者是否有遇到过这个问题,那么是不是说GetComponent方法有时的确会造成GC呢?&/p&&p&答案是否定的。&/p&&p&这是因为朋友是在&strong&Editor中运行&/strong&,并且&strong&GetComponent返回Null&/strong&的情况下,才会出现堆内存分配的问题。&br& 我们还可以继续我们的测试,这次把TestComp组件从场景中去除,同时把测试次数改为100000。我们在Editor运行测试,可以看到结果如下:&br&&figure&&img src=&/v2-0a9c5451dfb0de2aaae0bd49428caea3_b.jpg& data-rawheight=&684& data-rawwidth=&1341& class=&origin_image zh-lightbox-thumb& width=&1341& data-original=&/v2-0a9c5451dfb0de2aaae0bd49428caea3_r.jpg&&&/figure&&br&10000次调用GetComponent方法,并且返回为Null时,观察Editor的Profiler,可以发现每一帧都分配了5.6MB的堆内存。&/p&&p&那么如果在移动平台上调用GetComponent方法,并且返回为Null时,是否会造成堆内存分配呢?&/p&&p&这次我们让这个测试跑在一个小米4的手机上,连接profiler观察堆内存分配,结果如图:&/p&&figure&&img src=&/v2-e55e9fbddd11ac86a86ea803e03}

我要回帖

更多关于 无聊打豆豆 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信