從零開(kāi)始23天完成一款A(yù)ndroid游戲開(kāi)發(fā)(七)
本文是這個(gè)系列的第七篇文章,記錄作者在第18至第20天的情況。
第18天:外星人圖形與圓形沖突、完美的子彈軌跡
今天我受夠了“射擊月亮”bug。有時(shí)候外星人即使在屏幕 中出現(xiàn),也可能射不中。我做了大量測(cè)試,在屏幕上布滿外星人并且設(shè)置月亮半透明以定位這個(gè)bug的原因。我發(fā)現(xiàn)測(cè)試擊中區(qū)域的坐標(biāo)偏移了一個(gè)bit位,但 即使解決了這個(gè)問(wèn)題原先的bug依然存在。外星人圖形不能簡(jiǎn)單用圓形覆蓋,否則玩家要么射不到外星人,要么會(huì)射到隱蔽在月亮下的外星人。
所以我決定使用圓形檢查。由于月亮比外星人大很多,能夠很容易地檢查外星人圖形邊緣的四個(gè)點(diǎn)是否都在圓形月亮內(nèi)。為了測(cè)試,我使用libGDX內(nèi)置的ShapeRender類(lèi),具體的實(shí)現(xiàn)代碼如下:
- 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完成以后,沿著月亮表面畫(huà)白色的圓圈。類(lèi)似地,給外星人邊界畫(huà)上長(zhǎng)方形。
測(cè)試一個(gè)點(diǎn)是否在圓內(nèi)的高效方法不是計(jì)算平方根(速度較慢)而是比較距離的平方。libGDX的內(nèi)置函數(shù)Circle.contains(x,y) 恰好實(shí)現(xiàn)了這個(gè)功能,所以我使用了這個(gè)函數(shù)進(jìn)行檢查。事實(shí)證明這個(gè)方法非常有效。我為半徑長(zhǎng)度增加了一些像素值,因?yàn)樗型庑侨酥g會(huì)有一些間隔。改動(dòng)后 的結(jié)果令我非常滿意。
完美的子彈軌跡
在這個(gè)游戲中,子彈是從距離屏幕下方50像素值的地方發(fā)射的。我使用了函數(shù)atan2讓子彈旋轉(zhuǎn)著擊中目標(biāo),但我的代碼中有一些錯(cuò)誤,在沒(méi)有射中目標(biāo)時(shí)錯(cuò)誤會(huì)經(jīng)常出現(xiàn)。為了理解這部分內(nèi)容,請(qǐng)注意在這個(gè)游戲所有的射擊都采用了HitScan策略。
譯注:HitScan與射擊目標(biāo)相對(duì),指的是射擊出的子彈不針對(duì)任何目標(biāo)而是摧毀子彈運(yùn)行軌跡上的任何物體。
在沒(méi)有射中目標(biāo)時(shí),現(xiàn)在的代碼將子彈軌跡延伸到屏幕盡頭,而以前的代碼把盡頭設(shè)置得太遠(yuǎn)。由于子彈的飛行使用了中間位置,結(jié)果看上去有很大的跳躍并且在子彈射出屏幕之前只能看到2、3個(gè)點(diǎn)。通過(guò)把結(jié)束點(diǎn)設(shè)置到屏幕的邊緣來(lái)解決了這個(gè)問(wèn)題,現(xiàn)在你能清楚地看到子彈在飛行。
這時(shí)又暴露出另外一個(gè)問(wèn)題:子彈有時(shí)候距離玩家接觸的屏幕點(diǎn)只有10到20個(gè)像素點(diǎn)。導(dǎo)致這個(gè)問(wèn)題有三個(gè)原因。第一個(gè)問(wèn)題,我使用了子彈的X坐標(biāo)和 Y坐標(biāo)。由于這個(gè)坐標(biāo)位于屏幕底部的角落。通過(guò)把子彈的中心坐標(biāo)加上一半的寬和高解決了這個(gè)問(wèn)題。但仍有一些子彈沒(méi)有射中。第二個(gè)問(wèn)題,我忘記設(shè)置原點(diǎn), 所以子彈圍繞著左下角進(jìn)行旋轉(zhuǎn)。這個(gè)問(wèn)題也解決了,但仍有一些朝屏幕左邊射射出的子彈沒(méi)有射中。
第三個(gè)問(wèn)題,我意識(shí)到當(dāng)子彈旋轉(zhuǎn)時(shí)寬度和高度是在變化的,所以子彈的中心點(diǎn)需要在旋轉(zhuǎn)后需要重新計(jì)算。解決了這個(gè)問(wèn)題,子彈就能正確地從玩家觸摸的地方射擊。修改后的代碼如下:
- // 子彈飛行
- 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天:每日挑戰(zhàn)和任務(wù)
每日挑戰(zhàn)是收集5個(gè)字母,操作方式和道具一樣。一旦收集了所有字母,就可以得到一些用于購(gòu)買(mǎi)道具的游戲幣。這是一個(gè)通過(guò)玩游戲獲取硬幣的簡(jiǎn)單方法,這個(gè)靈感是受到“地鐵跑酷”(Subway Surfers)的啟發(fā)。
任務(wù)由許多子任務(wù)組成,通過(guò)完成這些子任務(wù)可以賺取硬幣。硬幣可以用于購(gòu)買(mǎi)升級(jí)道具和消費(fèi)物質(zhì),如盔甲、炸彈等等。每天的任務(wù)由三部分組成,你必須完成所有三項(xiàng)子任務(wù)才能獲得獎(jiǎng)勵(lì)。
我發(fā)現(xiàn)使用內(nèi)置的文本換行來(lái)顯示任務(wù)比較簡(jiǎn)單。然而行高會(huì)顯得過(guò)大,而且直接修改代碼沒(méi)有辦法減小行高。因此我選擇編輯由BMFont生成的.fnt文件,進(jìn)行如下調(diào)整:
- lineHeight=33
變成
- lineHeight=23
在開(kāi)始生成位圖時(shí),我在字母的四周增加了5個(gè)像素的陰影,所以現(xiàn)在需要把高度減少了10像素(上面減少5像素,下面減少5像素)。
在為此查找文檔時(shí),我發(fā)現(xiàn)了一些先前遺漏的問(wèn)題:在為游戲選擇字體時(shí),可能數(shù)字看起來(lái)效果不是很好。數(shù)字1看起來(lái)很修長(zhǎng),而數(shù)字11看起來(lái)很奇怪。要解決這個(gè)問(wèn)題,可以為圖中的字體設(shè)置固定寬度。
- font.setFixedWidthGlyphs("0123456789");
這樣效果看起來(lái)會(huì)非常好。但由于已經(jīng)決定使用修長(zhǎng)字體,因而沒(méi)有采用固定寬度。
第20天:周挑戰(zhàn)、用戶(hù)數(shù)據(jù)持久化、Java日期災(zāi)難
周挑戰(zhàn)是在一周內(nèi)收集特定數(shù)目的星星,從而獲得一些優(yōu)異的獎(jiǎng)勵(lì),如8個(gè)原子彈、5個(gè)盔甲等等。我用Gimp做了一個(gè)很棒的金色星星并在嘗試了不同的 閃爍和星光效果,但是這些看上去效果不是特別好。所以我想到了強(qiáng)化道具的粒子效果,對(duì)它進(jìn)行改變直到滿足星星的要求。星星有了自己的閃爍節(jié)奏,而且可以在 屏幕上同時(shí)顯示星星和強(qiáng)化道具。
我還添加了玩家數(shù)據(jù)的加載和保存。這個(gè)比我想象中要簡(jiǎn)單。我以為必須學(xué)習(xí)一些Android的數(shù)據(jù)存儲(chǔ)API,但libGDX提供了簡(jiǎn)單鍵值存儲(chǔ)類(lèi)。只要調(diào)用以下代碼進(jìn)行初始化:
- Preferences prefs = Gdx.app.getPreferences("DroneInvaders");
然后使用get(“key”, defaultValute)和set(key,value)進(jìn)行值的讀寫(xiě)。
我唯一遇到的麻煩是時(shí)間問(wèn)題。為了持續(xù)跟蹤天挑戰(zhàn)和周挑戰(zhàn),必須存儲(chǔ)最后玩游戲的時(shí)間。當(dāng)玩家開(kāi)始游戲,系統(tǒng)比較這個(gè)時(shí)間并重新設(shè)置一些計(jì)數(shù)器。理 論上我可以阻止玩家將系統(tǒng)日歷修改到過(guò)去的時(shí)間,但是我不想這么做。當(dāng)時(shí)間回滾時(shí),我所做的是設(shè)置新的每日挑戰(zhàn)和周挑戰(zhàn)并且重置星星和搜集到的字母?jìng)€(gè)數(shù)。
為了實(shí)現(xiàn)這個(gè)功能,必須獲取上一次玩游戲的時(shí)間并計(jì)算與當(dāng)前的時(shí)間差。是否是同一天、一天前或幾天前都會(huì)影響計(jì)算結(jié)果。我在谷歌上搜索到很多討論這 個(gè)問(wèn)題的網(wǎng)站以及StackOverflow問(wèn)題。大多數(shù)答案很好笑。許多程序員簡(jiǎn)單地用相差的秒數(shù)來(lái)計(jì)算時(shí)間差,然后除以60*60*24得到天數(shù),完 全忽略了夏令時(shí)和閏秒。有人會(huì)爭(zhēng)辯說(shuō),對(duì)一個(gè)游戲來(lái)說(shuō)這個(gè)差別影響不大。但是我不喜歡每年收到2次大量的bug報(bào)告。另一些家伙簡(jiǎn)單地通過(guò)從開(kāi)始到結(jié)束日 期一天天累加天數(shù)。這些循環(huán)看起來(lái)是正確的,但是計(jì)算結(jié)果還是會(huì)丟失了部分時(shí)間。比如一個(gè)對(duì)象在1月1號(hào)上午5點(diǎn)存儲(chǔ)了,然后你在1月2好晚上23點(diǎn)計(jì)算 時(shí)間差,在第一個(gè)時(shí)間點(diǎn)上加上1天仍然比第二個(gè)時(shí)間點(diǎn)少。但是按他們的計(jì)算方法,實(shí)際增加了2天。
在這種情況下,我使用的一個(gè)技巧是總是設(shè)置前一次游戲的日期為早上10點(diǎn),而設(shè)置最后一次游戲的日期為下午5點(diǎn)。盡管夏令時(shí)總是在晚上改變,但是這個(gè)設(shè)置是安全的。因?yàn)榧词谷绻幸惶煊腥藳Q定夏令時(shí)的變化發(fā)生在中午,在這之間同樣也有7個(gè)小時(shí)。
相關(guān):
從零開(kāi)始23天完成一款A(yù)ndroid游戲開(kāi)發(fā)(一)
從零開(kāi)始23天完成一款A(yù)ndroid游戲開(kāi)發(fā)(二)
從零開(kāi)始23天完成一款A(yù)ndroid游戲開(kāi)發(fā)(三)
從零開(kāi)始23天完成一款A(yù)ndroid游戲開(kāi)發(fā)(四)
從零開(kāi)始23天完成一款A(yù)ndroid游戲開(kāi)發(fā)(五)
從零開(kāi)始23天完成一款A(yù)ndroid游戲開(kāi)發(fā)(六)
從零開(kāi)始23天完成一款A(yù)ndroid游戲開(kāi)發(fā)(八)
翻譯:bigosaur ImportNew.com