第42節 - LibGDX: WorldController & WorldRenderer Class
這一節我會介紹LibGDX的兩個自定類別 - WorldController & WorldRenderer,這兩個類別的主要功能是把程式碼分類,一個好的程式結構,會讓程式有更好的閱讀和維護,也可減少除錯的時間。
WorldController類別 - 這個類別用來把所有有關"更新時間",則"deltaTime"有關的程式碼放在這裡。
WorldRender類別 - 這個類別用來把其他程式碼,例如: create()方法內所產生的物件和render()方法內須要顯示在屏幕上的有關的程式碼放在這裡。
以下會用不同的例子詳細說明:
例子1 - Not Using WorldController & WorldRenderer Class
這個例子是第33節的例子2,這個例子很簡單,沒有用上WorldController和WorldRenderer類別。
以下是Class Diagram:
- 把ApplicationAdapter抽象類別實作ApplicationListener介面。
- 把MyDemo42類別繼承ApplicationAdapter抽象類別,所有程式碼,包括以上提及的deltaTime,create()方法內所產生的物件和render()方法內須要顯示在屏幕上的有關的程式碼放在MyDemo42類別內。
LibGDX程式
DesktopLauncher.java
- DesktopLauncher是PC Desktop的Starter Class,我們在DesktopLauncher內設定顯示的大小為1024 X 768 px。
- 把bullet.png、hero1Atlas.pack和hero1Atlas.png儲存到Android的Assets文件夾內(/data/...)。
把map.tmx和tileAtlas.png儲存到Android的Assets文件夾內(/map/...)。
把bgsound1.mp3和gun1.mp3儲存到Android的Assets文件夾內(/sound/...)。
MyDemo42.java
- 建立一個speed變數(float type),並設定值為150。
- 建立一個xPosition變數(float type)。
- 建立一個deltaTime變數(float type)。
- 設定deltaTime初始值為0.0f。
- 設定xPosition的初始值為-(1920/2)。
- 用Gdx.graphics.getDeltaTime()方法敢得 兩格(two frames)動畫之間的時間,再傳入deltaTime變數內。
- 設定sprite位置的初始值。
- 根據最新deltaTime的值,更新xPosition位置。
執行程式:
例子2 - WorldController & WorldRenderer Class
例子2會把例子1加上WorldController和WorldRenderer類別。
以下是Class Diagram:
- 把ApplicationAdapter抽象類別實作ApplicationListener介面。
- 建立WorldController類別,把有關deltaTime的程式碼放在WorldController類別內,再把WorldController物件(worldController)傅入DyDemo42_1類別內,這樣做,就可以在DyDemo42_1類別內執行worldController的方法。
- 建立WorldRenderer類別,把WorldController物件(worldController)傅入,再把WorldRenderer物件(worldRenderer)傅入DyDemo42_1類別內,這樣做,就可以在DyDemo42_1類別內執行worldRenderer的方法。
注意,所有程式碼,包括以上提及create()方法內所產生的物件和render()方法內須要顯示在屏幕上的有關的程式碼放在WorldRenderer類別內。
LibGDX程式
DesktopLauncher.java
- DesktopLauncher是PC Desktop的Starter Class,我們在DesktopLauncher內設定顯示的大小為1024 X 768 px。
- 把bullet.png、hero1Atlas.pack和hero1Atlas.png儲存到Android的Assets文件夾內(/data/...)。
把map.tmx和tileAtlas.png儲存到Android的Assets文件夾內(/map/...)。
把bgsound1.mp3和gun1.mp3儲存到Android的Assets文件夾內(/sound/...)。
MyDemo42_1.java
- 首先建立兩個private的物件變數worldRenderer和worldController。
- 在create()方法內,建立worldRenderer和worldController物件。
- 在render()方法內,執行worldController.update()方法和worldRenderer()方法。
- 別忘記呼叫worldRenderer.dispose()方法,釋放資源。
WorldRenderer.java
- 首先建立一個private的物件變數worldController。
- 建立WorldRenderer類別的Constructor,把worldController傳入。
- 現在就可以把animationTime更改為worldController.getAnimationTime()。
- 把deltaTime更改為worldController.getDeltaTime()。
WorldController.java
- 首先建立兩個private的物件變數deltaTime和animationTime。
- 建立WorldController類別的Constructor,設定deltaTime和animationTime的初始值為0.0f。
- 建立一個新的update()方法,讀取deltaTime的數值,並設定deltaTime和animationTime的更新值。
- 建立deltaTime和animationTime的getter和setter,getter和setter可以用來傳入和讀取WorldController類別內的變數值。
注意,我會在其他章節詳細介紹getter和setter。
執行程式:
注意,程式執行結果與例子1完全相同。
例子3 - Use WorldController & WorldRenderer Class in Section 41
例子3會把第41節的2D platform Game加上WorldController和WorldRenderer類別。
以下是Class Diagram:
- 把第41節的2D platform Game加入WorldRenderer類別和WorldController類別。
- 把WorldRenderer類別實作Disposable介面,這樣做,我們就必須定義dispose()方法。
- 把WorldRenderer類別實作InputProcessor介面,這樣做,我們就可以定義所有關於Input的方法。
- 把Bullet、 BackgroundSound和GunSound的物件傳入WorldRenderer類別內。
LibGDX程式
DesktopLauncher.java
- DesktopLauncher是PC Desktop的Starter Class,我們在DesktopLauncher內設定顯示的大小為1024 X 768 px。
- 把bullet.png、hero1Atlas.pack和hero1Atlas.png儲存到Android的Assets文件夾內(/data/...)。
把map.tmx和tileAtlas.png儲存到Android的Assets文件夾內(/map/...)。
把bgsound1.mp3和gun1.mp3儲存到Android的Assets文件夾內(/sound/...)。
MyDemo42_2.java
- 首先建立兩個private的物件變數worldRenderer和worldController。
- 在create()方法內,建立worldRenderer和worldController物件。
- 在render()方法內,執行worldController.update()方法和worldRenderer()方法。
- 別忘記呼叫worldRenderer.dispose()方法,釋放資源。
WorldRenderer.java
- 首先WorldRenderer類別,並把WorldRenderer類別實作Disposable和InputProcessor介面。
- 建立一個private的物件變數worldController。
- 建立WorldRenderer類別的Constructor,把worldController傳入。
- 把animationTime更改為worldController.getAnimationTime()和把deltaTime更改為worldController.getDeltaTime()。
- 別忘記呼叫dispose()方法,釋放資源。
WorldController.java
- 首先建立兩個private的物件變數deltaTime和animationTime。
- 建立WorldController類別的Constructor,設定deltaTime和animationTime的初始值為0.0f。
- 建立一個新的update()方法,讀取deltaTime的數值,並設定deltaTime和animationTime的更新值。
- 建立deltaTime和animationTime的getter和setter,getter和setter可以用來傳入和讀取WorldController類別內的變數值。
注意,我會在其他章節詳細介紹getter和setter。
Bullet.java
- 首先在Bullet類別的Constructor內,把bulletPosition和bulletVelocityX傳入。
bulletPosition就是主角hero1的子彈發射位置。 - 建立一個update()方法,把子彈的位置加上bulletVelocityX傳入的數值。
BackgroundSound.java
- 首先在BackgroundSound類別的Constructor內,把bgsound1.mp3傳入Sound物件(sound)內。
- 建立一個playSound()方法,用sound.loop()方法重複播放背景音樂,音量是0至1,0.5f代表設定音量為一半。
GunSound.java
- 首先在GunSound類別的Constructor內,把gun1.mp3傳入Sound物件(sound)內。
- 建立一個playSound()方法,用sound.play()方法播放發射子彈音效,音量是0至1,0.5f代表設定音量為一半。
注意,發射子彈音效不須要重複播放,所以我們不用sound.loop()方法。
執行程式:
例子4 - Use Game Class
例子4會把MyDemo42_3類別繼承Game抽象類別,這是因為Game類別內有一個setScreen()方法,它可以用來轉換畫面,例如在第16節 - LibGDX: Splash Screen用來做過埸畫面。
以下是Class Diagram:
- 把MyDemo42_3類別繼承Game抽象類別。
- 建立一個新的LevelOneScreen類別,並實作Screen介面,再把LevelOneScreen物件(levelOneScreen)傳入到MyDemo42_3,它可以用來轉換畫面。
- 把WorldRenderer類別實作Disposable介面,這樣做,我們就必須定義dispose()方法。
- 把WorldRenderer類別實作InputProcessor介面,這樣做,我們就可以定義所有關於Input的方法。
- 把Bullet、 BackgroundSound和GunSound的物件傳入WorldRenderer類別內。
LibGDX程式
DesktopLauncher.java
- DesktopLauncher是PC Desktop的Starter Class,我們在DesktopLauncher內設定顯示的大小為1024 X 768 px。
- 把甩bullet.png、hero1Atlas.pack和hero1Atlas.png儲存到Android的Assets文件夾內(/data/...)。
把map.tmx和tileAtlas.png儲存到Android的Assets文件夾內(/map/...)。
把bgsound1.mp3和gun1.mp3儲存到Android的Assets文件夾內(/sound/...)。
MyDemo42_3.java
- 首先把MyDemo42_3繼承Game抽象類別。
- 建立一個LevelOneScreen物件(levelOneScreen)傳入到setScreen()方法內,它可以用來轉換畫面。
LevelOneScreen.java
- 首先建立三個private的物件變數game、worldRenderer和worldController。
- 建立LevelOneScreen類別的Constructor,把game傳入。
- 在show()方法內,建立worldRenderer和worldController物件。
- 在render()方法內,執行worldController.update()方法和worldRenderer()方法。
- 注意,我在第24節 - LibGDX: Disposable Interface介紹過,Screen介面的dispose()方法和Game類別的dispose()方法不同,根據LibGDX官方文件解釋,Screen介面的dispose()方法是不會自動執行,所以我們必須在畫面轉換時在hide()執行dispose()方法。
WorldRenderer.java
- 首先WorldRenderer類別,並把WorldRenderer類別實作Disposable和InputProcessor介面。
- 建立一個private的物件變數worldController。
- 建立WorldRenderer類別的Constructor,把worldController傳入。
- 把animationTime更改為worldController.getAnimationTime()和把deltaTime更改為worldController.getDeltaTime()。
- 別忘記呼叫dispose()方法,釋放資源。
WorldController.java
- 首先建立兩個private的物件變數deltaTime和animationTime。
- 建立WorldController類別的Constructor,設定deltaTime和animationTime的初始值為0.0f。
- 建立一個新的update()方法,讀取deltaTime的數值,並設定deltaTime和animationTime的更新值。
- 建立deltaTime和animationTime的getter和setter,getter和setter可以用來傳入和讀取WorldController類別內的變數值。
注意,我會在其他章節詳細介紹getter和setter。
Bullet.java
- 首先在Bullet類別的Constructor內,把bulletPosition和bulletVelocityX傳入。
bulletPosition就是主角hero1的子彈發射位置。 - 建立一個update()方法,把子彈的位置加上bulletVelocityX傳入的數值。
BackgroundSound.java
- 首先在BackgroundSound類別的Constructor內,把bgsound1.mp3傳入Sound物件(sound)內。
- 建立一個playSound()方法,用sound.loop()方法重複播放背景音樂,音量是0至1,0.5f代表設定音量為一半。
GunSound.java
- 首先在GunSound類別的Constructor內,把gun1.mp3傳入Sound物件(sound)內。
- 建立一個playSound()方法,用sound.play()方法播放發射子彈音效,音量是0至1,0.5f代表設定音量為一半。
注意,發射子彈音效不須要重複播放,所以我們不用sound.loop()方法。
執行程式:
注意,程式執行結果與例子3完全相同。
為甚麼LevelOneScreen要實作Screen介面?
很簡單,因為這樣做我們就可以把我們的2D Platform Game連接到第26節 - LibGDX: Advanced Game Menu Screen (using AssetManager)上,如下圖:
以下是Class Diagram:
- 第26節 - LibGDX: Advanced Game Menu Screen (using AssetManager)。
- 這一節,第42節 - LibGDX: WorldController & WorldRenderer Class。
以下是第42節 - LibGDX: WorldController & WorldRenderer Class程式執行的結果:
注意,程式執行結果與第41節完全相同。