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

說明

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

開發準備

具體參考flutter環境搭建, 筆者環境信息git

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

New Flutter Project

咱們大概目錄爲:github

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

main.dart是咱們代碼的主入口. lib.src用來存放咱們整個遊戲的邏輯代碼文件. assets則是用來存放咱們的圖片資源文件.canvas

項目入口 main.dart

在這個文件中, 咱們能夠製做一個菜單, 先保留着. 咱們只留一下按鈕, 點擊按鈕後. 將咱們的遊戲界面, 入棧到Router中, 開始咱們的遊戲.部份代碼以下:bash

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

遊戲入口enter.dart

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

// 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 中繪製咱們的背景動畫.ide

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);
}
複製代碼

運行效果以下 動畫

運行效果

接下來咱們須要將遊戲的背景圖繪製到背景中, 這裏咱們調用的是ui

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;
}
複製代碼

由於錄屏的緣由, 因此顯示比較卡頓.

讓背景循環起來

在正常的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);
複製代碼

最後讓咱們看一下效果:

總結

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

相關文章
相關標籤/搜索