說明
小編也是初學(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)他的二個方法 paint 和 shouldRepaint, 在當(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);
效果如下:
讓背景動起來
在本次探動畫探究中, 我使用 AnimationController 與 CurvedAnimation 完成我們的效果. 有關(guān)這二個類的具體文檔參考AnimationController 與 CurvedAnimation.
我們先在 Enter 的 initeState 中聲明二個實例,
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;
}
<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);
最后讓我們看一下效果:
總結(jié)
第一部份, 大工告成. 在接下來幾天. 我會把其他的元素的相關(guān)邏輯加上.
git傳送門