【Flame 學習筆記】系列文章目錄:連結
【Flutter 學習筆記】線上課程教學影片:連結
【Flutter 學習筆記】系列文章目錄:連結
在這篇文章中,我們將深入探討 Flutter 的 Flame 遊戲引擎中的 FlameGame
類別,並了解其如何實現一個基於元件的遊戲架構。這個架構稱為 Flame 元件系統(Flame Component System,簡稱 FCS),它透過一棵元件樹來管理遊戲中的所有元件,並負責調用這些元件的更新和渲染方法。
FlameGame
類別是 Flame 引擎的核心,允許開發者透過構造函數中的 children
參數直接添加元件,或使用 add
和 addAll
方法在其他地方添加元件。通常,我們會將元件添加到一個名為 World
的預設世界中,這個世界可以透過 FlameGame.world
訪問,並且添加元件的方式與其他元件相同。
以下是一個簡單的 FlameGame
實作範例,展示如何在 onLoad
方法中和構造函數中添加兩個元件:
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/widgets.dart';
/// 一個渲染箱子精靈的元件,大小為 16 x 16。
class MyCrate extends SpriteComponent {
MyCrate() : super(size: Vector2.all(16));
@override
Future<void> onLoad() async {
sprite = await Sprite.load('crate.png');
}
}
class MyWorld extends World {
@override
Future<void> onLoad() async {
await add(MyCrate());
}
}
void main() {
final myGame = FlameGame(world: MyWorld());
runApp(
GameWidget(game: myGame),
);
}
如果你在 build
方法中實例化遊戲,則每當 Flutter 樹重建時,遊戲也會被重建,這通常會比你預期的更頻繁。為了避免這種情況,你可以先創建遊戲的實例,然後在小部件結構中引用它,或者使用 GameWidget.controlled
構造函數。
GameLoop
模組是遊戲循環概念的一個簡單抽象。大多數遊戲都基於兩個方法:
render
方法用於繪製當前遊戲狀態的畫布。update
方法接收自上次更新以來的微秒數,並允許你移動到下一個狀態。GameLoop
被所有 Flame 的遊戲實作所使用。
每當遊戲需要調整大小時,例如當方向改變時,FlameGame
會調用所有元件的 onGameResize
方法,並將此信息傳遞給相機和視口。FlameGame.camera
控制應該在視口中心的坐標點,預設為 [0,0](Anchor.center)。
FlameGame
的生命週期回調(如 onLoad
、render
等)按以下順序被調用:
onGameResize
onLoad
onMount
update
render
onRemove
當 FlameGame
首次添加到 GameWidget
時,生命週期方法 onGameResize
、onLoad
和 onMount
將按此順序被調用。然後,update
和 render
將在每個遊戲 Tick 中按序列被調用。如果 FlameGame
從 GameWidget
中移除,則會調用 onRemove
。如果 FlameGame
被添加到新的 GameWidget
,則序列將從 onGameResize
重複。
onGameResize
和 onLoad
的調用順序與其他元件相反,這是為了在資源加載或生成之前計算遊戲元素的大小。
onRemove
回調可用於清理子元件和緩存數據:
@override
void onRemove() {
// 根據遊戲需求選擇性執行。
removeAll(children);
processLifecycleEvents();
Flame.images.clearCache();
Flame.assets.clearCache();
// 在遊戲被移除時執行的其他代碼。
}
在 FlameGame
中,子元件和資源的清理並不會自動進行,必須明確添加到 onRemove
回調中。
Flame 的 FlameGame
類別提供了一個名為 debugMode
的變數,預設為 false
。不過,你可以將其設置為 true
以啟用遊戲元件的除錯功能。請注意,這個變數的值會在元件添加到遊戲時傳遞,因此如果你在運行時更改 debugMode
,則不會影響已添加的元件。
有關 debugMode
的更多資訊,請參考 Flame 的除錯文檔。
要更改 FlameGame
的背景顏色,你需要重寫 backgroundColor()
方法。在以下範例中,背景顏色被設置為完全透明,以便可以看到 GameWidget
背後的元件。預設背景顏色為不透明的黑色。
class MyGame extends FlameGame {
@override
Color backgroundColor() => const Color(0x00000000);
}
請注意,背景顏色在遊戲運行時無法動態更改,但如果你希望它動態變化,可以繪製一個覆蓋整個畫布的背景。
如果你正在開發一個單一遊戲應用程式,可以選擇性地將 SingleGameInstance
混入應用於你的遊戲。這在構建遊戲時是一個常見的場景:有一個全螢幕的 GameWidget
,其中只包含一個遊戲實例。
添加這個混入可以在某些情況下提供性能優勢。特別是,元件的 onLoad
方法在元件添加到其父元件時保證會開始執行,即使父元件尚未掛載。因此,對 parent.add(component)
的 await
將始終保證完成元件的加載。
使用這個混入非常簡單:
class MyGame extends FlameGame with SingleGameInstance {
// ...
}
Game
類別是一個低階 API,當你想要實現遊戲引擎的結構功能時可以使用。Game
類別不實現任何更新或渲染功能。
這個類別還包含生命週期方法 onLoad
、onMount
和 onRemove
,這些方法在遊戲被加載、掛載或移除時由 GameWidget
(或其他父元件)調用。onLoad
只在類別第一次添加到父元件時被調用,而 onMount
(在 onLoad
之後被調用)則在每次添加到新父元件時被調用。onRemove
在類別從父元件移除時被調用。
使用 Game
類別可以提供更大的實現自由度,但如果使用它,你將錯過 Flame 中的所有內建功能。
以下是一個 Game
實作的範例:
class MyGameSubClass extends Game {
@override
void render(Canvas canvas) {
// ...
}
@override
void update(double dt) {
// ...
}
}
void main() {
final myGame = MyGameSubClass();
runApp(
GameWidget(
game: myGame,
)
);
}
Flame 遊戲可以通過以下兩種方式暫停和恢復:
pauseEngine
和 resumeEngine
方法。paused
屬性。當遊戲被暫停時,遊戲循環將被有效地暫停,這意味著在恢復之前不會進行任何更新或新的渲染。在遊戲暫停期間,可以使用 stepEngine
方法逐幀推進遊戲執行。這在最終遊戲中可能不太有用,但在開發過程中逐步檢查遊戲狀態時非常有幫助。
當應用程式被送到背景時,遊戲將自動暫停,並在應用程式回到前景時恢復。這種行為可以通過將 pauseWhenBackgrounded
設置為 false
來禁用。
class MyGame extends FlameGame {
MyGame() {
pauseWhenBackgrounded = false;
}
}
在當前的 Flutter 穩定版本(3.13)中,這個標誌在非移動平台(包括網頁)上實際上會被忽略。
在優化遊戲時,追蹤每幀更新和渲染所需的時間可能會很有用。這些數據可以幫助檢測代碼中運行較快的區域,並幫助識別渲染時間較長的視覺區域。
要獲取更新和渲染時間,只需將 HasPerformanceTracker
混入添加到遊戲類別中。
class MyGame extends FlameGame with HasPerformanceTracker {
// 可以訪問 `updateTime` 和 `renderTime` 屬性。
}
在這篇文章中,我們深入探討了 Flame 遊戲引擎中的 FlameGame
類別及其元件系統。透過這個系統,開發者可以輕鬆地管理遊戲中的元件,並利用遊戲循環、生命週期回調、除錯模式等功能來構建高效的遊戲應用。