沙盘模拟游戏大神请进 寻找一沙 盘游戏求助

RTS游戏有很多,可能大家比较熟悉的有Warcraft III (dota)和 StarCraft,&br&早期西木的沙丘,红色警戒更是rts游戏的鼻祖,带给我们无限的欢乐和回忆。&br&还有当下比较流行lol与dota2,实际上都是孙子辈的游戏了。&br&&br&那么他们到底是怎么做到高频操作又同步的呢?&br&&br&&b&同步机制&/b&&br&&br&假设游戏中A,B两个玩家移动,并同时向对方发出射击指令&br&如果没有合适的同步机制&br&那么可能出现的情况有&br&1 A屏幕显示B已经被杀死,B屏幕显示A已经被杀死&br&2 或者在瞄准后确打不到对方&br&图中玩家Plyaer1,Plyaer2在两个不同的客户端,表现出不同效果&br&&figure&&img src=&https://pic1.zhimg.com/6acdc512a5f34613fcd8_b.png& data-rawwidth=&554& data-rawheight=&433& class=&origin_image zh-lightbox-thumb& width=&554& data-original=&https://pic1.zhimg.com/6acdc512a5f34613fcd8_r.jpg&&&/figure&&br&&br&因为网络是有延时的,而每个玩家的网络情况都不尽相同。&br&还有每帧渲染的延迟(早期的计算机性能不够好的时候会出现这个问题)&br&同步机制最重要的作用就是解决延迟等可能发生不一致的情况。&br&&br&&b&同步机制的分类&/b&&br&&b&Peer-to-peer模式&/b&: 没有服务器,每个玩家互相连接,各自模拟整个流程.典型的lockstep模式&br&优点:减少主机带来的延时&br&缺点:容易作弊&br&&figure&&img src=&https://pic2.zhimg.com/77bb6f1cb5bea2cbaecd45_b.png& data-rawwidth=&495& data-rawheight=&286& class=&origin_image zh-lightbox-thumb& width=&495& data-original=&https://pic2.zhimg.com/77bb6f1cb5bea2cbaecd45_r.jpg&&&/figure&&br&&b&Client-Server模式&/b&&br&所有的操作需经过服务器确认后才能进行客户端模拟,如arpg传奇类都是此架构,如果延时高就会有明显的卡顿。&br&优点:服务器是绝对的权威,可以防止作弊,可以做更多的管理与限制&br&缺点:服务器变的更复杂,服务器断线,所有玩家断线,属于服务器依赖型。&br&&br&&figure&&img src=&https://pic1.zhimg.com/a6f6fdbcac_b.png& data-rawwidth=&448& data-rawheight=&286& class=&origin_image zh-lightbox-thumb& width=&448& data-original=&https://pic1.zhimg.com/a6f6fdbcac_r.jpg&&&/figure&&br&&br&早期的RTS游戏大多采用Lockstep方案来设计,像罗马帝国,沙丘之类。&br&Lockstep最早用于军队中&br&&figure&&img src=&https://pic1.zhimg.com/171d4c617b5a16f69928b76ace204374_b.png& data-rawwidth=&494& data-rawheight=&341& class=&origin_image zh-lightbox-thumb& width=&494& data-original=&https://pic1.zhimg.com/171d4c617b5a16f69928b76ace204374_r.jpg&&&/figure&&br&就是说玩家的数据每个时间段同步一次,同步的走。&br&&br&&br&标准的lockstep模式&br&1 每个玩家互相连接,整个游戏过程划分成一组turn指令帧,由玩家自我模拟&br&2 游戏速度取决于网络最慢的那个玩家&br&3 一个玩家掉线不会影响到其他玩家&br&&br&&b&什么是Turn?&/b&&br&一个turn可以理解成1个回合,相信大家都玩过回合制游戏吧&br&只是这个turn非常短,大概100MS-200MS&br&玩家相互之间发送的指令在每个turn间隔发出&br&&br&&figure&&img src=&https://pic1.zhimg.com/ce391a087c4_b.png& data-rawwidth=&328& data-rawheight=&173& class=&content_image& width=&328&&&/figure&每个玩家只需要接收指令,并在本地播放指令就可以啦&br&&br&War3如何运算伤害?&br&玩家到底是发送什么指令到主机,主机到底参与了什么计算呢?&br&实际上玩家都只需要发送基本的指令如选择单位,移动单位,使用技能1234,点击物品栏1-6,可以通过APM查看软件看到一些基本操作事件&br&&figure&&img src=&https://pic2.zhimg.com/2dbc77a4df380ebf6329b1_b.png& data-rawwidth=&540& data-rawheight=&566& class=&origin_image zh-lightbox-thumb& width=&540& data-original=&https://pic2.zhimg.com/2dbc77a4df380ebf6329b1_r.jpg&&&/figure&&br&也就是说所有的一切伤害计算都是在本地计算完成的&br&包括伤害,暴击,命中,刷怪等,只要初始化好随机数种子就可以啦&br&玩家只是发送操作指令,如点击坐标(0,1, 0),左键框选(100,100,50,50)等&br&每个玩家都在模拟全部的流程&br&&br&那么War3到底算不算使用lockstep模式,或者是特殊的client-server?&br&其实可以通过几个问题判断出&br&1 非主机玩家卡是否可以影响到其他玩家,如果不会,那么更可能是client-server模式&br&2 可以通过抓包工具拦截网络数据包的流向,来判断是否是peer to peer的连接方式还是只连接到主机(或通过主机强制掉线方式判断)。&br&一个外国朋友的回答&br&&br&&figure&&img src=&https://pic4.zhimg.com/f2e3cd93ccbd85cabb8b_b.png& data-rawwidth=&728& data-rawheight=&221& class=&origin_image zh-lightbox-thumb& width=&728& data-original=&https://pic4.zhimg.com/f2e3cd93ccbd85cabb8b_r.jpg&&&/figure&&br&个人也认为War3是基于Client-Server的一种的特殊模式,主机肯定需要验证一些逻辑。&br&主机负责广播每个client的指令&br&这存在两个问题&br&&ol&&li&本机(非主机)发出的指令,如果超时或者丢包,是否直接丢弃?&br&&/li&&li&其他玩家的指令,主机转发未成功确认,如何处理?&br&&/li&&/ol&&br&第一个问题&br&&ol&&li&如果是本机(非主机)发出的指令超时,可以直接丢弃.(如果不丢弃,其他玩家就必须等待结果,这样会导致挂起,而且会非常频繁,这里还有udp协议容易丢包的原因,但是war3好像并没有经常性的挂起)&/li&&/ol&&br& 还有一种可能,客户端得知之前的turn没有发送成功,把当前这轮的指令和上一轮的指令进行合并,然后一起发出,这样本地客户端就不会有任何的异样了。&br&&br&例如玩家移动到A后再移动到B&br&上个turn的指令是移动到A点,但是没有发成功,下个turn的指令先移动到A,再移动到B,这样在客户端就不会有丢失的感觉啦,还是可以正常的模拟而不会影响到其他玩家。&br&&br&2. 收其他玩家的指令超时,那么属于我们自身网络的问题,如果丢弃必将导致游戏进程不同步,所以服务器必须将他们的turn指令都缓存起来,&br&或者缓存一部分turn指令集,在我网络稳定的时候,把丢失的那一部分turn指令集发给我,而我只需要下载那个list加快gameupdate就好啦。&br&&br&有些朋友问到外挂的问题&br&相信玩过魔兽的人基本都用过,实际上像战争迷雾,显示单位等只会保存一个状态值在内存中,只要定位到内存地址,改一下变量值就好了,一般是服务器是不会检测这个的。&br&而攻击力,道具数量等,由于大家都需要模拟,你本地修改了,会影响到其他人,程序就会发生蝴蝶效应。&br&开图挂应该是这类游戏最常见的了。&br&&figure&&img src=&https://pic1.zhimg.com/a429d14114baeab904248_b.png& data-rawwidth=&794& data-rawheight=&385& class=&origin_image zh-lightbox-thumb& width=&794& data-original=&https://pic1.zhimg.com/a429d14114baeab904248_r.jpg&&&/figure&&figure&&img src=&https://pic1.zhimg.com/056f8f323c8f_b.png& data-rawwidth=&823& data-rawheight=&353& class=&origin_image zh-lightbox-thumb& width=&823& data-original=&https://pic1.zhimg.com/056f8f323c8f_r.jpg&&&/figure&&br&&br&至于现在非常流行的 Dota2 和 英雄联盟,会额外的加入更多服务器来验证和计算一些外部数据,&br&但内部原理是一致的,早期的游戏与现在的网游不可同日而语。&br&欢迎各游戏圈朋友加-Q群
RTS游戏有很多,可能大家比较熟悉的有Warcraft III (dota)和 StarCraft, 早期西木的沙丘,红色警戒更是rts游戏的鼻祖,带给我们无限的欢乐和回忆。 还有当下比较流行lol与dota2,实际上都是孙子辈的游戏了。 那么他们到底是怎么做到高频操作又同步的呢? …
&figure&&img src=&https://pic3.zhimg.com/v2-c9edb6eb_b.jpg& data-rawwidth=&501& data-rawheight=&239& class=&origin_image zh-lightbox-thumb& width=&501& data-original=&https://pic3.zhimg.com/v2-c9edb6eb_r.jpg&&&/figure&&blockquote&此文章是关于技能模块的第三篇文章,文中的一些概念和介绍请阅读前两篇文章:&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&一个MMORPG的常规技能系统 - 知乎专栏&/a& 以及 &a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&技能系统的同步机制 - 知乎专栏&/a&&/blockquote&&br&&p&游戏在真实的环境中,有些特殊情况需要处理,本文介绍技能模块是如何处理人为作弊和现实中的网络导致的一些问题。&/p&&p&主要介绍四个部分:&/p&&ol&&li&防外挂&/li&&li&网络延迟问题解决&/li&&li&网络卡顿和抖动&/li&&li&流量优化&/li&&/ol&&p&注意,本文默认介绍的是玩家的技能处理,也就是技能的控制端在玩家的客户端。对于控制端在服务器的小怪,基本没有前三个问题。&/p&&p&每个游戏的技能系统的实现不同,处理方式也有可能不太一样,本文所使用的技能系统参考之前写的文章:&/p&&h2&1.防外挂&/h2&&p&由于技能是客户端先行,因此技能模块很多逻辑是放在客户端的,由客户端控制技能流程并且通知服务器执行相应的功能。由此可见,技能是由客户端发起的,服务端必须对收到的技能执行命令进行验证以保证技能确实是可用的,防止玩家通过外挂重复发送技能释放消息无限次释放技能。&/p&&p&每个游戏技能系统实现不同,可能对应的逻辑也不太一样。基本原则是:&br&1.服务端保存技能释放可用性的相关信息,比如技能CD、技能蓝量等。&br&2.技能结算在服务端执行,客户端管理技能执行流程。&br&3.服务端每次真正的释放技能之前,对技能进行判断是否可用。&br&4.服务端收到的技能执行消息后,根据实现系统的规则进行相应验证。&/p&&p&下面,以我们的技能系统为例,介绍我们是如何实现防外挂的。&/p&&p&在我们系统中,技能同步包括三类同步消息:&/p&&ul&&li&技能根节点enter (root_enter): 表示技能树根结点进入执行,表示一个技能树开始。&/li&&li&技能叶子节点enter(action_enter): 表示技能的执行节点进入执行,表示一个技能执行模块开始执行,有一个执行模块有后摇时间,根据后摇时间,他会自动结束。&/li&&li&根节点exit(root_exit) :表示技能树结束。&/li&&/ul&&p&服务端会接受到客户端发来的这三种技能消息,其中,服务端收到root_enter和action_enter消息后,需要对消息的真实性进行验证,root_exit表示技能结束,和防外挂没有关系,无需验证。&/p&&p&由于技能的信息都是以root_enter来控制的,比如技能CD、技能耗蓝和沉默/晕眩等导致的技能是否可用,因此,当服务端收到root_enter的时候,首先要判断这个技能是否真的可以释放,判断后进入相关逻辑。&/p&&p&action_enter是技能真正的执行消息,技能模块并没有方法判断一个执行节点(技能树叶子节点)是否可用,因此,当收到action_enter的消息时,只能根据root节点的信息进行判断。我们进行了两种判断:&/p&&ul&&li&判断一:在root_enter和root_exit执行期间,表示正在执行这个大技能,收到action_enter后,判断这个action是否属于正在执行的大技能叶子节点,若不属于,不能执行。&/li&&li&判断二:我们还判断了action_enter消息对应的执行节点的顺序,保证执行节点是按照合法的顺序执行的,而不能一直执行某一个特别牛逼的叶子节点。&/li&&li&此外,服务端执行action节点的时候,不能同时执行多个,每次只能执行一个action节点,并且需要持续相应的时间(action节点的后摇时间),上一个action节点执行结束后才能执行下一个节点。&/li&&/ul&&p&基于以上机制,可以保证服务端收到的技能消息只有合法的消息才可以执行。&/p&&blockquote&战斗模块的防外挂主要包括技能模块移动模块,把这两块解决外挂问题,战斗模块基本就能解决外挂问题了。等有空我写一下我们在移动模块是如何进行的防外挂机制。&/blockquote&&h2&2.网络延迟&/h2&&p&在真实的网络环境中,网络延迟是难免的。造成的结果是,服务端执行逻辑一直晚于客户端。这种网络延迟并不会造成错误,但是在网络延迟大的时候会造成表现和结算不能对应上。为了解决着这个问题,一般采取的方式是基于网络延迟时间让服务端加快执行速度,去追赶客户端。&/p&&p&以技能结算为例,当server端收到action_enter消息后,根据当前时间和客户端开始时间可以计算出网络延迟,将服务端的前摇时间减少两个网络延迟,当客户端收到技能结算消息时,正好是客户端的技能结算时间。参考文章 &a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&技能系统的同步机制 - 知乎专栏&/a&&/p&&br&&figure&&img src=&https://pic4.zhimg.com/v2-3edbd32c8dcc07_b.png& data-rawwidth=&577& data-rawheight=&489& class=&origin_image zh-lightbox-thumb& width=&577& data-original=&https://pic4.zhimg.com/v2-3edbd32c8dcc07_r.jpg&&&/figure&&br&&h2&3.网络波动和卡顿&/h2&&p&对于手机游戏来说,网络波动和卡顿也是难免的,这种情况造成的结果就是,原本按一定的顺序以一定的时间间隔到达服务器/客户端的消息,可能同时到达了服务器/客户端,或者时间间隔忽大忽小。&/p&&p&一般来说,当服务器由于网络卡顿同时接收到多个技能开始执行的消息时,可以通过两种方式进行处理。&/p&&p&1.接到技能执行消息后马上执行,这样导致的问题时可能在同一帧收到多条技能执行消息,并且在同一帧执行多个技能。这个策略的好处是处理方式比较简单,而且能让服务器尽快的跟客户端同步。但是为了防止玩家使用外挂同时发送多个技能执行请求,这个策略是不可行的。&/p&&p&2.当接收到多条技能执行消息时,按序依次执行技能,每个技能的执行时间结束后,才执行下一条技能。这种策略的问题是,若服务器的延迟与客户端较大,如果玩家一直在不停的放技能,会导致服务器与客户端的延迟越来越大。&/p&&p&为了解决此类问题,我们在客户端和服务端采取不同的处理方式。&/p&&h3&3.1 proxy服务端(主控端是玩家控制的客户端)&/h3&&p&服务器是技能真正执行的地方,需要保证技能正确的执行。因此,服务端基于第二种方式解决网络卡顿,同时增加了一些逻辑,以保证服务端不会和客户端延迟过大。&/p&&h4&3.1.1 消息队列&/h4&&p&proxy服务端以队列的形式保存下来收到的技能消息并依次执行。&/p&&p&当服务端收到技能同步消息后,就将消息存入队列,技能执行模块就根据队列依次执行,其中,action_enter会执行一段时间,后摇时间点到达后结束。root_enter和root_exit对技能执行状态进行控制。&/p&&h4&3.1.2 防止服务端延迟过大&/h4&&p&在某些情况下,服务端可能迟于户端较长时间。比如网络卡顿,导致客户端多次释放技能的消息同时到达服务端。为了解决这种问题,我们通过两种机制,让服务端追赶客户端。&/p&&ul&&li&当接收到一个新的root_enter信息时,马上清空掉队列中的所有技能消息,执行队列中对应的root_exit消息。然后执行新的root_enter信息。此策略表现是:当玩家执行一个新的技能,服务端之前还没有执行的技能就不再执行了。&/li&&li&叶子节点的持续时间(后摇时间)根据网络延迟进行一定的减少,给定一个系数比如0.8,一方面保证服务端不会快速的执行多个action节点,同时可以让服务端尽快的追上客户端。&/li&&/ul&&h3&3.2 proxy客户端 (主控端是玩家控制的客户端或者是服务器控制的怪物)&/h3&&p&客户端只是执行技能的表现,在网络条件较差的情况下,我们只要保证游戏的正确性(不出错误,不影响服务器正确运行,网络条件变好后游戏可以正确运行)即可,至于表现和用户体验,尽力就可以了。&/p&&p&在我们游戏中,客户端的proxy端采用的是接到消息后马上执行的策略。客户端接到既能执行信息,那么就把之前正在执行的技能停止掉,然后执行新的就好了。&/p&&h2&4 流量优化&/h2&&p&流量的优化基本上没有太多通用的技巧,最基本也是最重要的就是:不要同步没有意义的消息。&/p&&p&这句话是废话,但是也是流量优化的指导方向。听起来很简单,但是实现起来非常难,甚至想不同步冗余信息是不可能的。&/p&&p&为什么说这件事很难,甚至是不可能的呢?&/p&&p&一,只有梳理清楚了执行逻辑,才能确定哪些同步消息是必要的,哪些是冗余的。执行逻辑一定要清晰。&/p&&p&二,比如一条消息,某些情况是不用同步的,有的情况又要同步。那么发,还是不发。再细节一点,比如一个参数,有的情况不需要同步,有的情况需要同步。如果对每种情况进行特殊化编写代码,代码可读性可能较差,如果发送一个通用的同步消息,可能消息量比较大。那么,做到什么程度?大概做到游戏流量可以接受的成都就好了。&/p&&p&还有些tips可以减小流量信息,比如:&/p&&ul&&li&有些常见的string,甚至是所有的string,可以将其转为一个int,客户端服务端都知道这个int代表什么 即可。&/li&&li&有些float,可以通过乘以100转为int传到客户端,客户端再除以100即可。&br&等...&/li&&/ul&&h2&4.其他&/h2&&p&本文基于底层的消息是保证消息顺序、保证不掉包、保证消息不被篡改、保证消息没有重发的。这件事,本身实现起来可能更加复杂。&/p&
此文章是关于技能模块的第三篇文章,文中的一些概念和介绍请阅读前两篇文章: 以及
游戏在真实的环境中,有些特殊情况需要处理,本文介绍技能模块是如何处理人为作弊和现实中的网络导致…
&p&可以看看这篇 &a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&帧同步(LockStep)该如何反外挂&/a& 。&/p&&p&多人竞技的帧同步比较容易反挂,王者也是通过客户端上传关键数据的hash投票找出作弊玩家。如果要对单机,双人等人数较少的帧同步游戏反挂,可以结算之后服务器再加速跑一边录像做验证,例如刀塔传奇。&/p&
可以看看这篇
。多人竞技的帧同步比较容易反挂,王者也是通过客户端上传关键数据的hash投票找出作弊玩家。如果要对单机,双人等人数较少的帧同步游戏反挂,可以结算之后服务器再加速跑一边录像做验证,例如刀塔传奇。
&p&四更了&/p&&p&&br&&/p&&p&补充一下平机王作者的答案。&/p&&p&fc游戏的画面组成,简单来说就是1个背景层+64个8*8像素的活动块。&/p&&p&找了张超级魂斗罗的图(小时候穷,家里没有魂斗罗,只有超级魂斗罗,打过无数遍)。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-cf65e162fafd0daac37f7dc_b.jpg& data-rawwidth=&762& data-rawheight=&614& class=&origin_image zh-lightbox-thumb& width=&762& data-original=&https://pic1.zhimg.com/v2-cf65e162fafd0daac37f7dc_r.jpg&&&/figure&&p&&br&&/p&&p&大家可以看到,这个直升机boss,如果单纯用活动块来组成,那么,64个活动块是远远不够的!&/p&&p&所以,聪明的制作人想出了一个奇技淫巧,就是用&b&背景层&/b&来做boss。&/p&&p&因为这是背景层,并且fc只支持一层背景,只要它移动,就是整个画面都在移动,所以只能用纯色的背景,不然就露馅了。比如说,如果画面上多了一个月亮,那么,直升机在动,月亮也会跟着直升机一起动。&/p&&p&而且,黑色又是看起来最“正常”的颜色(可以强行解释成夜晚),所以fc大部分巨大boss都是晚上打的。&/p&&p&当然,也不一定要黑色,比如&/p&&figure&&img src=&https://pic3.zhimg.com/v2-ba1a9f89d118fad788534fdc89a7e1d2_b.jpg& data-rawwidth=&787& data-rawheight=&624& class=&origin_image zh-lightbox-thumb& width=&787& data-original=&https://pic3.zhimg.com/v2-ba1a9f89d118fad788534fdc89a7e1d2_r.jpg&&&/figure&&p&&br&&/p&&p&&b&你们以为自己打的是敌人?其实打的是背景。&/b&&/p&&p&&br&&/p&&p&&br&&/p&&p&补充&br&本来只是想补充一下平机王作者的答案,没想到大家都赞我了,那就再补充点内容吧。&/p&&p&大家看这2张图&/p&&figure&&img src=&https://pic4.zhimg.com/v2-b04c768d2dcecc4fd88f6f_b.png& data-rawwidth=&256& data-rawheight=&224& class=&content_image& width=&256&&&/figure&&p&&br&&/p&&p&这张图是有背景的,就是天上那些云(其实严格来说,地面 楼房 云都是背景)&/p&&p&然后走过这个房子之后&/p&&figure&&img src=&https://pic4.zhimg.com/v2-818cbf6c1dab7e8bd2f3fb_b.png& data-rawwidth=&256& data-rawheight=&224& class=&content_image& width=&256&&&/figure&&p&&br&&/p&&p&背景就变成纯黑的了&/p&&p&这就是为了用背景绘制boss,把背景的其他东西都去掉了&/p&&p&然后,打完boss之后&/p&&figure&&img src=&https://pic3.zhimg.com/v2-bd2e0e336ebe24f9e07cd6dd35b8a3fe_b.png& data-rawwidth=&256& data-rawheight=&224& class=&content_image& width=&256&&&/figure&&p&&br&&/p&&p&果然,云又回来了……&/p&&p&另一个关键点是,fc活动块是64个,但是同行活动块最多8个,如果同行超过8个,就会出现闪烁现象。&/p&&p&也就是说宁可让一些活动块消失,也要让同行活动块控制在8个以内。&/p&&p&再回到这个图&/p&&figure&&img src=&https://pic4.zhimg.com/v2-b04c768d2dcecc4fd88f6f_b.png& data-rawwidth=&256& data-rawheight=&224& class=&content_image& width=&256&&&/figure&&p&&br&&/p&&p&可以很直观地看出,这些小兵,由于超过同行活动块的限制,产生闪烁,变得残缺不全。&/p&&p&再回到这关的直升机boss&/p&&p&之前说过,boss是背景,但是boss身上的组件有可能是活动块,比如说boss身上的炮管。&/p&&p&那就测试一下吧&/p&&p&看准,起跳,截图&/p&&p&成果如下&/p&&figure&&img src=&https://pic1.zhimg.com/v2-a1b2fa4cb77fae402e52bcb3e7b4d818_b.png& data-rawwidth=&256& data-rawheight=&224& class=&content_image& width=&256&&&/figure&&p&&br&&/p&&p&没有闪烁!没有闪烁!&/p&&p&炮管如果是活动块组成,一个活动块的像素是8*8,那么一个炮管至少要2*2个活动块叠加,人物也要2*2个活动块,再加上爆炸产生的那个冲击波,同一行至少要12个活动块,远超过了同行8个的限制,肯定是会产生闪烁的。&/p&&p&所以,直升机就是100%背景构成的。&/p&&p&&br&&/p&&p&有位网友 &a class=&member_mention& href=&//www.zhihu.com/people/de92cddfc04f9d3dbede5cebf3005ec7& data-hash=&de92cddfc04f9d3dbede5cebf3005ec7& data-hovercard=&p$b$de92cddfc04f9d3dbede5cebf3005ec7&&@Winston qiu&/a& 说,为什么会产生闪烁现象,在这就稍微说一下。&/p&&p&我们先来个找茬游戏。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-d9fe436d916b9_b.png& data-rawwidth=&1920& data-rawheight=&1080& class=&origin_image zh-lightbox-thumb& width=&1920& data-original=&https://pic2.zhimg.com/v2-d9fe436d916b9_r.jpg&&&/figure&&p&&br&&/p&&figure&&img src=&https://pic1.zhimg.com/v2-1a9b4c937ff5e8eab26d14_b.png& data-rawwidth=&1920& data-rawheight=&1080& class=&origin_image zh-lightbox-thumb& width=&1920& data-original=&https://pic1.zhimg.com/v2-1a9b4c937ff5e8eab26d14_r.jpg&&&/figure&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-73cda9e02ac88a6ab9adb1_b.png& data-rawwidth=&1920& data-rawheight=&1080& class=&origin_image zh-lightbox-thumb& width=&1920& data-original=&https://pic2.zhimg.com/v2-73cda9e02ac88a6ab9adb1_r.jpg&&&/figure&&p&&br&&/p&&p&&br&&/p&&p&无奖竞猜,大家能找出3张图有几个区别?&/p&&p&前面说过,宁可让活动块消失,也要使同行活动块控制在8个。&/p&&p&但是,总不能让同一个活动块消失,不然敌人都隐形了,那怎么玩?(而且第一张图是让子弹消失,那就要命了,怎么死的都不知道)&/p&&p&那怎么办?轮流来呗。&/p&&p&fc的画面是每秒60帧,这一帧让这几个活动块消失,下一帧让那几个活动块消失,谁也不吃亏。&br&你消失一下,我消失一下,看起来就是闪烁的感觉了。&/p&&p&&br&&/p&&p&&br&&/p&&p&不要问我为什么主角颜色那么奇怪,我会告诉你我用触摸屏玩不开无敌根本打不到关底吗?&/p&&p&&br&&/p&&p&&br&&/p&&p&再次补充&/p&&p&评论区有个眼尖的网友 &a class=&member_mention& href=&//www.zhihu.com/people/36d3a83a7f41ac7f0ba38& data-hash=&36d3a83a7f41ac7f0ba38& data-hovercard=&p$b$36d3a83a7f41ac7f0ba38&&@Trueng Charlie&/a& 看出问题来了,如果直升机和地面都是同一层背景的话,为何两者会相对位移?&/p&&p&我真的只是想补充一下 &a class=&member_mention& href=&//www.zhihu.com/people/8d9ff2eab2f161adc85a38& data-hash=&8d9ff2eab2f161adc85a38& data-hovercard=&p$b$8d9ff2eab2f161adc85a38&&@平机王作者&/a& 的答案而已,但是,看大家这么热情啊,我一句话不说也不好……&/p&&p&其实这个是用了水平切割原理, &a class=&member_mention& href=&//www.zhihu.com/people/b5a95d20c& data-hash=&b5a95d20c& data-hovercard=&p$b$b5a95d20c&&@Thinkraft&/a& 的一篇文章写的很详细了,我就通俗地说一下吧。&/p&&p&fc画面是一行一行地输出的,但是输出的时候可以把这些行给错开,造成相对位移的现象。&/p&&p&fc上很多游戏都用了这个方法造成N多层卷轴的假象,比如忍者龙剑传系列。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-30eaf97148be73aeeeeead8_b.png& data-rawwidth=&256& data-rawheight=&224& class=&content_image& width=&256&&&/figure&&p&&br&&/p&&p&比如忍者龙剑传2的这张图,草地 火车 森林 远山都是相对移动的,4重卷轴,厉不厉害?达到了sfc的水准。&/p&&p&这就是水平切割的威力。&/p&&p&群众:明明说相对位移,你放一张静态图,搞毛线啊?&/p&&p&没错,静态图看不出什么东西。&br&但是,再看一组忍者龙剑传的图就明白了。&/p&&p&相对冷门的一款GB上的《忍者龙剑传 决战摩天楼》&/p&&figure&&img src=&https://pic3.zhimg.com/v2-9a13e4dcf8b28fe03e9cbbcd_b.png& data-rawwidth=&160& data-rawheight=&144& class=&content_image& width=&160&&&/figure&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-138b348bfec34c06846fb_b.png& data-rawwidth=&160& data-rawheight=&144& class=&content_image& width=&160&&&/figure&&p&&br&&/p&&figure&&img src=&https://pic2.zhimg.com/v2-1befd85b9abcdbf01fea6d_b.png& data-rawwidth=&160& data-rawheight=&144& class=&content_image& width=&160&&&/figure&&p&&br&&/p&&p&大家可以明白了吧,这就是隼龙如何切割bos……呸,水平切割如何切割boss的。&/p&&p&很惭愧,明明说的是fc,结果用了GB,因为fc上实在想不出更好的例子了,如果大家能想到的话,请告诉我。&/p&&p&ps:求赞求关注啊~&/p&&p&ps2:很惭愧,水平不足,只能说一些浅显的东西,如果大家想对fc的原理有更深入的了解,请移步本问题下 &a class=&member_mention& href=&//www.zhihu.com/people/b5a95d20c& data-hash=&b5a95d20c& data-hovercard=&p$b$b5a95d20c&&@Thinkraft&/a& 的答案&/p&&p&ps3:居然上日报了~&/p&
四更了 补充一下平机王作者的答案。fc游戏的画面组成,简单来说就是1个背景层+64个8*8像素的活动块。找了张超级魂斗罗的图(小时候穷,家里没有魂斗罗,只有超级魂斗罗,打过无数遍)。 大家可以看到,这个直升机boss,如果单纯用活动块来组成,那么,64个…
&p&如果你看不明白原帖中的ECS(似乎很多人有这个反应),我觉得其实是很正常的,尤其是从开始写代码到今天只接触过OOP的人,而OW本身是一个很宏大的项目,你没法很好的去理解这个例子,假如你真的有兴趣了解ECS,那么我给你们举个很简单的例子,以及说一下ECS的基本Motif(调性):&/p&&p&首先是规则区别,我还是要重复一个Key(我记得似乎在别的帖子说过):&/p&&p&1,OOP的核心思想是:我是什么——我是一个角色对象,我是一个子弹对象。&/p&&p&2,ECS的核心思想是:我有什么——我有render,我有move,我有motion。&/p&&p&就拿子弹和角色来说,如果我们是一个很简单的射击游戏,或者是Top-Down-Shooter的,或者更简单直白的,FC上的坦克相信都知道吧?&/p&&p&如果用OOP的方式做,它里面有角色(坦克)对象和子弹对象,伪代码&/p&&p&class tank {&/p&&p&//这里仅说一些有代表性的,就不细节设计了&/p&&p&public tank()&/p&&p&public Vector2 move(direction) //有一个move方法来移动坦克,在这个方法里面,我们根据面向、速度等因素来让坦克的坐标发生位移,同时我们让坦克的动作发生了变化。注意:我们把坦克的渲染其实也放在了这里,所谓的渲染包括了移动时候播放的动画等。&/p&&p&public bullet fire() //我们通过这个来让坦克开火,产生子弹&/p&&p&public void kill()
//我们通过这个来让坦克在HP小于等于0的时候死亡。&/p&&p&}&/p&&p&class bullet {&/p&&p&public bullet(){}&/p&&p&public Vector2 move(direction) //子弹也会根据方向移动,这毫无疑问。注意,我们把子弹的渲染也放在了这里,所谓渲染包括了子弹移动的时候贴出他的位置。&/p&&p&public void hit() //子弹命中了什么东西之后要调用这个,用来消除子弹。&/p&&p&}&/p&&p&我相信不用写game,大多人也知道这个game要怎么写,毕竟这么简单的游戏。&/p&&br&&p&那么我们来看看ECS是怎么做这个的?伪代码&/p&&p&moveComponent{&/p&&p&enum direction
//移动方向,不做进一步解释了&/p&&p&float moveSpeed //移动速度,我想你也明白&/p&&p&}&/p&&p&positionComponent{&/p&&p& //位于地图的坐标,不解释了&/p&&p&&/p&&p&}&/p&&p&function moveSystem(entities){ //这个system只关心带有positionComponent和moveComponent的entity,所以entities里面的所有entity都必然有个moveComponent和positionComponent&/p&&p&for (entity in entities){&/p&&p&
这里就不详细写了,执行的事情就是根据moveComponent中的direction和moveSpeed,为positionComponent中的x,y重新赋值。&/p&&p&}&/p&&p&}&/p&&p&renderComponent{&/p&&p& //贴图的信息,可以是资源名,等等,老做游戏了你应该理解这个&/p&&p&}&/p&&p&function renderSystem(entities){&/p&&p&//这个系统的工作,就是根据自己关心的带有positionComponent和renderComponent的对象,来进行渲染工作。&/p&&p&}&/p&&p&collisionComponent{&/p&&p& //FC的坦克大战中,所有的子弹和坦克,其实都是矩形的。&/p&&p& //可以理解为阵营,同一阵营的互相忽略碰撞,当然写在这里面并不是最好的方案,具体要看需求,目前的需求下这还算凑合。&/p&&p&hitEntities: array&entity&; //用于记录碰撞到的entity&/p&&p&}&/p&&p&tankComponent{&/p&&p&//证明这是一个tank,并且记录坦克需要的数据&/p&&p&int hp&/p&&p&}&/p&&p&bulletComponent{&/p&&p&//证明这是一个子弹,并记录子弹需要的信息&/p&&p&int power&/p&&p&bool hitSomeThing&/p&&p&}&/p&&p&function collisionSystem(entities){&/p&&p&//这个system关心的是collisionComponent和positionComponent,他的工作就是根据position和collisionComponent.rect来判断2个entity是否碰撞,如果碰撞,则将彼此加入到对方的collisionComponent.hitEntities数组中去。&/p&&p&}&/p&&p&function damageSystem(entities){&/p&&p&//这是一个很有意思的system,他只关心带有collisionComponent,然后会根据collisionComponent.hitEntities中的entity提供的信息配合被捕捉的entity的其他component(比如tankComponent和bulletComponent)进行特殊处理。&/p&&p&//这个system的工作之一:如果这个entity并没有tankComponent,同时还没有bulletComponent,for循环执行continue,检查下一个entity。&/p&&p&//如果这个entity拥有tankComponent,证明这是坦克,则遍历collisionComponent.hitEntities,根据其中带有bulletComponent的entity进行处理,你可以同时销毁他们全部,并且都对这个坦克进行伤害,也可以根据被认为是子弹(带有bulletComponent的entity)的个数特殊处理,比如被3个子弹命中则hp提高10000等,根据设计需求来实现。&/p&&p&//值得注意的是:即使entity.tankComponent.hp&=0,也不由我来进行下一步操作,我就是一个damageSystem,我负责的就是damage(造成伤害),造成伤害的后果,管我鸟事儿。&/p&&p&}&/p&&p&function tankDestroySystem(entities){&/p&&p&//我只关心带有tankComponent的entity,我的任务是如果entity.tankComponent.hp&=0,则将他们杀死。&/p&&p&}&/p&&p&function bulletDestroySystem(entities){&/p&&p&//和tankDestroySystem差不多,但我只负责对bulletComponent.hitSomeThing==true的对象进行dispose()&/p&&p&}&/p&&p&这就是一个典型的ECS的游戏写法,他没有坦克对象,子弹对象,但是他有坦克和子弹&/p&&p&tankEntity = entities.new()&/p&&p& .add(new positionComponent(x,y))&/p&&p& .add(new renderComponent(xxxx))&/p&&p& .add(new collisionComponent(rect(x,x,x,x), y)&/p&&p& .add(new tankComponent(x))&/p&&p&bulletEntity = entities.new()&/p&&p& .add(new positionComponent(x,y))&/p&&p& .add(new renderComponent(xxxx))&/p&&p& .add(new collisionComponent(rect(x,x,x,x), y)&/p&&p& .add(new bulletComponent(x))&/p&&p&你会发现,子弹和坦克唯一的区别就是一个是子弹,一个是坦克。你可能要问,为什么没有moveComponent?因为moveComponent只有在移动的时候才需要添加上去,当移动完毕,就可以把它移除掉,这对于效率来说,还算是一种优化。&/p&&br&&p&到这里,我们是不是可以思考另外一个问题——在一个3岁小孩子眼里,画出来的羊和活着的羊有什么区别?区别就是:活着的羊会动,他有motionComponent和moveComponent。当然话题收回来,现在,我们发现游戏中还没有设计地形,对,没有地形玩什么?&/p&&p&OOP方式:&/p&&p&class ObstacleGrid{&/p&&p&//tilebased游戏,地图格当然不能没有,当然这种游戏需要的只是会阻挡的地形格子信息而已。&/p&&p& //贴图资源&/p&&p& //能否被子弹击穿&/p&&p&Vector2 //位置&/p&&p&.....&/p&&p&public void break() //被子弹销毁了&/p&&p&}&/p&&p&看起来加一个对象不复杂,但是当地图上有了这些obstacleGrids之后,你要在game中coding什么呢?很多,比如坦克移动的时候,你不能穿越他,再比如子弹命中他的时候……等等等等。&/p&&br&&p&ECS方式,我们要做的事情很清晰:&/p&&p&wallComponent{&/p&&p&//证明我是阻挡地形,别在意名字的细节……&/p&&p&&/p&&p&}&/p&&p&function obstacleSystem(entities){&/p&&p&//这个system关心的,只有带有wallComponent和collisionComponent的entity,根据collisionComponent.hitEntities进行对应的处理,相信你动动脑子能想明白。&/p&&p&//这里有一个独特与oop的地方,如果我(墙壁entity)阻挡了坦克或者子弹,我要做的是对对方entity.remove(moveComponent),导致对方不能发生移动。&/p&&p&}&/p&&p&阻挡地形:&/p&&p&obstacleGrid = entities.new()
.add(new positionComponent(x,y))
.add(new renderComponent(xxxx))
.add(new collisionComponent(rect(x,x,x,x), y)
.add(new wallComponent(x))&/p&&p&看到这里,发现为啥他和子弹和坦克如此接近?是不是能突然冒出一个有意思的想法?如果策划说了,我的阻挡地形也会移动!是不是OOP很懵逼?但ECS就很好处理了?&/p&&br&&p&以上是一个简单的思路上的例子,区别了ECS和OOP,ECS中你说一点耦合不存在可能吗?是可能的,system之间是否必须耦合(其实必须优先执行aSystem再执行bSystem就可以认为是耦合)取决于你的programming和对业务的理解能力。当然,我想说的是,请看&/p&&br&&figure&&img src=&https://pic3.zhimg.com/v2-0bf7aec698eb9caaf0c85b67c6774162_b.png& data-rawwidth=&586& data-rawheight=&295& class=&origin_image zh-lightbox-thumb& width=&586& data-original=&https://pic3.zhimg.com/v2-0bf7aec698eb9caaf0c85b67c6774162_r.jpg&&&/figure&&p&是不是符合Component中没有任何行为?no functions&/p&&p&是不是符合没有状态?任何system都不关心游戏状态&/p&&p&第三个、第四个因为业务简单就没有体现出来&/p&&p&最后一个是不是system之间互不关心?当然!&/p&&br&&p&所以,请认真的去理解ECS的特性和好处,不要轻易的做出危险的类比!&/p&&br&&p&算了,吐槽就去掉吧,还是讲讲道理。&/p&
如果你看不明白原帖中的ECS(似乎很多人有这个反应),我觉得其实是很正常的,尤其是从开始写代码到今天只接触过OOP的人,而OW本身是一个很宏大的项目,你没法很好的去理解这个例子,假如你真的有兴趣了解ECS,那么我给你们举个很简单的例子,以及说一下ECS…
&figure&&img src=&https://pic1.zhimg.com/v2-468db3966df9cbc5e3ad738e2acf1f5b_b.jpg& data-rawwidth=&906& data-rawheight=&985& class=&origin_image zh-lightbox-thumb& width=&906& data-original=&https://pic1.zhimg.com/v2-468db3966df9cbc5e3ad738e2acf1f5b_r.jpg&&&/figure&&p&上一次参加 GDC 是在七年前。有趣的是,2010 那年,John Carmack 获得了终生成就奖 (Lifetime Achievement Award);而 2017 年的 GDC 上,获得这个奖项的是 Tim Sweeney。这是两位真正的行业传奇。他们的代码,令我满心钦佩且受教良多。&/p&&p&----------&/p&&p&这一篇快速记录中,我粗粗地整理了一下自己听过的演讲,并把那些觉得很有收获的标注了一下。还有不少演讲是因为分身乏术错过的,这里也一并记了,这样晚些时候可以到 GDC Vault 里去听回放。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-468db3966df9cbc5e3ad738e2acf1f5b_b.jpg& data-rawwidth=&906& data-rawheight=&985& class=&origin_image zh-lightbox-thumb& width=&906& data-original=&https://pic1.zhimg.com/v2-468db3966df9cbc5e3ad738e2acf1f5b_r.jpg&&&/figure&&ul&&li&维护和更新地址:&a href=&https://link.zhihu.com/?target=https%3A//github.com/mc-gulu/dev-awesomenesses/blob/master/awesome-gdc17.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&(github.com) dev-awesomenesses/awesome-gdc17&/a&&/li&&/ul&&p&----------&br&&/p&&p&还有一个&a href=&https://link.zhihu.com/?target=http%3A//mp.weixin.qq.com/s/C0vkeiCsXKXLGtsA3pB7ag& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&半小时的访谈&/a&,是陈杰老师和我一起最后一天晚上做的。头一次上镜头,感觉自己不忍直视 (捂脸逃~~&/p&&p&对内容感兴趣又不想花时间看视频的同学,可以扫视下面的零散文字——这其实是我在录节目前记录的一些简短脚本 (就在我手上端着的 Surface 里)。&/p&&p&----------&br&&/p&&p&GDC17 总体感觉:&strong&严谨&/strong&&/p&&p&传统 hack -& 规模越来越大,复杂度 hold 不住 -& 机器性能的提高使得不少 hack 不再有意义 -& 通过严谨的设计来简化问题&/p&&p&主要分享三堂课内容:&/p&&ul&&li&&strong&英雄联盟的制作&/strong&&/li&&li&&strong&守望先锋的逻辑状态和同步&/strong&&/li&&li&&strong&寒霜引擎的图形渲染架构改造&/strong&&/li&&/ul&&p&&strong&1. (LOL) 游戏制作 Production&/strong&&/p&&p&a. 英雄的制作 (DNA)&/p&&p&新做的内容需要先把握好 DNA,这样你的新内容就有根,就有灵魂 &/p&&ul&&li&&strong&Design&/strong& 就是你的核心设计,简单的说就是玩什么,对英雄而言就是技能和对应的技巧和玩法&/li&&li&&strong&Narrative&/strong& 叙事,来历,前因后果,三个问题:你是谁,你从哪里来,你到哪里去&/li&&li&&strong&Art&/strong& 艺术形象,不完全是原画 (考虑实现性),是一种感觉 (feeling),画面感&/li&&/ul&&p&(此处小结为本文新增) 总得来说, D 对应了交互体验,N 对应了世界观,A 对应了视觉和感性,合起来的 DNA 是确保新内容的感染力的核心,&/p&&p&b. 预制作周期&/p&&p&一个英雄的制作周期是 9 个月,在开始做实际成品之前的所有阶段(统称为预制作阶段)的时间加起来,足足5个月。&/p&&p&也就是说磨刀5个月,砍柴4个月。&/p&&p&要是在国内,9个月的 deadline 五个月了还没出东西,呃,是还没开始出东西,就算老板不说啥,自己也会不好意思,对吧?&/p&&p&为什么这么做呢?扎实,结实,夯实。 &/p&&p&这样出来的东西是能够协调地跟游戏里的其他部分融合的。不然呢?可破坏的场景跟技能表现冲突了~ 下雨的特效跟英雄的光环冲突了~ 做英雄花了3个月,然后各种堵窟窿花了10个月。&/p&&p&对于成熟的设计团队,只有你给他时间和空间去沉淀,才能扎实,结实,夯实,做出好东西。&/p&&p&DNA 和 Pre-Production,就是这个分享给我的最大的两点启发。&/p&&p&&strong&2. (Overwatch) 网络同步的现代的架构设计&/strong&&/p&&p&教科书般的设计,是上面提到的严谨的体现。&/p&&p&为什么这么讲呢?跟现在的系统化方案比起来,以前觉得还可以的一些方案顿时显得很土很山寨。&/p&&p&拿录像重放系统举例子,对比现代的网络模块和游戏逻辑结合的调试系统&/p&&p&单步单帧逻辑重放,可视化的完整状态调试。&/p&&p&旁边识货的同学,眼都直了。&/p&&p&&strong&3. (Frostbite) 图形渲染架构改造&/strong&&/p&&p&两套东西:FrameGraph, 显式显存管理 &/p&&p&以前手写的渲染流程,缝缝补补加开关;后来 Gamebryo 实现了一套朴素的 RenderFrame 机制 (有逻辑零碎化的问题);现在通过 FrameGraph 完全达到结构化渲染流程的效果。&/p&&p&FrameGraph 用临时生成 lambda 表达式的方法,保留了以前的线性逻辑,(好思路)&br&跟这个相比,几年前的那套初级机制简直不值一提(在实现功能的基础上追求简单优雅和有效),同样是严谨性的体现。&/p&&p&显式显存管理,分块,按照生命期的长短去尽量重复利用块来降低峰值的占用&/p&&p&对于渲染系统,我们可以在新的图形 API (DX12 / Vulkan) 上很自然地引申出&strong&前端&/strong&和&strong&后端&/strong&概念&/p&&ul&&li&前端 (并行化)从材质系统到 drawcall 的产生和提交 (传统的应用程序部分)&/li&&li&后端 (从降低碎片化,改善显存利用率开始,逐步接管)(传统的显卡驱动部分)&/li&&/ul&&p&这是 Frostbite 对应的新渲染架构给我方向上的启发。&/p&&br&&ul&&li&永久链接: &a href=&https://link.zhihu.com/?target=http%3A//gulu-dev.com/post/-gdc17& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&GDC 2017 技术选荐合辑&/a&&/li&&li&本文遵循 &a href=&https://link.zhihu.com/?target=http%3A//creativecommons.org/licenses/by-nc-nd/4.0/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Creative Commons BY-NC-ND 4.0&/a& 许可协议&/li&&/ul&&br&&p&[完]&br&Gu Lu&br&[]&/p&
上一次参加 GDC 是在七年前。有趣的是,2010 那年,John Carmack 获得了终生成就奖 (Lifetime Achievement Award);而 2017 年的 GDC 上,获得这个奖项的是 Tim Sweeney。这是两位真正的行业传奇。他们的代码,令我满心钦佩且受教良多。----------这一篇快…
&p&做游戏技术主要讲究的是套路,以及对套路的掌握程度。比如说你要搞个体积光,那么从用mesh+uv动画,到volumetric scattering你都得知道,而且要知道这些方案的优缺点,以及具体的实现细节,比如camera会不会到体积光里边之类的,这种细节的了解会让你更加有信心做出各种技术决策。&/p&&p&所以3a级游戏的大神技术也是对各种领域的套路玩得比较溜,这里我也分享一些做游戏20来年自己领悟出的一些套路吧。&/p&&p&美术用工具输出的3d模型,材质一定要做一个导出插件,否则做出来和进引擎效果不一样,就只能做做手游这种无光照的手绘贴图质量的产品了。&/p&&p&动画要想不滑步,就只能用animation driven的方法,这个需要跟策划做好深度的沟通,得让策划知道移动不是说配置一个速度就可以了,得去找动画师一起调整移动的动画。&/p&&p&动态天气的困难不是制作上的困难,是可以用lightmap的物件会少很多,性能不一定扛得住,这个要在项目初期一定得和制作团队沟通清楚。&/p&&p&球类游戏要达到非常自然的动画,一方面动画肯定是要动捕的,另一方面更重要的是搭建一套动画选择机制,然后根据运动中的球类位置来选择最合适的动画来匹配,再加上小部分的ik。&/p&&p&头上冒字和冒血这种hud就应该用hud的标准方法来制作,切记不能涂省事用ui的方法,对性能效率会有非常大的影响。&/p&&p&音频对最终品质影响很大,一般遵循46原则,即图像资源在最终包中占6成,音频资源占4成。给音频设计师配置一个独立的程序来达成音频的设计需求。&/p&&p&状态机最好是用可视化的方法来实现,游戏中80%的bug都是和状态没切对有关,有个可视化的状态机对于找bug非常方便。&/p&&p&control是最需要状态机化设计的(不同的情况下按同样的按键达成不同的逻辑)&/p&&p&做面部表情,如果不是捕捉就用骨骼简单搭搭就好,如果是捕捉,制作成本会变得非常的高(制作各种morphing target)&/p&&p&对最终画面影响最大的是镜头效果,所以尽量节省渲染时间留给镜头特效。一般影响最大的是校色,如果硬件平台允许尽量给美术提供带深度校色的工具。&/p&&p&有比较宽广视野的游戏,室外可以用一些很取巧的方法来模拟mie scattering和rayleigh scattering,加上哪怕是假的对画面的提升也是巨大的。&/p&&p&对于不同的数据采用不同的配置方法,不要什么数据都用excel,对于有可视化需求的配置,比如ui或者角色身上需要装配一些武器的,提供可视化的工具。对于需要描述父子关系的例如技能树解锁之类的用json或者xml来描述,对于纯数字逻辑的就用csv就好了。&/p&&p&场景的材质如果制作normal map对于你们团队比较复杂,多加一层detail map也能对效果获得较大的提升。&/p&&p&角色的材质,如果性能有限或者说制作成本无法承受的话specular map比normal map的效果要好。&/p&&p&如果角色会近距离看脸,脸部的眼睛一定要单独拿出来处理,脸可以糊,眼睛一定不能糊。&/p&&p&脚步声的标准做法是在地面上放一层低模做和脚的碰撞检测(脚步声如果追求真实,最少得有3个以上的样本随机)&/p&&p&如果要提升动画效果,考虑主角的前臂加上twist骨骼,左肩和右肩不要从脖子上搭建骨骼,要从胸口开始(做head lookat的时候效果会更好)&/p&&p&衣服的标准做法是通过贴图来控制哪些顶点受skin和物理影响的权重比,那张贴图还可以用一个通道来控制流汗的时候哪些地方的smothness要提高。&/p&&p&搭建一个运行时数据库,并且游戏中经常需要访问的数据都放入数据库,可以类似redis那样非常简单的提供一个set和get就行了,运行时数据库可以帮助你找到绝大多数bug。&/p&&p&制作ai尽量使用行为树,并且花时间一定要让策划具备编辑和调式行为树的能力。&/p&&p&顶点色可以各种花式使用,无论是用来bake ao还是决定detail map的权重,都可以用很低廉的代价来获得非常棒的效果。&/p&&p&动画不是在任何时候都可以blend的,要想效果好,牢记:左脚落地的时候只能blend到左脚落地开始的动画,右脚同上。&/p&&p&做上下楼梯的locomotion的时候,用椭球碰撞体会非常简单的获得还不错的效果。&/p&&p&如果画面会经常快速的运动,可以加入一个vector based motionblur,效果会出奇的好。&/p&&p&要想场景生动,一定要使用decal,一个方便美术的decal工具可以让美术一天之内让整个场景提升几个档次。&/p&&p&暂时想到这些,不说了,休息好了继续干活。&/p&&p&~~~~~继续更新一波套路&/p&&p&解决alpha透贴排序的标准方法是两次绘制,第一次在不透明队列里渲染一次alpha test(cutout),打开zwrite,第二次在transparent队列里开alpha blend不开zwrite,ztest lessequal渲染就能还原美术在maya里看到的效果。&/p&&p&弹簧是个非常好用的物理组件,其虎克系数可以有效的模拟力的衰减来做出很多感人的效果,从乳摇,臀摇,尾巴,头发马尾,布料,脸上被重拳击中的肌肉变化都可以看到弹簧的身影。&/p&&p&adobe fuse cc + mixamo可以非常快速的搭建模型和动画来帮助策划找到剧情的场景的感觉。&/p&&p&脚下ik从程序来讲是很方便实现的,大多数引擎都有ik功能,但是工作流程我发现很多公司都没有正确使用。正确的流程是对输出的动画有一个左右脚高低的分析工具,逐动画生成每个脚步离水平面的高度曲线,然后运行时根据脚步的高低来决定ik和skin的权重。&/p&&p&镜头的设置很重要,最好给美术一个和相机镜头类似的算法来反过来计算fovx和fovy,我看到很多产品的fov都是一水的60之类的。&/p&&p&对于类似铁丝网或者其他半透物体的mipmap的标准做法是先自动生成mipmap,然后要美术手动的来降低不同lod贴图的alpha,换句话说近处看有铁丝网,远看就只有框中间近乎全透了来降低锯齿感。&/p&&p&dx10以上的平台上特效的shader加一句根据当前depth和已经绘制的depth的差来控制alpha可以实现简单且效果不错的软粒子效果。&/p&&p&头发的物理效果可以参考衣服的做法,发根受skin影响多,发梢受物理多一些即可。(额前刘海受物理要关掉重力)&/p&&p&如果实时sss负担太重,可以简单的把模型厚度信息烘培在顶点色的某个通道上,然后只需三两行代码就可以让皮肤有sss效果。&/p&&p&看到好多项目用双面材质只是在shader中简单加一句cull none,这个是完全错误的实现。正确的做法是需要把三角形索引信息走vs传递进来,然后判断是否顺逆时针,对于反面需要把法线反转才能获得正确的渲染结果。&/p&&p&先更新到这。&/p&&p&完了,感觉思绪开始打开了,又更新一些,怕一会会忘。&/p&&p&动画驱动的模式下,转向最少需要8个动画,原地左转90,右转90,左转180,右转180,移动同样四个,然后根据控制器的输入来决定混合的权重,一般神海这种级别的产品光locomotion牵扯到的动画会在40-50个这种量级。&/p&&p&对于衣物和皮肤,detail normal十分重要,可以瞬间让你的衣服能看出针织的材质。&/p&&p&处理声音的时候,远处的声音混响需要程序来手动提升,近处降低混响,对临场感增加很多。&/p&&p&声音如果样本有限,也一定要做随机,哪怕就一个脚步声样本,播放的时候也应该随机pitch和volume。&/p&&p&给策划提供一个调整动画曲线的工具,横轴是动画时间,纵轴是动画播放的速度,策划通过这个工具可以调整出情绪非常饱满的动画效果(例如:攻击前摇动画速度变慢,攻击过程加速之类的)这个需要和动画师沟通清楚,要求他们只需要k好几个相关pose即可。&/p&&p&卡通渲染的时候有一步是非常关键也是很多公司都忽略的,就是手动调整模型法线,这一步得在美术工具内完成,通常是定义toon shading的光方向之后,通过调整法线来让一些部位有“看起来”较为舒服的受光,主要是鼻子,腋下和两个胯这三个位置。&/p&&p&游戏加载的正确做法是,在出包的时候对各资源的单位加载时间进行预计算(适合配置固定的主机游戏)。之后在运行时加载的时候,根据每一帧cpu的可用空闲时间来决定当前帧可以加载的资源类型和数量。&/p&&p&渲染阴影的时候,很多人都会忽略绘制阴影那个pass的优化,以前做上一代游戏机的时候所有会有实时阴影的模型都会做一个低模和专门的材质(如果有透贴阴影的需求)。&/p&&p&对于写实类型的游戏,decals的正确制作方法是拿相机去拍,比如地上的水渍,墙上的裂痕,车上的划痕之类的,开闪光灯拍照,然后回来在ps里面抠出来。&/p&&p&有很多arpg的游戏中都有霸体的设计,霸体中不会受击,其实这个的正确做法是动画新增一个受击层,在播放霸体动画的同时轻微的blend一个受击动画,打击感会舒服很多。&/p&&p&午饭时间!&/p&&p&反响不错啊,再来一波=)&/p&&p&写实风格的贴图,推荐尽量使用Substance制作,打包不同目标平台的时候,可以根据目标平台的实际硬件情况,决定是Bake出Texture还是用Procedual Texture,其实很多时候会发现很多PBR贴图除了Google图片出来修改之外,只有Substance才能比较好的制作出来。&/p&&br&&p&有一个关于Animation Driven的系统中,AI的制作问题,主要是因为动画状态机自己有自己的行为准则,所以传统的AI设计思路就变得比较无力,比如说我希望我的NPC每0.3秒攻击一次,但是攻击动画本身就超过0.3秒了,怎么办?或者说状态机的逻辑无法响应到0.3秒的攻击。这个时候需要跟策划沟通,在Animation Driven的系统下,要完全的分开设计,NPC的大脑是AI,NPC的体格是动画状态机,很多时候AI的设计要根据体格的情况来酌情调整。要在行为树里增加一些检查身体体格的节点,比如说,我要转向某个方向,那么在适当的时候需要检查自己的身体是否真的转到指定方向了;同时对于大脑向身体发送的指令要分优先级,优先级的实现方法是该指令的有效时长,比如说我的身体在受击的状态中,但是我的大脑仍然下命令让我攻击一次对方,这条指令优先级存在时间0.2秒,如果受击状态在0.2秒内复原了,则该指令身体可以执行一次,如果0.2秒内身体仍然无法攻击,就丢掉此次指令。&/p&&br&&p&Volumetric Cloud/Fog/Light的使用一定要提前和美术进行商量,因为计算过程中牵扯到大量的Depth sampling,所以画面中多大会存在多大面积的这类效果一定需要严格控制,否则到了后期不得不移除这些效果的时候会让整个产品瞬间降低一个档次。&/p&&br&&p&在一些中低端设备上可以用一些简单的压缩算法来把RGB + Luminance除以一个除数来压缩到32bits的颜色空间中来获得非常划算的HDR效果,相比较RGBM,用除数可以免费获得“假”正确的alpha blend效果。这一条无需和美术沟通,直接上就行。(移动平台可用,效果很好)&/p&&br&&p&关于差值,差值计算最大的问题是需要和策划沟通清楚,越是符合现实生活运动规则的,只能使用物理的方法进行差值,物理的方法是没有办法精确的保障差值的最终位置和时间,但是却可以获得完美的差值效果。这里有一个非常小的tips来实现物理差值:一个物体从旋转R0,位置P0,差值到旋转R1,位置P1,需要差值的时候,在物体上挂一个弹簧,弹簧的另外一端挂在位置P1上,另外物体的运动规则增加两条:1,每帧运动转角不能超过XX度;2,物体的实际运动位置是当前朝向 * 力矩;即可,差值出来效果棒棒哒,无论是飞个火球还是跟踪导弹都可以用这个。&/p&&br&&p&美术资源的优化,这件事情比较好的方法是要严格的关注,但是尽量不要过早的修改这些问题,因为开发的过程中始终要留一些空间用来提升画面效果,所以过早的修改掉性能瓶颈,可能会导致到最后不得不放弃已经集成进版本的一些效果,非常不划算。所以等大家都对画面比较满意了之后,再进行一轮性能优化,结果还能再增加一些效果,对于美术大兄弟们来说就变成一件很好的事情了。&/p&&p&本来没打算写这么多,只是开始写了个头,就发现还有很多类似的问题也都可以罗列出来,于是出现这么多杂乱的内容,回头有时间会仔细整理整理,比如说配个图什么的,如果有朋友对于一些tips有疑义或者想知道一些详细的实现细节,欢迎私信单聊。&/p&&br&&p&应某同学的要求&/p&&p&更新一波手游的经验ヽ(o?ωo? )ゝ&/p&&p&贴图进版本的时候做一个alpha通道的剥离,一方面可以比较方便的适配安卓ETC1压缩格式,另一方面后期要压包的时候alpha通道可以随意缩大小,美术基本无感知。&/p&&p&如果是UI资源比较重的游戏可以采取layout和资源分离的方式来获得大量的性能提升。针对unity引擎来说就是遍历ui里所有用到的material,把里面的贴图替换成4*4的空白贴图,另外建立一个从材质对应贴图的索引。因为手机是ssd的缘故所以磁盘io读取贴图的速度是非常快的,这样就可以把整个ui的layout都留在内存中不用切场景释放,不同场景对应加载不同的贴图做一个运行时的对应即可,加载ui的时间能提升80%。这一步建议新项目中前期使用,后期项目做这种级别的修改不划算,风险高。&/p&&p&如果是动画资源比较重的产品,建议使用自定义的动画格式,采取float16的四元数储存关键帧数据,然后加载时还原引擎需要的动画数据,以前做wii上的产品,1000个左右的人形动捕数据可以压缩到23m左右。&/p&&p&经历过很多手游项目的全屏大图比较多(推广图,loading图),有的会占到整个贴图量的30%甚至更多,这时候一定要优化大图的制作工艺,和美术沟通把背景,文字和前景拆开制作,一方面可以更好的压缩(方形,2的次幂),另一方面背景图可以采取比较极端的压缩参数。&/p&&p&加载的过程中往往最后会卡一下,这个是第一次shader提交到gpu产生编译消耗的时间,可以通过在屏幕上绘制一个透明的三角形来完成warmup,这一步基本上主流引擎都有接口,如果单帧消耗过高,可以考虑分布在若干帧里完成。&/p&&p&打击感的一个小trick,击中的一瞬间把时间tick调成0持续个0.1秒左右,又轻巧效果又好。&/p&&p&搭建UI框架的时候给每页ui增加一个fadein和fadeout的接口并且管理好状态机的切换,今后可以很方便策划或者美术增加ui打开和关闭的动态效果。&/p&
做游戏技术主要讲究的是套路,以及对套路的掌握程度。比如说你要搞个体积光,那么从用mesh+uv动画,到volumetric scattering你都得知道,而且要知道这些方案的优缺点,以及具体的实现细节,比如camera会不会到体积光里边之类的,这种细节的了解会让你更加有…
----------------------------&br&&br&本文同时发在我的 Blog,在那里或可获得更好的阅读体验:&br&&a href=&//link.zhihu.com/?target=http%3A//gulu-dev.com/post/-open-world& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&[知乎] 开放世界游戏中的大地图背后有哪些实现技术?&/a&&br&&br&----------------------------&br&&br&首先肯定一下,这是一个非常有趣的问题。在这个答案里,我将尝试先回答主干问题,再对补充说明里的几个问题简单说一下。&br&&br&下面是本文将涉及到的一些相关技术的列表,(需要说明的是,这些技术单独来看并不复杂,实际动手实现并理解各种取舍以后,在项目当中针对具体的需求去设计和搭配才是关窍之所在)&br&&br&----------------------------&br&&br&&b&一、程序技术篇:算法和架构(Programming Algorithms & Architecture)&/b&&br&1. 无限循环的平铺地图(Infinite Tiling)&br&2. 可预测随机数和无限宇宙(Predictable Random)&br&3. 精度问题解决方案(Precision Problem Solving)&br&4. 超大地形的处理 (Terrain Visualization)&br&
4.1 古典算法(从 GeoMipMapping,Progressive Mesh 到 ROAM) &br&
4.2 层次的艺术(Quadtree 和 Chunked LOD)&br&
4.3 以GPU为主的技术(Paging,Clipmap 到 GPU Terrain)&br&5. id tech 5 的 megatexture (超大地表上的非重复性海量贴图)&br&6. 过程式内容生成 (Procedural Content Generation)&br&
6.1 过程式纹理(Procedural Texturing)&br&
6.2 过程式建模(Procedural Modeling)&br&&br&&b&二、内容制作篇:设计和创造(Content Design & Creation)&/b&&br&1. 随机地图类游戏 (Diablo II) 中地图的拼接&br&2. 无缝大世界 (World of Warcraft) 中区域地图的拼接&br&3. 卫星地质数据的导入,规整化和再加工(一些飞行模拟类游戏)&br&4. 超大地图的协同编辑:并行操作,数据同步,手动和自动锁的运用&br&&br&&b&三、异次元篇:我们的征途是星辰大海&/b&&br&1. 终极沙盒(EVE):当规模大到一定程度——宇宙级别的混沌理论与蝴蝶效应&br&2. 打通两个宇宙(EVE & Dust):发现更广阔的世界——宇宙沙盒游戏和行星射击游戏联动&br&&br&----------------------------&br&&br&## 一、程序技术篇:算法和架构(Programming Algorithms & Architecture)&br&&br&### 1. 无限循环的平铺地图(Infinite Tiling)&br&&br&我们就从最平淡无奇的无限循环平铺地图说起吧。这应该是最原始,也是最没有技术含量的开放世界构筑方式了。&br&&br&技术上由于过于朴素,也没什么好说的,就是在同一个坐标系内像铺地砖那样展开,坐标对齐即可,就是接头处需要注意一下,不要穿帮就行。但是千万别因为简单就小看这个技术哟,上面列表里面的不少技术都是在循环平铺的基础上发展起来的,下面我们就来瞧一瞧吧。&br&&br&按照维度的不同,循环平铺有下面三大类:&br&&br&a. 在一维方向上扩展的横版卷轴游戏(以动作类游戏为主)和纵版卷轴游戏(以射击类游戏为主)。这些类型的游戏里,为了避免循环平铺给玩家带来的重复的疲劳,卷轴游戏会添加一些随机或动态的元素,比如超级玛丽里的背景上云朵的位置,分出多个层次以不同速率卷动的背景层,等等。&br&&br&&figure&&img src=&https://pic2.zhimg.com/ba1ab15421cdadd00a30dd3c736e997d_b.jpg& data-rawwidth=&392& data-rawheight=&220& class=&content_image& width=&392&&&/figure&&br&&br&&br&b. 在二维方向上循环平铺的固定视角2D游戏。这一类游戏里,比较典型的就是 Diablo。暗黑中的随机地图生成,在本质上,就是叠加了一定的随机性,约束和边界条件的循环平铺效果。&br&&br&&figure&&img src=&https://pic3.zhimg.com/47a0c8c78f1adf3afcee_b.jpg& data-rawwidth=&550& data-rawheight=&413& class=&origin_image zh-lightbox-thumb& width=&550& data-original=&https://pic3.zhimg.com/47a0c8c78f1adf3afcee_r.jpg&&&/figure&&br&&br&&br&c. 在 3D 游戏里循环平铺高度图,形成连绵不断的地形效果。这在早期的模拟飞行射击类游戏里比较常见,现在已经很难搜到图了,我在上大学的时候写的第一个地形渲染 demo 就是平铺的,可惜刚刚翻硬盘已经找不到了555。这一类游戏,在平铺时适当地辅以一些贴图的变化,可以在很省内存的条件下,做出非常不错的效果。&br&&br&找不到游戏内的图,拿下面这个高度图来凑数吧。请大家脑补一下,把下面这个灰度图立体化之后,一块一块像地砖一样循环平铺以后,3D渲染出来的连绵起伏的直抵地平线(好吧,直抵远裁剪面)的山脉的壮观效果吧。&br&&br&&figure&&img src=&https://pic2.zhimg.com/b9c4ebfd28bccc8475ac1d_b.jpg& data-rawwidth=&512& data-rawheight=&512& class=&origin_image zh-lightbox-thumb& width=&512& data-original=&https://pic2.zhimg.com/b9c4ebfd28bccc8475ac1d_r.jpg&&&/figure&&br&&br&----------------------------&br&&br&### 2. 可预测随机数和无限宇宙(Predictable Random)&br&&br&(本节内的描述和算法,部分参考了《Game Programming Gems I》 [“2.0 Predictable Random Numbers”](&a href=&//link.zhihu.com/?target=http%3A//www.gameenginegems.net/gemsdb/article.php%3Fid%3D75& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Graphics and Game Gems Database&/a&) 一文,请感兴趣的同学自行查找原文通读)&br&&br&有个传说中的游戏叫 Elite ,不知道有没有同学玩到过。据说这游戏运行在32K内存的机器上,其中16K还是只读的ROM。这游戏据说拥有难以匹敌的游戏深度:近乎无限个行星,每一个都有各自的名字和特征。&br&&br&可预测随机数本身是游戏内运用非常广泛的一个技术,这里我们着重谈一下它在为游戏提供(微观上)更丰富的细节和(宏观上)更广阔的世界的作用。这一技术的最重要原则是,为了在一个游戏世界中给出无限空间的幻觉,我们需要满足两个分解条件,可以把它们成为宏无限(macro-infinite)和微无限(micro-infinite)”。前者涉及到问题的空间规模,后者则任意一个对象所支持的最小细节层次级别。&br&&br&----------------------------&br&&br&从实现上来说,如何设定随机种子是这个技术的核心。由于给定一个随机种子,生成的随机序列是完全可预测的,那么根据游戏内的一些时空的设定,通过对随机种子进行一些定制,得到在游戏内任意某个时刻和某个空间点上完全可预测的行为就是可行的了。&br&&br&最简单的是使用以下几个元素与随机种子配合计算:&br&1. 世界坐标(即 X Y Z 值,既可以表示空间中的某个点,也可以表示某个区域)&br&2. 系统时间(可以用真实时间,也可以用游戏内设计者定义的时间,如果是前者的话需要考虑离线时的处理)&br&3. 正态分布(在游戏里建一个查找表即可,这是最廉价的方案)&br&&br&这些因素加上对应的随机序列,已经可以营造出宏观上比较有深度的一个宇宙空间了。理论上,如果所有的随机性都是由给定的随机种子产生,而这些随机种子要么是游戏定义的常量,要么是查表得到,要么是均匀流逝,要么是由更高层次的随机种子生成,那么这样一层一层上溯到尽头的话,任何一个游戏内的宇宙,都可以归因到一个初始的种子,这个种子,就是决定论中经典物理学的所谓的&b&第一推动&/b&吧。其实如果真做到了这一点,我们大可以把这个种子交给玩家,在首次进入游戏的时候掷一个 2^64 骰子。这是真正的上帝创世的感觉,想象一下,上帝说,要有光,于是掷出了骰子,第一推动怦然落地,整个时空的巨大齿轮开始运转,在不同的时间点和空间点上,更多的随机序列被生成出来...&br&&br&&figure&&img src=&https://pic1.zhimg.com/50bde2249146cce4ea9c_b.jpg& data-rawwidth=&640& data-rawheight=&517& class=&origin_image zh-lightbox-thumb& width=&640& data-original=&https://pic1.zhimg.com/50bde2249146cce4ea9c_r.jpg&&&/figure&&br&&br&&br&这幅图来自于游戏 Frontier:Elite II(出自[这篇文章](&a href=&//link.zhihu.com/?target=http%3A//rakanalysis.wordpress.com//from-the-archive-frontier-elite-ii-a-retrospective-review/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&rakanalysis.wordpress.com&/span&&span class=&invisible&&//from-the-archive-frontier-elite-ii-a-retrospective-review/&/span&&span class=&ellipsis&&&/span&&/a&)),下面配的字样是:&b&“This picture doesn't even begin to show the scale of the universe.”&/b& 大家感受一下。&br&&br&----------------------------&br&&br&微观上本质上也是一样的,只是把发散的过程倒过来,变成了逐级收敛的过程。为了在某一个点上放大时,能得到尽可能细致,准确和一致的表现,我们需要对较低层次的世界定义更丰富的规则和约束,比如黑洞对周围的影响情况,双星体系的轨道,恒星的种类与行星个数之间的对应关系,不同恒星系结构下行星表面的大气构成,等等。这样才能较好地平衡多样性和独特性,带来更真实的模拟效果和更令人信服的游戏体验。&br&&br&----------------------------&br&&br&### 3. 精度问题解决方案&br&&br&当足够大尺度的世界被创建出来时,就会自然而然地遇到精度的问题。同时这也是补充说明中提到的一个问题,这里我们简单介绍一下几种实践中的解决方案。&br&&br&先描述一下问题吧,我们知道,IEEE754 中规定的32位浮点数中,有23位是用来表示尾数的。也就是说,对于接近1的浮点数,可能会带来 2E-24 左右的误差(大约是 0.)以现实单位计算的话,如果游戏世界是边长为100km的正方形,那么在这个正方形的最远角落里,我们的最小空间单位是约 7.8 毫米;而如果是中国这么大的面积的话,空间误差将达**半米**左右。那么可以想象一下,如果是宇宙级别的游戏,采用32位浮点数作为全地图精度,那么实际误差可能会有多么大。&br&&br&在实践中,这种误差可能会带来以下这些影响:&br&&br&1. &b&无法将相邻的对象对齐&/b&。这种情况,场景美术(关卡设计师)应该会比较头疼,这对于游戏的编辑器来说是大问题了。物件没法在引擎编辑器里对齐;在不同的平台上,对齐也不一样;甚至不同的编译器,同一个编译器的不同版本编出来的引擎;对齐都不一样 ... 所以说处女座不要做 LD :P。&br&2. &b&模型间的穿插和裂缝&/b& 本来封闭的墙角可能漏个洞,本来放在地上的石头变成了悬浮在空中。这实际上是上一种的变种。&br&3. &b&骨骼动画的抖动&/b& 由于世界矩阵往往参与骨骼动画的运算,误差可能会被逐级放大,在那些最远离根骨骼的骨头上(也是玩家最容易注意到的地方),抖动可能会发生得非常剧烈。&br&4. &b&物理模拟失真&/b& 一些柔体的模拟可能会直接失败,而刚体也可能会产生怪异的运动。碰撞系统也可能无法正常工作。&br&&br&所有这些一旦发生,都是很容易觉察的。一旦你发现在一个很大的坐标上有这些问题,而接近原点处问题却消失了,那么很有可能就是精度在作怪。而需要注意的是,出现这种问题,只和游戏中出现的数字的规模和跨度有关,和游戏选择了什么样的长度单位(如用毫米还是公里做单位)无关。&br&&br&----------------------------&br&&br&那么怎样使用有限的坐标精度来描述较大尺度的世界呢?&br&&br&最直接的方案是 &b&使用双精度浮点数 &/b&(64 位),如果这是可接受的选择,那么就不必费心引入后面讨论的复杂度了。&br&&br&其次是 &b&区域划分法&/b& 。我看到 &a data-hash=&1e2cccc3ce33& href=&//www.zhihu.com/people/1e2cccc3ce33& class=&member_mention& data-editable=&true& data-title=&@Milo Yip& data-hovercard=&p$b$1e2cccc3ce33&&@Milo Yip&/a&
同学已提到,不过这里出于完整性的考虑,再简单说一下。正如 Milo 同学所说,“把世界划分成不同的区域,在区域内的计算使用其局部坐标系统。”相对应的跨区访问,需要对应的“本地A -& 世界 -& 本地B”的坐标转换。&br&&br&还有一个方案是 &b&节点中转法&/b&。正如移动电话的基站用来承载和协调整个通信网络那样,我们在游戏的给定活动区域使用静态信标,所有的逻辑上与之相关的单位,都以该信标的坐标作为参考单位,这样也可以做到把数据访问局部化。相距足够远的两个物体(相当于上面的跨区访问)交互总是通过静态信标来完成(正如移动电话网络中发生的那样),这样的好处是相关的复杂度可以隔绝在这个中转系统的内部。&br&&br&此外
&a data-hash=&9a5b0b946e064a3a170f04c2d3563a78& href=&//www.zhihu.com/people/9a5b0b946e064a3a170f04c2d3563a78& class=&member_mention& data-editable=&true& data-title=&@凯丁& data-hovercard=&p$b$9a5b0b946e064a3a170f04c2d3563a78&&@凯丁&/a&
同学提到了一个&b&坐标转换法&/b&,“所有位置信息都以角色位置为中心做一次转换”。这正是 《Game Programming Gems IV》 [“4.0 Solving Accuracy Problems in Large World Coordinates”](&a href=&//link.zhihu.com/?target=http%3A//www.gameenginegems.net/gemsdb/article.php%3Fid%3D280& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Graphics and Game Gems Database&/a&) 文中的方案。这个方案可以解决部分问题(主要是渲染相关的问题),但是仍有一些问题需要考虑,比如:1. (上面提到的)编辑器内操作的物体,在序列化到文件中时,精度丢失的问题。2. 大部分物理模拟通常需要一个角色无关,摄像机无关的全局坐标系。等等。&br&&br&----------------------------&br&&br&### 4. 超大地形的处理 (Terrain Visualization)&br&&br&终于说到对超大地形的处理了。可以说从上世纪九十年代起,超大地形的可视化,一直是3D游戏领域热门的话题。今天我们就借着这个机会,把相关的算法和实现理一理吧。&br&&br&考虑到篇幅太长的话,俺的手指头招架不住,再一个不少对这个话题感兴趣的同学可能压根就不是程序员,一些实现细节可能我就只是简单提一下,贴代码什么的还是算了,尽量保证整篇文章的信息浓度适中吧。&br&&br&----------------------------&br&&br&总的来说,这十多年来,地形渲染技术的发展史就是一部生动的对现代GPU的开发,利用和改进史。整个过程大致可以分成三个阶段:一开始,GPU处理顶点能力很弱,这个时期的各种精巧算法(如一些VDPM和后期的ROAM),**尽力在用CPU来降低顶点的总量**,避免一不留神压垮图形系统;后来,图形系统的能力上去了,人们开始更多地考虑到**把地形系统融入到通用的场景管理**中去,如四叉树八叉树什么的就是在这个阶段被广泛应用的;再往后,GPU已经很强大了,CPU由于要承担更复杂的游戏逻辑,越来越成了整个系统的瓶颈,这个阶段,人们琢磨的更多的是,怎么**利用GPU给CPU减负**了,一直到如今,由 GPGPU 带动起来的异构计算,也都是这个路数。&br&&br&----------------------------&br&&br&由于内容比较杂,超大地形这个段落,按上面的描述,咱们分为三个小段分开来讲吧。让俺先沏上一杯碧螺春,为客官一一道来。&br&&br&#### 4.1 古典算法(从 GeoMipMapping,Progressive Mesh 到 ROAM) &br&&br&&b&GeoMipMapping&/b& 是从纹理的 MipMapping 技术演化来的一个地表处理技术,原理上是根据任何一小块地形在屏幕上显示的实际尺寸(主要跟与摄像机的距离和起伏程度有关)来选择对应密度的网格,然后把不同分辨率的网格之间以某种方式拼接起来(没有这一步的话就会有裂缝),本质上是一种比较粗糙的区域 LOD 算法。顺便说一下,由于那时候针对顶点级的处理很多,导致这种T型裂缝很常见,以至于有个专门的名字叫“T-Junction”,针对这个的处理在当时也有很多方案。&br&&br&&figure&&img src=&https://pic2.zhimg.com/a009ae667fd9_b.jpg& data-rawwidth=&551& data-rawheight=&398& class=&origin_image zh-lightbox-thumb& width=&551& data-original=&https://pic2.zhimg.com/a009ae667fd9_r.jpg&&&/figure&&br&&br&这是俺刚刚到老硬盘里刨出来的大三时写的 GeoMipMapping 代码,编了一下居然还能跑起来。有点土,别介意:P 可以看到不同的 MipMap 级别是用不同的颜色渲染的,也可以看到接头处 T 型裂缝的处理。唉,这代码勾起了俺的青葱回忆啊,那就顺便再来两张 T 型裂缝的示意图和消除过程吧。&br&&br&&figure&&img src=&https://pic3.zhimg.com/14d993ba8c1bdde03a2e_b.jpg& data-rawwidth=&522& data-rawheight=&535& class=&origin_image zh-lightbox-thumb& width=&522& data-original=&https://pic3.zhimg.com/14d993ba8c1bdde03a2e_r.jpg&&&/figure&&br&&br&----------------------------&br&&br&&figure&&img src=&https://pic1.zhimg.com/cd4ebdf412bfe4538a6ddd1fb7b2a3fc_b.jpg& data-rawwidth=&612& data-rawheight=&495& class=&origin_image zh-lightbox-thumb& width=&612& data-original=&https://pic1.zhimg.com/cd4ebdf412bfe4538a6ddd1fb7b2a3fc_r.jpg&&&/figure&&br&&br&----------------------------&br&&br&&b&Progressive Mesh&/b& 是后来很流行的技术 Simplygon 的前身,原理上基本也是一致的,就是以某种方式渐变性地化简某个给定的 Mesh。&br&&br&渐进式网格有两种:视点无关的 (View-Independent Progressive Mesh,VIPM) 和视点相关的 (View-Dependent Progressive Mesh,VDPM)。两者的区别就是,前者预先离线生成好所有的渐变过程,运行时直接用就行(也是后来 Simplygon 采用的方案),而后者随着摄像机的位置和角度的变化,生成对应的简化模型。两相对比,VIPM的好处是运行时运算开销低,简化模型的效果好,缺点是费内存(因为数据都存下来了,当然后来增量的方式能省一些),而VDPM在当时是不错的选择,因为跟VIPM相比不用费额外的内存,而且对于视点(就是摄像机)变化不剧烈的应用,不需要每帧处理和更新对应的简化模型(普通的MMO类的一般一秒一次就够了),此外由于一些简单的遮挡剔除和背面剔除,能够比VIPM裁掉多得多的顶点(一般能多裁1/3到一半吧,在当时这可是头等大事)。&br&&br&总的来说,至少在当时,两者的应用都比较广泛,而到了后来,显存越来越大,总线却越来越紧张,VDPM这种典型的刷顶点的算法(比较费总线带宽)就逐渐失去了市场,这是后话了。&br&&br&大家可以在[这里](&a href=&//link.zhihu.com/?target=http%3A//www.cbloom.com/3d/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&3d Page (www.cbloom.com/3d)&/a&)看到一些 PM 在地形渲染上的应用。图咱就不上了,大家可以到 Simplygon 的网站上去看。&br&&br&----------------------------&br&&br&&b&ROAM&/b& 可算作是上面提到的 VDPM 更进一步了。这个算法实际上借鉴了当时主流引擎的标配BSP的思路,想利用二叉树这个最简洁的空间描述数据结构,把(CPU端的)顶点消减发挥到极致。整个地表被组织成一个巨大的二叉树,有两个队列,一个是分割队列,一个是合并队列,分别用于处理摄像机移动时,增加进入视野的区域细节和消减退出视野的区域细节。精心设计的 ROAM 效果非常华丽(尤其是在线框模式下),你会看到在各种因素的影响(包括局部坡度,与摄像机的夹角,遮挡情况等等)下,各种三角形像魔术般的不断地变幻,生成和擦除超多的细节,效果非常魔幻。我印象很深的是当时连续打Quake3两个小时完全无感的我,调试这玩意的时候,每每不到十分钟眼就花了。&br&&br&网上找了两张比较典型的 ROAM 大家感受一下吧。&br&&br&&figure&&img src=&https://pic2.zhimg.com/07be8d1e331b9b1b7ebae1_b.jpg& data-rawwidth=&1003& data-rawheight=&749& class=&origin_image zh-lightbox-thumb& width=&1003& data-original=&https://pic2.zhimg.com/07be8d1e331b9b1b7ebae1_r.jpg&&&/figure&&br&&br&----------------------------&br&&br&&figure&&img src=&https://pic3.zhimg.com/d276affe4ed461cb33408e_b.jpg& data-rawwidth=&636& data-rawheight=&398& class=&origin_image zh-lightbox-thumb& width=&636& data-original=&https://pic3.zhimg.com/d276affe4ed461cb33408e_r.jpg&&&/figure&&br&&br&----------------------------&br&&br&&br&#### 4.2 层次的艺术(Quadtree 和 Chunked LOD)&br&&br&其实用于空间管理的树状结构有四叉树和八叉树(还有上面的二叉树),但地表通常以前者居多。是因为,从小范围来看,变化剧烈的地形是3D的,适合八叉树在xyz三个方向上扩展;但当尺度大到一定规模之后,地形通常退化为相对扁平的2D空间,就像摊平了的地球表面那样,在竖直的Z方向上变化相对不大,而XY平面则是可能无限延伸的。&br&&br&&b&Quadtree&/b& 四叉树很直白,具体的细节我就不讲了。值得一提的是四叉树往往也同时用于场景管理的快速剔除和查找,从理论上来讲,四叉树是一个平面上最迅速的用于剔除空间,定位一个物体,内存开销也是相对较低的数据结构。当用于地形渲染时,顶点剔除的效率也很高,我印象中仅次于高度优化的 ROAM。内存开销低主要是因为四叉树是可以完美展开到一个位数组里的,这样的话意味着整个树的利用率达到了百分之百——所有的空间都用来存储数据而不是维持结构。&br&&br&不过四叉树也不是啥都好,T型裂缝就比 GeoMipMapping 难处理,因为存在跨级的多段 T 缝,如下图:&br&&br&&figure&&img src=&https://pic2.zhimg.com/1b93ffaf7dfefee85af1_b.jpg& data-rawwidth=&326& data-rawheight=&311& class=&content_image& width=&326&&&/figure&&br&&br&&br&除此之外还有一些细节问题,这里就不一一说明了,地形的四叉树渲染还是有很多细节需要细心处理的,此处暂且放下不表。&br&&br&----------------------------&br&&br&&b&Chunked LOD&/b& 是一种杂合改良的 LOD,其实糅合了上面说的不少细节,本质上是一种分区块地消减细节的技术。所谓 Chunk 是批量处理的一种方式,只是一种粒度划分的单位而已,跟现在 Java 的 GC 里分区回收概念上差不多。&br&&br&下面是典型的 Chunked LOD 后的效果:&br&&br&&figure&&img src=&https://pic3.zhimg.com/f061aa7fd657ea01a4fbb4b5b7579c7a_b.jpg& data-rawwidth=&1015& data-rawheight=&378& class=&origin_image zh-lightbox-thumb& width=&1015& data-original=&https://pic3.zhimg.com/f061aa7fd657ea01a4fbb4b5b7579c7a_r.jpg&&&/figure&&br&&br&顶点多次过滤优化后的效果:&br&&br&&figure&&img src=&https://pic2.zhimg.com/fcaffd00ee8d67b93aa2701_b.jpg& data-rawwidth=&901& data-rawheight=&439& class=&origin_image zh-lightbox-thumb& width=&901& data-original=&https://pic2.zhimg.com/fcaffd00ee8d67b93aa2701_r.jpg&&&/figure&&br&&br&&br&效果在当时还是很惊艳的。通常不到50k的渲染数据量已经能有非常逼真的效果了。&br&&br&----------------------------&br&&br&#### 4.3 以GPU为主的技术(从 Paging,Clipmap 到 GPU Terrain)&br&&br&上面的基本上都是传统方案,这一节我们将逐渐过渡并挨个介绍一下以 GPU 为运算主体的算法。&br&&br&----------------------------&br&&br&所谓&b&分页&/b&(Paging)实际上是仿效虚拟内存的运行机制的一种方法。由于地表的顶点数据都是静态数据,适合常驻显存。当世界尺度较大时,显存没法一次放入所有数据,那么系统就像虚拟内存那样,把那些暂时没有用到的数据交换出去。随着游戏的进行,Paging In/Out 也在不断进行,辅以一定的异步机制,加载到显存的延迟可以被很好地掩盖。玩家的直观感受就是:哇,海量的细节。&br&&br&&figure&&img src=&https://pic1.zhimg.com/ea740fdbaed05c5102354_b.jpg& data-rawwidth=&996& data-rawheight=&798& class=&origin_image zh-lightbox-thumb& width=&996& data-original=&https://pic1.zhimg.com/ea740fdbaed05c5102354_r.jpg&&&/figure&&br&&br&----------------------------&br&&br&而 &b&Clipmap &/b&则比 Paging 更进一步,以金字塔的形式逐级把数据排列好,直接整体更新和渲染。从这里也可以看出 GPU 时代人们的思维方式的逐步变迁。从以前顶点级别(Vertex Level)的“锱铢必较”,到后来的一次多塞一点也无所谓,只要批次(Batch)少就 OK。下图可以看出 Clipmap 的基本思路。&br&&br&&figure&&img src=&https://pic2.zhimg.com/811bdc0e3fb8be4ab928d5_}

我要回帖

更多关于 沙盘制作 的文章

更多推荐

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

点击添加站长微信