我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

查志強發表於2014-07-10

【原文:http://www.open-open.com/news/view/2e89b3

  第 9 天這是一款第一人稱視角射擊遊戲,但它絕不老套

        在與人們談論起這款遊戲的時候,為它定義一個明確的分類確實很難。雖然可以將它看作一款傳統的街機遊戲,但與那些到處移動自己的飛船、直線開火 的街機遊戲不同的是——你的位置是固定的並且可以按照指令向任意方向開槍。經過仔細回想,我從來沒有見過一模一樣的遊戲,所以不要試圖把它歸到那些現有的 分類中。我能給出的最為貼切的描述是:與太空入侵者類似,但是遊戲中沒有飛船。這樣的描述把人們完全搞暈了。

        今天我重寫了製造外星人的程式碼,並且把外星人的顏色都改成了白色。這樣就可以在遊戲執行過程中用同一個子畫面更新外星人的顏色,假設你沒有發現 外星人是單色的。除了 boss 之外,我都打算這樣處理。boss 會增加一些發光的動態效果之類的。誰知道呢,也許會有藝術家發現遊戲的潛力並且創作出給力的效果。如果發現這種情況請聯絡我。

        我考慮在敵人將要“升級”的時候給出提示,像是“外星人已經升級”這樣帶有金屬感的文字會與外星人一起出現,

        爆炸的程式碼也進行了統一。一開始是通過一個 2D“畫素”陣列展示敵人被炸成碎片。現在這段程式碼進行了優化,各種外星人都使用了同樣的效果,只有 boss 除外。boss 會分階段炸成碎片。這個設計我還需要仔細考慮。

        無論如何,如果你對玩這個遊戲感興趣,這個.apk 檔案就是迄今為止的全部成果。雖然沒有什麼特色,但是你能看到我一直在努力。

        第 10 天讓遊戲玩起來更有意思

        我已經開始懷疑這個概念是否真的具有較長時間的可玩性。如果外星人可以左右移動遊戲會變得更好玩一點。我新增了兩種攻擊隊形看上去感覺好多了。 我還增加了擊毀外星人爆炸時鏡頭抖動的效果,這樣遊戲感覺更鮮活了。重新填充彈藥的功能也實現了,現在如果你把十發容量的彈夾打空就不得不重新填裝。在使 你中斷射擊重新填充彈藥的同時可玩性也增加了。我把一些外星人醜陋的品紅色替換成冷色調的藍白色效果好看多了。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        我想如果設定成玩家只操控螢幕的下半部分——有護盾遮擋的地方,螢幕的上半部分就可以一直看到敵人的情況而不會被玩家的手指擋到。這種情況是經 常出現的。一開始護盾可能有半個螢幕大,隨著敵人的攻擊護盾逐漸被打掉直到槍口裸露沒有任何防護。雖然這意味著可能要畫一把槍卻不失為一個好主意,我可能 會試試。

        星空之戰的名字已經取好了。我嘗試了其他 20 幾個名字,但由於太空主題的遊戲太多很難找到一個免費的名字。

        第 11 天增加外星人種類、攻擊方式,第一級 boss 出現

        第一個 boss 出現在第 10 關出現,它的外形就是個大方塊。我稱它為 Borg,雖然它和 Borg 一點不像。它會不斷髮射外星人,你必須打很多次才能消滅它,我為幹掉 boss 的場景設定了一個大的震動。boss 是由 10×10 的畫素積木組合而成爆炸效果很炫。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        我還加入了個新蛇形移動攻擊,外星人落下時會左右移動並然後下降不斷重複直至到達底部。我在這個攻擊模式中稍稍加快了移動速度,這樣玩起來更具有挑戰性。

        我還畫了一些好看的外星人並給它們起了名字,每種型別都由 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 字型。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        今天弄懂了 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(它是立方形的,被摧毀以後變成許多大立方塊)。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        我還新增了盾牌。以前我曾經用 Inkscape 畫過這種漂亮的盾牌標誌,還從某個 YT 教程裡獲得了靈感。我試著照著教程做(教程使用的是 Adobe Illustrator,不是 Inkscape),但是失敗了。我開始觀察一些喜歡的盾牌,注意到盾牌只是由一些分支、曲線或梯度構成。我把 Hairy 放上去,看起來很不錯。這個還可以用作 Android 標誌。

        不管怎樣,這個盾牌可以隨時引入並且持續 20 秒。後面的一些升級可以使盾牌持續更長時間。如果外星人碰到盾牌會加速損耗能量,這樣即使盾牌消失你仍然可以幹掉它們。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        盾牌的圖形看起來很像力場,使用 Gimp 再配合手動修改可以讓它變得更好看。我想要一個漂亮的曲線而不是直線,因為直線好像不能新增梯度陰影效果。也許有一些技巧可以做到,但是目前我還不知道。最後,我結合了不同角度的多重線性梯度,出來的效果非常棒。

        我試著在這個盾牌標誌上面新增一些熒光效果,但是看上去有些太刺眼了。我會把它留在飛行過程中吃到能量升級時使用。

        有了這些新圖形,遊戲看起來更完整了。我還在考慮在哪裡放置分數倍增器比較合適,還有是不是需要顯示當前攻擊波。

        我還在想玩家需要在玩之前買一些升級裝備,但這需要一些硬幣,或者類似的錢,目前,快速遊玩顯然還不支援。也許可以在一些外星人後面留一些水晶或者一種類似隨機升級的裝備會出現。再或者你經歷了多少波,就得到多少硬幣。

        第 14 天:完成所有低階外星人圖形

        所有 7 種低階外星人都完成了.我剛剛做好了 Catcher,Humaniod 和 Scorpio 是昨天晚上做的。我還做了一些基本圖形放在商店裡,玩家可以從那兒買到升級產品。

        關於外星人 boss 我又有了一些新想法。其中一種像蛇,身體由多段組成,需要分段消滅。

        第二種外星人 boss 自己也可以製造 boss。如果你不能及時摧毀它,它就會放出另一個 boss。第二種 boss 吐出普通外星人並向下移動(以便為下一個留出空間)。

        第三種 boss 是一種特別的生物,它能夠自我分裂。當它被打中時,會分裂成兩個相同的外星人。每一個分裂出的外星人能量是有母外星人的一半。外星人會一直分裂,直到變成一堆能量為 1 的外星人。打死那些外星人就可以結束遊戲。

        第四種 boss 是……好吧,讓我留一些驚喜給你們,玩的時候就知道了。我敢肯定對一些玩家而言,第四種 boss 是非常討厭的,除非他們發現這種 boss 的規律。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        安排音樂時間

        今天剩下的時間裡,我瀏覽了一些免費音樂。有許多音樂網站,但成千上百的音樂逐個聽過去並不好玩。多數免費的音樂網站都是垃圾。我在 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。減掉了一個皮膚開關。

        通過這種處理使我得以有各種不同的選擇來填充主選單,同時我還加入了滾動字幕給出玩法提示。我真的很喜歡這個概念,但很少有遊戲使用它。有的遊戲只顯在一開始的時候有個提示。也許他們不想讓玩家看主選單時分心吧。

        下面是購買強化道具的商店選單:

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        強化道具

        關於道具我又有了一些新點子。一種是可以暫時讓外星人減速,另一種是在短時間內積分 x5。我正在考慮移除之前商店裡的“雙倍積分”道具。有些玩家真的很能得高分,所以這可能是一個壞主意。

        另一方面,在下次裝彈前能增加射速的道具可能會大受歡迎,所以我正在加入。

        我希望商店能保持只有 7 個道具,這樣就能剛好在一個螢幕內顯示。但現在我不肯定所有可能的升級……拭目以待吧。

        第 16 天:從 GDX 遊戲中錄製影片

        視訊地址:www.youtube.com/embed/RUy177pvT8I?rel=0

        我曾想過在 YouTube 上傳遊戲視訊,然後用 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 檔案,用剩下的壓制視訊即可。而且這種方法也很容易與音樂同步,因為可以隨意新增或刪除幀。

        用截圖生成 YouTube 視訊

        由於 Android 螢幕預設解析度是 480×800,而最接近 YouTube 的解析度是 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 來匯出視訊。YouTube 建議 720p 的視訊採用H.264 格式和 5000 以上的位元率 。他們還建議兩個B幀(RGB)。這裡是執行的命令:

1 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

        這樣就生成了一個質量過硬的 YouTube 遊戲視訊。在這篇文章的開始,你可以看到我的成果。至於音訊,我只是提取了一些遊戲的音軌並沒有捕捉實際遊戲中的音訊。

        第 17 天:Android 圖示、完成道具

        我喜歡 Android 允許(甚至建議)圖示不是圓角矩形。這樣可以賦予遊戲自己的個性風格。起初,我考慮過給這遊戲做一個特殊的圖示,但我真的非常非常喜歡這個畫著外星人畫素 圖形的盾。我用 Inkscape 製作,這樣就可以輸出任意大小的圖片(而不像在 GIMP 下製作的其他一些圖形)。獻上 Drone Invaders 官方圖示:

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        豐富的道具

        下面的視訊顯示所有收藏的強化道具:

        http://www.youtube.com/embed/SZ73G0n6cm4?rel=0

        我準備了原子彈,但名字還沒有最終確定。也許會叫核彈、鈽炸彈、智慧炸彈或完全不同的東西。它會摧毀螢幕上的一切。Boss 能抵擋一兩個,但遇到三個炸彈一樣完蛋。在系統內部,每個 Boss 有 20 點血而炸彈有 8 點的傷害。普通攻擊就是 1 點傷害,除非你升級鐳射。

        其次,有3 路散彈。射擊三次仍然要更換彈夾。這是一個非常強大的道具,有了它,真是人擋殺人佛當殺佛,清理掉一波波的怪物和 boss。

        第三,自動重灌填。正如名字那樣,你的鐳射會自動載入。所以可以自由地射擊,射擊,再射擊。

        第四,減速。它只是減緩外星人的移動速度,其他一切速度正常。在前 20 關這玩意兒相當廢柴。但越到後來,你就越覺得它有用。

        第五,雙倍積分。在道具作用期間,獲得的點數翻一倍。我仍然在考慮是否要在達到某個分數的時候給予獎勵,但達到高分仍是一件很酷的事情。

        第 18 天:外星人圖形與圓形衝突、完美的子彈軌跡

        今天我受夠了“射擊月亮”bug。有時候外星人即使在螢幕中出現,也可能射不中。我做了大量測試,在螢幕上佈滿外星人並且設定月亮半透明以定位 這個 bug 的原因。我發現測試擊中區域的座標偏移了一個 bit 位,但即使解決了這個問題原先的 bug 依然存在。外星人圖形不能簡單用圓形覆蓋,否則玩家要麼射不到外星人,要麼會射到隱蔽在月亮下的外星人。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        所以我決定使用圓形檢查。由於月亮比外星人大很多,能夠很容易地檢查外星人圖形邊緣的四個點是否都在圓形月亮內。為了測試,我使用 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)恰好實現了這個功能,所以我使用了這個函式進行檢查。事實證明這個方法非常有效。我為半徑長度增加了一些畫素值,因為所有外星人之間會有一些間 隔。改動後的結果令我非常滿意。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        完美的子彈軌跡

        在這個遊戲中,子彈是從距離螢幕下方 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)的啟發。

        任務由許多子任務組成,通過完成這些子任務可以賺取硬幣。硬幣可以用於購買升級道具和消費物質,如盔甲、炸彈等等。每天的任務由三部分組成,你必須完成所有三項子任務才能獲得獎勵。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        我發現使用內建的文字換行來顯示任務比較簡單。然而行高會顯得過大,而且直接修改程式碼沒有辦法減小行高。因此我選擇編輯由 BMFont 生成的 .fnt 檔案,進行如下調整:

lineHeight=33

        變成

lineHeight=23

        在開始生成點陣圖時,我在字母的四周增加了 5 個畫素的陰影,所以現在需要把高度減少了 10 畫素(上面減少 5 畫素,下面減少 5 畫素)。

        在為此查詢文件時,我發現了一些先前遺漏的問題:在為遊戲選擇字型時,可能數字看起來效果不是很好。數字 1 看起來很修長,而數字 11 看起來很奇怪。要解決這個問題,可以為圖中的字型設定固定寬度。

font.setFixedWidthGlyphs ("0123456789");

        這樣效果看起來會非常好。但由於已經決定使用修長字型,因而沒有采用固定寬度。

        第 20 天:周挑戰、使用者資料持久化、Java 日期災難

        周挑戰是在一週內收集特定數目的星星,從而獲得一些優異的獎勵,如 8 個原子彈、5 個盔甲等等。我用 Gimp 做了一個很棒的金色星星並在嘗試了不同的閃爍和星光效果,但是這些看上去效果不是特別好。所以我想到了強化道具的粒子效果,對它進行改變直到滿足星星的要 求。星星有了自己的閃爍節奏,而且可以在螢幕上同時顯示星星和強化道具。

我是如何從0開始,在23天裡完成一款Android遊戲開發的 – Part4~7

        我還新增了玩家資料的載入和儲存。這個比我想象中要簡單。我以為必須學習一些 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


相關文章