1. 程式人生 > >Libgdx Developer's Guide(Libgdx開發者手冊)-8(一個簡單的遊戲)

Libgdx Developer's Guide(Libgdx開發者手冊)-8(一個簡單的遊戲)

在潛入libgdx提供的API之前,讓我們建立一個非常簡單的“遊戲”,這個遊戲每個模組都將觸及一點以讓我們有種整體感覺。我們會引入一些概念,但不會太深入。

我們來看看:
  • 基本檔案訪問
  • 清屏
  • 繪圖
  • 使用照相機
  • 基本輸入處理
  • 播放音效

專案設定

按照 Project Setup中的步驟進行。使用以下名稱:

  • 應用程式名: drop
  • 包名: com.badlogic.drop
  • 遊戲類: Drop

一旦匯入到Eclipse中,你應該擁有4個工程: drop, drop-android, drop-desktop 和 drop-html5。

遊戲

遊戲的想法很簡單:

  • 用水桶捕捉雨滴
  • 水桶位於螢幕下方
  • 雨滴每秒隨機從螢幕頂部產生並加速落下
  • 玩家可以通過滑鼠/觸屏或者左右鍵來水平移動水桶
  • 遊戲沒有結尾,想像它是一禪宗般的體驗 :)

資源

我們需要一些圖片和音效來使遊戲看起來稍微漂亮一些。對於圖形,定義目標解析度為800x480畫素(Android橫屏)。如果遊戲執行的裝置沒有該解析度,則縮放所有東西以適應螢幕。注意:對於 高質量的遊戲,你可能考慮會不同的螢幕解析度提供不同的資源。這本身是一個大話題,這裡不深入。

雨滴和水桶在垂直方向佔用的螢幕應該小於十分之一,因此定義它們的大小為64x64畫素。

從以下地址獲取資源:

為了讓遊戲可以使用這些資源,必須把它們放在Android工程的assets資料夾下。把這4個檔案命名為:drop.wav, rain.mp3, droplet.png 和 bucket.png,並把它們放在 drop-android/assets/

資料夾下。桌面應用和HTML5工程都連結至該資原始檔夾,因此只需要儲存一次。

配置啟動類

鑑於我們的需求,我們現在開始配置不同的啟動類。先從桌面應用開始。開啟drop-desktop/下的類Main.java。我們需要一個 800x480 的視窗並設定標題為"Drop"。程式碼如下:

package com.badlogic.drop;

import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;

public class Main {
   public static void main(String[] args) {
      LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
      cfg.title = "Drop";
      cfg.width = 800;
      cfg.height = 480;
      new LwjglApplication(new Drop(), cfg);
   }
}

轉到Android工程,我們想要應用在橫屏執行。因此需要修改工程根目錄下的AndroidManifest.xml,如下: 

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.badlogic.drop"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="5" android:targetSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="landscape"
            android:configChanges="keyboard|keyboardHidden|orientation">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

配置工具已經為我們填寫了正確的值,android:screenOrientation設定為"landscape"。如果要在豎屏模式運行遊戲,就把該屬性設定為 "portrait"。

我們希望節省電池並禁用振動器和指南針。這需要在工程的MainActivity.java中做以下修改:


package com.badlogic.drop;

import android.os.Bundle;

import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;

public class MainActivity extends AndroidApplication {
   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
        
      AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
      cfg.useAccelerometer = false;
      cfg.useCompass = false;
        
      initialize(new Drop(), cfg);
   }
}

我們不能定義Activity的解析度,這是由Android作業系統設定的。像之前設定的一樣,無論裝置解析度是多少,我們會縮放800x480的目標解析度到當前裝置解析度。

最後,我們要確保HTML5工程也使用一個800x480的繪製區。因此需要修改html5工程下的GwtLauncher.java檔案:

package com.badlogic.drop.client;

import com.badlogic.drop.Drop;

public class GwtLauncher extends GwtApplication {
   @Override
   public GwtApplicationConfiguration getConfig () {
      GwtApplicationConfiguration cfg = new GwtApplicationConfiguration(800, 480);
      return cfg;
   }

   @Override
   public ApplicationListener getApplicationListener () {
      return new Drop();
   }
}

注意:  我們不需要為該平臺指定使用的OpenGL版本,因為它只支援2.0。

現在所有的啟動類都配置完成了,讓我們開始實現這個有趣的遊戲。

程式碼

我們希望把程式碼分成幾部分。簡單起見,我們把所有東西都放在核心工程的Drop.java檔案中。 

載入資源

第一個任務是載入資源並儲存它們的引用。通常在ApplicationListener.create()方法中載入資源,因此程式碼如下: 

public class Drop implements ApplicationListener {
   Texture dropImage;
   Texture bucketImage;
   Sound dropSound;
   Music rainMusic;
   
   @Override
   public void create() {
      // load the images for the droplet and the bucket, 64x64 pixels each
      dropImage = new Texture(Gdx.files.internal("droplet.png"));
      bucketImage = new Texture(Gdx.files.internal("bucket.png"));
      
      // load the drop sound effect and the rain background "music"
      dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));
      rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3"));
      
      // start the playback of the background music immediately
      rainMusic.setLooping(true);
      rainMusic.play();

      ... more to come ...
   }

   // rest of class omitted for clarity

每個資源都在Drop類中擁有一個欄位,因而後續我們可以引用它。create()方法的前兩行載入雨滴和水桶的圖片。Texture表示一個儲存於視訊RAM裡的已載入圖片。通常不直接繪製Texture。 Texture通過向其構造器傳入一個資原始檔的FileHandle來載入。這種FileHandle的例項是通過byGdx.files裡其中一個方法來獲得的。不同的檔案型別有很多,我們在這裡使用 "internal" 檔案型別來引用資源。Internal 的檔案位於Android工程的assets目錄中。Eclipse中,桌面應用和HTML5工程通過連結以引用該目錄。

接下來載入音效與背景音樂。Libgdx 區分音效和音樂,音效儲存在記憶體裡,音樂無論儲存在哪都會被轉換為流。音樂通常太大不能完全儲存在記憶體裡,因此作此區分。根據經驗,如果你的示例小於10秒則要使用一個Sound例項,更長的音訊就要使用Music例項。

通過Gdx.app.newSound()Gdx.app.newMusic()來載入SoundMusic。這兩個方法都需要一個FileHandle,像Texture的構造器一樣。

create()方法末尾,我們讓Music例項迴圈並立即播放。 如果你執行這個應用,你會看到一個漂亮的粉紅色背景,能聽到落雨聲。


Camera 和 SpriteBatch

接下來我們建立 一個Camera 和 SpriteBatch。我們使用前者以保證使用目標解析度800x480畫素來呈現應用,而不管實際的解析度是多少。SpriteBatch 是一個特殊的類用來繪製2D圖形,比如我們已經載入的紋理。

我們向類中加入兩個新欄位,命名為 camera  和 batch:

   
OrthographicCamera camera;
   SpriteBatch batch;

create() 方法中我們首先這樣建立 camera :

   camera = new OrthographicCamera();
   camera.setToOrtho(false, 800, 480);

這樣就可以確保camera一直為我們展示一個800x480單位寬的遊戲區。想象它是一個虛擬視窗。目前我們把畫素作為單位,這樣簡單一些。使用其它單位也沒什麼,如meters或其他什麼。Cameras非常強大,它能作很多事,我們在此基礎手冊中不再詳述。檢視剩下的使用者手冊來獲取更多資訊。

然後建立 SpriteBatch (仍然在 create()方法中):

 
  batch = new SpriteBatch();

通過建立這些,我們差不多已經完成所有運行遊戲所需要的東西。

加入水桶

最後缺少的水桶和雨滴。讓我們想想要用代表描述什麼:

  • 一個有x/y座標的水桶/雨滴在800x480大小的空間。
  • 在遊戲區表示出水桶/雨滴的寬高。
  • 水桶/雨滴的圖形表示,我們已經通過Texture例項載入過了。 

因此,為了描述水桶與雨滴,我們需要儲存它們的位置和大小。Libgdx提供一個Rectangle類可以達成這個目的。開始先建立一個表示水桶的Rectangle。新增一個新欄位:

   Rectangle bucket;

create() 方法中例項化Rectangle並指定其初始值。我們想讓水桶比底部高出20畫素,並水平居中。

   bucket = new Rectangle();
   bucket.x = 800 / 2 - 64 / 2;
   bucket.y = 20;
   bucket.width = 64;
   bucket.height = 64;

我們將水桶水平居中,並放在離螢幕底部20畫素高的地方。等等,為什麼bucket.y設定為20,不應該是480 - 20嗎?預設情況下,所有在libgdx(與OpenGL)中顯示的東西其y軸都指向上方。水桶的x/y座標定義在水桶的左下角,繪圖的原點位於螢幕左下角。矩形的寬高設定為64x64,小於目標解析度高度的十分之一。

注意: 可以 更改配置 使y軸向下並且原點在螢幕左上角。 OpenGL 和 camera 類非常靈活,你可以在2D和3D下使用幾乎任何一種視角。

渲染水桶

是時候渲染水桶了。首先要做的是用深藍色清屏。更改render() 方法如下:

   @Override
   public void render() {
      Gdx.gl.glClearColor(0, 0, 0.2f, 1);
      Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

      ... more to come here ...
   }

如果你使用高階類如Texture 或 SpriteBatch,那關於OpenGL你只需要知道這兩行。第一個呼叫把清屏色設定為藍色。其引數分別是紅,綠,藍和該顏色的透明度,每個的取值範圍都是[0, 1]。下一個呼叫命令OpenGL直接去清屏。

然後呼叫camera去更新。Cameras 使用一個稱作矩陣的數學實體負責建立渲染的座標系。每次更改camera屬性都要重新計算這些矩陣。我們不在這個簡單的例子中做這些,但每幀更新一次camera是一個很好的實踐:


  
 camera.update();

現在可以顯示水桶了:

   batch.setProjectionMatrix(camera.combined);
   batch.begin();
   batch.draw(bucketImage, bucket.x, bucket.y);
   batch.end();

第一行告訴 SpriteBatch 使用camera指定的座標系。如前所述,這是由一種叫做矩陣的東西完成的,確切地說,叫投影矩陣。camera.combined欄位就是這樣一個矩陣。SpriteBatch將從那裡在座標系中渲染前面描述過的所有東西。

下面告訴 SpriteBatch 啟動一個新的batch。為什麼要這麼做,batch又是什麼?OpenGL 最討厭只告訴它一個單獨的圖片,它希望一次性告訴它儘可能多的多個圖片。

SpriteBatch 類就可以幫助 OpenGL 。它會記錄SpriteBatch.begin() 和 SpriteBatch.end()之間的所有繪製命令。一旦呼叫SpriteBatch.end(),它會一次性把提交所有的繪畫請求,這讓渲染過程加速很多。剛開始這些或者看起來很煩,但正是這一點造成了每秒60幀顯示500個sprite和每秒20幀顯示100個sprite之間的差別。 

使水桶移動起來 (觸屏/滑鼠)

是時候讓使用者控制水桶了。之前我們提到過要讓使用者拖動水桶。讓我們稍微簡化一下。如果使用者觸控式螢幕幕(或按下滑鼠),我們希望水桶圍繞這一點水居中。在render() 方法最後面新增以下程式碼:

   if(Gdx.input.isTouched()) {
      Vector3 touchPos = new Vector3();
      touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
      camera.unproject(touchPos);
      bucket.x = touchPos.x - 64 / 2;
   }

首先我們通過呼叫 Gdx.input.isTouched()來查尋輸入模組當前螢幕是否被觸控(或滑鼠被按下)。接下來我們把觸屏/滑鼠人座標轉換到camera的座標系。這很有必要,因為觸屏/滑鼠的座標系很可能我我們用來顯示物件的座標系不一致。

Gdx.input.getX() 和 Gdx.input.getY() 返回當前 觸控/滑鼠位置(libgdx也支援多點觸控,但這是另一個話題了)。要把這些座標轉換到我們的camera的座標系,需要呼叫camera.unproject() 方法,這需要一個Vector3, 一個三維向量。建立這樣的向量,設定當前觸控/滑鼠的座標並呼叫該方法。該向量就會包含水桶所在座標系的觸控/滑鼠的座標。最後,我們更改水桶位置以圍繞觸控/滑鼠的座標居中。

注意: 總是例項化新的物件是非常非常壞的一種方法,比如這裡的Vector3物件。原因是垃圾回收器不得不頻繁地清除這些短命的物件。在桌面應用中這不是一個大問題,但在Android裡,垃圾回收器會導致幾百毫秒的暫停因而會很卡。為了解決這個特殊問題,可以簡單地將touchPos作為Drop類的一個欄位,而不是總是例項化。

注意#2: touchPos 是一個三維向量。你可能想知道為什麼我們只操作2D時還需要它。OrthographicCamera實際上是3D camera,它也有z座標。想想CAD應用,它們也使用3D正交camera。我們只是簡單地用它來繪製2D圖形。 

使水桶移動起來 (鍵盤)

在桌面和瀏覽器中,也可以接收鍵盤輸入。當左右方向鍵被按下時,使水桶移動起來。

我們希望水桶移動時不振動,無論向左或向右,每秒200畫素單位。要實現這種基於時間的移動,我們需要知道最近一幀和當前幀之間經過的時間。下面是相應的做法:

   if(Gdx.input.isKeyPressed(Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime();
   if(Gdx.input.isKeyPressed(Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime();

Gdx.input.isKeyPressed() 方法告訴我們特定的鍵是否被按下。列舉類Keys包含所有libgdx支援的鍵碼。Gdx.graphics.getDeltaTime()方法返回最近上幀和當前幀之間所經歷的秒數。我們只需修改水桶的x座標,每次加上/減去 100單位。

同時還要保證水桶處於螢幕範圍內。

 
  if(bucket.x < 0) bucket.x = 0;
   if(bucket.x > 800 - 64) bucket.x = 800 - 64;

新增雨滴

對雨滴來說,我們儲存一個Rectangle例項列表,每一個用來跟蹤一個雨滴的位置及大小。為這個列表加入一個欄位:

   Array<Rectangle> raindrops;

Array 類是libgdx的一個公共類用來替代標準Java集合如ArrayList。後者的問題是很多情況下它會產生垃圾。Array類嘗試儘可能多地減少垃圾。Libgdx提供其他垃圾回收器可以回收的集合,如hashmaps或sets等。

我們也需要保持跟蹤產生雨滴的最後時間,因此我們新增另一個欄位:


 
  long lastDropTime;

我們要用納秒來儲存這個時間,因此使用long型別。

為便於建立雨滴,我們寫一個方法叫spawnRaindrop(),它例項化一個新的Rectangle,把它設定到螢幕頂部的一個隨機位置,並新增到raindrops陣列。

   private void spawnRaindrop() {
      Rectangle raindrop = new Rectangle();
      raindrop.x = MathUtils.random(0, 800-64);
      raindrop.y = 480;
      raindrop.width = 64;
      raindrop.height = 64;
      raindrops.add(raindrop);
      lastDropTime = TimeUtils.nanoTime();
   }

該方法很明瞭。MathUtils類是一個libgdx類提供豐富的數學相關的靜態方法。這個例子中,它返回一個介於0和 800-64 之間的隨機數。TimeUtils是另一個libgdx類,它提供一個非常基礎的時間相關的靜態方法。該例中我們以納秒記錄當前時間,後續我們要以此判斷是否產生一個新雨滴。

  create()方法中,我們例項化雨滴陣列並開始產生第一個雨滴。

我們在create()方法中例項該陣列:

   raindrops = new Array<Rectangle>();
   spawnRaindrop();

接下來在render()方法中新增幾行,來檢查自從產生一個新雨滴以來所經歷的時間,如果需要的話再建立一個新雨滴:

   
if(TimeUtils.nanoTime() - lastDropTime > 1000000000) spawnRaindrop();

我們也需要讓雨滴動起來,讓我們採取簡單的方法,讓它們以每秒 200畫素/單位 的恆定速度移動。如果雨滴位置螢幕底部以下,我們就從數組裡移除它。

   Iterator<Rectangle> iter = raindrops.iterator();
   while(iter.hasNext()) {
      Rectangle raindrop = iter.next();
      raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
      if(raindrop.y + 64 < 0) iter.remove();
   }

雨滴需要渲染。在SpriteBatch中新增渲染程式碼後如下:

   batch.begin();
   batch.draw(bucketImage, bucket.x, bucket.y);
   for(Rectangle raindrop: raindrops) {
      batch.draw(dropImage, raindrop.x, raindrop.y);
   }
   batch.end();

最後一個調整:如果雨滴碰到了水桶,我們希望播放下雨聲並從陣列移動該雨滴。我們簡單地向雨滴迴圈更新處新增下面幾行:

      if(raindrop.overlaps(bucket)) {
         dropSound.play();
         iter.remove();
      }

Rectangle.overlaps() 方法檢查是否一個矩形和另一個矩形重疊。在此例中,我們讓下雨音效播放並從陣列移除該雨滴。

清理

使用者可以在任何時候關閉應用。對這個簡單的例子而言沒什麼要處理的。然而,通常來說幫助作業系統收拾殘局是一個很好的主意。

任何實現了Disposable介面的libgdx類並且因此帶有adispose()方法,都需要在不使用時手動銷燬。在我們的例子中紋理,聲音,音樂和SpriteBatch都是符合條件類。身為好市民,我們如下這樣實現{ApplicationListener#dispose() 方法: 

   @Override
   public void dispose() {
      dropImage.dispose();
      bucketImage.dispose();
      dropSound.dispose();
      rainMusic.dispose();
      batch.dispose();
   }

一旦你銷燬一個資源,就不可以在任何地方訪問它。

可銷燬資源通常是一些不能被Java的垃圾回收器處理的本地資源。這就是我們為什麼要手動銷燬的原因。Libgdx 提供豐富的方法來管理資源。讀剩下的開發手冊檢視這些方法。

處理暫停/恢復

每次使用者接到一個電話或按下home鍵時,Android都 有暫停和恢復應用程式的標記。Libgdx在這種情況下會自動為你做很多事情,比如:重新載入可能丟失的圖片(OpenGL上下文丟失,對它而言是很嚴重的一個話題),暫停和恢復音樂流等。

我們的遊戲其實不需要處理暫停和恢復。當用戶回到應用時,遊戲會接著上次離開時的狀態繼續執行。通常人們會實現一個暫停按鈕讓使用者點選螢幕繼續。這留給讀者作為練習。檢視下ApplicationListener.pause()ApplicationListener.resume()方法。

完整的原始碼

這是我們這個簡單遊戲的原始碼:

package com.badlogic.drop;

import java.util.Iterator;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.audio.Sound;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.TimeUtils;

public class Drop implements ApplicationListener {
   Texture dropImage;
   Texture bucketImage;
   Sound dropSound;
   Music rainMusic;
   SpriteBatch batch;
   OrthographicCamera camera;
   Rectangle bucket;
   Array<Rectangle> raindrops;
   long lastDropTime;
   
   @Override
   public void create() {
      // load the images for the droplet and the bucket, 64x64 pixels each
      dropImage = new Texture(Gdx.files.internal("droplet.png"));
      bucketImage = new Texture(Gdx.files.internal("bucket.png"));
      
      // load the drop sound effect and the rain background "music"
      dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));
      rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3"));
      
      // start the playback of the background music immediately
      rainMusic.setLooping(true);
      rainMusic.play();
      
      // create the camera and the SpriteBatch
      camera = new OrthographicCamera();
      camera.setToOrtho(false, 800, 480);
      batch = new SpriteBatch();
      
      // create a Rectangle to logically represent the bucket
      bucket = new Rectangle();
      bucket.x = 800 / 2 - 64 / 2; // center the bucket horizontally
      bucket.y = 20; // bottom left corner of the bucket is 20 pixels above the bottom screen edge
      bucket.width = 64;
      bucket.height = 64;
      
      // create the raindrops array and spawn the first raindrop
      raindrops = new Array<Rectangle>();
      spawnRaindrop();
   }
   
   private void spawnRaindrop() {
      Rectangle raindrop = new Rectangle();
      raindrop.x = MathUtils.random(0, 800-64);
      raindrop.y = 480;
      raindrop.width = 64;
      raindrop.height = 64;
      raindrops.add(raindrop);
      lastDropTime = TimeUtils.nanoTime();
   }

   @Override
   public void render() {
      // clear the screen with a dark blue color. The
      // arguments to glClearColor are the red, green
      // blue and alpha component in the range [0,1]
      // of the color to be used to clear the screen.
      Gdx.gl.glClearColor(0, 0, 0.2f, 1);
      Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
      
      // tell the camera to update its matrices.
      camera.update();
      
      // tell the SpriteBatch to render in the
      // coordinate system specified by the camera.
      batch.setProjectionMatrix(camera.combined);
      
      // begin a new batch and draw the bucket and
      // all drops
      batch.begin();
      batch.draw(bucketImage, bucket.x, bucket.y);
      for(Rectangle raindrop: raindrops) {
         batch.draw(dropImage, raindrop.x, raindrop.y);
      }
      batch.end();
      
      // process user input
      if(Gdx.input.isTouched()) {
         Vector3 touchPos = new Vector3();
         touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
         camera.unproject(touchPos);
         bucket.x = touchPos.x - 64 / 2;
      }
      if(Gdx.input.isKeyPressed(Keys.LEFT)) bucket.x -= 200 * Gdx.graphics.getDeltaTime();
      if(Gdx.input.isKeyPressed(Keys.RIGHT)) bucket.x += 200 * Gdx.graphics.getDeltaTime();
      
      // make sure the bucket stays within the screen bounds
      if(bucket.x < 0) bucket.x = 0;
      if(bucket.x > 800 - 64) bucket.x = 800 - 64;
      
      // check if we need to create a new raindrop
      if(TimeUtils.nanoTime() - lastDropTime > 1000000000) spawnRaindrop();
      
      // move the raindrops, remove any that are beneath the bottom edge of
      // the screen or that hit the bucket. In the later case we play back
      // a sound effect as well.
      Iterator<Rectangle> iter = raindrops.iterator();
      while(iter.hasNext()) {
         Rectangle raindrop = iter.next();
         raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
         if(raindrop.y + 64 < 0) iter.remove();
         if(raindrop.overlaps(bucket)) {
            dropSound.play();
            iter.remove();
         }
      }
   }
   
   @Override
   public void dispose() {
      // dispose of all the native resources
      dropImage.dispose();
      bucketImage.dispose();
      dropSound.dispose();
      rainMusic.dispose();
      batch.dispose();
   }

   @Override
   public void resize(int width, int height) {
   }

   @Override
   public void pause() {
   }

   @Override
   public void resume() {
   }
}

接下來怎麼走

這是一個非常基礎的例子,展示怎樣用Libgdx建立一個簡易的遊戲。一些東西還可以再改進,比如使用Pool類來迴圈使用Rectangles,我們在刪除雨滴時讓垃圾回收器都把它們回收了。如果一個批處理中給它許多不同圖片,OpenGL就不好用。在我們的例子中沒問題,因為我們只有兩個圖片。通常我們會把所有不同圖片放在單個Texture裡,也被稱作TextureAtlas。 

我強烈推薦你讀剩下的開發手冊,並檢出Git倉庫裡的demo和測試。程式設計快樂。