基於Flutter Canvas的飛機大戰(一)

美味的小布丁發表於2019-01-24

說明

小編也是初學者,為了瞭解flutter動畫的使用與效果, 決定親自定手用flutte寫一款小遊戲出來. 並將過程中的跳過的坑記錄下來.

開發準備

具體參考flutter環境搭建, 筆者環境資訊

  • Android Studio 3.2
  • macOS 10.14
  • flutter v1.1.10-pre.136
  • dart 2.0

New Flutter Project

我們大概目錄為:

  • assets
    • images
  • lib
    • main.dart
    • src
      • enter.dart

main.dart是我們程式碼的主入口. lib.src用來存放我們整個遊戲的邏輯程式碼檔案. assets則是用來存放我們的圖片資原始檔.

專案入口 main.dart

在這個檔案中, 我們可以製作一個選單, 先保留著. 我們只留一下按鈕, 點選按鈕後. 將我們的遊戲介面, 入棧到Router中, 開始我們的遊戲.部份程式碼如下:

 RaisedButton(
      onPressed: () {
        Navigator.push(context, MaterialPageRoute(builder: (_) {
          return GameEnter();
        }));
      },
      child: Text("開始遊戲")
)
複製程式碼

遊戲入口enter.dart

enter.dart是我們整個遊戲的主入口. 在這個入口中, 我們載入資源, 進行整體的繪圖操作. 我們在enter.dart中定義我們的主畫板, 關於CustomPaint的說明參考: 官方DOC, MainPainter 繼承自 CustomPainter, 按官方的說明我們繼承並實現他的二個方法 paintshouldRepaint, 在當前狀態下. 整個介面是空白的, 什麼都沒有. 接下來我們定義我們的遊戲背景.

// CustomPaint.painter
class MainPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
  }
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    return oldDelegate != this;
  }
}
// GameEnter.build
Widget build(BuildContext context) {
    return CustomPaint(
      painter: MainPainter(),
    );
}
複製程式碼

遊戲背景

我們在 src 下, 新建一個叫做bg.dart的檔案, 並新建一個 Background的類, init函式, 是用來在Enter 中載入我們遊戲所需要的資原始檔, paint 函式是用來在 MainPainter 中繪製我們的背景動畫.

class Background {
  // 初始背景的偏移量
  double offsetY = -100.0;
  // 螢幕的寬度
  double screenWidth;
  // 螢幕的高度
  double screenHeight;
  // 畫布滾動的速度
  double speed = 10;
  // 載入的背景圖片
  ui.Image image;
  // 二張背景圖的縱座標點
  double y1 = 100.0;
  double y2 = 0.0;
  
  // 建構函式
  Background();
  
  // 初始化, 各種資源
  Future<VoidCallback> init() async {
    return null;
  }
  
  // 繪圖函式
  paint(Canvas canvas, Size size) async {
    Rect screenWrap = Offset(0.0, 0.0) & Size(screenWidth, screenHeight);
    Paint screenWrapPainter = new Paint();
    screenWrapPainter.color = Colors.red;
    screenWrapPainter.style = PaintingStyle.fill;
    canvas.drawRect(screenWrap, screenWrapPainter);
  }
}

複製程式碼

Enter入口繪製背景

接下來我們要將我們的遊戲背景真正的繪製在我們的手機上. 我們在 Enter 的初始化函式 initeState 中 初始化 Background 例項, 並進行資源初始化. 然後在 MainPainter 的繪圖介面上, 增加我們的繪圖邏輯

void paint(Canvas canvas, Size size) {
    background.paint(canvas, size);
}
複製程式碼

執行效果如下

執行效果

接下來我們需要將遊戲的背景圖繪製到背景中, 這裡我們呼叫的是

Canvas.drawImage(Image image, Offset offset, Painter paint) API

Background.paint 函式中我們增加以下程式碼, 然後執行

Paint paint = new Paint();
canvas.drawImage(image, Offset(0, 0), paint);
複製程式碼

效果如下:

靜態圖背景

讓背景動起來

在本次探動畫探究中, 我使用 AnimationControllerCurvedAnimation 完成我們的效果. 有關這二個類的具體文件參考AnimationControllerCurvedAnimation. 我們先在 EnteriniteState 中宣告二個例項,

 animation = CurvedAnimation(
  parent: controller,
  curve: Curves.linear,
);
animation.addListener(() {
  setState(() {});
});
animation.addStatusListener((status) {
  if (status == AnimationStatus.completed) {
    controller.repeat();
  }
});
複製程式碼

controller在有幾個控制動畫的方法

  • forward() 向前運動
  • stop() 停止
  • reverse() 向後運動 (這個概念, 我也沒懂. 暫時擱這)

我們去監聽 animation 每次動畫值的改變, 增加監聽函式, 通過 setState 去觸發當前檢視的重新整理.

我們去監聽 animation 動畫狀態, 判斷是否動畫結束,從而呼叫 repeat 方法, 使動畫一直迴圈下去. 通過以上程式碼. 執行後發現, 每一幀都會觸發 Background的重繪, 通過這點我們每次更改背景圖起繪點的座標, 就可以達到動畫的效果.

paint(Canvas canvas, Size size) async {
    ...
    y1 += 10;
}
複製程式碼

基於Flutter Canvas的飛機大戰(一)

因為錄屏的原因, 所以顯示比較卡頓.

讓背景迴圈起來

在正常的2D飛機類遊戲中, 遊戲的背景是迴圈滾動的 ,常見的處理方法是, 二張背景圖,頭尾相連迴圈繪製, 當其中某個背景圖, 滾出螢幕視野, 將其重新定位到上一張背景圖的正上方, 來回往復, 從而達到背景迴圈滾動的效果. 在這裡我們為二張圖景圖, 起繪點座標增加以下的邏輯,

y1 = y1 + 1 * speed;
y2 = y2 + 1 * speed;
if (y2 > image.height) {
  y2 = y1 - image.height;
}
if (y1 > image.height) {
  y1 = y2 - image.height;
}
複製程式碼

在這次專案中, 由於我找到的背景圖比較小, 沒有辦法撐滿整個螢幕, 所以我在繪製的時候, 將Canvas進行了縮放操作.

canvas.scale(1, screenHeight / image.height);
複製程式碼

最後讓我們看一下效果:

基於Flutter Canvas的飛機大戰(一)

總結

第一部份, 大工告成. 在接下來幾天. 我會把其他的元素的相關邏輯加上. git傳送門

相關文章