iT邦幫忙

2024 iThome 鐵人賽

DAY 3
0
Mobile Development

用Flutter Flame做遊戲!Live!系列 第 3

用CustomPaint與CustomPainter畫朵花

  • 分享至 

  • xImage
  •  

Flutter是個「萬事萬物皆Widget」的程式語言。
但這代表了什麼特殊意義嗎?
如果用Android的原生語言框架來說明和對比,可能會比較清楚。

Android的原生框架中,介面元件是View,而所有的View都有個可以從View內獲取Canvas的函數「getView」。相較之下,Flutter的Widget就不具備此特性。
(這延伸出一個問題:這兩種框架Render的方式一樣嗎?顯然有不同,但先不去探究。)

在Android中,要進行Canvas繪圖,可以直接使用View或任何擴充自View的物件。差別只在於效能而已。
但在Flutter中,「建議」經由CustomPainter來進行Canvas繪圖。

abstract class CustomPainter extends Listenable {
    void paint(Canvas canvas, Size size);
}

如上面的程式碼顯示,只要擴充「CustomPainter」,就能使用「paint」函數來取得Canvas進行自己想要的繪圖工作。
但是CustomPainter不是個Widget,所以無法在Widget組成的WidgetTree中使用,必須要使用「CustomPaint」當作Widget,然後將「CustomPainter」當作前者的傳入參數來使用。


class CustomPaint extends SingleChildRenderObjectWidget {
    const CustomPaint({
        super.key,
        this.painter,
        this.foregroundPainter,
        this.size = Size.zero,
        this.isComplex = false,
        this.willChange = false,
        super.child,
      }) : assert(painter != null || foregroundPainter != null || (!isComplex && !willChange));
}

更具體一點的使用範例,如下:

  @override
  Widget build(BuildContext context) {
    return CustomPaint(painter: CustomPainter()...);
  }

https://ithelp.ithome.com.tw/upload/images/20240915/20130496Mqi4LdUfOH.png

來做個簡單的圖形吧!
像這樣畫「一朵花」。(這是花~這是花~這是花~這是一朵花~~你要說它不是花、像別的東西我也無所謂~~)
圖中雖然是兩朵,但其實是一朵花畫兩次。


  @override
  void paint(Canvas canvas, Size size) {
    Paint paint = Paint();
    paint.color = const Color(0xff000000);
    paint.strokeWidth = size.width/15;
    paint.strokeCap = StrokeCap.round;

    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 2;

    for (int i = 0; i < 720; i++) {
      if(i % 30 == index){
        // 計算每條線的角度
        double angle = ((reverse)?-1:1) * 2 * pi / 720 * i;

        // 使用 sin 和 cos 函數來計算終點的 x 和 y 座標
        double x = center.dx + radius * cos(angle);
        double y = center.dy + radius * sin(angle);
        double x2 = center.dx + (radius * cos(angle)/1.8);
        double y2 = center.dy + (radius * sin(angle)/1.8);
        // 畫一條線,從圓心到計算出的終點
        canvas.drawLine(Offset(x2, y2), Offset(x, y), paint);
      }

    }
  }

這裡的paint函數中有傳入一組「Size size」,這是CustomPaint這個Widget的大小。同時會以這個Widget的「左上角」定義為「X,Y軸」的起點「0,0」。

(這個起點的定義需要特別注意,因為跟慣常數學繪圖使用的二維軸方向邏輯不一樣,所以如果使用慣常的邏輯去試著畫圖,畫出來的東西會顛倒。)

這裡使用的繪圖法是用數學計算,所以會看到數次使用「sin」「cos」等數學運算符號/公式(?),求出一條線的起點與終點後,丟給「canvas.drawLine」這個函數指令在Canvas上畫線。
這樣的做法雖然好懂,但(可能)很吃效能。反而是Canvas工具提供「旋轉畫布」的辦法,「將畫布旋轉一定角度後,重複在同樣的起始與終點座標上畫線。」


特別注意這裡對數學運算的使用。
實務上,數學運算吃效能,所以很多時候會想辦法找投機的方式做它。
例如儘量讓圖形對稱,則一次的數學願算結果,只需要正負顛倒或相反處理後就可以產生兩次運算的結果。


如果連續重疊兩個CustomPaint,就會得到上面截圖的結果。
就不另外附上程式碼,隨有心的人自己嘗試了。


上一篇
從Canvas開始講起 (為什麼你應該來試著學學看怎麼用這東西)
下一篇
來使用Flame引擎...
系列文
用Flutter Flame做遊戲!Live!32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言