第23節 - LibGDX: Advanced Game Menu Screen (using Abstract Screen)
這一節我會介紹用一個AbstractScreen抽象類別重寫Game Menu Screen。
AbstractScreen Class
我在第22節 - LibGDX: Game Menu Screen介紹過Game Menu Screen的內容,大部分的Screen類別(例如:LevelScreen, OptiosScreen, CreditsScreen等等)都有很多相同的部份,我們可以建立一個抽象類別(Abstract Class)叫AbstractScreen,它可以把畫面相同部分放在AbstractScreen類別內,這樣做就可以簡化程式,如下圖:

- Start Screen, Options Screen, Credits Screen和Facebook Link都是Class,Meun Screen是Class,所以它們的關係是Association。因為畫面可以向前或向後,所以是Bi-directional(雙向)。
- 因為Start Screen, Options Screen, Credits Screen和Facebook Link都須要是Screen類別,它們須要實作Sceen介面。
甚麼是抽象類別(Abstract Class)?
當您定義類別時,可以僅宣告方法名稱而不實作當中的內容,這樣的方法稱之為抽象方法(Abstract Class),如果一個類別中包括了抽象方法,則該類別稱之為抽象類別(Abstract Class)。
Abstract class的特徵:
- Abstract Method只需要宣告, 不需要實作。
- 使用關鍵字 abstract 宣告的類別 (class) 並不能夠被實體化 (Instantiated) ,也就是說不能用 Abstract 的類別建立物件 (Object) ,但 abstract 類別可當成父類別 (Superclass) 給子類別 (Subclass) 繼承 (Inherit) 。
- 繼承Abstract Class的子類別必須實作父類別的Abstract Method, 否則這子類別還是個Abstract Class。
- Abstract Class除了可以包括抽象方法外,也可以包括普通的方法。
- 另外注意,Abstract Class也可以不包括抽象方法,只包括普通的方法。
沒有抽象方法的Abstract Class和一般普通類別有甚麼分別?
以上提及過,最主要是Abstract Class不能建立物件,但普通類別可以建立物件。如果你覺得一個類別只是供給Subclass使用,而不須要獨立使用,我們就可以使用Abstract Class(注意,我們將要設計的AbstractScreen抽象類別就是不須要獨立使用的好例子,它只供給LevelScreen, OptionsScreen, CreditsScreen和FacebookScreen使用)。
以下用Java程式舉例說明:
Java - 例子1
例子1是一個簡單的抽象類別。

- 建立一個抽象父類別(Parent),再在抽象父類別內建立一個抽象方法methodP1()。
- 建立一個子類別(Son),再在子類別內實作methodP1()方法。
- 執行結果。
Java - 例子2
例子2把抽象方法methodP1()刪除,就成為我曾經提及的沒有抽象方法的Abstract Class。

- 建立一個抽象父類別(Parent),再在抽象父類別內建立一個普通方法methodP1()。
- 建立一個子類別(Son),再在子類別內覆寫methodP1()方法和執行super.methodP1()方法。
- 執行結果。
Java - 例子3
例子3和例子2相似,Parent類別是一個普通類別,所以可以建立Parent物件(p)。

- 建立一個父類別(Parent),再在父類別內建立一個普通方法methodP1()。
- 建立一個子類別(Son),再在子類別內覆寫methodP1()方法和執行super.methodP1()方法。
- 執行結果。
以下例子就是嘗試在抽象父類別(Parent)內建立Parent物件(p),程式就會顯示錯誤:

- 建立一個抽象父類別(Parent),再在抽象父類別內建立一個普通方法methodP1()。
- 建立一個子類別(Son),再在子類別內覆寫methodP1()方法和執行super.methodP1()方法。
- 執行結果。
- 在抽象父類別(Parent)內建立Parent物件(p),程式就會顯示錯誤。
建立AbstractScreen抽象類別
原本的LevelScreen.java
首先,我們看看LevelScreen、OptionsScreen和CreditsScreen程式,它們都有相同的部分,我們就可以把相同的部分放在AbstractScreen.java內,下圖為LevelScreen程式,紅色地方為相同部分:

AbstractScreen.java
以下是AbstractScreen.java程式,我們把LevelScreen、OptionsScreen和CreditsScreen相同的部分放在AbstractScreen內:

- 注意,我加入了一個getSkin()方法,它用來把mainmenu.json和mainmenu.pack傅入,這樣做除了AbstractScreen內可以呼叫getSkin()外,在LevelScreen內就可以呼叫super.getSkin()。
新的LevelScreen.java
大家可以看到以下的LevelScreen.java程式就簡潔很多了:

- 把LevelScreen類別繼承AbstractScreen抽象類別。
- 在show()方法內除了呼叫super.show()外,再執行只有LevelScreen類別獨有的部分。
- 在render()方法內呼叫super.render(),把所有Actor顯示在屏幕上。
- 在hide()方法內呼叫super.hide(),則執行AbstractScreen抽象類別內的hide()方法。
注意,Screen介面的dispose()方法和Game類別的dispose()方法不同,根據LibGDX官方文件解釋,Screen介面的dispose()方法是不會自動執行,所以我們必須在畫面轉換時在hide()執行dispose()方法,我會在第24節 - LibGDX: Disposable Interface詳細介紹。 - 在dispose()方法內呼叫super.dispose(),則執行AbstractScreen抽象類別內的dispose()方法。
用AbstractScreen抽象類別重寫Game Menu Screen
以下詳細介紹用AbstractScreen抽象類別重寫Game Menu Screen的每一個類別程式:
DesktopLauncher.java

- DesktopLauncher是PC Desktop的Starter Class,我們在DesktopLauncher內設定顯示的大小為500x700px。
MyDemo23.java

- 在MyDemo23.java內把MyDemo23 extends Game。
在Game類別內,create()是未定義內容,所以在MyDemo23 class內必須定義內容。
定義create()方法,在create()方法內用setScreen()方法把畫面跳到splashScreen。
SplashScreen.java
我在第17節 - LibGDX: Splash Screen介紹過Splash Screen的製作,Splash Screen跳到Game Menu Screen,再按不同按鈕跳到不同的畫面。

- 用Texture產生一個Texture物件(例如:texture)然後把圖片(splashScreen.png)傳入。
用Image產生一個Image物件(例如:splashImage)然後把texture物件傳入。 - 在show()方法內,把image加入Stage舞台內,再用Stage內還有一個addAction()方法把開埸畫面做出逐漸呈現的效果。
1-Actions.alpha(0)-設定alpha=0,則把image設定為全透明。
2-Actions.fadein(0.5f)-把image做出逐漸呈現的效果。
3-Actions.delay(2)-把動作延遲2秒。
4-Actions.run()-把現時splashScreen畫面跳到menuScreen畫面。 - show()方法會設定所有動作,但尚未顯示在屏幕上,我們必須在render()方法內用輪入:
1-stage.act();-更新所有Actor。
2-stage.draw();-把所有Actor顯示在屏幕上。 - 注意,Screen介面的dispose()方法和Game類別的dispose()方法不同,根據LibGDX官方文件解釋,Screen介面的dispose()方法是不會自動執行,所以我們必須在畫面轉換時在hide()執行dispose()方法,我會在第24節 - LibGDX: Disposable Interface詳細介紹。
- 最後別忘記把在dispose()方法內輸入texture.dispose();和stage.dispose()。
MainScreen.java
我們會用第17節的MenuScreen.java檔案再加入"Button Click"事件處理。
- Button "Start"會把畫面跳到Level Screen。
- Button "Options"會把畫面跳到Options Screen。
- Button "Credits"會把畫面跳到Credits Screen。
- Button "Share Facebook"會把畫面跳到Facebook網頁。
以下列出MenuScreen.java的操作原理:

- 設定所有Member Variables。
- 建立Skin物件(skin),把mainmenu.json和mainmenu.pack傳入。
- 建立Image物件(bg),把skin和bg圖片傳入。
再建立Button物件(startbutton, optionsbutton, creditsbutton和facebookbutton),把skin和(startButton, optionsButton, creditsButton和facebookButton)圖片傳入。 - 設定它們的位置。
- 用介面元件的addListener()方法監聽"Click"事件。
注意,facebookbutton按鈕會把畫面跳到一個預設的網站,我用www.hkprogram.com作例子。 - 用介面元件的addActor()方法把所有Image物件和Button物件傳入stage內。
- 用setInputProcessor()方法把stage傳入,再設定touch和key input事件。
- show()方法會設定所有動作,但尚未顯示在屏幕上,我們必須在render()方法內用輪入:
1-stage.act();-更新所有Actor。
2-stage.draw();-把所有Actor顯示在屏幕上。 - 注意,Screen介面的dispose()方法和Game類別的dispose()方法不同,根據LibGDX官方文件解釋,Screen介面的dispose()方法是不會自動執行,所以我們必須在畫面轉換時在hide()執行dispose()方法,我會在第24節 - LibGDX: Disposable Interface詳細介紹。
- 最後別忘記把在dispose()方法內輸入skin.dispose();和stage.dispose()。
AbstractScreen.java
以下是AbstractScreen.java程式:

- 把AbstractScreen抽象類別實作Screen介面。
- 設定所有Member Variables。
- 建立一個getSkin()方法,它用來把mainmenu.json和mainmenu.pack傅入,這樣做除了AbstractScreen內可以呼叫getSkin()外,在LevelScreen、OptionsScreen和CreditsScreen類別內就可以呼叫super.getSkin()。
- 建立MenuScreen物件(menuScreen)。
- 建立Button物件(backButton),把getSkin()和backbutton圖片傳入。
- 設定它們的位置和用介面元件的addListener()方法監聽"Click"事件。
- 用setInputProcessor()方法把stage傳入,再設定touch和key input事件。
- show()方法會設定所有動作,但尚未顯示在屏幕上,我們必須在render()方法內用輪入:
1-stage.act();-更新所有Actor。
2-stage.draw();-把所有Actor顯示在屏幕上。 - 注意,Screen介面的dispose()方法和Game類別的dispose()方法不同,根據LibGDX官方文件解釋,Screen介面的dispose()方法是不會自動執行,所以我們必須在畫面轉換時在hide()執行dispose()方法,我會在第24節 - LibGDX: Disposable Interface詳細介紹。
- 最後別忘記把在dispose()方法內輸入skin.dispose();和stage.dispose()。
LevelScreen.java
以下是LevelScreen.java程式,把LevelScreen類別繼承AbstractScreen抽象類別:

- 把LevelScreen類別繼承AbstractScreen抽象類別。
- 在show()方法內除了呼叫super.show()外,再執行只有LevelScreen類別獨有的部分。
- 在render()方法內呼叫super.render(),把所有Actor顯示在屏幕上。
- 在hide()方法內呼叫super.hide(),則執行AbstractScreen抽象類別內的hide()方法。
注意,Screen介面的dispose()方法和Game類別的dispose()方法不同,根據LibGDX官方文件解釋,Screen介面的dispose()方法是不會自動執行,所以我們必須在畫面轉換時在hide()執行dispose()方法,我會在第24節 - LibGDX: Disposable Interface詳細介紹。 - 在dispose()方法內呼叫super.dispose(),則執行AbstractScreen抽象類別內的dispose()方法。
OptionsScreen.java
以下是OptionsScreen.java程式,把OptionsScreen類別繼承AbstractScreen抽象類別:

- 把OptionsScreen類別繼承AbstractScreen抽象類別。
- 在show()方法內除了呼叫super.show()外,再執行只有LevelScreen類別獨有的部分。
- 在render()方法內呼叫super.render(),把所有Actor顯示在屏幕上。
- 在hide()方法內呼叫super.hide(),則執行AbstractScreen抽象類別內的hide()方法。
注意,Screen介面的dispose()方法和Game類別的dispose()方法不同,根據LibGDX官方文件解釋,Screen介面的dispose()方法是不會自動執行,所以我們必須在畫面轉換時在hide()執行dispose()方法,我會在第24節 - LibGDX: Disposable Interface詳細介紹。 - 在dispose()方法內呼叫super.dispose(),則執行AbstractScreen抽象類別內的dispose()方法。
CrdeditsScreen.java
以下是CreditsScreen.java程式,把CreditsScreen類別繼承AbstractScreen抽象類別:

- 把CreditsScreen類別繼承AbstractScreen抽象類別。
- 在show()方法內除了呼叫super.show()外,再執行只有LevelScreen類別獨有的部分。
- 在render()方法內呼叫super.render(),把所有Actor顯示在屏幕上。
- 在hide()方法內呼叫super.hide(),則執行AbstractScreen抽象類別內的hide()方法。
注意,Screen介面的dispose()方法和Game類別的dispose()方法不同,根據LibGDX官方文件解釋,Screen介面的dispose()方法是不會自動執行,所以我們必須在畫面轉換時在hide()執行dispose()方法,我會在第24節 - LibGDX: Disposable Interface詳細介紹。 - 在dispose()方法內呼叫super.dispose(),則執行AbstractScreen抽象類別內的dispose()方法。
FacebookScreen.java
以下是FacebookScreen.java程式,把FacebookScreen類別繼承AbstractScreen抽象類別。
注意,FacebookScreen類別內其實與LevelScreen,OptionsScreen和CreditsScreen類別沒有相同的地方,但為了把所有Screen類別系一 說明,所以才把FacebookScreen類別也繼承AbstractScreen抽象類別。

- 把FacebookScreen類別繼承AbstractScreen抽象類別。
- 在render()方法內,用batch.draw(),把texture傳入和設定顯示位置,最後顯示在屏幕上。
- 注意,Screen介面的dispose()方法和Game類別的dispose()方法不同,根據LibGDX官方文件解釋,Screen介面的dispose()方法是不會自動執行,所以我們必須在畫面轉換時在hide()執行dispose()方法,我會在第24節 - LibGDX: Disposable Interface詳細介紹。
- 最後別忘記把在dispose()方法內輸入texture.dispose();和batch.dispose()。
執行結果:
以下是LibGDX-Advanced Game Menu Screen (Using Abstract Screen)程式執行的結果: