1。还没有真正的次世代网游,更别指望它能赚钱
好几次有朋友问我,次世代网游到底是什么东西
我说,次世代网游就是模型至少上万面,贴图每张都起码2048,法线图,高光图,多层蒙版一个都不能少;动态光满天飘,还都是开着阴影的;体积云,体积雾,体积光,全都是立体的,酷;水面的反射,折射,波纹,浪花,样样精彩;超大型,超豪华场景,无限视野。就一个字:真实!哦不对,是两个字,超真实。
这样的游戏用现在能配到的最好的机器,跑起来FPS也一定不允许超过15,否则那就不能叫次世代了。
说白了,次世代就是你用当下的机器完全跑不起来的游戏,要么怎么能叫“次”世代呢。
这样的游戏,不是让大多数玩家能玩得起来的,不是拿来卖钱的,就跟北京的商品房一样,是给大家去追捧的。最多,会等Intel,Nvidia来给开发商返点利。嗯,我是说,大概可能估计会有的吧。
次世代的游戏其实已有不少了,比如战争机器,比如战神,比如彩虹六号……但次世代的网游还没有。
魔兽是次世代吗?不是,完全不是。
永恒之塔是次世代吗?也不是,它也还差的远。
天下二,剑网三就差的更远了。
2。真正赚钱的游戏技术都很普通
也许你会说,真正的次世代游戏都还没有出来,你怎么就敢预言他们不能赚钱呢?
是的,我不能,如果我有未卜先知的本领,那我早就不再需要靠做游戏来养活自己了。
可是,我们却能够看到现在赚钱的游戏是什么样的,这些是明明白白摆在那里的。
魔兽世界:把这个游戏誉为国内3D游戏的教程书完全不为过,不过你承不承认,策划也好,程序也好,美术也好,都从这里面学到了很多东西,模仿了很多东西,在目前国内的游戏里面,到处都能找到魔兽的影子。
可是魔兽又用到了多神奇的技术?主角模型算上所有部件,3000多面,各部件的贴图组合在一起,512大小,没有法线没有高光,绝大多数还都只是一层贴图,偶尔有一些多层混合的。地形最简单的分块4层混合,最简单的lightmap。水面,把镜头拉远一点都能看出来贴图形状。天空盒,一个普通的m2模型。
可是,魔兽所表现出来的整体场景效果,有哪一个游戏敢说超越呢?
天龙八部,基于开源引擎Ogre制作的典范,也因为天龙八部鼓舞了国内好多使用Ogre开发的小团队。
不得不承认,Ogre所使用的技术是最朴实的,朴实到我这样一个3D新手都能拿来修修改改,做点简单的demo。
同样不得不承认,天龙的画面效果确实很一般,2.5D的场景,固定的视角,轻盈的有些像纸片人的模型,可是,这并不妨碍他每月近两亿的收入。
梦幻,大话,DNF,征途,传奇……
除了这些表面上能看到的技术以外,背后的技术是同样的道理。
早期的单服务器,分线方式,依然沿用在现在很多主流的游戏服务器端,并且依然是非常赚钱的项目。而类似于BigWorld的高深架构,事实上也并没有成功的项目。如果把天下二的商业结果跟其他项目一比较的话。
3。2D游戏比3D游戏赚钱
我一样很认同,未来的趋势是3D,但是,那时候赚钱的3D项目不应该是现在这个样子的。
以国内游戏玩家的年龄及文化层次来看,要让他们接受“右键旋转朝向,左键旋转视角”太过于困难,而即使是一个很熟悉3D操作模式的老玩家,进入到一个新的场景中,要分辨出“上北下南,左西右东”也是很烦人的一件事。
如何尽可能的使用上3D的表现力,但又避免掉目前3D游戏的复杂操作模式,这要看未来谁先能走好这一步。
但是,在3D真正应用起来之前,目前还是2D的天下。
国内最赚钱的梦幻,还有大话系列,同样最高在线超过200万的DNF、征途,还有那不应被忘记了传奇。
不用历数这些名字,文化部2009网游行业发展报告上统计的结果是,2D游戏收入占整个游戏行业收入达70%多。
这也无怪乎腾迅到现在还在开发2D新项目,以3D起家的完美也要开2D项目,网易的大话到3代了还是2D,把unreal3应用得纯熟的韩国人也同样还在制作2D游戏。
4。游戏开发并没有什么高深的技术
首先需要明确的一点,游戏项目是工程项目,不是科研项目。
工程项目的目的是在有限的人力跟财力之下实现出既定的需求,而这个需求从前面的分析可以知道,要求并不高,所以,需求的实现过程也就并没有多么高深。
至少在我经历过的项目里,没有什么惊天地泣鬼神似的英雄人物,没有创造出多么伟大的算法,我们所做的,只是使用现在的技术,现有的方法,拼合成一个软件产品,一个融合了程序、美术、策划劳动力的软件产品。
游戏开发的过程里,没有,也不需要多厉害的技术高手,需要的仅仅只是有耐心,有责任心的普通技术人员。
5。游戏的卖点在内容而不是画面,但是画面不够好却没有机会去展现内容
说这一点不是想强调到底是程序重要还是美术重要,或者是策划更重要。这三者是缺一不可,而且哪一方弱都不行的。
我想说的是,游戏真正留住玩家靠的还是内容。
一样是拿现在赚钱的游戏来说,梦幻没有华丽的3D场景跟画面,天龙有3D,但没人会说那里面有华丽的场景,DNF的2D画面还是非常粗糙的,唯独好一点的魔兽,但他的市场表现在国内游戏里面来说,并不算太强。
但是好的画面在最开始的几分钟里却是相当重要的,这就好比是长的帅的人能够更吸引女孩子一样。
也许你能用你的魅力,你的钱袋子来打动女人,但如果你穿着一件破衣服,脸上只有着残缺美,那你后面那些魅力,那些优点永远没有机会展示出来。游戏也是一样。
至少,你的新手村一定要做到富丽堂皇。
6。游戏并不需要追求太多的游戏性,提供一个交流的平台就行
这是我最近的感悟。
很多人玩游戏其实就是为了打发时间,我也问过很多沉迷于魔兽,沉迷于偷菜,沉迷于这些那些游戏的人,包括偶尔玩一下的,包括职业玩家,包括像我这样,为了游戏而玩一下的人。
游戏靠什么来留住人,在这一点上达成共识并不难,那就是里面的朋友。所以,给玩家营造一个更好的交流氛围,交流环境,做到这一点了,游戏玩法可以要多俗有多俗。
又在游戏里面,还有社区里面接触了一些新生代的玩家们,似乎家族是一个很流行的东西。这其实可以看作是以前游戏里公会的升级版。在某个儿童游戏里,一个玩家带着我去参观他们的家族,带我一个个拜见他们的官员。可我并没有看到这些官员的头衔,于是我问,你们这些官员是怎么来的?答曰:自己封的。
就好像公园里的小道一样,有时候,游人们会按照自己的喜好在草地上走出一些新的路来,这些路才是最合理的。
为什么不顺着这些玩家的路,把这些功能做的更强大一点呢。
其实,把社群的功能做得更强大,更高级一点,那就像文明。或者做的更容易,更低龄一点,那就像过家家。不管是怎样,应该在系统里就增强了交流的便利性,甚至可以在玩家一加入到游戏中,就开始引导着他加入社群。只有在社群里,他才能找到家的感觉,他才会因为朋友们而留下来。
当然,怎么找对这条路,走好这条路,可不像写下这几行字这么简单。
根据文化部2009年游戏行业发展白皮书上的数据,游戏运营失败比例30%左右,游戏开发失败比例50%左右,当然这个是指登记注册了公司的,其实还有更多小团队,小工作室,直接死在了起步的路上。
单从这个比例来看,似乎创业的风险还是挺大的,每一个成功活下来的公司都踩着一个或几个死去公司的尸体。可是,相比起大家常说的,十家创业公司一家成功,这个比例也不算高,而且,要是再看看成功的游戏公司获得的回报,这个风险也就真的不大了。
说游戏创业的风险不如说小创业团队最终的结局。
几个志同道合的朋友走到一起,当然是想把事情做成功的,但是美好的期望不能保证全都能实现,不过,即使这个过程失败了,也不一定全都失败,除了最好的结局之外,也还是有其他几种可能性的。
从最好到最坏,大致有以下这么六种结局:
1。游戏在预期时间内开发成功,并上线运营
当然,这是最理想的结局,也是团队组建初期给自己设定的目标。
国家政策规定,运营网络游戏的公司注册资金必须一千万以上,另外游戏的宣传推广费用一般都比较高,所以如果没有足够的资金实力,独立运营的可能性不大。
但是如果一切都进展顺利,顺利的将游戏开发完成,顺利的找到了投资人,自己组建运营团队其实是最好的选择。因为都知道,运营才是创造利润的地方。
2。游戏开发成功,但不具备独立运营的条件,转做单纯的开发商,把产品代理给别人运营
如果很不幸的,没有足够的资金实力去运营自己的游戏,或者,创业伙伴们对自己运营游戏的把握都不大,也找不到一个放心的人来做,再或者,投资人不希望由开发团队自己来运营。那么,把产品代理给专业的运营公司,获得利润分成,也是一个不错的选择。
其实很多的创业小团队最终选择的都是这条路,更不用说参与18计划,赢在巨人计划的那些团队了,本身就是属于投资公司旗下的。
运营毕竟不同于开发,游戏开发需要解决是技术问题与团队内成员的合作问题,而运营面对的竞争却有可能是血淋淋的,商场如战场,没有十足的把握与充分的资金支持,需要慎重对待。
但是代理给别人最大的一个问题是收入将受制于人,首先这个分成比例可能会并不高,另外,如果游戏比较成功了,运营公司很可能会山寨一款同样的游戏,并把用户引入他自己的游戏,而避免与原来的开发商分成。这样的例子在国内已不鲜见。
3。游戏开发过程中遇到问题,团队加入大公司,以工作室或独立部门方式存在
团队最终加入大公司的情况,也并不一定是自己遇到了问题,也有可能是合作的需要。
当创业团队度过了最初的一段时间,游戏基本成形,开始大量的内容制作的时间,将可能需要招聘大量开发人员,这时,人员的管理不像以前几个核心人员那样,完全靠自觉了。而创业的伙伴们显然对技术的把控比对人员的把控能力要强,也许,找一家成熟的公司,由他们来处理这些人事、财务方面的琐事要容易得多。
另外,也有可能投资人希望开发团队能够在自己身边,能够经常看到开发成果,能够让自己更放心。毕竟,投进去的钱那是真金白银,花钱的人不在身边,心里总会有些不大放心。
还有可能就是团队确实遇到了问题,比如资金方面的问题,找一家有实力的公司进行合作,整体并入大公司,成立一个新的部门,或者独立工作室,继续原来的工作。
这种一般会与公司按比例分红,但因为公司在开发期就在支持付工资,也承担了风险,所以这个分红比例不会比代理分红高,但对于当初创业的核心开发人员来说,也绝对比在公司打工强。
4。开发团队整体被收购,继续回到打工状态
这种结局就已经是不大愿意看到的了。
当然,收购的方式也有多种,如果收购后在新公司成立部门,继续进行原来项目的开发,那与上一种情况其实相差不多,但因为是收购,支付了费用,或者是以股权方式收购的,所以开发团队的身份会有改变,说白了,就是现在是纯粹的打工仔,不能再谈更多的条件。
不是,既然是被收购,那还是会有一点收入,让初创人员有一定的回报。
5。创业失败,团队整体加入新公司,在新的公司里再继续开发新项目
如果很不幸的,团队最终因资金问题,或者合作问题,不得不结束创业的过程。
这时也还是有可能继续博一次,比如找一家有实力的大公司,以团队方式加盟,可以带上以前未完成的项目,或者不带项目。当然,进入新公司后做什么项目,这很难再由自己决定了,另外团队成员是否会被新公司拆散,这也不由自己控制。
但总之,团队加盟新公司,比单打独斗要好。
这只能算是不得已而为之的最后选择了吧。
6。团队失败,解散。各回各家,各找各妈。各自回到自己的打工状态
最坏的情况就是,创业过程失败,最终团队完全解散,或者还没有完全解散的人也找不到合适的去处,这时只能是各回各家,各找各妈了。
进入新一轮的打工状态。
每一个创业的团队都是带着对未来美好的憧憬开始的,并且在开始的时候也是认真在做的,但有人的地方就有江湖,也许合作的过程并不像想象的那么顺畅,也许后来才发现投资人并不像之前承诺的那样大方,也许,大家都错误的估计了自己的能力。
但是,每一个勇敢走出第一步的团队,每一个敢于走上创业这条路的个人都是英雄。
至少,他们的人生有了这段经历而变得精彩起来,至少,他们向成功的人生方向又迈出了一步。
这比那些只会赞叹某某游戏公司有多赚钱的人要强得多,这比那些只敢临渊羡鱼而不敢退而结网的人要可敬得多。
或者,走向创业路上第一步的时候,可以问一问自己,两年或者三年以后,希望自己能是个什么状态。再问一问自己,如果失败了,最坏将是个什么状态。然后再回身看一看现在的自己,现在是怎样一个状态。如果今天换到行业里最好的公司,有同等条件下最好的待遇,两年或三年后自己将可以是怎样的状态。
或许,你会觉得,还是应该走出去试一试。
好游戏的标准很难定义,往往商业上的成功与品质上的优秀并不是完全一致的。那就不谈好游戏,说说怎么做成一款游戏吧。
做成的意思是指能够成功上线运营,至少,保证能有个两万人同时在线吧,这样,其实也就是做成了,一年还能有两千多万的收入,如果只是个小团队开发,那足够收回成本并让开发人员获得基本满意的回报了。只是,她可能不算好游戏而已。
游戏,虽不是软件项目里最复杂的,但也属于最复杂的软件项目之一吧。倒不是说游戏程序写起来有多难,而是游戏项目的需求变更太频繁,游戏项目涉及到的人员配合太复杂。
策划的思路始终在变,朝三暮四是常事,绕个大圈又回到原点也是会有的;运营方随时也会跟进,提出一些更有利于运营推广的想法要求程序去实现。
程序、策划、美术,完全不搭边的一群人,思维方式跟工作习惯存大巨大的差异,却要在一起,把各自的工作揉合起来,成为最终的游戏产品。
这一切,说起来似乎并不复杂,但在执行过程中一旦出现点小差错,一般都是人为的一点小问题,后果基本上却会是灾难性的,之前所付出的努力都付之东流。
但是我们又不能说这些需求的变更不应该有。正是因为大家都在为这项目而努力,都希望做好产品,才会有这样的反复。有句话说的好,好游戏是改出来的。
程序、策划、美术的配合更是无法避免的。以前那种靠一两个程序员单挑所有事情的时代已经成为历史,即使今天某个人确实有这能力,原画画的超级棒,3D模型做的一流好,程序还写的让人刮目相看,可是,时间总是有限的。今天一个优秀的创意,如果等个十来年才能实现出来,那等到出来的那一天,也只能放进博物馆了。
而且,以目前的小型MMORPG项目来看,就算是效率比较高的创业小团队,就算是在一些既有代码的基础上修改来完成项目,也至少需要20到30人年的开发时间。
大概这些描述已经给你蒙上了一层悲观的阴影,事实上做成一款游戏的风险其实并没有想象的那么大。
每一个在游戏公司呆过的人都清楚,游戏开发其实也就那点事。
策划的需求虽然总在变动,但到了今天,没有哪个程序会硬编码策划们的逻辑需求。脚本的引入完全可以让策划们自己去配置、调试、验证一些想法,如果能做的更进一步,提供GUI的工具让他们更方便的去修改这些东西,那策划们将会更少来麻烦程序。
一个称手的工具会让美术人员以最高的效率去制作游戏内容。有时候你会发现,程序只需要花上半天时间为编辑器增加一个很小的功能,却能让美术减少几天的工作量。而美术花上几分钟时间检查所做的东西是否符合程序制定的规范,也能让程序兄弟们少花那两三天的时间去调试到底是什么原因让程序崩溃。
理想的情况下,程序给美术和策划们制作好了工具,游戏的一切内容,都与程序再无关。程序所要关注的,是如何让游戏运行的更顺畅,如何让策划配置的自由度更高,如何让这些工具更好用,如何最大可能的减轻美术与策划的工作量。
再加上这些相互的理解与支持,合作会变得越来越顺畅,大家会更愿意为对方多付出那么一点点时间。我想,真正做到了这一点,在一些团队里经常会听到的美术、策划与程序们相互之间的抱怨也就没有了。
当然,这样的情况有些过于理想化,也许这些不一定都能做到。
实际上你可能依然会在团队里听到程序对美术不遵守规范的抱怨,依然会听到美术对工具不能完全满足使用需求的指责,依然会有策划过来要求程序反复修改或者实现某个功能。
本来嘛,程序、策划、美术工作性质的差异,思考问题方式的差异,使得他们很难完全一致,这时,我们需要有一两个能够顾全到大局的人,去化解这些问题。这一两个人的以身作则,还有他们的感染力,也在带动着团队的合作向着良性的方向发展。
很多时候,箭在弦上,只要有人轻抚一下,紧张局势便可破解。很多时候,只是缺少这样一两个愿意主动站出来的人。每一个团队都会有核心,所谓的核心,并不仅仅在于技术上的主导性,很多时候,他需要把他当作是团队的领头人,主动承担起这些责任。
只要找对了两个这样的人,我相信,小团队要做到和谐融洽还是比较容易的。
如果顺利的话,团队成员每天都能够看到自己所做的东西一点一点在成型,一点一点在完善,这对团队的士气将是很大的鼓舞,每个人都看到未来将会是怎样。
小团队相比大公司,优势在于行动迅速,但劣势在于抗风险能力较弱,更需要注意士气的维持。所以,一个始终充满激情的旗手就显得很重要了。他不应该离大家很远,更不能高高在上,他就是集体的一份子,每个人都能看到的一份子。
中国的国情使得车库文化不大可能产生,即使是对于小团队来说,可是,在我们还在成长的过程中,每个人都应该坐在一张桌子上。
我也始终认为,开发人员本身是并不复杂的,不管是程序、策划还是美术。尤其是一些真正想做好事情的人,在大家认同的合作方式下,有共同的利益诉求,每一个理性人必将选择通力合作,首先把项目做成功。
每一个团队成员,尤其是核心人员,只要心里面阳光一些,再阳光一些,没有那些所谓的办公室政治,没有那些所谓的圈子圈套。小团队十来号人,只有一个圈子,就是这个集体。那么,做事情其实是应该很顺畅的。
只要找对了人,做成一款游戏,其实并不难。
游戏有多赚钱,看一下上市游戏公司的财报就知道了。http://tech.163.com/caibao,这里有国内互联网公司财报汇总,摘录09年第四季度几家公司的游戏业务赢利情况,见下表:
公司 | 游戏营收(万元) | 毛利润(万元) | 毛利率 |
盛大 | 133600 | 80200 | 60% |
完美 | 54000 | 52600 | 86.60% |
畅游 | 48250 | 28800 | 92% |
巨人 | 27300 | 23200 | 83.90% |
当然,这是占据了国内游戏行业收入一大半的几家大公司,不过就算是小公司,只要能成功让产品上线,利润率也是一样的。
从上市公司的财报里可以看到,一般MMORPG的每活跃付费用户平均每月贡献收入大致在60元左右,有些游戏会高很多,有些稍低一些。也就是说,只要有2万活跃付费用户,以85%的利润率来计算,扣除运营成本后的月利润就能达到100万。
不过,目前游戏的类型基本上都是道具收费,这样不是每个玩游戏的人都会掏钱。按照畅游财报中披露的信息,非付费用户向付费用户的转化比例为18%,其他游戏的这个比例应该也不会相差太多,2 * 18%,月收入100万就需要有11万的活跃用户。
活跃用户的统计方法一般是如果该账号在一天内累计在线时间超过2小时,刚该玩家的活跃天数就加1,如果在线时间在半小时到2小时之间,则活跃天数加0.5,在线时间小于半小时的不算活跃天数。当然,这是每日活跃用户数的统计方法。
我们以2小时为单位,只要在每个时间单位内有11 / 12万用户在线,则就达到了10万活跃用户。也就是说,平均在线只要不到一万人就达到了月入100万的目标。
这个数字基本上是可信的。可以再看一看完美的财报,完美Q4财报显示,平均同时在线(ACU)为115.7万人,网游收入为5.4亿元,平均每月为1.8亿,这样每1万平均在线贡献的收入为 18000 / 115.7 = 155万,这个数字比前面计算的不到一万同时在线贡献收入100万还要高,这是因为完美的ARPU值比畅游高,为每月74元。
所以,每当跟别人介绍我是做游戏时,别人都会感叹一下,游戏啊,挺赚钱的,我也笑笑,确实是挺赚钱的。可是,如此赚钱的产品,开发人员的回报又有多少呢?
再来看一下上市公司的财报。依然是畅游的,“非美国通用会计准则产品开发费用为510万美元,环比增长5%,同比减少12%。环比增长主要是由于聘用了更多的游戏研发人员而导致员工薪酬与福利费用的增加。同比减少主要是由于公司管理层奖金方案的调整——之前授予的股权激励在IPO后价值提高,因而公司减少其现金奖励。”
从这句话上可以看出来,这个研发费用不仅仅只是开发人员的工资,还包括了由于员工的相关福利,也包含了为员工所缴纳的保险,公积金等费用。另外也还包括了奖金,以及股权奖励等等。就这520万,也只占到了营收的7%。
在公司管理层解读财报的时候有另外一句话,“截止到2009年12月31日,共有520位工程师。地面推广团队中正式员工有50人,另外还有临时雇员大约还有600人。”不知道这510万的研发费用是否包括了上面提到的地面推广人员和临时雇员,暂时认为其不包含吧,假定这些钱都花在了520位开发人员身上。
另外也不知道这510万是否包含研发相差的办公室租金等费用,这个估计应该包含吧。
公司为员工缴纳的保险,公积金跟我们交的一样,会占工资的20%多,办公开支不大好算,另外福利也不好说,比如聚餐,组织出去玩等,这些不好统计,还不排除有一些无法统计到的灰色内容。简单一点,就算这510万有50%最后以money的形式发到了开发人员手上吧,包括每月固定工资与奖金。这样,平均每人每月是1600美元。
因为大部分公司的奖金都是以多发一个或者几个月的工资形式来发放的,据说畅游每年相当于能发16个月工资,1600 * 12 / 16 = 1200美元。平均8000元的月薪,可能大部分人会感觉到自己并没有这么多。当然,因为有一小部分人有股权的奖励,他们的收入远超过了8000 * 16,所谓20%的精英获得了80%的利益 :) 其实也是差不多的。
如此高利润率的游戏产品,到头来生产者们的回报却也还是一样。所以,再听到人说游戏很赚钱时,有必要好好对其解释一番 :) 游戏确实是赚钱,可钱并不在我们这些人的口袋里。
再来看近期网易的大话二团队集体离职,大明龙权开发团队集体跳槽完美,还有华义成都差不多整个公司跳到腾讯,金山的团队到网易去开发大唐,盛大的英雄年代团队出走征途等等,这不能说是员工不够忠诚,实在是利益分配的不合理。新的老板只要肯多拿出一个甚至半个百分点,分到开发人员的手中就是沉甸甸的诱惑了。
而且还有另外一条很重要的因素,游戏的成功与否很大程度上来自于那几个开发人员。纵观国内游戏厂商,基本上都是只靠着开发团队做的一款游戏就撑起了整个公司,让公司挤进二线、一线游戏厂商行列,甚至靠着这一款游戏去赚美国股民的钱。当然这种成功也有很大的偶然性。
就像一个正在创业路上的朋友所说,游戏是能让你迅速从杨白劳变成黄世仁的最好选择。当前,前提是你不是在为别人打工。
Ogre正在开发中的版本,1.7版,引入了新的Terrain Component与Paging Component,Ogre终于开始对地形渲染进行官方的支持了。Ogre官方论坛上sinbad也提到了这个新的Terrain Component的特性及目前的进展,不过他自己也说,虽然他很希望1.7版能在今年发布,但是结果却也很难预测 :(
曾几何时,在Ogre中寻求更加完善的地形渲染支持是多少人都在做的事,Ogre源代码库中的Terrain Scene Mananger只能实现最基本的高度图渲染功能,搭配一张普通texture和一张detail texture,根本无法实现出我们想要的真实地形效果。于是,Addon论坛上不少人也开始了为Ogre扩展地形渲染支持的工作,比较有名的是PLSM,这部分代码经过不少的修改,之后也被纳入了Ogre的Octree Scene Manager代码库,不过其依然还是相当的简陋。另外还有一个就是Myrddin,这里是论坛上的介绍页面,在1.7版出来之前,这应该是最好的地形渲染选择了。
所以,之前使用Ogre的游戏要么自己来写地形渲染这一块,要么干脆就不要地形,一切皆mesh,比如最近挺火的火炬之光(Torchlight)。
其实,一切皆Mesh也并不是不可以,相反,用Mesh能够表现出更加细腻真实的效果,比如场景本来就是由大师的地下城或者陡峭的山脉构成时。就像前几年玩过的“地牢围攻2”。当然,全Mesh场景的问题也是很明显的,其需要渲染的面数太多,所以,这样的游戏也只好采用固定的斜视角,并且打上很近的雾来减少需要渲染的对象数,就如同前面提到过的两个例子,“地牢围攻”和“火炬之光”那样。
从OgreSVN上checkout出来代码,简单看了下相关的代码注释,其特性还是挺让人期待的。
首先,地形资源有了自己的文件格式,不再是以前的terrain.cfg和terrain.png或terrain.raw了,
另外,terrain和paging以Component的形式实现,不再依赖于Scene Manager的实现。
然后,贴图的混合也已基本实现。目前地形渲染的一个pass最多支持6层贴图,地形渲染常用的normal map, light map, colour map,specular map都已经支持,从其提供的几张试验用的截图来看,多层混合、法线以及高光实现的都已经没有问题了。
另外还有一个额外的特性是支持运行时对地形的修改,并能在后台线程中对地形数据进行加载和保存。
虽然这个特性在目前的游戏中都不需要,因为地形编辑一般是在场景制作的时候完成,也就是由美术人员在制作地图时就已确定好了,在游戏中只需要将地图文件读出来并渲染到屏幕,游戏进行过程中也不允许对地形数据进行任何破坏和修改。
其实,从技术上来说,这并不是绝对的限制,地形及场景完全是可以破坏的,可以重建的,比如,可以让一颗炸弹落过的地方永久的留下一个弹坑,让火烧过的地方只留下一片残骸,树木及杂草都被烧毁,另外,城镇与村落也不需要是美术预先编辑好的,玩家完全可以在一块空旷的地方建起一座城来,当然也可以把别人的一座城烧毁掉,等等。
只是,当地形及场景改变后服务器需要同步大量的数据给客户端,并且,如何保证这些数据的完整性和一致性。这最主要的还是受限于网速的原因,如果网络速度足够快,我们完全可以把游戏做成瘦客户端,甚至无客户端,就像现在的flash web game一样,客户端总是去服务器上取最新的场景数据,这样就不再有问题了。
但是,另外一个问题可能会稍麻烦一些。当地形和场景改变后,服务器端的AI相关数据会受到很大的影响,比如寻路数据,不论是用nav mesh还是用waypoint,这些数据都要重新构造,而这个构造过程一般来说将会是漫长的,但是地形的重建却可能会是相当频繁的。。。还有AI对象的出生数据,等等,这都需要我们花一些精力去思考。
当然,一切能够想得到的问题都不会是大问题,总会有方法去解决它们。今天觉得不可能做到的事,随着明天硬件环境的提升,新的算法的实现,这也将成为我们在游戏中能够亲眼看到的事实。
最近在我的关注领域内的消息还真不少
1。Unity2.6发布,并且将indie版免费。(当然,之前也有30天的试用版)Unity的制作人说过一句话:如果做web,2D就用flash,3D就用Unity。虽然这是一句宣传词,不过,Unity也确实挺不错。
2。Android 2.0 SDK发布,可恶的GFW,好在Android Setup Tools非常体谅我们这些墙里的人,升级工具里有个选项叫https,当然,这是通过牺牲速度来换取的。真不明白GFW封堵sourceforge, developer.android这样的开发者网站是何用意,怕国外的先进技术毒害了我们这群求知的人们?
另外,摩托的支持Android 2.0的新手机也即将发布,售价是诱人的199刀,不知道国内有没有渠道能够买到。当然,这199刀不会是跟之前的iPhone售价一样吧。
3。一款叫做Torchlight的游戏发布,其制作人以前在北方暴雪呆过,所以,这游戏怎么玩怎么像是暗黑卡通版。另外,据说其网络版在一年半后发布,国内由完美时空代理。不过,我更加关注的是它使用Ogre渲染引擎。
4。Qt4.6正式发布,新特性确实添加的很快,想拿它来做工具试试。
这篇文章所使用的方法来源于 CuteQt博客 上的一篇文章:http://www.cuteqt.com/blog/?p=868
生成对象内存布局所使用的方法来自于vc8编译器一个未公开的参数,即 /d1 reportSingleClassLayoutXXX或者 /d1 reportAllClassLayout,MSDN上关于这个的简单说明及示例可以见这里:http://blogs.msdn.com/vcblog/archive/2007/05/17/diagnosing-hidden-odr-violations-in-visual-c-and-fixing-lnk2022.aspx
一般来说用reportSingleClassLayout,因为reportAllClassLayout会生成许多中间类的结果,干扰我们的分析。
按照上面博客中介绍的方法,一步步来跟踪各种虚函数使用情况下的对象内存布局,可以很直观的看到虚函数是怎样分布的,各种继承情况下虚函数是又是怎样生成的,等等。
一点简单的总结:
1。以前常说的,因为虚函数的使用,使得类多了一个vtable指针,大小增加了4字节,其实是不完全正确的。因为一个class内可能会有多个虚表的存在,比如当有多个带虚函数的父类时。
2。多个虚函数在虚表里的顺序由其定义顺序决定。如果是从多个父类派生来的,则这多个虚表的顺序由父类的申明顺序而定。
3。多继承情况下的同名虚函数处理使用了一种叫做thunk的技术,这样通过不同的基类调用同名的虚函数时,都会调用到相同的函数体内。
4。虚基类的实现多了一个vbi表,记录的是虚基类中的虚函数表地址信息。同时在派生类中计算好了到这个vbi表的偏移,这样虚基类里的虚函数在菱形继承关系的派生类中就只有了一份实现。
5。使用的时候其实不必要意这些细节问题,编译器在生成函数调用代码时已经帮我们处理好了,即这个函数在哪个虚表中,是第几个虚函数,最终也就是要做多少个字节的偏移。
不得不承认,QT在Nokia的怀抱中越活越滋润,如长成的少女一般越来越漂亮。
程序速度越来越快,Nokia QT推出的第一个版本运行速度比原来快了近一半,而即将推出的4.6版还将把图形渲染底层重写,以进一步提升运行速度。
平台支持范围也越来越广:S60手机平台的支持,当然,这是Nokia收购QT的最主要目标吧,最新的Windows7和Max OS X以及更多的Unix平台的支持,让QT无孔不入。
完全独立的IDE:QT Creator,虽然说用惯了VS IDE之后在刚接触这个Creator时有很多的不习惯,但QT要做到更大可能的跨平台支持,有一个统一的IDE和编译环境也是必要的,另外,Nokia也提供了Addons以以VS或者Eclipse IDE集成。并且,在新的1.3版Creator上还将使用一个叫做jom的程序来代替VS默认使用的nmake程序,最终应该将会完全摆脱VS吧。
使用硬件加速的OpenVG实现以达到高速的矢量图形绘制,Nokia打算无视Flash的存在?当然,这与“实现自己的Creator”,“使用自己的jom代替nmake”一样,所有的技术都要自己来DIY。或许,只有把所有的技术都掌握在自己手上才是最可靠的。
对触控系统的支持是未来所有操作系统都必须要具备的,包括Windows7,包括iPhone,包括Android,QT4.6在这一块上也不甘落后。
QT的野心从其博客上的一篇文章似乎能够看出点端倪。做游戏,这个跟QT以前所专注的GUI开发八杆子也打不着的领域也要囊括进来,那还有什么东西的出现是你所不敢想象的呢?
也许,这就是未来的软件开发大平台。
或者,正如QT的一个博客站名一样:QT Everywhere!
DXSAS : DirectX Standard Annotations and Semantics,DirectX引入的一项在Shader代码与应用程序之间进行数据绑定的规范,当出到0.8时被广泛用于各种应用程序中,后来从MS的产品线中神秘消失,有关DXSAS的规范文档被从MSDN中移除,只留下几页引用说明。。。
简单来说,当我们在DX中使用Shader的时候,我们会使用一个常量表来设置各种参数值,其中最重要的当属世界变换矩阵了。这在用DX写游戏程序时没有问题,但是在其他一些Shader编辑工具中问题就出来了,如何设置这些变量?当然,每种工具可以提供自己的方法,比如现在Max,Maya都提供了自己的方法在Shader中绑定世界变换矩阵,但问题是每种工具提供的方法都不一样,这样写出来的Shader文件就麻烦了。。。
于是,MS这时站出来,提出了一个标准:DXSAS。这是目前还能找到的DXSAS的一段描述:
Standard annotations and semantics (DXSAS) provide a method of using shaders in a standard way that enables shaders to be used with tools, applications, and game engines. DXSAS defines a set of semantics and annotations that are attached to host application values and effect parameters for the purpose of sharing effects.
地址:http://msdn.microsoft.com/en-us/library/bb173004(VS.85).aspx
有标准是好事,而且我们也能看到,大量的Shader工具及游戏引擎也使用到了SAS,比如FXComposer,比如RenderMonkey,比如CryEngine。。。当然,各工具可能对SAS都会有自己的扩展,但大部分还都是按照标准来的。
可是,为什么MS突然就将这标准移除了。。。而且,没有任何理由的。。。以至于FXComposer1.8版本以前的文档中附带的SAS描述章节也跟着一并删除,我在FXComposer2.5上找遍了整个手册也没找到这个 WorldViewProjection 从哪里来,翻遍了MSDN上HLSL有关的内容也没有看到这几个关键字。无奈Google之,原来受此困惑的人还真不少。
好在,Nvidia推出了一份新的文档,描述SAS的使用方法,或许Nvidia也很困惑,“which came from DirectX 9, and are sometimes hard to find”。。。但是新的FXComposer手册中对这些只字未提却是我仍然很困惑的,对于像我这样的一个新手来说,完全不可能知道这些东西是如何来的,如何去找这些Semantics的定义。
Nvidia网站上的SAS说明文档:http://developer.nvidia.com/object/using_sas.html
以及FXComposor官方论坛上置顶的关于SAS文档的说明贴,当时我竟然没看到这个置顶贴 :( http://developer.nvidia.com/forums/index.php?showtopic=1358
其他人提出的一些相关疑问:
http://developer.nvidia.com/forums/index.php?showtopic=750
http://developer.nvidia.com/forums/index.php?showtopic=31
http://developer.nvidia.com/forums/index.php?showtopic=1061
http://developer.nvidia.com/forums/index.php?showtopic=1347
http://developer.nvidia.com/forums/index.php?showtopic=1394
留下这些记录,也许有跟我一样的初哥们,少点困惑 :)
看到CppBlog上翻译的一篇游戏主循环,想起之前也看到过一篇类似的文章,因为笔记本上第一篇记录就是这个主循环的简短翻译,对比了一下,发现这篇文章对实现细节的描述更多一些,也发上来与大家共享。
这篇文章最早是出现在flipcode的论坛上,地址在这里,后来有人整理了一下,并添加了更多的描述,也就是下面的内容。原贴地址在这里。
文章中关于网络的描述是指局域网环境下的情况,另外这个主循环也没有考虑到windows环境下与Windows Message Loop的结合,如果是应用在windows环境下,可以再参考下这里,把两者结合基本上就差不多了。
翻译并未严格遵照原文,为了读起来流畅,很多句子都是按照我个人的理解来描述。
This article is about a way of structuring a game's main loop. It includes techniques for handling view drawing with interpolation for smooth animation matched to the frame-rate with fixed-step game logic updating for deterministic game logic. A lot of this is still pretty much a work-in-progress as I muddle my way through, learning better ways of doing things or new tricks to add to my bag, so please bear with me.
这篇文章描述的是如何组织游戏主循环的一种方法。内容包括了如何处理平滑的动画绘制,并且能够根据当前帧率做正确的动画插值,另外还要保证固定的游戏逻辑更新帧率,以确保游戏逻辑的计算结果是确定的。
这些内容的实现有很多都还在进行中,我也在不断地学习更好的方法,以及将一些新的技巧添加进来,所以,希望能够给我一些耐心与宽容。
The heart of a game, any game, is the game loop. This is where the action takes place, where the guns fire and the fireball spells fly. In some games, the concept of the game loop may be diffused among different components or game states, which implement their own version of the game loop to be exectued at the proper time, but the idea is still there.
任何游戏的核心都是游戏主循环。游戏的动作执行、子弹的射击以及火球魔法的飞行等等都是在这里实现。
在一些游戏中,你可能会找不到一个唯一的游戏主循环,取而代之的是,你会在一些组件及状态机中找到各个不同版本的主循环。
其实这个原理也是一样的,只不过是把原来的一个主循环拆分成了多个,这样可以控制游戏在不同的时间及状态下执行不同的循环过程。
The game loop is just that: a loop. It is a repeating sequence of steps or actions which are executed in a timely and (hopefully) efficient manner, parceling out CPU time to all of the myriad tasks the game engine is required to perform: logic, physics, animation, rendering, handling of input. It must be constructed in a deterministic, predictable fashion so as to give expected behavior on a wide array of hardware configurations. Classic failures in this regard include old pre-Pentium DOS-based games that were synchronized to run well on old hardware, but which did not have the controls in place to control the speed on newer hardware, and consequently ran so rapidly that they became unplayable on new hardware. With such a broad diversity of hardware as now exists, there must be tighter controls on the game loop to keep it running at a consistent speed, while still taking advantage of more powerful hardware to render smoother animation at higher framerates.
简单来说,游戏主循环就是一个循环过程。
在这个不断重复执行的过程中,我们需要把CPU时间按照一定的规则分配到不同的任务上,这些任务包括:逻辑、物理、动画、渲染、输入处理等等。
游戏的主循环必须保证是以一种确定的、可预测的方式来执行,这样才能在大量的不同硬件配置环境下都得到我们所期望的相同行为。
以前的DOS游戏曾经出现过这样的问题,它们在旧的硬件上运行的很好,但是放到新的硬件上以后就失去控制了,游戏的运行速度变的如此之快,以至于根本无法去玩它。
现在市场上存在这么多的硬件种类,所以就必须要紧紧地控制住游戏的主循环,保证他们以一个固定的速度运行,但同时又能获得这些强大的硬件所带来的好处:以尽可能高的帧率来渲染出更平滑的动画。
Older games frequently tied the rendering of the view very closely to the game loop, drawing the view exactly once per logic update and waiting for a signal from the display system indicating a vertical retrace period, when the electron gun in the CRT monitor was resetting after drawing the screen. This synchronized loops to a predictable rate based on the refresh rate of the monitor, but with the advent of customizable refresh settings this leads again to unpredictable loop behavior. Retrace synchronization is still useful, especially to avoid visual artifacts when rendering the view, but is less useful as a means for synchronizing the game logic updating, which may require finer control.
以前的游戏经常将渲染过程与游戏循环紧密地绑在一起,首先执行一次逻辑更新,然后绘制画面,接着等待显示系统触发一个垂直同步信号,之后就是下一轮循环周期:逻辑更新、渲染、等待……周而复始。
这种同步的循环方法在显示器的刷新率可预测的情况下是有效的,但是当可以自定义刷新率以后,这种行为又变得不可预测了。
垂直同步仍然是有用的,尤其是在避免画面的渲染出现撕裂的情况下,但是用来同步游戏的逻辑更新就没多大用了,这时可能需要更好的控制方法。
The trick, then, is to separate game logic from rendering, and perform them in two separate sub-systems only marginally tied to each other. The game logic updates at it's own pace, and the rendering code draws the screen as fast as it possibly with the most accurate, up-to-date data the logic component can provide.
这种方法就是将游戏逻辑更新与屏幕渲染过程分离开,将他们放到两个分离的子系统中去处理,只是在需要的时候才与另一个打交道。
游戏的逻辑更新严格按照计划来执行,但是屏幕渲染则以它所能达到的最大速率来进行。
The system I am accustomed to using is based on a Tip of the Day ( http://www.flipcode.com/cgi-bin/msg.cgi?showThread=Tip-MainLoopTimeSteps&forum=totd&id=-1 ) posted to http://www.flipcode.com by Javier Arevalo. It implements a loop wherein the game logic is set to update a fixed number of times per second, while the rendering code is allowed to draw as rapidly as possible, using interpolation to smooth the transition from one visual frame to the next.
这里描述的方法基于Javier Arevalo发表在flipcode Tip of the Day 上的代码来实现。
在这个游戏主循环里,游戏逻辑更新被设置为每秒执行固定次数,但同时渲染代码被允许执行尽可能多次,并且还使用了插值来使得两个渲染帧之间的动画变化尽可能的平滑。
Briefly, here is the code. I will then attempt in my own crude fashion to explain the workings, though I suggest you check out the original tip at the above link to read Javier's explanation, as well as the forum posts accompanying it which offer up insights and suggestions on how the performance of the loop may be improved.
闲话少说,下面首先是代码,然后我会简单的按我的方式描述一下代码的工作原理,同时我建议你阅读一下上面链接地址所给出的内容,其中有Javier的解释,并且论坛上的回贴也有一些不错的内容,包括别人的评论和一些关于如何提高效率的建议。
(注:flipcode论坛早就已经关闭,上面的链接地址已经失效,flipcode上只保留有一些优秀内容的archives,在这里能找到这篇原文,其中包括Javier的解释)
time0 = getTickCount();
do
{
time1 = getTickCount();
frameTime = 0;
int numLoops = 0;
while ((time1 - time0) > TICK_TIME && numLoops < MAX_LOOPS)
{
GameTickRun();
time0 += TICK_TIME;
frameTime += TICK_TIME;
numLoops++;
}
IndependentTickRun(frameTime);
// If playing solo and game logic takes way too long, discard pending time.
if (!bNetworkGame && (time1 - time0) > TICK_TIME)
time0 = time1 - TICK_TIME;
if (canRender)
{
// Account for numLoops overflow causing percent > 1.
float percentWithinTick = Min(1.f, float(time1 - time0)/TICK_TIME);
GameDrawWithInterpolation(percentWithinTick);
}
}
while (!bGameDone);
Structurally, the loop is very simple. The above snippet of code can encapsulate the entire workings of your game.
从结构上来说,这个主循环非常简单。上面的代码片段基本上能够囊括你的游戏的整个工作过程。
First of all, the main loop portion is embodied as the do{} while(!bGameDone); block. This causes the loop to run endlessly, until some game condition indicates that it is finished and it is time to exit the program, at which point the loop ends and the game can be properly shut down. Each time through the loop, we perform game logic updates, input updating and handling, and rendering. Now, for a breakdown of the sections of the loop.
首先,主循环的执行过程被包在do…while循环块中,这使得游戏主循环将永不结束地运行,直到游戏明确地被告知需要结束并且退出程序时为止。
在每次进入循环的时候,我们会执行游戏逻辑的更新,输入更新与处理,还有渲染。接下来,把循环分为几个片段来描述。
time1 = getTickCount();
frameTime = 0;
int numLoops = 0;
while ((time1 - time0) > TICK_TIME && numLoops < MAX_LOOPS)
{
GameTickRun();
time0 += TICK_TIME;
frameTime += TICK_TIME;
numLoops++;
}
This portion is the game logic update sequence that forces the game logic (physics updates, object motion, animation cycling, etc...) to update a set number of times per second. This rate is controlled by the TICK_TIME constant, which specifies the number of milliseconds the logic update is supposed to represent in real time. It probably won't take that long to perform, in which case the update won't be performed again until enough time has passed. For example, with TICK_TIME=40, each logic represents 40 milliseconds, thus forcing the code to update game objects at a rate of 25 times per second.
这部分代码处理游戏逻辑的更新,并且强制要求游戏逻辑每秒执行固定次数。
游戏的逻辑处理包括物理更新、对象行为、动画循环等等。更新速率通过TICK_TIME常量指定,其含义为两次逻辑更新的时间间隔,即经过TICK_TIME后应该再次进行更新,而不是说一次逻辑更新要持续这么长时间。
例如,TICK_TIME = 40,其含义为每次游戏逻辑更新后表现40毫秒,这将使得每秒游戏对象会被更新25次。
The logic is encapsulated in it's own while loop. It is possible during a given frame that game logic will take too long. If a logic update cycle goes overtime, we can delay rendering for a bit to give ourselves a little extra time to catch up. The while loop will continue to process game logic updates until we are no longer overtime, at which point we can go ahead and continue on with drawing the view. This is good for handling the occasional burp in logic updating, smoothing out the updates and keeping them consistent and deterministic; but in the case that the logic repeatedly goes overtime, it is possible to accumulate more time-debt than the loop can handle. Thus, the MAX_LOOPS constant is in place to dictate a maximum number of times the loop can repeat to try to catch up. It will force the loop to dump out periodically to handle other tasks such as input handling and rendering. A loop that is constantly running at the MAX_LOOPS limit runs like hell and is about as responsive as a tractor with four flat tires, but at least it keeps the loop operating, allowing the user to pass input to terminate the program. Without the MAX_LOOPS failsafe, it would be entirely possible for a slow computer to lock up with no way to exit as it chokes on the loop, trying to catch up but getting further and further behind.
逻辑更新的代码被包在他自己的while循环中。
可能在某一帧里游戏逻辑的处理时间会非常长,甚至会超时,这时我们可以稍稍暂停一下屏幕的渲染,使得游戏逻辑更新能够在这段时间里赶上来。
这个while循环就是用来让游戏逻辑继续更新,直到我们不再超时,之后我们继续游戏的主循环并且进行屏幕渲染。这可以很好地处理突发的逻辑更新超时,使得我们的更新更加平滑,并保证逻辑的一致性和可预测性。
但是如果游戏逻辑处理持续地超时,甚至使得我们的主循环无法处理过来,这时MAX_LOOPS将会起到作用,他将使游戏控制权从逻辑更新中跳出来,去执行如输入处理和屏幕渲染等其他任务。
MAX_LOOP常量限制了这里的while循环用来追赶逻辑处理时间时最多能重复的次数。这样当游戏真的出现完全无法处理完逻辑更新的时候,用户也有机会结束程序。
如果没有MAX_LOOP的检测,很有可能会出现一台很慢的电脑试图追上逻辑处理时间,但是却越追越远,而又没有机会退出这个过程,最后陷在了这个死循环中……
IndependentTickRun(frameTime);
This section is where input is gathered, events are pumped from the event queue, interface elements such as life bars are updated, and so forth. Javier allows for passing how much time the logic updates took, which can be used for updating on-screen clock or timer displays and the like if necessary. I've never found occasion to use it, but I regularly pass it anyway on the off-chance I'll need it someday. frameTime basically gives the amount of time that was spent in the logic loop performing updates.
这部分代码用来处理用户输入的捕获,事件会从事件队列中被取出来处理,界面元素,如血条等,会在这里被更新,还有其他类似的处理。
Javier在这里留了一个frameTime参数,用来指明逻辑更新所花的时间。可以用这个值来更新游戏屏幕上的时钟或定时器等类似信息。
虽然我目前还没有找到机会用它,但我还是习惯性地把它带上了,也许某天我会需要。
In order for the game loop to run predictably, you should not modify anything in this step that will affect the game logic. This portion of the loop does not run at a predictable rate, so changes made here can throw off the timing. Only things that are not time-critical should be updated here.
为了使游戏循环的运行是可预测的,你不应该在这里修改任何可能影响游戏逻辑的东西,因为此部分在游戏主循环中的执行次数是不可预测的,所以在这里只能做一些时间无关的更新。
// If playing solo and game logic takes way too long, discard pending time.
if (!bNetworkGame && (time1 - time0) > TICK_TIME) time0 = time1 - TICK_TIME;
This is where we can shave off our time debt if MAX_LOOPS causes us to dump out of the logic loop, and get things square again--as long as we are not running in a network game. If it is a single-player game, the occasional error in the timing of the game is not that big of a deal, so sometimes it might be simpler when the game logic overruns it's alloted time to just discard the extra time debt and start fresh. Technically, this makes the game "fall behind" where it should be in real time, but in a single player game this has no real effect. In a network game, however, all computers must be kept in synchronization, so we can't just cavalierly discard the pending time. Instead, it sticks around until the next time we enter the logic update loop, where the loop has to repeat itself that many more times to try to catch up. If the time burp is an isolated instance, this is no big deal, as with one or two cycle overruns the loop can catch up. But, again, if the logic is consistently running overtime, the performance of the game will be poor and will lag farther and farther behind. In a networked game, you might want to check for repeated bad performance and logic loop overruns here, to pinpoint slow computers that may be bogging the game down and possibly kick them from the game.
当由于满足了MAX_LOOP条件而跳出逻辑循环时,可以在这里减掉多加的上TICK_TIME时间,并且只在单机游戏时才这样做。
如果是在单机游戏中,偶尔出现时间上的错误是不会有什么大问题的,所以当游戏逻辑执行超时后也没有多大关系,我们简单的把多花的时间减掉,然后重新开始。从技术上来说,这会使得游戏有一点点时间上的落后,但在单机游戏里这不会有什么实际的影响。
但是在网络游戏中这样做却不行,所有连网的电脑都必须要保持时间上的同步。
在网络游戏中,如果某台电脑落后了,他应该保持其落后的状态,然后在下一次进入逻辑更新循环时,自己让自己多重复几次,以便追赶上来。
如果时间延迟只是个孤立事件,这将不会有多大问题,经过一两次超速就会追赶上来,但是如果游戏逻辑更新总是超时,游戏的表现将会非常糟糕,并且最终将会越来越滞后。
在网络游戏中,你可能需要检查出这些持续超时,表现总是很差的电脑,并将这些可能拖慢整个游戏环境的电脑踢出去。
// Account for numLoops overflow causing percent > 1.
float percentWithinTick = Min(1.f, float(time1 - time0)/TICK_TIME);
GameDrawWithInterpolation(percentWithinTick);
This is where the real magic happens, in my opinion. This section performs the rendering of the view. In the case of a loop where TICK_TIME=40, the logic is updating at 25 FPS. However, most video cards today are capable of far greater framerates, so it makes no sense to cripple the visual framerate by locking it to 25 FPS. Instead, we can structure our code so that we can smoothly interpolate from one logic state to the next. percentWithinTick is calculated as a floating point value in the range of [0,1], representing how far conceptually we are into the next game logic tick.
在这里我们将进行屏幕绘制。
当TICK_TIME = 40时,逻辑更新帧率为25FPS,但是现在大多数显卡都能支持更高的渲染帧率,所以我们没必要反而锁定渲染帧率为25FPS。
我们可以组织我们的代码,让我们能够平滑的从一个逻辑状态到下一个状态做插值。percentWithinTick为一个0到1之间的浮点数,表示我们应该向下一个逻辑帧走多远。
Every object in the game has certain state regarding LastTick and NextTick. Each object that can move will have a LastPosition and a NextPosition. When the logic section updates the object, then the objects LastPosition is set to it's currentNextPostion and a new NextPosition is calculated based on how far it can move in one logic frame. Then, when the rendering portion executes, percentWithinTick is used to interpolate between these Last and Next positions:
每个可移动的对象都有LastPosition和NextPosition。逻辑更新部分的代码在执行时会将对象的LastPosition设置为当前的NextPosition,再根据它每一帧所能移动的距离来计算出NextPosition。
然后,在渲染对象的时候,可以再用percentWithTick来在Last和Next位置之间进行插值。
译注:
time0为最后一个逻辑帧所在的时间点,time1为当前实际时间,(time1 – time0) / TICK_TIME表示当前时间比最后一个逻辑帧所在时间超出了多少百分比,用这个百分比来向下一逻辑帧做插值。
如果机器比较快,在两个逻辑更新时间点之间执行了多次屏幕渲染,这样插值就有效了。此时time0不变,time1一直在增长,可以根据增长的百分比来计算动画应该从最后一次执行逻辑更新的位置向下一次逻辑更新所在的位置走多远。
也就是上文所说的,在Last和Next位置之间进行插值。插值后的实际位置,即DrawPosition的计算方法如下:
动画播放的插值也是类似。
DrawPosition = LastPosition + percentWithinTick * (NextPosition - LastPosition);
The main loop executes over and over as fast as the computer is able to run it, and for a lot of the time we will not be performing logic updates. During these loop cycles when no logic is performed, we can still draw, and as the loop progresses closer to the time of our next logic update, percentWithinTick will increase toward 1. The closer percentWithinTick gets to 1, the closer the a ctual DrawPosition of the object will get to NextPosition. The effect is that the object smoothly moves from Last to Next on the screen, the animation as smooth as the hardware framerate will allow. Without this smooth interpolation, the object would move in a jerk from LastPosition to NextPosition, each time the logic updates at 25FPS. So a lot of drawing cycles would be wasted repeatedly drawing the same exact image over and over, and the animation would be locked to a 25FPS rate that would look bad.
游戏的主循环以电脑所能够达到的最大速度不停地运行,在大多数时间里,我们将不需要执行逻辑更新。
但是在这些不执行逻辑更新的周期里,我们仍然可以执行屏幕渲染。当循环的进程越接近我们的下一次逻辑更新时间,percentWithinTick就接近1;percentWithinTick越接近1,DrawPosition的位置就越接近NextPosition。
最后的效果就是游戏对象在屏幕上慢慢的、平滑地从Last位置移动到Next位置,动作将以硬件帧率所能允许的程度尽可能的平滑。
如果没有这个平滑插值过程,对象位置将会从上一帧所在的LastPosition直接跳到下一帧所在的位置NextPosition。大量的渲染周期都被浪费在把对象反复渲染到相同的位置上,并且动画也只能被锁定在25FPS,使得看起来效果非常差。
With this technique, logic and drawing are separated. It is possible to perform updates as infrequently as 14 or 15 times per second, far below the threshold necessary for smooth, decent looking visual framerate, yet still maintain the smooth framerate due to interpolation. Low logic update rates such as this are common in games such as real-time strategy games, where logic can eat up a lot of time in pathfinding and AI calculations that would choke a higher rate. Yet, the game will still animate smoothly from logic state to logic state, without the annoying visual hitches of a 15FPS visual framerate. Pretty danged nifty, I must say.
使用了这项技术之后,逻辑更新与屏幕渲染被分离开了。
这将允许我们把逻辑更新帧率降低到14或15FPS,这远远低于平滑的动画渲染所需要的帧率,但是在使用动画插值之后却仍然能维持平滑的渲染帧率。
在实时策略类游戏中可能会使用这样低的逻辑更新帧率,这里逻辑更新会因寻路和AI计算而占用大量的时间,在这种情况下使用高的逻辑更新帧率显然不行。
但是,游戏却仍然能够在不同的逻辑状态之间做平滑的动画过渡,不会因为只有15FPS的渲染帧率而出现那些令人生厌的动画跳跃现象。
非常的漂亮,我不得不说。
I've created a simple program in C to demonstrate this loop structure. It requires SDL ( http://www.libsdl.org ) and OpenGL. It's a basic bouncy ball program. The controls are simple: Press q to exit the program or press SPACE to toggle interpolation on and off. With interpolation on, the loop executes exactly as described to smooth out the movement from one logic update to the next. With interpolation off, the interpolation factor percentWithinTick is always set to 1 to simulate drawing without interpolation, in essence locking the visual framerate to the 25FPS of the logic update section. In both cases, the ball moves at exactly the same speed (16 units per update, 25 updates per second), but with interpolation the motion is much smoother and easier on the eyes. Compile and link with SDL and OpenGL to see it in action: http://legion.gibbering.net/golem/files/interp_demo.c