前一篇介紹的「CustomPaint」與「CustomPainter」只是個用來取得Canvas進行繪圖的Flutter標準元件而已,但真正要製作遊戲,使用這兩個東西是不夠的,是個策略上與效能上都不理想的選擇。
在Flutter上要開發遊戲就還是要使用遊戲引擎。
遊戲引擎其實是種框架。有時候會高度整合了一套自己專屬的開發者介面,例如Unity,它有自己專用的開發介面,裡面整合了其他的程式語言,例如「C#」或「JavaScript」。
但Flame就是完全依附在程式語言「Flutter」之下,它並沒有自己的專屬開發介面,開發者用什麼工具界面開發Flutter、Flame就跟著使用什麼樣的工具介面。
Flame是個Google官方所推出的Flutter專用遊戲框架。
這套遊戲引擎現階段主力是以2D遊戲為主,因為Flutter橫跨的平台很廣,所以它並未充分使用各種平台的硬體加速功能,例如GPU。
所以並不是說它不能製作3D遊戲,只是需要的技術等級與耗費的心力時間跟最終成果的水準實在嚴重不成比例。
遊戲的主體是「FlameGame」,程式主框架如下:
import 'package:flame/game.dart' as flame;
class MyGame extends flame.FlameGame {
@override
Future<void> onLoad() async {
...
}
@override
void update(double dt) {
super.update(dt);
...
}
@override
void render(Canvas canvas) {
...
}
}
可以看到它也有屬於自己的獲得「Canvas」的方法。
既然有Canvas,那之前的CustomPainter其實就可以重複使用喔!
例如之前用來畫花的CustomPainter(FlowerPainter)。
A物件或許是設計給A系統使用的,但不表示B系統不能使用A物件,只要理解並利用A物件的架構邏輯去設計B系統即可。
但在使用Customainter前還是先把FlameGame講完。
FlameGame跟CustomPainter一樣,無法當成Widget一樣使用,必須一使用「GameWidget」才能顯示FlameGame的內容。
這裡提供一個簡單的範例...
Container(
width: 300,
height: 300,
child: GameWidget(
game: MyGame(),
),
)
(MyGame就是個FlameGame擴充而來的物件。)
既然CustomPainter有「paint」函數來接受外層傳入Canvas,那沒道理FlameGame不能將自己的Canvas傳入。
FlowerPainter flowerPainter = FlowerPainter(...);
@override
void render(Canvas canvas) {
super.render(canvas);
flowerPainter.paint(canvas, Size(80, 80));
}
這個FlowerPainter(參考上一篇)有個缺點就是「它的中心位置強治設在Widget提供的Size中央」。
如上面的範例,因為大小設為「長寬各80」,所以中心座標點是(40,40)。這可以優化提升。
FlameGame中有個「update」函數,這個函數是我認為「還是使用遊戲引擎比較好」的關鍵原因。
簡單來說,它提供了一個類似多執行序的功能。
例如:我希望這朵花不停的轉動。
這個功能就可以在「update」中設計實現。(設計?實現?程式設計領域的用語有時候真的讓人很困惑。)
double newIndex = 0;
@override
void update(double dt) {
super.update(dt);
newIndex += dt*33.333;
flowerPainter.setIndex( (newIndex.toInt()%30));
}
「dt」是「距離上次執行update過了多久?(單位是秒,有小數點。)」
因為設計上,花朵每秒轉「30度」,轉一圈要十二秒。
使用時間做運算、而不是「每次update旋轉固定角度」,因為「update」執行頻率可能不穩定,這是為了在不同效能的裝置上,遊戲運行都能得到穩定相似的結果(好方便做連線遊戲)。
副作用是在效能較差的裝置上,畫面楨數降低幅度往往更大,(效能少30%?楨數只剩50%,不是70%。)