你敢相信吗,这些游戏竟是用Java来完成的
我知道传统认为,Java并不适合用来做游戏,马上我们将会谈到Java不适合做游戏的原因,随后我也会一一解释如何解决这些问题,随着时间的推移,以前很多认为不可能完成的事,现在也都已经不再是障碍了。
比如Java做的这两款游戏:
Minecraft(我的世界)Spiral Knights on Steam(螺旋骑士)
这说明了一点,就是Java是可以用来做游戏滴,关键看使用的人如何去用。
使用Java的优点,应该很多人都知道,开发快,IDE支持好,网络支持好,类库多,跨平台等等,那么为什么Java并没有被用来制作游戏的几个常见理由:
1.Java的程序运行需要预先在各个操作系统上安装虚拟机;
2.Java有GC停顿,会阻碍客户体验;
首先第一个问题,这个的解决依赖于大概14年时候Java推出的JavaFX新一代图形控件,该控件有一个native compiling的工具,可以将JavaFX的代码打成不同操作系统上的独立运行的包,比如打成Windows上的exe文件,打成MacOSX上的dmg文件,用户拿到之后,双击就可以运行啦。而在最新版本的Java中,JavaFX已经集成进Java,成为Java的一部分啦,所以不需要单独下载JavaFX,只需要下载Java,JDK之后,就能使用JavaFX,以及native compiling工具,是不是很方便呢?Native Compiling打包可以参考专栏之前写的一篇文章:JavaFX的几个新特性,让Swing彻底过时。
第二个问题,GC停顿,这个是一个很大的topic,Java的GC里面讲究甚多,这里只能简单说明一下,Java传统的GC算法呢,在启动GC的时候,会暂停整个虚拟机的执行,然后等GC完成之后,再继续执行,而人对于超过一定时间的停顿,是可以感知到的,一旦GC停顿超过该感知的界限,玩游戏的人的体验就会变差,嗯,这么说有些过于感性了,我们来一点数字。
一般游戏的帧数是20帧到60帧之间,少数会冲到90帧,帧数意思就是每一秒游戏画面刷新的次数,60帧的意思就是一秒钟内游戏画面刷新60次,为什么游戏的帧数会在20-60之间呢?
因为低于20帧,人就能看出画面的停顿,超过60帧,帧与帧之间的停顿时长,基本上就超出了人可以感知的范围了,也就是说,如果两个不同画面的停顿超过1/20秒,能看到的就是一幅一幅不同的图片,而不是动画,而要让人看到动画的效果,不同两个画面之间的停顿,至少要在1/20秒也就是50ms以内,而人视觉感知的极限,就是1/60秒,也就是16或者17ms,如果停顿时间短于16ms比如是10ms,那么人在视觉上是很难感知到的,也就是说,一秒刷新60次,跟一秒刷新90次,在人看来,几乎没啥差别,所以就能看到iphone或者android手机的广告,60帧如丝般顺滑,blablabla,简而言之,就是我们要把刷新画面做到一秒刷新20次以上,60次最佳。
那这个时候我们就能看出来啦,GC停顿如果超过50ms,客户就能感受到GC,如果低于16ms,那就很完美。
说明一下虽然超过50ms就能感知到,但是一般情况下,只要这种50ms级别的停顿不频繁发生,客户体验并不差,比如10min触发一次50ms的停顿,这有关系吗?玩个游戏又不是搞导弹拦截,偶尔来个50ms的卡顿会死人还是会怀孕啊?而且网游里面,公网的延迟经常超过50ms这个量级,尤其是手游的破网络。
有技巧,首先要修改GC的策略,在新版本的Java中,加入了新的GC策略G1,该策略将会在Java9中成为缺省的GC策略,该策略允许设置目标停顿时间,解释一下目标停顿时间的意思,就比如我们设置目标停顿时间为10ms,那么GC会尽可能在10ms内结束,如果完不成该目标,则暂停GC,程序恢复执行,等下一次GC继续,如此反复,所以G1策略的总停顿时间,会超过CMS策略,但是每一次GC停顿,都会控制在一定时长以内,但是不能保证一定在该时长以内结束,只是虚拟机会尽量完成这个目标,这是一。在打包时候,ant build file里面用fx:deploy, fx:platform和fx:jvmarg来设置JVM参数,which包括了GC策略等参数。
其次,我们要减少GC的时长,肿么做?将对象pool起来,也就是说,需要复用对象,不能频繁生成并销毁对象,这样非常消耗GC资源,会明显增加GC停顿时长。举个例子,如果我们做的是一个射击游戏,那么发射出去的子弹,我们需要pool起来,比如把子弹对象都放在一个list里面,子弹出了边界之后,标记对象为无效状态,但是并不从list中remove掉,这样因为有list的引用在,该子弹对象不会被GC掉,下一次发射的子弹,先找一遍有没有标记为无效状态的对象,如果用,则使用该对象,如果找不到,再增加一个新的对象,放入list,这样子弹对象数量随着游戏的进行,会趋近于一个定值,而因为有list这个引用在,所以对象不会被GC掉,这样GC的压力就小了很多,即便触发了full gc,其执行时间也在一定范围之内,有助于我们实现目标GC停顿时间。
渲染时候尽可能调用底层的API,也就是View的部分,请使用诸如Canvas之类的控件,在安卓上,就用SurfaceView,在IOS上好一点,有SpriteKit可以使用,而不是其它的控件,比如ImageView等,因为普通的ImageView需要换Image的时候,需要重新生成一个Image对象,图像对象可是很大一个东西,频繁生成销毁这种对象。
以上是Java做游戏当中出现的一些问题,希望大家能够有所收获,学到一些知识,大家如果有什么不懂的问题欢迎大家提出来,跟我一起交流交流。