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

更新於 發佈於 閱讀時間約 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
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
企業的價值取決於其未來能夠創造的現金流量,而這些現金流的價值需要根據時間價值原則進行折現,以反映其當前價值。 「現金流量折現法」或是「貼現現金流模型」(Discounted Cash Flow, DCF)就是藉由 預測未來的自由現金流量: 企業在扣除運營成本、稅收、資本支出後,能夠自由支配的
Thumbnail
https://www.youtube.com/watch?v=0L2OgNQDzTc 之前看了啾啾鞋這部影片,裡面說退坑一款手遊,也說到一些觀念,就如下圖這樣
遊戲是單純的RPG類型小遊戲 都是一個人做的非常厲害 遊戲共三章應該不太會再有後續了 每段大約40分內可完成 優點:音樂好聽,畫風出色,主角配角的人物刻劃優秀,劇情很戳我的心,CG多,難度親民,有中文 缺點:提示少分支多,完整體驗找找攻略可能會有比較好,缺乏超展開,有懸疑要素但不多,戰鬥難度簡單
Thumbnail
介紹一個優質的遊戲知識部落格:遊戲設計中藥鋪,其中「Game Design 資源分享表」十分推薦遊戲開發者閱讀。另外提到Gamker攻壳是一個專業的遊戲評鑑頻道,其深入的評論幫助作者入坑《健身環大冒險》。作者在後記也分享了自己在遊戲開發上的經歷和挑戰。
Thumbnail
一個獨立開發者,立志打造顛覆性的遊戲體驗,在開拓全新玩法的道路上,砥礪前行。 大家好,我正在開發一款獨立遊戲,以魔法戰鬥為主題,如果想要嘗試遊戲的demo,歡迎到下方連結處,加入我的Discord群組。 魔法與戰鬥的世界---開發中的遊戲   這款遊戲發生在一個飄浮於雲端之上的世界,
Thumbnail
他對於外遇真的太敏感,避免不了的一堆解釋和爭執 雖然老師已經和婷婷說過不希望她再去煩惱其他的事,但婷婷在施法過一個月的時候,突然傳訊息來跟老師說,她和冠宇又吵了一場架,這次嚴重到冠宇想動手把婷婷家裡開的店砸了,老師問她發生了什麼事,婷婷說,她覺得即使小雯還不太喜歡她,她還是想為小雯出一份心力,所以
Thumbnail
這篇內容,簡單介紹了GameMaker的遊戲製作原理。包括Object、參數、程式碼等概念。同時也簡單介紹了GameMaker的適用範圍和特色。
跟 AI 機器人表達自己的需求實際上是在訓練自己的表達能力。明確向對方說你的目的是什麼,給予什麼樣的參考資料和範本。最後要求對方產出的作品是什麼。這樣的流程不論是講給 AI 或人類都很重要。那些會說 AI 很難用的人,或許現實中也常常覺得別人怎麼都聽不懂自己想說什麼,這種人即使跟 AI 表達需求,
Thumbnail
一場奇幻的冒險 生命的旅程,彷彿是一場奇幻的冒險,而我們每一個人都是冒險者。在這個旅途中,我們經歷著各種挑戰、歡笑、淚水,而最讓人難以忘懷的,往往是過去的陰影。學會如何放下,是一種深刻的藝術,一種能夠讓我們重新獲得內心平靜的能力,因為過去不會回來,而我們卻能夠轉身面對未來。 放下是一場持久的修行
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
企業的價值取決於其未來能夠創造的現金流量,而這些現金流的價值需要根據時間價值原則進行折現,以反映其當前價值。 「現金流量折現法」或是「貼現現金流模型」(Discounted Cash Flow, DCF)就是藉由 預測未來的自由現金流量: 企業在扣除運營成本、稅收、資本支出後,能夠自由支配的
Thumbnail
https://www.youtube.com/watch?v=0L2OgNQDzTc 之前看了啾啾鞋這部影片,裡面說退坑一款手遊,也說到一些觀念,就如下圖這樣
遊戲是單純的RPG類型小遊戲 都是一個人做的非常厲害 遊戲共三章應該不太會再有後續了 每段大約40分內可完成 優點:音樂好聽,畫風出色,主角配角的人物刻劃優秀,劇情很戳我的心,CG多,難度親民,有中文 缺點:提示少分支多,完整體驗找找攻略可能會有比較好,缺乏超展開,有懸疑要素但不多,戰鬥難度簡單
Thumbnail
介紹一個優質的遊戲知識部落格:遊戲設計中藥鋪,其中「Game Design 資源分享表」十分推薦遊戲開發者閱讀。另外提到Gamker攻壳是一個專業的遊戲評鑑頻道,其深入的評論幫助作者入坑《健身環大冒險》。作者在後記也分享了自己在遊戲開發上的經歷和挑戰。
Thumbnail
一個獨立開發者,立志打造顛覆性的遊戲體驗,在開拓全新玩法的道路上,砥礪前行。 大家好,我正在開發一款獨立遊戲,以魔法戰鬥為主題,如果想要嘗試遊戲的demo,歡迎到下方連結處,加入我的Discord群組。 魔法與戰鬥的世界---開發中的遊戲   這款遊戲發生在一個飄浮於雲端之上的世界,
Thumbnail
他對於外遇真的太敏感,避免不了的一堆解釋和爭執 雖然老師已經和婷婷說過不希望她再去煩惱其他的事,但婷婷在施法過一個月的時候,突然傳訊息來跟老師說,她和冠宇又吵了一場架,這次嚴重到冠宇想動手把婷婷家裡開的店砸了,老師問她發生了什麼事,婷婷說,她覺得即使小雯還不太喜歡她,她還是想為小雯出一份心力,所以
Thumbnail
這篇內容,簡單介紹了GameMaker的遊戲製作原理。包括Object、參數、程式碼等概念。同時也簡單介紹了GameMaker的適用範圍和特色。
跟 AI 機器人表達自己的需求實際上是在訓練自己的表達能力。明確向對方說你的目的是什麼,給予什麼樣的參考資料和範本。最後要求對方產出的作品是什麼。這樣的流程不論是講給 AI 或人類都很重要。那些會說 AI 很難用的人,或許現實中也常常覺得別人怎麼都聽不懂自己想說什麼,這種人即使跟 AI 表達需求,
Thumbnail
一場奇幻的冒險 生命的旅程,彷彿是一場奇幻的冒險,而我們每一個人都是冒險者。在這個旅途中,我們經歷著各種挑戰、歡笑、淚水,而最讓人難以忘懷的,往往是過去的陰影。學會如何放下,是一種深刻的藝術,一種能夠讓我們重新獲得內心平靜的能力,因為過去不會回來,而我們卻能夠轉身面對未來。 放下是一場持久的修行