【文章內(nèi)容簡(jiǎn)介】
以及 Destroyed,每一個(gè) MIDlet 在任何時(shí)刻只可能處于其中的一個(gè)狀態(tài)。這三種狀態(tài)的轉(zhuǎn)換關(guān)系如圖 所示:MIDlet 有三個(gè)狀態(tài),分別是 pause、active 和destroyed。在啟動(dòng)一個(gè) MIDlet 的時(shí)候,應(yīng)用管理軟件會(huì)首先創(chuàng)建一個(gè) MIDlet 實(shí)例并使得他處于 pause 狀態(tài),當(dāng) startApp()方法被調(diào)用的時(shí)候 MIDlet 進(jìn)入 active 狀態(tài),也就是所說(shuō)的運(yùn)行狀態(tài)。在 active 狀態(tài)調(diào)用 destroyApp(boolean unconditional)或者 pauseApp()方法可以使得 MIDlet 進(jìn)入 destroyed 或者 pause 狀態(tài) [7]。天津理工大學(xué) 2022 屆本科畢業(yè)設(shè)計(jì)說(shuō)明書(shū)10D e s t r o y A p p ( )消減狀態(tài) ( D e s t r o y e d ) 停止?fàn)顟B(tài) ( P a u s e d ) 運(yùn)行狀態(tài) ( A c t i v e ) S t a r t A p p ( ) 呼叫 M I D l e t 的構(gòu)造函數(shù) D e s t r o y A p p ( ) P a u s e A p p ( ) 圖 Midlet流程值得一提的是 destroyApp(boolean unconditional)方法,事實(shí)上,當(dāng) destroyApp()方法被調(diào)用的時(shí)候,AMS 通知 MIDlet 進(jìn)入 destroyed 狀態(tài)。在 destroyed 狀態(tài)的 MIDlet 必須釋放了所有的資源,并且保存了數(shù)據(jù)。如果 unconditional 為 false 的時(shí)候,MIDlet 可以在接到通知后拋出 MIDletStateChangeException 而保持在當(dāng)前狀態(tài),如果設(shè)置為 true 的話,則必須立即進(jìn)入 destroyed 狀態(tài)。本程序運(yùn)行程序后允許用戶選擇執(zhí)行選項(xiàng)菜單,在開(kāi)始游戲后將先從外部文件載入地圖文件,對(duì)背景的所有物體進(jìn)行繪圖。在程序運(yùn)行的主線程中,畫(huà)面刷新將以一定的頻率對(duì)屏幕重繪,實(shí)時(shí)反映整個(gè)游戲的進(jìn)行狀態(tài)。在 Player 線程中開(kāi)啟游戲背景音樂(lè)。游戲開(kāi)始后先繪制地圖,并將各個(gè)對(duì)象初始化。游戲中的普通食物設(shè)置為初始顯示isShow = true,而獎(jiǎng)勵(lì)食物設(shè)置為初始不顯示 isShow = false,當(dāng)用戶達(dá)到獎(jiǎng)勵(lì)食物的出現(xiàn)條件時(shí)再設(shè)置 isShow = true,予以顯示。在屏幕重繪的主程序中,將在每次的循環(huán)中判斷若干事件,以便程序進(jìn)入相關(guān)的分支執(zhí)行相關(guān)的反應(yīng)代碼。如:設(shè)是否與屏幕邊緣、食物以及自身相撞、屏幕上相關(guān)信息的繪制等。如果選擇無(wú)線雙人對(duì)戰(zhàn),還需要對(duì)對(duì)方的得分進(jìn)行實(shí)時(shí)的傳送與顯示,以及對(duì)最終的勝負(fù)進(jìn)行判斷。Client 類(lèi)與 Server 類(lèi)分別對(duì)應(yīng)客戶機(jī)與服務(wù)器,Sender 類(lèi)負(fù)責(zé)對(duì)客戶機(jī)與服務(wù)期間傳送數(shù)據(jù)。程序?yàn)樾枰瓿瑟?dú)立功能的模塊還設(shè)置了其他單獨(dú)的類(lèi)。splashScreen 類(lèi)負(fù)責(zé)顯示初始載入畫(huà)面,F(xiàn)ood 類(lèi)實(shí)現(xiàn)食物的顯示。 本程序需要解決的主要技術(shù)問(wèn)題1. 游戲程序是一項(xiàng)精度要求很高的程序系統(tǒng),繪圖事件、鍵盤(pán)事件都會(huì)以極高的頻率在后臺(tái)等待響應(yīng),若有絲毫的差別都將很容易導(dǎo)致程序在運(yùn)行不久后可能出現(xiàn)嚴(yán)重錯(cuò)誤,甚至死循環(huán)。因此,其邏輯設(shè)計(jì)應(yīng)當(dāng)相當(dāng)嚴(yán)謹(jǐn),需將所有可能發(fā)生的事件及意外情況考慮在設(shè)計(jì)中。天津理工大學(xué) 2022 屆本科畢業(yè)設(shè)計(jì)說(shuō)明書(shū)112. 游戲中為了美觀,提高游戲性,可能需要采用外部文件引入的圖片貼圖,以及 MID 背景音樂(lè)。背景音樂(lè)可以根據(jù)用戶的需要隨時(shí)進(jìn)行開(kāi)啟和關(guān)閉。游戲的開(kāi)始信息畫(huà)面也是吸引用戶的重要部分。3. 為了滿足游戲的需要,游戲需要加入難度調(diào)整的功能,根據(jù)選擇難度的不同,蛇的移動(dòng)速度需要進(jìn)行相應(yīng)的調(diào)整,以增強(qiáng)游戲的挑戰(zhàn)性。4. 游戲需要進(jìn)行實(shí)時(shí)的得分計(jì)算,蛇每吃到一個(gè)食物進(jìn)行加分,每得到一定的分?jǐn)?shù),還會(huì)出現(xiàn)獎(jiǎng)勵(lì)食物,吃到的加分會(huì)更多。游戲的結(jié)尾會(huì)對(duì)用戶的最終得分加以顯示。5. 蛇每吃到一個(gè)食物,就會(huì)在地圖上在生成一個(gè)新食物,新食物的生成不應(yīng)與蛇的位置相重疊,缺乏真實(shí)感。所以每一次生成新食物都需要進(jìn)行相關(guān)的碰撞檢測(cè)。6. 以無(wú)線網(wǎng)絡(luò)進(jìn)行雙人對(duì)戰(zhàn)時(shí),需要在屏幕上顯示自己和對(duì)方的當(dāng)前得分,在游戲結(jié)束時(shí),需要根據(jù)雙方的得分進(jìn)行判斷勝負(fù),并在屏幕上顯示雙方的勝負(fù)結(jié)果。7. 游戲需要建立高分榜,將用戶打出的最高分加以保存,可以隨時(shí)察看游戲的最高分,以增強(qiáng)游戲的挑戰(zhàn)性。記錄分?jǐn)?shù)的存儲(chǔ)方式也需要有較好的解決方案。手機(jī)中由于處理器和內(nèi)存空間、存儲(chǔ)空間都十分有限,其數(shù)據(jù)庫(kù)系統(tǒng)與普通 PC 大相徑庭。其數(shù)據(jù)庫(kù)結(jié)構(gòu)較為簡(jiǎn)單,被稱(chēng)之為 RMS 系統(tǒng)。8. Java 是基于虛擬機(jī)的半解釋型編譯系統(tǒng),其執(zhí)行效率較 C++等完全編譯后的程序會(huì)低很多,程序如果不進(jìn)行精簡(jiǎn)和優(yōu)化,將可能導(dǎo)致運(yùn)行的不流暢。除開(kāi)發(fā)過(guò)程中對(duì)結(jié)構(gòu)上的控制、變量的使用、算法的優(yōu)化等優(yōu)化外,還可以使用混淆器(Obfuscator)進(jìn)行程序打包后的優(yōu)化。以上相關(guān)技術(shù)細(xì)節(jié)和整體流程將分別在以下小節(jié)闡述。 程序中的幾項(xiàng)技術(shù) Canvas類(lèi)Canvas 是 MIDP 提供的低級(jí)用戶界面類(lèi)。和高級(jí)用戶界面相比,Canvas 擁有更大的靈活性。由于 Canvas 不提供任何現(xiàn)成的可視化組件,所有在 Canvas 顯示的圖形和文本都必須通過(guò) Graphics 繪制出來(lái)。因此,開(kāi)發(fā)人員可以完全獲得界面控制能力,能精確控制每一個(gè)像素的位置,在游戲開(kāi)發(fā)中,這是必不可少的。Canvas 提供了鍵盤(pán)事件,并定義了允許將鍵盤(pán)按鍵映射為游戲控制鍵的函數(shù)。鍵盤(pán)事件由鍵代碼指定,但這樣控制游戲會(huì)導(dǎo)致缺乏通用性,并不是每個(gè)設(shè)備的鍵盤(pán)布局都適合游戲的操作。應(yīng)當(dāng)將鍵代碼轉(zhuǎn)換為游戲鍵的代碼,以便硬件開(kāi)發(fā)商能定義他們自己的游戲鍵布局。 Graphics 類(lèi)Graphics 是 包中用于繪制簡(jiǎn)單 2D 圖形的類(lèi) [8]。它具有 24 位深度天津理工大學(xué) 2022 屆本科畢業(yè)設(shè)計(jì)說(shuō)明書(shū)12色彩的繪制能力,以三原色分別各占一個(gè)字節(jié)表示其顏色。程序只能在 paint()函數(shù)中使用Graphics 繪制。文本的繪制是基于定位點(diǎn)操作的。定位點(diǎn)的最大作用就是最小化計(jì)算文本繪制位置所需的工作量。定位點(diǎn)是由三個(gè)水平常量(LEFT,HCENTER,RIGHT)和三個(gè)垂直常量(BOTTOM,BASELINE,TOP)組合而成的,組合的方式按照“或”操作。數(shù)字玲也可以做定位點(diǎn)的值,它代表的組合方式是 TOP|LEFT。在 Graphics 中還定義了一個(gè)垂直常量VCENTER,但是它不能用于繪制文本。一般認(rèn)為文本的垂直居中沒(méi)有意義,而且也很難實(shí)現(xiàn)[9]。但是 VCENTER 可以用于繪制圖片的 drawImage()方法 [10]。繪制圖片的方法與文本非常類(lèi)似,不同的是由于圖片沒(méi)有基線(baseline )的概念,因此在繪制圖片時(shí)不能使用BASELINE 常量。 Socket 開(kāi)發(fā) MIDP 無(wú)線連網(wǎng)游戲(JSR 118)增加了對(duì) TCP/IP 和 UDP 協(xié)議的支持??梢允褂?Socket 開(kāi)發(fā)聯(lián)網(wǎng)游戲, 規(guī)范并沒(méi)有規(guī)定設(shè)備必須支持 Socket,因此在使用之前應(yīng)該參考設(shè)備規(guī)范看其是否支持 Socket 通信 [11]。 增加了下面兩個(gè)接口來(lái)支持基于 IP 的 Socket 網(wǎng)絡(luò)編程:Socket 類(lèi)用在用戶端,用戶構(gòu)造一個(gè) Socket 類(lèi)可以建立與服務(wù)器的連接。Socket 類(lèi)的構(gòu)造方法有四種:? Socket(String,int)構(gòu)造一個(gè)連接指定主機(jī)、指定端口的流 Socket。? Socket(String,int,boolean)構(gòu)造一個(gè)連接指定主機(jī)、指定端口的 Socket 類(lèi),boolean 類(lèi)型的參數(shù)用來(lái)設(shè)置是流 Socket 還是數(shù)據(jù)報(bào) Socket。? Socket(IAddress,int,boolean)構(gòu)造一個(gè)連接指定 Inter 地址、指定端口的流Socket。? Socket(IAddress,int,boolean)構(gòu)造一個(gè)連接指定 Inter 地址、指定端口的 Socket 類(lèi),boolean 類(lèi)型的參數(shù)用來(lái)設(shè)置是流 Socket 還是數(shù)據(jù)報(bào) Socket。構(gòu)造完 Socket 類(lèi)后,就可以通過(guò) Socket 類(lèi)建立輸入、輸出流,通過(guò)流來(lái)傳送數(shù)據(jù)。ServerSocket 類(lèi)用在服務(wù)器端,接收用戶端傳送的數(shù)據(jù)。ServerSocket 類(lèi)的構(gòu)造方法有兩種:? ServerSocket(int)在指定端口上構(gòu)造一個(gè) ServerSocket 類(lèi)。? ServerSocket(int,int)在指定端口上構(gòu)造一個(gè) ServerSocket 類(lèi),并進(jìn)入監(jiān)聽(tīng)狀態(tài),第二個(gè) int 類(lèi)型的參數(shù)是監(jiān)聽(tīng)時(shí)間長(zhǎng)度 [12] 。天津理工大學(xué) 2022 屆本科畢業(yè)設(shè)計(jì)說(shuō)明書(shū)13 RMS 數(shù)據(jù)庫(kù)系統(tǒng)MIDP 為 MIDlets 提供了一種永久存儲(chǔ)和后來(lái)讀出數(shù)據(jù)的數(shù)據(jù)庫(kù)解決方案,被稱(chēng)為Record Managerment System(RMS),是一種類(lèi)簡(jiǎn)單的基于記錄的數(shù)據(jù)庫(kù) [13]。很顯然,手機(jī)上的數(shù)據(jù)庫(kù)系統(tǒng)不可能有 PC 上的強(qiáng)大功能。微小的存儲(chǔ)空間也限制了它們的結(jié)構(gòu)不能過(guò)于復(fù)雜。RMS 是專(zhuān)門(mén)針對(duì)移動(dòng)設(shè)備的服務(wù)的。RMS 包中包括 RecordStore 類(lèi)。在一個(gè) MIDlet suite 包里的所有 MIDlet 都允許創(chuàng)建多個(gè)記錄集,只要它們賦有不同的名稱(chēng)。當(dāng) MIDlet 包從平臺(tái)中被移除后,所有與該包有關(guān)的的記錄集都同時(shí)會(huì)被移除。同一個(gè)包內(nèi)的 MIDlets 可以直接互相訪問(wèn)它們的記錄集,不同包內(nèi)也可產(chǎn)生共享,但這需要有包的授權(quán)屬性決定。訪問(wèn)模式會(huì)在準(zhǔn)備提供共享的 RecordStore 被建立時(shí)被創(chuàng)建。訪問(wèn)模式允許私有使用或訪問(wèn)。記錄是字節(jié)數(shù)組。開(kāi)發(fā)者可以利用 InputStream 的派生類(lèi)DataInputStream、DataOutputStream 以及 ByteArrayInputStream、ByteArrayOutputStream 將不同種類(lèi)的數(shù)據(jù)類(lèi)型打包,以字節(jié)流的形式發(fā)送和接收。區(qū)別記錄的唯一標(biāo)記是他們的 ID 值,作為記錄集的主鍵。第一項(xiàng)記錄的 ID 是 1,其后的每個(gè)記錄 ID 遞增。Record 是以字節(jié)為基本單位來(lái)存放的,所以所有要寫(xiě)入 record 的數(shù)據(jù)都必須先將其轉(zhuǎn)為字節(jié)才能寫(xiě)入,從 record 所讀出來(lái)的數(shù)據(jù)也是字節(jié),必須將其轉(zhuǎn)換為原先寫(xiě)入時(shí)的數(shù)據(jù)類(lèi)型才有意義。然而讀取或?qū)懭氲淖止?jié)數(shù)組都只能代表一個(gè)字段的信息,如果需要讀取或?qū)懭攵鄠€(gè)字段就必須要將數(shù)據(jù)轉(zhuǎn)換成字節(jié)信息,并且提供適當(dāng)?shù)臋C(jī)制來(lái)分隔這些信息。利用輸入/輸出流這一種方法較上一種復(fù)雜,但是較為實(shí)用。方法一中所有的字段只能以字符串的形式存儲(chǔ),要對(duì)這些字段作進(jìn)一步的處理非常麻煩。利用輸入輸出流可以寫(xiě)入及讀取不同數(shù)據(jù)類(lèi)型的數(shù)據(jù),做法是在寫(xiě)入數(shù)據(jù)時(shí)先將一個(gè) DataOutputStream 數(shù)據(jù)流對(duì)象串接到一個(gè)ByteArrayOutStream 數(shù)據(jù)流對(duì)象,然后再依字段的數(shù)據(jù)類(lèi)型用 writeInt()、writeBoolean() 等方法寫(xiě)入,最后把 ByteArrayOutputStream 內(nèi)的元素?cái)?shù)據(jù)寫(xiě)入 record 中。反之若要讀取數(shù)據(jù),則先要串接一個(gè) DataInputStream 對(duì)象和 ByteArrayInputStream,依字段的數(shù)據(jù)類(lèi)用 readInt()、readBoolean()等方法讀取。 PNG 圖片格式PNG(Portable Network Graphics)格式是 MIDlet 唯一支持的圖象格式 [14],PNG 具體格式由 PNG Specification,Version 定義的。PNG 格式提供透明背景的圖象,這對(duì)繪制游戲畫(huà)面和被操縱主角極有幫助。物體之間重疊時(shí)最上層圖片也不會(huì)覆蓋超過(guò)其有效象素外的部分。天津理工大學(xué) 2022 屆本科畢業(yè)設(shè)計(jì)說(shuō)明書(shū)14 內(nèi)存的優(yōu)化技術(shù)手機(jī)內(nèi)存空間小,所以在程序設(shè)計(jì)時(shí)應(yīng)該注意以下幾點(diǎn),以盡量減少內(nèi)存的使用:?繪圖是很占用時(shí)間的,所以盡可能的減少 Graphics 函數(shù)的調(diào)用?盡可能的將變量定義在循環(huán)以外!?盡最大可能的進(jìn)行對(duì)需要的數(shù)據(jù)進(jìn)行預(yù)先計(jì)算并將結(jié)果保存在緩沖里?String 類(lèi)很容易產(chǎn)生垃圾內(nèi)存,盡可能的使用 StringBuffer 代替 String 或用 final static 來(lái)定義之?盡量使用 static final 修飾函數(shù),而避免 synchronized 修飾符?對(duì)于頻繁調(diào)用的函數(shù)要使用盡可能少的參數(shù)!?盡可能的使用和來(lái)代替*和/?與 0 比較比與其他數(shù)值比較快?局部變量比其他類(lèi)型的變量運(yùn)算要快?在 switch()中盡量使用連續(xù)的小數(shù)值判斷?盡量使用乘法而不使用除法 [15]。 混淆器(Obfuscator)的使用Java 語(yǔ)言并沒(méi)有完全編譯成二進(jìn)制可執(zhí)行文件, 文件是一種介于源程序和二進(jìn)制之間的一中基于半解釋的字節(jié)碼,需要虛擬機(jī)來(lái)執(zhí)行。它包括了所有的信息。然 很容易被反編譯為源代碼,從而不能保護(hù)作者的知識(shí)成果。目前流行的如 decode,JAD 等反編譯工具可以以很快的速度生成源文件。如果不加以施行有效的措施,將造成嚴(yán)重的后果。由此引入混淆器的概念?;煜鲗⒋a中的所有變量、函數(shù)、類(lèi)的名稱(chēng)變?yōu)楹?jiǎn)短的英文字母代號(hào),如果缺乏相應(yīng)的函數(shù)名指示和程序注釋?zhuān)词贡环淳幾g,也將難以閱讀。 混淆器的作用不僅僅是保護(hù)代碼,它也有精簡(jiǎn)編譯后程序大小的作用。由于以上介紹的減少變量、函數(shù)的命名長(zhǎng)度的關(guān)系, 文件中減少這些冗余的信息?;煜螅w積大約能減少 25%,這對(duì)當(dāng)前費(fèi)用較貴的無(wú)線網(wǎng)