基于Flutter Canvas的飛機大戰(zhàn)(一)

說明

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

開發(fā)準(zhǔn)備

具體參考flutter環(huán)境搭建, 筆者環(huán)境信息

  • 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中定義我們的主畫板, 關(guān)于CustomPaint的說明參考: 官方DOC,
MainPainter 繼承自 CustomPainter, 按官方的說明我們繼承并實現(xiàn)他的二個方法 paintshouldRepaint, 在當(dāng)前狀態(tài)下. 整個界面是空白的, 什么都沒有. 接下來我們定義我們的游戲背景.

// 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函數(shù), 是用來在Enter 中加載我們游戲所需要的資源文件, paint 函數(shù)是用來在 MainPainter 中繪制我們的背景動畫.

class Background {
  // 初始背景的偏移量
  double offsetY = -100.0;
  // 屏幕的寬度
  double screenWidth;
  // 屏幕的高度
  double screenHeight;
  // 畫布滾動的速度
  double speed = 10;
  // 加載的背景圖片
  ui.Image image;
  // 二張背景圖的縱坐標(biāo)點
  double y1 = 100.0;
  double y2 = 0.0;
  
  // 構(gòu)造函數(shù)
  Background();
  
  // 初始化, 各種資源
  Future<VoidCallback> init() async {
    return null;
  }
  
  // 繪圖函數(shù)
  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 的初始化函數(shù) initeState 中 初始化 Background 實例, 并進行資源初始化. 然后在 MainPainter 的繪圖接口上, 增加我們的繪圖邏輯

void paint(Canvas canvas, Size size) {
    background.paint(canvas, size);
}

運行效果如下


運行效果

接下來我們需要將游戲的背景圖繪制到背景中, 這里我們調(diào)用的是

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

Background.paint 函數(shù)中我們增加以下代碼, 然后執(zhí)行

Paint paint = new Paint();
canvas.drawImage(image, Offset(0, 0), paint);

效果如下:


靜態(tài)圖背景

讓背景動起來

在本次探動畫探究中, 我使用 AnimationControllerCurvedAnimation 完成我們的效果. 有關(guān)這二個類的具體文檔參考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() 向后運動 (這個概念, 我也沒懂. 暫時擱這)

我們?nèi)ケO(jiān)聽 animation 每次動畫值的改變, 增加監(jiān)聽函數(shù), 通過 setState 去觸發(fā)當(dāng)前視圖的刷新.

我們?nèi)ケO(jiān)聽 animation 動畫狀態(tài), 判斷是否動畫結(jié)束,從而調(diào)用 repeat 方法, 使動畫一直循環(huán)下去.
通過以上代碼. 運行后發(fā)現(xiàn), 每一幀都會觸發(fā) Background的重繪, 通過這點我們每次更改背景圖起繪點的坐標(biāo), 就可以達到動畫的效果.

paint(Canvas canvas, Size size) async {
    ...
    y1 += 10;
}
image

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

讓背景循環(huán)起來

在正常的2D飛機類游戲中, 游戲的背景是循環(huán)滾動的 ,常見的處理方法是, 二張背景圖,頭尾相連循環(huán)繪制, 當(dāng)其中某個背景圖, 滾出屏幕視野, 將其重新定位到上一張背景圖的正上方, 來回往復(fù), 從而達到背景循環(huán)滾動的效果. 在這里我們?yōu)槎垐D景圖, 起繪點坐標(biāo)增加以下的邏輯,

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);

最后讓我們看一下效果:

image

總結(jié)

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末糠馆,一起剝皮案震驚了整個濱河市岸梨,隨后出現(xiàn)的幾起案子袋狞,更是在濱河造成了極大的恐慌葫笼,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,496評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奕谭,死亡現(xiàn)場離奇詭異撵幽,居然都是意外死亡斯稳,警方通過查閱死者的電腦和手機纹蝴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來踪少,“玉大人塘安,你說我怎么就攤上這事≡荩” “怎么了兼犯?”我有些...
    開封第一講書人閱讀 157,091評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長集漾。 經(jīng)常有香客問我切黔,道長,這世上最難降的妖魔是什么具篇? 我笑而不...
    開封第一講書人閱讀 56,458評論 1 283
  • 正文 為了忘掉前任纬霞,我火速辦了婚禮,結(jié)果婚禮上栽连,老公的妹妹穿的比我還像新娘险领。我一直安慰自己,他們只是感情好秒紧,可當(dāng)我...
    茶點故事閱讀 65,542評論 6 385
  • 文/花漫 我一把揭開白布绢陌。 她就那樣靜靜地躺著,像睡著了一般熔恢。 火紅的嫁衣襯著肌膚如雪脐湾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,802評論 1 290
  • 那天叙淌,我揣著相機與錄音秤掌,去河邊找鬼。 笑死鹰霍,一個胖子當(dāng)著我的面吹牛闻鉴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播茂洒,決...
    沈念sama閱讀 38,945評論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼孟岛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了督勺?” 一聲冷哼從身側(cè)響起渠羞,我...
    開封第一講書人閱讀 37,709評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎智哀,沒想到半個月后次询,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,158評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡瓷叫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,502評論 2 327
  • 正文 我和宋清朗相戀三年屯吊,在試婚紗的時候發(fā)現(xiàn)自己被綠了送巡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,637評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡雌芽,死狀恐怖授艰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情世落,我是刑警寧澤淮腾,帶...
    沈念sama閱讀 34,300評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站屉佳,受9級特大地震影響谷朝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜武花,卻給世界環(huán)境...
    茶點故事閱讀 39,911評論 3 313
  • 文/蒙蒙 一圆凰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧体箕,春花似錦专钉、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至娃兽,卻和暖如春菇民,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背投储。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評論 1 266
  • 我被黑心中介騙來泰國打工第练, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人玛荞。 一個月前我還...
    沈念sama閱讀 46,344評論 2 360
  • 正文 我出身青樓娇掏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親勋眯。 傳聞我的和親對象是個殘疾皇子驹碍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,500評論 2 348

推薦閱讀更多精彩內(nèi)容