昨天下午筆者已經完成了背景動畫的循環播放. 晚上筆者就開發中發現的問題在stackoverflow上進行提問. 問題大概內容:html
如何在Canvas中, 將一個較小的圖片, 拉伸平鋪 問題連接git
這個問題, 收到了二個有效的回答github
進過筆者測試canvas
兩者視覺效果類似, 但是 paintImage 的性能問題, 嚴重消耗了GPU資源. 查看了paintImage的源碼, 發現這個函數實現的方式也是調用了 drawImageRect, 這個問題.有興趣的同窗能夠深刻了解一下. 共同探討一下, 也行對於Flutter性能優化有很大的幫助.性能優化
void paintImage(
...
if (centerSlice == null) {
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
canvas.drawImageRect(image, sourceRect, tileRect, paint);
} else {
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
canvas.drawImageNine(image, centerSlice, tileRect, paint);
}
if (needSave)
canvas.restore();
}
複製代碼
本篇咱們的主要任務是, 在畫板上增長咱們控制的飛機, 能夠操做飛機移動.bash
考慮到咱們將來要繪製玩家的戰機. 還要繪製敵機. 咱們先抽象出一個 Plan 的類, 方便之後咱們的開發.咱們在 src 下, 新建一個叫 plan.dart的文件. 定義他的方法.async
abstract class Plan {
void init() {}
void moveTo(double x, double y) {}
void destroy() {}
void paint(Canvas canvas, Size size) async {}
}
複製代碼
接下來咱們就能夠定義的的 MainHero咱們的主角了. 咱們的src下新建一個 hero.dart, 引用並繼承 Plan, 並實如今上邊定義的方法. 關於基本方法與屬性以下:ide
enum PlanStatus {stay, move, die}
class MainHero extends Plan {
// 飛機的中心座標x
double x = 100.0;
// 飛機的中心座標y
double y = 100.0;
// 戰機寬度
double width = 132.0;
// 戰機高度
double height = 160.0;
ui.Image image;
@override
void init() async {
// TODO: implement init
image = await Utils.getImage('assets/images/hero.png');
}
@override
void moveTo(double x, double y) {
// TODO: implement moveTo
}
@override
void destroy() {
// TODO: implement destroy
super.destroy();
}
@override
void paint(Canvas canvas, Size size) {
Rect paintArea = Offset(100, 100) & Size(width, height);
Rect planArea = Offset(0, 0) & Size(image.width, image.height)
canvas.save();
// 將畫布向左上方偏移, 把繪圖點, 遷移到飛機正中心
canvas.translate( -width / 2, -height / 2);
canvas.drawImageRect(image, planArea, paintArea, new Paint());
frameIndex++;
canvas.restore();
}
}
複製代碼
在本次咱們的繪圖接口用的是 drawImageRect, 使用方法參考文檔, 咱們在遊戲的 Enter入口文件中, 新建一個主角的實例, 完成初始化, 與繪圖的邏輯, 具體細節與背景圖相似, 咱們就不細說了.函數
廢話很少說, 直接上效果圖性能
在咱們玩過的飛機類遊戲裏邊. 咱們控制的飛機一般都會有一個動態效果, 這個動態的效果會加強玩家的視覺體驗, 筆者從網上找到了一份遊戲飛機的動效以下:
這個飛機動效是一個 gif 類型的文件循環播放, 給人以動態的感受. 我查閱了 flutter 貌似沒有直接繪製 gif的接口. 因此咱們只能用繪製靜態圖的方式去想辦法讓 飛機動起來, 作過h5的同窗可能比較瞭解, 在早期html界面中的動畫是由多幀拼接成一個膠片, 循環播放, 形成一種視覺停留的動畫效果. 這裏咱們依然採用這種方式去實現本次的動態效果. 咱們經過ps, 把每一幀拼接作成一個有2幀的132*80長幀圖;接下來, 咱們就要盤這張圖,對咱們的 MainHero進行改造, 把他動態顯示在咱們的屏幕上. 咱們給它增長二個屬性和一個方法, 每一次屏幕刷新, 咱們都把 frameIndex 進行加1的操做, 當達到最後一幀, 將 frameIndex重置爲0, 這樣咱們的飛機就能夠動起來了
// 總幀數
int frameNumber = 2;
// 當前幀數
int frameIndex = 0;
// 動態獲取飛機的長幀圖的繪製區域
Rect getPlanAreaSize(int _frameIndex) {
double perFrameWidth = image.width / frameNumber;
double offsetX = perFrameWidth * _frameIndex;
double offsetY = 0;
if (offsetX >= image.width) {
frameIndex = 0;
return this.getPlanAreaSize(0);
}
return Offset(offsetX, offsetY) & Size(66.0, 80.0);
}
複製代碼
效果圖以下:
關於控制飛機飛行的思路是, 咱們經過監聽屏幕, 手指的運動, 動態的更新飛機繪製 (x,y) 的座標點, 從而達到咱們想要的效果.
Flutter的文檔中, 咱們找到了 GestureDetector 接口, 在 Enter 入口中 咱們用GestureDetector控件包圍住咱們的CustomPaint畫板 控件。咱們接下來的工做就是,使用 GestureDetector 控件來捕獲用戶的拖動事件。並更新咱們 MainHero 的座標點.
實現方式以下:
Widget build(BuildContext context) build () {
...
return GestureDetector(
child: CustomPaint(
painter: MainPainter(background: background, hero: hero)
),
onPanStart: (DragDownDetails) {
hero.moveTo(DragDownDetails.globalPosition.dx, DragDownDetails.globalPosition.dy);
},
onPanUpdate: (DragDownDetails) {
hero.moveTo(DragDownDetails.globalPosition.dx, DragDownDetails.globalPosition.dy);
}
)
}
複製代碼
接下來咱們來改造咱們的 MainHero 類, 完善他的 moveTo 方法. 在遊戲過程當中, 咱們手指拖動, 飛機不可能以閃現的方式進行閃動, 它須要一點點移動到咱們的想要的位置. 咱們在 MainHero中定義幾個屬性與方法
// 飛行目標點座標
double _x;
double _y;
double speed = 20;
// 動態計算新的座標點
void calculatePosition() {}
複製代碼
咱們在這裏用一張圖, 去展現新舊座標點以前的關係:
經過以上這張圖, 咱們要以明白在飛機在x與y軸上, 速度的矢量關係與運算方法, 咱們完善咱們的 calculatePositionvoid moveTo(double x, double y) {
// TODO: implement moveTo
this._x = x;
this._y = y;
}
void calculatePosition() {
Point p1 = Point(x, y);
Point p2 = Point(_x, _y);
double distance = p1.distanceTo(p2);
double flyRadian = acos(((y - _y) / distance).abs());
// 判斷位移方向
if (_x < x) {
x -= speed * sin(flyRadian);
} else {
x += speed * sin(flyRadian);
}
if (_y < y) {
y -= speed * cos(flyRadian);
} else {
y += speed * cos(flyRadian);
}
}
複製代碼
經過以上改造, 咱們進行測試發現, 在運動到終點時,飛機會在終點發生抖動, 排查問題發現, 是咱們的calculatePosition方法, 在計算x值的時候, 會在最後一次計算中, 產生一個 |x - _x| > 0的結果, 因此飛機會在座標點來回的跳動. 爲了不這種狀況, 咱們再次改造 calculatePosition 方法
咱們爲 MainHero 增長一個飛機的飛行狀態, 當飛機與目標點及其接近時, 直接手動覆蓋(x, y), 並將飛機的狀態設爲 stay.// stay 無人控制, 自由飛行
// move 有人控制, 飛行運動狀態
// die 死了
enum PlanStatus {stay, move, die}
void calculatePosition() {
...
// 避免抖動, 作一個判斷. 距離
if (distance < 10) {
x = _x;
y = _y;
status = PlanStatus.stay;
return null;
}
}
// 同時爲了更好的優化咱們的Pain方法函數, 咱們爲其增長一個邏輯的判斷
void paint(Canvas canvas, Size size) {
...
if (status == PlanStatus.move) {
calculatePosition();
}
}
複製代碼
經過以上改造, 咱們看一下最終的效果.
第二部份, 大工告成, 內容可能會有錯別字, 請你們指出, 我將進行改正, 剩下的邏輯. 我會一點點補上, 若是以爲本篇內容對您有幫助, 期待您的贊~ git傳送門