iT邦幫忙

2023 iThome 鐵人賽

DAY 5
0
SideProject30

年輕人,想玩自己寫!系列 第 5

第四章,去找吧!我把所有的財寶都放在Component裡了~ Flame Component System

  • 分享至 

  • xImage
  •  

工程師視角:
PM不知道在做什麼,反正今天沒人來煩我。
來補充說明一下Flame的特色,並且先把一些必定會有的功能模組化好了~
換頁、按鈕、位移、操控、動畫
在Flame裡,這些都和他的Component有關。
簡單的概念如下:

  1. 可以把FlameGame比做一張畫布,可能會有什麼行為先和他說一下(實作對應的Callback、Detection)。
  2. 接著讓你想放的東西繼承對應的Component,改寫裡面的方法。
  3. 然後在FlameGame onLoad時用 add(Component) 方法將你要的東西如進去。

就會出現在畫面上囉~

Flame Component System(FCS)

  • Components 樹狀結構
    所有的Components都繼承自抽象父類別Component,
    而且裡面都有add()方法可以加入新的Components。
    https://ithelp.ithome.com.tw/upload/images/20230920/20162723TzcKgOl1Nk.png

  • Components 生命週期與參數
    https://ithelp.ithome.com.tw/upload/images/20230920/20162723UBOlBTa5e5.png
    觸發時機:

  1. onGameResize : 畫面尺寸變化時
  2. onLoad:初始化時
  3. onMount:加到父節點時
  4. onRemove:被移除時
  5. update:每一幀
  6. render:每一幀

Priority:
每個Component都有記錄優先級的整數(int),priority。
數值越大就越靠近畫面(Z軸)
Component Keys:
可以給定Component一個KEY的名稱,這樣之後就可以用這個名稱來找到他。
像Android的findViewById,或js的getElementById()

final myComponent = Component(
  key: ComponentKey.named('player'),
);

flameGame.findByKey(ComponentKey.named('player'));

實作一些常用的Component

  1. RouterComponent
    在需要換頁的情況下,我們一定會用到RouterComponent
    因為Route和原本有引入的class重名了,所以import game.dart as 'g'
import 'package:flame/game.dart' as g;

class MyGame extends g.FlameGame {
  late final g.RouterComponent router;

  @override
  Future<void> onLoad() async {
    add(
      router = g.RouterComponent(
        routes: {
          'splash': g.Route(SplashScreenPage.new),
          'home': g.Route(HomePage.new),
        },
        initialRoute: 'splash',
      ),
    );
  }
}

然後在其他頁要繼承Component、實作HasGameReference

class SplashScreenPage extends Component with TapCallbacks, HasGameReference<MyGame>{
  @override
  Future<void> onLoad() async {
    addAll([
      TextBoxComponent(
        text: 'Start!',
        textRenderer: TextPaint(
          style: const TextStyle(
            color: Color(0xffffffff),
            fontSize: 48,
          ),
        ),
        align: Anchor.center,
        size: game.canvasSize,
      ),
    ]);
  }

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

  @override
  void onTapUp(TapUpEvent event) => game.router.pushNamed('home');
}

上面的code就會在最一開始顯示SplashScreenPage,
然後我在那面上有放一個「Start」的文字(用下面的TextBoxComponent做的),
當按下時就會自動導向HomePage了~

  1. PositionComponent
    這個Component是許多重要組件的父類別,不論是精靈圖、小動畫、按鈕,甚至虛擬把手都是他的子類別。
    我們就來用他來做一個客製作的按鈕吧~
    先繼承PositionComponent 然後實作 TapCallbacks
class RoundedButton extends PositionComponent with TapCallbacks{
  
}

到時候使用時,我們會想傳入文字、顏色、框、要觸發的事件

class RoundedButton extends PositionComponent with TapCallbacks{

  final String text;                     ///文字
  final void Function() action;          ///事件
  final TextPainter _textDrawable;       ///文字drawable
  late final Offset _textOffset;         ///算出Offset,讓文字置中
  late final RRect _rrect;               ///畫出按鈕的形狀
  late final Paint _borderPaint;         ///框線畫筆
  late final Paint _bgPaint;             ///背景色畫筆

  RoundedButton({
    required this.text,
    required this.action,
    required Color color,                ///顏色
    required Color borderColor,          ///框線
    super.anchor = Anchor.center,        ///位置,沒傳入的話就在畫面中間
  }) : _textDrawable = TextPaint(
    style: const TextStyle(
      fontSize: 20,
      color: Color(0xFF000000),
      fontWeight: FontWeight.w800,
    ),
  ).toTextPainter(text) {
    size = Vector2(150, 40);
    _textOffset = Offset(
      (size.x - _textDrawable.width) / 2,
      (size.y - _textDrawable.height) / 2,
    );
    _rrect = RRect.fromLTRBR(0, 0, size.x, size.y, Radius.circular(size.y / 2));
    _bgPaint = Paint()..color = color;
    _borderPaint = Paint()
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2
      ..color = borderColor;
  }


  @override
  void render(Canvas canvas) {
    canvas.drawRRect(_rrect, _bgPaint);
    canvas.drawRRect(_rrect, _borderPaint);
    _textDrawable.paint(canvas, _textOffset);
  }

}

最後實作按下會有什麼反應

@override
  void onTapDown(TapDownEvent event) {
    scale = Vector2.all(1.05);  ///放大一點點
  }

  @override
  void onTapUp(TapUpEvent event) {
    scale = Vector2.all(1.0);  ///變回來,並觸發事件
    action();
  }

  @override
  void onTapCancel(TapCancelEvent event) {
    scale = Vector2.all(1.0); ///變回來
  }

這樣就完成一個按鈕的小元件了,可以任意的放在想要的畫面上。

展示:
展示gif

PM視角:企劃、UI、使用者需求…好忙啊/images/emoticon/emoticon06.gif
參考:
官方文件(Component)
【Flutter&Flame游戏 - 拾壹】探索构件 | Component 使用细节


上一篇
第三章,道理我都懂,所以那個Code呢?
下一篇
第五章,永遠沒有準備好的一天
系列文
年輕人,想玩自己寫!31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言