當你為全世界的iPhone和iPad使用者推出基於C/C++的iOS遊戲時,你怎麼忍心讓忠實的Android使用者無法享受同樣的樂趣?我不能,所以我得把《Pop Corny》移植到Android平臺。這是一次有趣的經歷,讓我受益匪淺,所以我要把我的心得體會分享給讀者。

pop corny(from harryballs.com)

pop corny(from harryballs.com)

基礎

首先,如果你安於xcode、蘋果生態圈的舒適開發環境,現在準備飛向那片叫作Android的大陸,那麼,請準備迎接困難和挑戰吧,因為Android提供的工具並不那麼合理,且基本上沒有檔案編制。

NDK(開發Android的本地應用的工具鏈和類庫)和SDK(軟體開發工具包)沒有關係。顯然,為了使原生程式碼支援Android平臺,谷歌已經很努力了,但我們開發原生程式碼還是不如用Java來得便利。

生成工具和程式非常重要。谷歌給NDK的開發者提供的工具,與構建Android平臺的工具相同,這裡的工具我指的是一套殼指令碼和生成檔案。

為了生成你的專案,你要做的是編寫生成檔案部分,這部分包括由NDK提供的主要生成檔案。這使使用者的學習難度增大,一開始就可能嚇倒一些人。然而,當你掌握它以後,你就會覺得還不錯,之後構建你的自定義生成檔案時,你可能會覺得更好了。

最後,你建成的是一個動態連結庫,Dalvic可以用JNI(Java的本地介面)裝載它。對啊,你的遊戲仍然是呼叫庫的Dalvic Java VM程式。

混合Java和原生程式碼

所以你不能全完擺脫Java。你的程式碼必須與之相容,這其實正是你想要的,因為幾乎所有Android應用程式介面(API)仍然只用Java編寫。另外,你可能想使用的大多數Android庫也是用Java編寫的。例如,如果你想採用Openfeint的排行榜和成就功能,使用Flurry分析工具,你就必須與Java打交道。

這是用Java Native Interface (JNI)完成的。JNI使在VM中執行的Java程式碼可以被用C/C++編寫的原生程式碼調出和調回。以下是程式碼如何從原生程式碼中調出Dashboard.open() 的例子:

jclass cls = javaEnv->FindClass(“com/openfeint/api/ui/Dashboard”);
jmethodID open = javaEnv->GetStaticMethodID(cls, “open”, “()V”);
javaEnv->CallStaticVoidMethod(cls, open);

以上程式碼唯一的問題是”()V”, 它是類函式的內部型別的署名。這是Java VM描述引數和類函式返回值的方法。

這種語法很容易出錯,我建議你始終使用”javap -s myclass”指令,它將所有類函式與它們的署名一同輸出。從那裡複製和貼上。記住,如果你拼錯了一個署名,你就只能在執行時發現。

即使最新版的NDK允許你用全原生程式碼寫一個活動,我仍然會按老方法在Java中寫活動,然後從那裡呼叫原生程式碼。

輸入

在Android上,處理觸控輸入比在iOS上更復雜一點兒,因為Android設計師認為有一個直接呼叫一系列“歷史”觸控事件的系統比較酷,而不是讓你挨個呼叫。除此之外,其他都是一樣的。你只需要確保你用了ACTIONUP和 ACTIONPOINTER_UP事件。

然而,存在於移植細節的許多其他方面的大問題是,這些事件是不同執行緒的。這可能會使一些iOS開發者感到吃驚,因為他們習慣於讓幾乎所有事件都發生在主執行緒迴圈中。

至少我是很意外,Android對執行緒非常大方。所以你要根據自己引擎的編碼方式來排列事件,然後將它們從相應執行緒中傳送到你的原生程式碼。

最後,還有按鈕,即真正的硬體按鈕——觸控。至少是後退鍵和主按鍵,要確保它們符合Android使用者的操作習慣。

Apple Android(from 2-soft.com)

Apple Android(from 2-soft.com)

聲音

這是Android讓我吃驚的另一點。請做好思想準備——居然沒有OpenAL!你一定難以置信,一臉絕望,不敢接受這個事實。

但這就是真相。如果你希望輕鬆地將基於OpenAL的聲音引擎移植到Android,恐怕你要大大地失望了。我認為這跟某些版權有關。所以,你能選擇的只有MediaPlayer、SoundPool和OpenSL ES了。前兩個是Java API,第三個是本地API。

MediaPlayer基本上是用於播放不需要低延遲的音樂和聲音。我本可以用它播放音樂,但我決定嘗試OpenSL。我試過OpenSL的引擎的音樂播放部分後,覺得不喜歡它的API。如果我一開始就知道,我就會直接選擇非常簡單的MediaPlayer。

SoundPool非常適合播放音效。它還幫你解壓了聲音,在記憶體中儲存未壓縮的現成樣本。

但它還是有自己的缺陷,在我的測試中,它不能支援超過1MB的效果。SoundPool之後還有一個很糟的歷史記錄。因為程式碼的紊亂情況,SoundPool會讓所有在第一代雙核手機中執行它的應用程式崩潰!最為典型的就是執行vanilla Android版本的三星Galaxy S2。

你能想象嗎?在店裡,你的遊戲執行得好好的,但有一天,讓你的遊戲崩潰的手機賣出了數百萬臺!三星在一年之後才解決了這個問題。從那以後,遊戲開發者不得不放棄SoundPool,在OpenSL ES上執行相同的功能。我跟你說過了,OpenSL ES並不好玩。

最壞的是,即使是現在,三星釋出的更新版本Android不會有這樣的問題了,但大多數使用者都沒有更新作業系統。所以甚至是在上個月,當我釋出《Pop Corny》時,大多數三星Galaxy S2的SoundPool還是有漏洞。我決定不放棄SoundPool,在執行有漏洞版本的SoundPool時進行簡單的檢測,並且完全不播放音效。

影像

謝天謝地,Android確實支援OpenGL!這下沒問題了。但你還是要小心Android的多執行緒特點,這樣就沒事了(所有GL指令都必須來自GL執行緒)。

但你必須準備好對付各種Android手機和平板電腦的解析度。你不再生活在iOS的生態系統中了,所以你要解決的不只是兩種高寬比(iPhone和iPad)的問題了。

對於《Pop Corny》,遊戲已經支援iPhone和iPad的高寬比了,所以我只讓程式碼接受某個範圍的高寬比,之後增加必要的黑條。

screen-sizes(from gamasutra)

screen-sizes(from gamasutra)

例如,某些手機擁有480×854畫素的古怪解析度,不重新設計整個遊戲居然就不能解決這一問題。所以,遊戲在這些手機上顯示的是黑條。

只載入適當的MipMap或更低階的紋理,也非常有用,但這取決於螢幕的解析度。這會節省寶貴的記憶體,特別是對於低端裝置,因為它們的螢幕解析度低。

當移植到Android時,你遇到的OpenGL主要問題是,處理活動生命週期。你可能已經知道了,Android上的任何事件都算一個活動。即使是一個小對話方塊也是一個活動。

問題是,當對話方塊出現時,它就會中斷你的當前活動,並且如果那個活動是你的OpenGL檢視,Android就會消除你的OpenGL活動!

這意味著,當對話方塊消失後,要返回你剛才的活動,你不得不重新載入所有OpenGL的資源。當使用者後臺執行你的遊戲時,或當使用者在遊戲執行時打電話,相同的問題出現了。

每次都要再次載入所有紋理,這是無論如何也不能接受的。我想了好一陣子才想出解決辦法。這可能是因為我沒有Android裝置做測試,所以我只能依靠低beta測試器反覆測試。

無論如何,3.0版的Android最終解決這個問題了。那個版本的GLViewSurface(遊戲邦注:GLSurfaceView的作用是使使用者能更容易更好地使用OpenGL渲染應用程式)加了一個名為setPreserveEGLContextOnPause(boolean) 的方法,當開啟時,它就會儲存GL活動。

但你知道在Android生態系統中很少人會升級作業系統。所以我要做的就是,從Android最新資源中獲取GLSurfaceView的類,做些調整,然後使用,而不是使用使用者手機中的。就這麼簡單。

然而,即使是那樣,許多手機還是丟失了GL活動。結果是,當GPU是Adreno時,無論GPU是否支援多活動,GLSurfaceView都不能儲存活動。

好吧,我嘗試的所有基於Adreno的裝置都可以儲存活動,只要移除在GLSurfaceView的資源中的測試,使遊戲在活動中斷後繼續進行。

資源

移植大業的最後一個障礙是,資源管理和載入。使用過iOS的人會很驚訝地發現,當安裝程式時,Android居然不會像iOS那樣解壓程式包。

檔案仍將保持.apk狀態,但它實際上是一個zip檔案。這引發了一連串的問題。你不能只是用自己信任的系統開啟檔案並讀取。你必須開啟apk檔案,然後挨個尋找你的檔案,解壓,最後再使用。

對於某些檔案,你可以跳過解壓部分,即某類構建過程儲存未壓縮成apk的檔案。大多數媒體檔案都已經壓縮了。如果你使用ant構建,你其實可以在無壓縮的列表當中新增更多檔案擴充名。

不幸的是,我對Eclipse(遊戲邦注:著名的跨平臺自由整合開發環境)沒辦法做同樣的事。使用apk的檔案描述符、字元補償和長度(可以從Java資源管理器中獲得),可以輕鬆地載入這些檔案(使用常用檔案處理功能)。

至於壓縮的檔案,你卻不得不用Java資源管理器完全地載入,然後使用JNI將所有檔案資料複製成C語言,這樣效率會很低。

所幸的是,繼2.3版本之後,谷歌加強了本地資源載入能力。所以如果你的裝置只支援2.3或以上版本,你可以忽略以上問題,直接使用本地API。它會幫你解決所有問題。

總結

正如你所見,Android平臺有它自己的缺陷。大多數時候是因為NDK還不夠成熟。不過,隨著新版本的釋出,它會越來越完善。當然,Android使用者最好能勤快一點地更新版本……

對於以上所有問題,你可能想編譯三個不同的CPU:ARM、ARM7和x86。現在僅有一些支援x86的平板電腦,但假以時日,我們還會看到更多這樣的平板電腦。

如果你原本是開發iOS遊戲,但移植到Android版本時不認真處理的話,位元組順序可能還是會給你帶來一些麻煩。但這不是因為位元組順序的不同,而主要是因為會檢測它的iOS/OSX特定C語言定義。

有時候會有一點兒麻煩,但努力總會得到回報的。最後,一個全新的世界等著你的遊戲去探索。Android使用者也非常熱情友好,我認為會比iOS使用者還更熱情得多。所以讓Android使用者也來玩我們的遊戲吧!

via:遊戲邦/gamerboom.com