我是如何從0開始,在23天里完成一款Android游戲開發的 – Part4~7
第 9 天這是一款第一人稱視角射擊游戲,但它絕不老套
在與人們談論起這款游戲的時候,為它定義一個明確的分類確實很難。雖然可以將它看作一款傳統的街機游戲,但與那些到處移動自己的飛船、直線開火 的街機游戲不同的是——你的位置是固定的并且可以按照指令向任意方向開槍。經過仔細回想,我從來沒有見過一模一樣的游戲,所以不要試圖把它歸到那些現有的 分類中。我能給出的最為貼切的描述是:與太空入侵者類似,但是游戲中沒有飛船。這樣的描述把人們完全搞暈了。
今天我重寫了制造外星人的代碼,并且把外星人的顏色都改成了白色。這樣就可以在游戲運行過程中用同一個子畫面更新外星人的顏色,假設你沒有發現 外星人是單色的。除了 boss 之外,我都打算這樣處理。boss 會增加一些發光的動態效果之類的。誰知道呢,也許會有藝術家發現游戲的潛力并且創作出給力的效果。如果發現這種情況請聯系我。
我考慮在敵人將要“升級”的時候給出提示,像是“外星人已經升級”這樣帶有金屬感的文字會與外星人一起出現,
爆炸的代碼也進行了統一。一開始是通過一個 2D“像素”數組展示敵人被炸成碎片。現在這段代碼進行了優化,各種外星人都使用了同樣的效果,只有 boss 除外。boss 會分階段炸成碎片。這個設計我還需要仔細考慮。
無論如何,如果你對玩這個游戲感興趣,這個.apk 文件就是迄今為止的全部成果。雖然沒有什么特色,但是你能看到我一直在努力。
第 10 天讓游戲玩起來更有意思
我已經開始懷疑這個概念是否真的具有較長時間的可玩性。如果外星人可以左右移動游戲會變得更好玩一點。我新增了兩種攻擊隊形看上去感覺好多了。 我還增加了擊毀外星人爆炸時鏡頭抖動的效果,這樣游戲感覺更鮮活了。重新填充彈藥的功能也實現了,現在如果你把十發容量的彈夾打空就不得不重新填裝。在使 你中斷射擊重新填充彈藥的同時可玩性也增加了。我把一些外星人丑陋的品紅色替換成冷色調的藍白色效果好看多了。
我想如果設定成玩家只操控屏幕的下半部分——有護盾遮擋的地方,屏幕的上半部分就可以一直看到敵人的情況而不會被玩家的手指擋到。這種情況是經 常出現的。一開始護盾可能有半個屏幕大,隨著敵人的攻擊護盾逐漸被打掉直到槍口裸露沒有任何防護。雖然這意味著可能要畫一把槍卻不失為一個好主意,我可能 會試試。
星空之戰的名字已經取好了。我嘗試了其他 20 幾個名字,但由于太空主題的游戲太多很難找到一個免費的名字。
第 11 天增加外星人種類、攻擊方式,第一級 boss 出現
第一個 boss 出現在第 10 關出現,它的外形就是個大方塊。我稱它為 Borg,雖然它和 Borg 一點不像。它會不斷發射外星人,你必須打很多次才能消滅它,我為干掉 boss 的場景設置了一個大的震動。boss 是由 10×10 的像素積木組合而成爆炸效果很炫。
我還加入了個新蛇形移動攻擊,外星人落下時會左右移動并然后下降不斷重復直至到達底部。我在這個攻擊模式中稍稍加快了移動速度,這樣玩起來更具有挑戰性。
我還畫了一些好看的外星人并給它們起了名字,每種類型都由 Java 類的名字命名。分別是:Hairy、Glider、Worker 和 Eater。
這是今天做的.jar 文件,只要你安裝了 java 就可以在 windows、linux、mac 上運行。在 linux 上我執行的命令是:
java -jar stardust.jar
為了保證正常的游戲效果,請注意窗口的高度不能小于 800。我在 1680×1050 的臺式機上運行良好,但在 1280×800 的筆記本上由于任務欄占據了一定空間窗口被垂直壓縮,因此我需要點擊外星人的下方進行射擊。我想如果要發布 PC 版的就必須解決這個問題。
第 12 天(第一部分):新游戲名 DRONE INVADER
名字終于選好了。備選名字有很多,但只有6、7 個是可用的。這一個看起來最符合游戲的主題。全新的主題也做好了,同樣選用了 Ruslan 字體。
今天弄懂了 Java 里 Comparable 和 Comparator 的區別。我改動了子彈部分的代碼,以便同時發射多個子彈(激光碎片)。子彈不必接觸到外星人才能打中,只需朝著一個方向發射,子彈便會自動攻擊外星人。這 樣就可以簡單地根據子彈發射時刻的軌跡判斷先擊中了哪個外星人,從而取代全碰撞檢測。這個方法對除 Boss 級外的所有級別都適用。 因為 Boss 會吐出其他外星人,他應該排在數組里的第一個,這樣子彈會飛過所有外星人直接打中 boss。如果子彈直接打中 boss,會保留之前的處理。但如果只是向某個方向射擊,會按照y坐標軸對外星人排序,然后打中最近的那個。
新特性會稍稍改變游戲體驗,現在玩起來更容易也更有趣了。射擊外星人不再會因為手指點擊出現誤操作。游戲從“精確碰撞”變為“射擊測試”,彈卡是 10 發的,以防你瞬間摧毀一切。
我想我應該把外星人的運動變得更有挑戰性一些,這樣游戲不至于太容易。
第 12 天(第二部分)
我開始喜歡上這個游戲了,而且是特別喜歡。我決定在睡前再做一會兒,在 16、26 這樣的關卡添加了一個大月亮,外星人就藏在月亮后面。月亮慢慢移動穿過屏幕,在 Boss 出現的時候正好從屏幕移出。這讓游戲難度陡然增加,因為很難判斷是否有外星人躲在月亮后面。由于月亮是圓形,這增加了操作長方形難度。我調查了精確像素檢 測方法,每當射中一個外星人像素會逐個顯示,特別是當月亮一直在轉的時候。然而這種方法恐怕會讓游戲變慢。原來的矩形被我減少了 12%,用矩形檢測取而代之。雖然并不是精確像素,但是工作得也很好。這種方式可以讓子彈穿過月亮打中有月亮做掩護的外星人。
我對現在的游戲體驗非常滿意。有趣,有挑戰性,而且很吸引人。現在我需要更多 boss,更多的外星人類型和能量升級。如果不升級能量想升到 60 級是非常難的,所以我試著增加足夠的內容,起碼在 100 級之前沒有重復的 boss 出現。我覺得增加能量升級會容易點,看看增加這些會以后游戲會變成什么樣。
我第一次感覺這會是一個很棒的游戲,會從一般的太空射擊游戲里脫穎而出。
第 13 天:盾牌、新 Boss
我加入了一個新 boss,現在有兩個 boss。以下是目前外星人的名字:Worker、Eater、Hairy、Glider。Boss 叫 Worker Boss(看起來像是更大的 Worker,而且會吐出很多小 Worker)和 Borg(它是立方形的,被摧毀以后變成許多大立方塊)。
我還添加了盾牌。以前我曾經用 Inkscape 畫過這種漂亮的盾牌標志,還從某個 YT 教程里獲得了靈感。我試著照著教程做(教程使用的是 Adobe Illustrator,不是 Inkscape),但是失敗了。我開始觀察一些喜歡的盾牌,注意到盾牌只是由一些分支、曲線或梯度構成。我把 Hairy 放上去,看起來很不錯。這個還可以用作 Android 標志。
不管怎樣,這個盾牌可以隨時引入并且持續 20 秒。后面的一些升級可以使盾牌持續更長時間。如果外星人碰到盾牌會加速損耗能量,這樣即使盾牌消失你仍然可以干掉它們。
盾牌的圖形看起來很像力場,使用 Gimp 再配合手動修改可以讓它變得更好看。我想要一個漂亮的曲線而不是直線,因為直線好像不能添加梯度陰影效果。也許有一些技巧可以做到,但是目前我還不知道。最后,我結合了不同角度的多重線性梯度,出來的效果非常棒。
我試著在這個盾牌標志上面添加一些熒光效果,但是看上去有些太刺眼了。我會把它留在飛行過程中吃到能量升級時使用。
有了這些新圖形,游戲看起來更完整了。我還在考慮在哪里放置分數倍增器比較合適,還有是不是需要顯示當前攻擊波。
我還在想玩家需要在玩之前買一些升級裝備,但這需要一些硬幣,或者類似的錢,目前,快速游玩顯然還不支持。也許可以在一些外星人后面留一些水晶或者一種類似隨機升級的裝備會出現。再或者你經歷了多少波,就得到多少硬幣。
第 14 天:完成所有低級外星人圖形
所有 7 種低級外星人都完成了.我剛剛做好了 Catcher,Humaniod 和 Scorpio 是昨天晚上做的。我還做了一些基本圖形放在商店里,玩家可以從那兒買到升級產品。
關于外星人 boss 我又有了一些新想法。其中一種像蛇,身體由多段組成,需要分段消滅。
第二種外星人 boss 自己也可以制造 boss。如果你不能及時摧毀它,它就會放出另一個 boss。第二種 boss 吐出普通外星人并向下移動(以便為下一個留出空間)。
第三種 boss 是一種特別的生物,它能夠自我分裂。當它被打中時,會分裂成兩個相同的外星人。每一個分裂出的外星人能量是有母外星人的一半。外星人會一直分裂,直到變成一堆能量為 1 的外星人。打死那些外星人就可以結束游戲。
第四種 boss 是……好吧,讓我留一些驚喜給你們,玩的時候就知道了。我敢肯定對一些玩家而言,第四種 boss 是非常討厭的,除非他們發現這種 boss 的規律。
安排音樂時間
今天剩下的時間里,我瀏覽了一些免費音樂。有許多音樂網站,但成千上百的音樂逐個聽過去并不好玩。多數免費的音樂網站都是垃圾。我在 Reddit 的 gamedev 上找到了一些推薦的網址,同樣在 gamedev.stackexhange.com 上可以看見,還有一些獨立的網站。除了 Jamendo, 這個有點貴,大多數免網站真的很糟糕。
我通常會自己創作音樂,用 MilkyTracker 或者一些其它破解程序。這個習慣是從 Amiga500 來的,在 .it 或 .s3m 調制器的時代這讓我感覺得心應手。但這次我覺得沒有時間這么做。通常創作一個好聽的音頻要花我3、4 周。然而,我可以再利用一些以前的作品,但我已經把它們都用到以前的游戲中了——說實話,沒有一個適合用到這次的游戲。我還是找到了一些 Kevin MacLeod 的音樂。這些音樂相當不錯,我決定就用他了。
是花上幾個小時去聽那些免費音樂,還是集中精力自己做,似乎真的取決于你對時間的估計。
第 15 天: Android“后退”按鈕、主菜單、固定坐標 bug
還記得第 11 天屏幕坐標和鼠標點擊射擊不到外星人的問題嗎?是的,那都是我的錯。幸運的是這讓我及時發現了很多下載游戲的 Android 用戶屏幕分辨率并不是 800×400。在那之前我是這樣直接轉換觸摸坐標到實際坐標:
float x = Gdx.input.getX () - 240f;float y = 400 - Gdx.input.getY ();
這不是正確的做法。簡單恰當的辦法是通過 GDX 進行轉換 :
Vector3 touchPos; touchPos.set(Gdx.input.getX (), Gdx.input.getY (), ); camera.unproject (touchPos);
在 Android 上處理“返回”按鈕
大多數網上的例子在處理“返回”按鈕時都談到重載 KeyDown 方法。不幸的是這種辦法要求使用 Stage,我沒有這么做。我知道現在的代碼里復制了很多 Actor 和 Stage,但那不重要。在下一個項目里我才會使用 Stage。
幸運的是,我找到了解決辦法。只要在 Game 子類的 create ()函數里添加下面函數:
Gdx.input.setCatchBackKey (true);
然后在 render ()方法中檢查否已經按下“返回”按鈕:
if (Gdx.input.isKeyPressed (Keys.BACK)) { Gdx.app.exit (); }
由于 render ()每秒鐘會被調用很多次,你可能需要一個 boolean 標記變量來檢測“返回”按鈕是否已釋放。
if (backReleased && Gdx.input.isKeyPressed (Keys.BACK)) { backReleased = false; Gdx.app.exit (); } else { backReleased = true; }
現在可以進入游戲,進入商店菜單,然后返回主菜單。當然,菜單只顯示選項,還沒有真正實現功能。
使用9-patch 處理動態大小的按鈕和容器
譯注:9-patch 一個對 png 圖片做處理的工具,能夠為生成一個“*.9.png”的圖片實現部分拉升。
我還學會了如何使用9-patch 創建漂亮的按鈕。有一次,我意識到不得不像繪制 10 個大小不同的選項按鈕,但樣子基本上一模一樣只有里面的內容不同。我甚至參考了 Gdx 按鈕,但最終還是決定自己 DIY 一個。在我游戲里,按鈕有一些特殊需求,在一個文本按鈕里要結合了 2 張圖、4 個文本以及 2 種不同字體。
無論如何,我得畫一個包括所有按鈕尺寸和其他的東西的 46×46 9-patch 圖片,然后寫一些代碼定制其他覆蓋在圖片上面的東西。我在構造函數里通過 TextureRegion 從大皮膚里提取9-patch。減掉了一個皮膚開關。
通過這種處理使我得以有各種不同的選擇來填充主菜單,同時我還加入了滾動字幕給出玩法提示。我真的很喜歡這個概念,但很少有游戲使用它。有的游戲只顯在一開始的時候有個提示。也許他們不想讓玩家看主菜單時分心吧。
下面是購買強化道具的商店菜單:
強化道具
關于道具我又有了一些新點子。一種是可以暫時讓外星人減速,另一種是在短時間內積分 x5。我正在考慮移除之前商店里的“雙倍積分”道具。有些玩家真的很能得高分,所以這可能是一個壞主意。
另一方面,在下次裝彈前能增加射速的道具可能會大受歡迎,所以我正在加入。
我希望商店能保持只有 7 個道具,這樣就能剛好在一個屏幕內顯示。但現在我不肯定所有可能的升級……拭目以待吧。
第 16 天:從 GDX 游戲中錄制影片
視頻地址:www.油Tube.com/embed/RUy177pvT8I?rel=0
我曾想過在 油Tube 上傳游戲視頻,然后用 recordmydesktop 程序錄制,但結果一團糟。由于 libGDX 和 RMD 不同步,我在屏幕上看到的是一堆零件,諸如被切掉了一半的精靈等等。我搜索了一下發現了幾篇有用的文章。基本上都是將每幀做成一個 PNG 文件然后組成視頻。可以想見這么做會耗費大量的磁盤空間,這對我不是大問題。我發現了一個很有用的帖子:
http://www.wendytech.de/2012/07/opengl-screen-capture-in-real-time/
然而,他們的代碼有一些問題。出于某種原因,當我用半透明精靈疊加在背景上時,由此產生的 PNG 文件在那塊區域會出現半透明像素。這樣生成的視頻會有很多亂七八糟的東西。我嘗試了不同的設置,甚至改變渲染代碼,但問題依舊。現在,只要一個簡單的處理 步驟——使用 ImageMagick(加入黑色背景)就可以解決這個問題。所以我想,如果無論如何都要做這步處理,我可能還要在 ImageMagick 中做垂直翻轉。所以我關掉了代碼中的Y軸翻轉,這使得它更有效率,從而沒有必要在每一幀中分配w *h*4 個字節的內存。在 800×480 的屏幕上,每一幀大約需要 1.5MB!
同時,處理幀率(跳幀)的代碼沒有怎么優化。處理過程跳過了幾個文件號,這沒什么問題。但同時還給每幀還創建了對應的 ScreenShot 對象,這完全沒有必要。譬如你正在錄制 30fps 的視頻而游戲運行速率是 60fps,你花了一半的時間在創建完全用不到的對象上。
最后,FPS 處理代碼似乎沒有釋放像素圖。所以如果你運行了很長的時間,RAM 會被吃光。
所以,我從 ScreenShot 類里提取出了全部的 FPS 代碼,剩下的代碼只負責處理連續視頻。我還注意到一些變量有初始化但從未使用過。現在 ScreenShot 類變得更加直觀并且易于理解:
public class ScreenShot implements Runnable { private static int fileCounter = ; private Pixmap pixmap; @Override public void run () { saveScreenshot (); } public void prepare () { getScreenshot (, , Gdx.graphics.getWidth (), Gdx.graphics.getHeight (), false); } public void saveScreenshot () { FileHandle file = new FileHandle ("/tmp/shot_"+ String.format ("%06d", fileCounter++) + ".png"); PixmapIO.writePNG (file, pixmap); pixmap.dispose (); } public void getScreenshot (int x, int y, int w, int h, boolean flipY) { Gdx.gl.glPixelStorei (GL10.GL_PACK_ALIGNMENT, 1); pixmap = new Pixmap (w, h, Pixmap.Format.RGBA8888); Gdx.gl.glReadPixels (x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixmap.getPixels ()); }
好了,全部就這么多。我在渲染循環中的每個渲染結尾加上了:
ScreenShot worker = new ScreenShot (); worker.prepare (); // grab screenshot executor.execute (worker); // delayed save in other thread
考慮到完整性,在 Screen 的子類添加了 executor:
private ExecutorService executor; ... executor = Executors.newFixedThreadPool (25);
現在,在我的酷睿 2 已經趕不上幀率了。這是好消息,一方面因為游戲速度變慢我能夠錄下更好的視頻,另一方面能更好地記錄截圖以供稍后導出視頻。所以我添加了一個截圖熱鍵。在 按住S鍵時開始錄制,當你只是記錄了一些有趣的片段,松開S鍵讓 PNG writer 趕上進度。當 CPU 的負荷恢復到正常,意味著 PNG 都生成好了,你可以再次開始錄制。
這種方式創建的視頻很容易編輯。只要刪除不需要的 PNG 文件,用剩下的壓制視頻即可。而且這種方法也很容易與音樂同步,因為可以隨意添加或刪除幀。
用截圖生成 油Tube 視頻
由于 Android 屏幕默認分辨率是 480×800,而最接近 油Tube 的分辨率是 1280 x720。因此需要將圖像縮放到 432×720 ,以保持寬高比。這樣兩邊會多出很多未使用的面積。你可以把你的 logo、廣告貼上去,甚至可以并排顯示兩個視頻。我決定用另一段視頻填補空白,那是我用一臺手持設備拍攝的,所以圖像更小只有 372×620。
現在,我創建了一個大小 1280×720 包含了 logo 的靜態圖像。現在我把它混合進游戲,并垂直翻轉。在 Linux 上,我使用這樣的命令:
for i in shot*png; do echo $i; convert $i -flip -filter Lanczos -resize 372x620 temp1.png; composite temp1.png back.png -geometry +126+56 $i; done
一旦所有的圖像都準備就緒,就可以運行 MEncoder 來導出視頻。油Tube 建議 720p 的視頻采用H.264 格式和 5000 以上的比特率 。他們還建議兩個B幀(RGB)。這里是執行的命令:
mencoder mf://shot*.png -mf w=1080:h=720:fps=25:type=png -ovc x264 -audiofile music.mp3 -oac copy -o movie.avi -x264encopts bitrate=5000:bframes=2:subq=6:frameref=3:pass=1:nr=2000
這樣就生成了一個質量過硬的 油Tube 游戲視頻。在這篇文章的開始,你可以看到我的成果。至于音頻,我只是提取了一些游戲的音軌并沒有捕捉實際游戲中的音頻。
第 17 天:Android 圖標、完成道具
我喜歡 Android 允許(甚至建議)圖標不是圓角矩形。這樣可以賦予游戲自己的個性風格。起初,我考慮過給這游戲做一個特殊的圖標,但我真的非常非常喜歡這個畫著外星人像素 圖形的盾。我用 Inkscape 制作,這樣就可以輸出任意大小的圖片(而不像在 GIMP 下制作的其他一些圖形)。獻上 Drone Invaders 官方圖標:
豐富的道具
下面的視頻顯示所有收藏的強化道具:
http://www.油Tube.com/embed/SZ73G0n6cm4?rel=0
我準備了原子彈,但名字還沒有最終確定。也許會叫核彈、钚炸彈、智能炸彈或完全不同的東西。它會摧毀屏幕上的一切。Boss 能抵擋一兩個,但遇到三個炸彈一樣完蛋。在系統內部,每個 Boss 有 20 點血而炸彈有 8 點的傷害。普通攻擊就是 1 點傷害,除非你升級激光。
其次,有3 路散彈。射擊三次仍然要更換彈夾。這是一個非常強大的道具,有了它,真是人擋殺人佛當殺佛,清理掉一波波的怪物和 boss。
第三,自動重裝填。正如名字那樣,你的激光會自動加載。所以可以自由地射擊,射擊,再射擊。
第四,減速。它只是減緩外星人的移動速度,其他一切速度正常。在前 20 關這玩意兒相當廢柴。但越到后來,你就越覺得它有用。
第五,雙倍積分。在道具作用期間,獲得的點數翻一倍。我仍然在考慮是否要在達到某個分數的時候給予獎勵,但達到高分仍是一件很酷的事情。
第 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 策略。
譯注:HitScan 與射擊目標相對,指的是射擊出的子彈不針對任何目標而是摧毀子彈運行軌跡上的任何物體。
在沒有射中目標時,現在的代碼將子彈軌跡延伸到屏幕盡頭,而以前的代碼把盡頭設置得太遠。由于子彈的飛行使用了中間位置,結果看上去有很大的跳 躍并且在子彈射出屏幕之前只能看到2、3 個點。通過把結束點設置到屏幕的邊緣來解決了這個問題,現在你能清楚地看到子彈在飛行。
這時又暴露出另外一個問題:子彈有時候距離玩家接觸的屏幕點只有 10 到 20 個像素點。導致這個問題有三個原因。第一個問題,我使用了子彈的X坐標和Y坐標。由于這個坐標位于屏幕底部的角落。通過把子彈的中心坐標加上一半的寬和高 解決了這個問題。但仍有一些子彈沒有射中。第二個問題,我忘記設置原點,所以子彈圍繞著左下角進行旋轉。這個問題也解決了,但仍有一些朝屏幕左邊射射出的 子彈沒有射中。
第三個問題,我意識到當子彈旋轉時寬度和高度是在變化的,所以子彈的中心點需要在旋轉后需要重新計算。解決了這個問題,子彈就能正確地從玩家觸摸的地方射擊。修改后的代碼如下:
// 子彈飛行 LaserBullet lb = new LaserBullet (tUI, 65, 64, 20, 40); lb.setPosition (, -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.com 譯文鏈接: http://www.importnew.com/6897.html