來學學如何使用 Flame 開發一個小遊戲吧

更新於 2024/12/18閱讀時間約 13 分鐘


raw-image

來到了 2023 年,今年就來學點不同的東西。本身興趣之一就是喜歡打電動,所以打從學寫程式以來,一直都有想自己寫遊戲的想法,但是一直都懶得去實現。自從學了 Flutter 之後,隱隱覺得 Flutter 的渲染模式其實跟遊戲十分相似,也從許多文章或影片中得知 Flame 這個基於 Flutter 的遊戲引擎,今天就來學學如何製作一個小遊戲吧。

引入 Flame

當然第一步最重要的就是,把 flame 套件引入到我們的專案中

flutter pub add flame

當我們執行下面命令後,我們就能在 pubspec.yml 中發現多了 flame

dependencies:
flutter:
sdk: flutter
flame: ^1.5.0

使用 GameWidget

開始使用 Flame 之後,第一個面對的 Widget 是 GameWidget,如同我們在 Flutter 中使用的各式各樣的 Widget,這個 GameWidget 也能直接被放在我們的 Flutter 程式中。GameWidget 有一個必要的參數:Game,可以想像成是整個遊戲最外層的 Component,有點類似於 Flutter 的 MaterialApp

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: GameWidget(
game: MyGame(),
),
);
}
}

class MyGame extends FlameGame {}

當我們執行上面這段程式碼後,會發現程式雖然可以執行,畫面卻是一片空白。這也是正常的,來為畫面加上一些東西吧。

class MyGame extends FlameGame {
@override
Future<void> onLoad() async {
camera.zoom = 4;
await add(Knight(
position: size / 2,
anchor: Anchor.center,
size: Vector2(120, 80),e
));
}
}

class Knight extends PositionComponent {
Knight({super.position, super.size, super.anchor});

@override
Future<void> onLoad() async {
var animation = await SpriteAnimation.load(
"knight_idle.png",
SpriteAnimationData.sequenced(
amount: 10,
stepTime: 0.1,
textureSize: Vector2(120, 80),
),
);
await add(SpriteAnimationComponent(
animation: animation,
size: size,
));
}
}

在上面我們中在 onLoad 中加入一個騎士的待機圖,當執行之後就會在畫面上看到騎士待機站立的遊戲畫面。

raw-image

為騎士加上不同動作

如果只有看著騎士在畫面上站著不動,這也不能算是個遊戲吧。為了讓他有點遊戲的感覺,我們可以加上一個行為:當點擊畫面,騎士就進行攻擊。

@override
Future<void> onLoad() async {
var idleAnimation = await SpriteAnimation.load(
"knight_idle.png",
SpriteAnimationData.sequenced(
amount: 10,
stepTime: 0.1,
textureSize: Vector2(120, 80),
),
);

var attackAnimation = await SpriteAnimation.load(
"knight_attacking.png",
SpriteAnimationData.sequenced(
amount: 10,
stepTime: 0.1,
textureSize: Vector2(120, 80),
loop: false,
),
);

await add(_animations = SpriteAnimationGroupComponent<KnightBehavior>(
current: KnightBehavior.idle,
size: size,
animations: {
KnightBehavior.idle: idleAnimation,
KnightBehavior.attack: attackAnimation
},
));
}

為了讓騎士有更多動作,首先我們需要先修改一下動畫,使用 SpriteAnimationGroupComponent 針對不同行為,使用不同的動畫。透過設定 SpriteAnimationGroupComponent 的 current 變數,來決定當下要播放靜止站立的動畫,或攻擊的動畫。

raw-image

新增點擊操作

當我們的騎士可以支援多種不同的動作之後,下一步就是指定觸發條件了,讓程式知道什麼時候要播放靜止站立的動畫,什麼時候要播放攻擊動畫。當使用者點擊畫面時,騎士就播放攻擊動畫。為此,我們需要修改幾個地方,以收取點擊事件。

  • MyGame 加上 HasTappableComponents
class MyGame extends FlameGame with HasTappableComponents {
}
  • Knight 加上 TapCallbacks 並覆寫 onTapUpcontainsLocalPoint
class Knight extends PositionComponent with TapCallbacks {

@override
Future<void> onLoad() async {}

@override
bool containsLocalPoint(Vector2 point) => true;

@override
void onTapUp(TapUpEvent event) {
_animations.current = KnightBehavior.attack;
}
}

我們可以把點擊之後的行為放在 onTapUp 的方法中,指定 SpriteAnimationGroupComponent 的 current 變數為 KnightBehavior.attack,騎士就會在點擊之後,進入攻擊狀態。

更進一步

在我們新增點擊操作之後,我們可以紀錄騎士進行了幾次攻擊,並顯示在畫面上,變成一個簡單的點擊計數小遊戲,就像 Flutter 專案預設的 App 一樣。

首先,我們在 MyGame 中加上 count 變數紀錄次數

class MyGame extends FlameGame with HasTappableComponents {
int count = 0;
}

然後在 Knight 加上 HasGameRef<MyGame> ,讓我們可以修改 MyGame 中的 counter

class Knight extends PositionComponent with TapCallbacks, HasGameRef<MyGame> {

@override
void onTapUp(TapUpEvent event) {
if (_animations.current == KnightBehavior.idle) {
_animations.current = KnightBehavior.attack;
gameRef.count++;
}
}

}

最後在 MyGame 的 onLoad 中加上一個 Counter 用來顯示次數。Counter 類似於 Knight 也是一個 Component,在 Counter 的 onLoad 中加上一個 TextComponent 顯示文字,並在複寫 update 方法,Flame 會在遊戲進行時,持續呼叫每個 Component 的 update 方法,讓我們可以更新 Component 的畫面,我們可以透過 update 方法持續更新最新計數了。

class MyGame extends FlameGame with HasTappableComponents {
int count = 0;

@override
Future<void> onLoad() async {
camera.zoom = 4;
await add(Knight(
position: size / 2,
anchor: Anchor.center,
size: Vector2(120, 80),
));
await add(Counter(
position: size / 2..sub(Vector2(0, 20)),
anchor: Anchor.center,
));
}
}

class Counter extends PositionComponent with HasGameRef<MyGame> {
Counter({super.position, super.size, super.anchor});

late TextComponent _text;

@override
Future<void> onLoad() async {
await add(_text = TextComponent(
anchor: anchor,
textRenderer: TextPaint(
style: const TextStyle(color: Colors.white, fontSize: 10),
),
));
}

@override
void update(double dt) {
_text.text = "Knight has attacked ${gameRef.count} times";
}
}

raw-image

結論

Flame 是基於 Flutter 框架之上的遊戲引擎,其中我們會碰到許多各式各樣的 Component,透過組合不同的 Component 完成各式各樣的遊戲效果,感覺有點像是在 Flutter 中組合各種不同的 Widget 來製作各式各樣的畫面。為了完成這個點擊小遊戲,我們使用了許多 Component,例如:FlameGameSpriteAnimationGroupComponentTextComponent …等等,文章中的程式放在這邊,有興趣的朋友也可以參考看看。

分享各種軟體開發技巧與心得
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
本文分享瞭如何使用 Notion Web Clipper 來儲存文章,並結合 Habit Stacking 技術克服維持新習慣的困難。同時探討如何使用 Flutter Web 和 Notion API 開發自用的 Chrome 擴展功能,提升個人資訊管理和閱讀效率。
Flutter Widget 能加速開發,但誤用 MediaQuery 可能導致不預期的重建。範例中,頁面因鍵盤觸發高度變動而刷新,隨機數重新生成。使用固定比例設計避免重建,顯示深入理解框架對穩定性的重要性。
Flutter 習慣在最頂層的 MaterialApp 或 CupertinoApp 中統一定義整個 app 的路由管理。當我們把所有頁面的路由管理都放在最頂層時,就會讓它變得很長,不容易維護。或許應該適時思考,是否某些頁面的路由不應該被管理在最頂層。
這篇文章說明在 Flutter App 中整合 Google Play 內購功能的流程。主要包含兩個部分:先在 Google Play Console 設定商品,然後使用 Flutter 的 in_app_purchase 套件實作購買功能。開發時需注意連線狀態、商品列表獲取,以及購買流程的實作。
本文分享瞭如何使用 Notion Web Clipper 來儲存文章,並結合 Habit Stacking 技術克服維持新習慣的困難。同時探討如何使用 Flutter Web 和 Notion API 開發自用的 Chrome 擴展功能,提升個人資訊管理和閱讀效率。
Flutter Widget 能加速開發,但誤用 MediaQuery 可能導致不預期的重建。範例中,頁面因鍵盤觸發高度變動而刷新,隨機數重新生成。使用固定比例設計避免重建,顯示深入理解框架對穩定性的重要性。
Flutter 習慣在最頂層的 MaterialApp 或 CupertinoApp 中統一定義整個 app 的路由管理。當我們把所有頁面的路由管理都放在最頂層時,就會讓它變得很長,不容易維護。或許應該適時思考,是否某些頁面的路由不應該被管理在最頂層。
這篇文章說明在 Flutter App 中整合 Google Play 內購功能的流程。主要包含兩個部分:先在 Google Play Console 設定商品,然後使用 Flutter 的 in_app_purchase 套件實作購買功能。開發時需注意連線狀態、商品列表獲取,以及購買流程的實作。
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
https://www.youtube.com/watch?v=0L2OgNQDzTc 之前看了啾啾鞋這部影片,裡面說退坑一款手遊,也說到一些觀念,就如下圖這樣
遊戲是單純的RPG類型小遊戲 都是一個人做的非常厲害 遊戲共三章應該不太會再有後續了 每段大約40分內可完成 優點:音樂好聽,畫風出色,主角配角的人物刻劃優秀,劇情很戳我的心,CG多,難度親民,有中文 缺點:提示少分支多,完整體驗找找攻略可能會有比較好,缺乏超展開,有懸疑要素但不多,戰鬥難度簡單
Thumbnail
介紹一個優質的遊戲知識部落格:遊戲設計中藥鋪,其中「Game Design 資源分享表」十分推薦遊戲開發者閱讀。另外提到Gamker攻壳是一個專業的遊戲評鑑頻道,其深入的評論幫助作者入坑《健身環大冒險》。作者在後記也分享了自己在遊戲開發上的經歷和挑戰。
Thumbnail
一個獨立開發者,立志打造顛覆性的遊戲體驗,在開拓全新玩法的道路上,砥礪前行。 大家好,我正在開發一款獨立遊戲,以魔法戰鬥為主題,如果想要嘗試遊戲的demo,歡迎到下方連結處,加入我的Discord群組。 魔法與戰鬥的世界---開發中的遊戲   這款遊戲發生在一個飄浮於雲端之上的世界,
Thumbnail
這篇內容,簡單介紹了GameMaker的遊戲製作原理。包括Object、參數、程式碼等概念。同時也簡單介紹了GameMaker的適用範圍和特色。
Thumbnail
Instrumect 是我從2022年10月開始製作的遊戲。 在那之前,其實嘗試過各種大大小小的遊戲開發,包括RPG、橫向射擊、2D平台遊戲等,但最後都熱情耗盡,所有項目皆斷尾(ゝ∀・)⌒☆ 當初會選擇開發那些類型,不外乎考慮市場熱門程度和製作難易度。而最後會沒了熱情,是因為我對那類遊戲興趣沒很
Thumbnail
學完基礎的Lua語法後,今天開始製作第一個遊戲畫面了!
Thumbnail
一、了解思維 二、大量體驗 三、拆解架構 四、找出樂趣
Thumbnail
算了算我的存圖,嗯,還可以寫三天,所以今天來寫一下遊戲的,遊戲的存圖我也掙蠻多張的,哈哈,不怕沒東西寫了。 「煙硝絮語」封測的時候我試玩過~之前終於公測了,十二月的時候,我拖到現在才寫😏
Thumbnail
這篇文章將會延續(上)、(中)的內容,談談遊戲開發測試原型的製作與驗證。
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Thumbnail
https://www.youtube.com/watch?v=0L2OgNQDzTc 之前看了啾啾鞋這部影片,裡面說退坑一款手遊,也說到一些觀念,就如下圖這樣
遊戲是單純的RPG類型小遊戲 都是一個人做的非常厲害 遊戲共三章應該不太會再有後續了 每段大約40分內可完成 優點:音樂好聽,畫風出色,主角配角的人物刻劃優秀,劇情很戳我的心,CG多,難度親民,有中文 缺點:提示少分支多,完整體驗找找攻略可能會有比較好,缺乏超展開,有懸疑要素但不多,戰鬥難度簡單
Thumbnail
介紹一個優質的遊戲知識部落格:遊戲設計中藥鋪,其中「Game Design 資源分享表」十分推薦遊戲開發者閱讀。另外提到Gamker攻壳是一個專業的遊戲評鑑頻道,其深入的評論幫助作者入坑《健身環大冒險》。作者在後記也分享了自己在遊戲開發上的經歷和挑戰。
Thumbnail
一個獨立開發者,立志打造顛覆性的遊戲體驗,在開拓全新玩法的道路上,砥礪前行。 大家好,我正在開發一款獨立遊戲,以魔法戰鬥為主題,如果想要嘗試遊戲的demo,歡迎到下方連結處,加入我的Discord群組。 魔法與戰鬥的世界---開發中的遊戲   這款遊戲發生在一個飄浮於雲端之上的世界,
Thumbnail
這篇內容,簡單介紹了GameMaker的遊戲製作原理。包括Object、參數、程式碼等概念。同時也簡單介紹了GameMaker的適用範圍和特色。
Thumbnail
Instrumect 是我從2022年10月開始製作的遊戲。 在那之前,其實嘗試過各種大大小小的遊戲開發,包括RPG、橫向射擊、2D平台遊戲等,但最後都熱情耗盡,所有項目皆斷尾(ゝ∀・)⌒☆ 當初會選擇開發那些類型,不外乎考慮市場熱門程度和製作難易度。而最後會沒了熱情,是因為我對那類遊戲興趣沒很
Thumbnail
學完基礎的Lua語法後,今天開始製作第一個遊戲畫面了!
Thumbnail
一、了解思維 二、大量體驗 三、拆解架構 四、找出樂趣
Thumbnail
算了算我的存圖,嗯,還可以寫三天,所以今天來寫一下遊戲的,遊戲的存圖我也掙蠻多張的,哈哈,不怕沒東西寫了。 「煙硝絮語」封測的時候我試玩過~之前終於公測了,十二月的時候,我拖到現在才寫😏
Thumbnail
這篇文章將會延續(上)、(中)的內容,談談遊戲開發測試原型的製作與驗證。