我是如何从0开始,在23天里完成一款Android游戏开发的 – Part7– 第18至第20天
本文由 ImportNew - 唐尤华 翻译自 bigosaur。如需转载本文,请先参见文章末尾处的转载要求。
本文是这个系列的第七篇文章,记录作者在第18至第20天的情况。如果你也希望参与类似的系列文章翻译,可以加入我们的Android开发 和 技术翻译 小组。
第18天:怪物和圆形冲突,像素级子弹轨迹
今天我受够了“月亮撞击”bug。问题表现为有时候外星人即使出现在屏幕中,也可能不被击中。我通过在屏幕上布满外星人和设置半透明的月亮做了大量测试,用于定位这个bug的原因。我发现测试击中区域的坐标偏移了一个bit位,但是即使解决了这个问题原先的bug还是没有好。整个外星人不能简单的覆盖圆形区域,所以玩家不是在射击外星人时失败,就是射到一些完全隐蔽的外星人。
我决定使用基于圆形的检查。由于月亮比外星人大很多,很容易能检查外星人边缘四个点是否都在月亮的圆内。为了测试这个,我使用了libGDX内置的ShapeRender类,具体代码实现如下:
shapeRenderer.setProjectionMatrix(camera.combined); shapeRenderer.begin(ShapeType.Circle); shapeRenderer.setColor(1, 1, 1, 1); shapeRenderer.circle(sMoon.getX() + 119, sMoon.getY() + 116, 167); shapeRenderer.end();
这个代码放在SpriteBatch完成之后,白色的圆圈沿着月亮表面画一圈。同样使用盒子的形状画上外星人边界。
测试一个点是否在圆圈内的有效方法不是使用平方根(计算速度慢)而是比较距离的平方。libGDX内置的函数Circle.contains(x,y)正好就是实现这个功能,所以我就使用了这个函数进行检查。事实证明这个方法非常有效。我把半径长度增加了一些像素值,因为所有精灵都有一些填充物,我对这个结果非常满意。
像素级的子弹轨迹
在这个游戏中,子弹是从距离屏幕下方50像素值的地方发射的。我使用了函数atan2使得子弹旋转并击中目标,但是在我的代码中有一些错误,尤其当失去目标时。为了理解这部分内容,请注意在这个游戏中对所有的射击都使用了射击扫描 (HitScan)。
当玩家失去目标时,代码将子弹轨迹延伸到屏幕的尽头。以前的代码把尽头的点设置得太远了。由于子弹的飞行使用了中间位置,结果看上去跳跃很大并且在子弹射出屏幕之前只能在屏幕上看到2-3个点。我通过设置结束点到屏幕的边缘来解决这个问题,现在你能确切地看到子弹在飞行。
但是这个又暴露了另外一个问题:子弹有时候距离玩家接触的屏幕点只有10-20个像素点。这个问题有三个原因导致。第一,我使用了子弹的X和Y坐标,而这个坐标位于低于底部的角落。我通过把子弹的中心坐标加上宽度和高度的一半解决这个问题。但是在有几些射击中仍然偏离了。第二个问题是我忘记设置原点了,所以子弹围绕着左下角进行旋转,我把这个问题也解决了,然而仍有一些超向屏幕左边射击发生错误了。
接着我意识到,当子弹旋转时,宽度和高度是在变化的,所以子弹的中心点需要在旋转后重新计算。解决了这个问题以后,子弹就能正确地从玩家触摸的地方射击。代码如下:
// 子弹飞行 LaserBullet lb = new LaserBullet(tUI, 65, 64, 20, 40); lb.setPosition(0, -450); lb.setOrigin(10, 20); lb.setRotation( (float)(Math.atan2(-x, 450f+y) * 180f / Math.PI) ); Rectangle r = lb.getBoundingRectangle(); x = (int)(x - r.width * 0.5f); y = (int)(y - r.height * 0.5f); lb.target.set(x, y); bullets.add(lb); Tween.to(lb, SpriteTweenAccessor.POSITION_XY, delay) .target(x, y).start(tweenManager);
第19天:日挑战和任务
日挑战是收集5个字母,操作方式和道具的一样。一旦你收集了所有字母,你得到一些可以用于购买道具的游戏币。这是一个通过玩游戏简单获取硬币的方法,我是受了“地铁跑酷“(Subway Surfers)这款游戏的启发创作了它。
使命是有许多任务组成,玩家通过完成这些任务赚取硬币。这些硬币可以用于购买升级道具和消费物质,如盔甲,炸弹等等。任务由三部分组成,你必须完成所有这三项任务才能获得奖励。
我认为使用内置的多行文本封装来完成这个任务是非常简单的。然而,行的高度太大了,我至少从代码角度没有找到方法减少高度值。我编辑了由BMFont生成的.fnt文件并做如下调整:
lineHeight=33
变成
lineHeight=23
在位图产生后,我在字母的四周增加了5个像素的阴影,所以我现在把高度减少了10像素(5像素上面,5像素下面)。
在为此查找文档的同时,我发现了一些先前我遗漏的东西:根据你为游戏选取的字体,数字看起来并不是很好,数字1看起来很苗条,而数字11看起来很奇怪。为了解决这个问题,你可以为图中的字体使用固定的宽度。
font.setFixedWidthGlyphs("0123456789");
这个效果看起来非常得好,但是我已经决定使用苗条的图形特性,所以放弃了使用它。
第20天:周挑战、保持用户数据、Java日期灾难
周挑战是在一周内收集特定数目的星星,从而获得一些优异的奖励,如8个原子弹,5个盔甲等等。我用Gimp做了一个很棒的金色的星星。我在它上面尝试了不同的粒子系统效果和发散的星光,但是这些看上去不是特别地好。所以我回到用于强化道具上的粒子系统效果上,改变它直到获得星星的特质。星星有着自己的闪烁步调,所以你可以看到星星和强化道具同时出现在屏幕上。
我同样在加载和保存玩家数据方面下了功夫。这个比我想象中要简单。我以为必须学习一些Android的数据存储API,但是对于简单的键值存储,libGDX提供了相应的类。只要通过以下代码进行初始化:
Preferences prefs = Gdx.app.getPreferences("DroneInvaders");
然后使用get(“key”,defaultValute)和set(key,value)去读和写值。
我唯一遇到的问题是时间问题。为了持续跟踪天挑战和周挑战,必须存储最后玩游戏的时间。当玩家开始游戏,系统比较这个时间并重新设置一些计数器。理论上,我可以阻止玩家将系统日历修改到过去的时间,但是我不想。当时间回滚时,我所做的是,设置新的天挑战和周挑战并且重置星星数目和捡到的信数目。
为了使这个工作,我必须获取上一次玩游戏的时间和当前的时间差。是否是同一天、确切的一天前或几天前都关系到结果。我通过谷歌上收索到很多有关于这个问题的网站,包括了StackOverflow。大多数答案很好笑,许多程序员简单用秒来计算时间差,然后除以60*60*24去获得天数,完全忽略了夏令时的问题和时间描述溢出问题。有人会争辩说这对一个游戏来说影响不大,但是每年2次的大量bug报告对于我来说不太喜欢。另一些家伙简单地通过从开始到结束一天接着一天把天数加起来。这些循环看起来是正确的,但是它们的计算结果是丢失了部分时间。比如,一个对象在1月1号上午5点存储了,然后你在1月2好晚上23点计算时间差,在第一个时间点上加上1天仍然比第二个时间点少,但是按它们的计算方法,增加了2天。
在这种情况下,我使用的一个技巧是总是设置早起的日期为像早上10点这样的一个时间,而设置后来那个日期为下午5点。尽管夏令时总是在晚上改变,但是这个设置是安全的。因为即使如果有一天有人决定夏令时的变化发生在中午,我们在这之间同样也有7个小时。
-- 扫描加关注,微信号: importnew --原文链接: bigosaur 翻译: ImportNew.com - 唐尤华
译文链接: http://www.importnew.com/6897.html
[ 转载请保留原文出处、译者、译文链接和上面的微信二维码图片。]