前言
在Flutter連續大版本的迭代下,發現2年前的項目bladeofgod/tank_combat (github.com)已經沒法跑了,為此進行了適配和代碼的重構。
相較於老項目,重構後的項目在代碼、設計以及註釋上更為規範、合理及詳盡,藉此,希望初學者能有所收穫。此外,由於是學習類demo,所以未經過審慎思考,如有設計不合理或不嚴謹的地方,還望指正。
ps:玩家無敵 :D
Flame
本項目依賴於Flame,而Flame是一個基於flutter的遊戲引擎庫。
Flame 引擎的目的是為使用 Flutter 開發的遊戲會遇到的常見問題提供一套完整的解決方案。
目前 Flame 提供了以下功能:
- 遊戲循環 (game loop)
- 組件/對象系統 (FCS)
- 特效與粒子效果
- 碰撞檢測
- 手勢和輸入支持
- 圖片、動畫、精靈圖 (sprite) 以及精靈圖組
- 一些簡化開發的實用工具類
Flame本身提供很多的組件,輔助管理Game Entity的生命週期、計算等。但是項目中並未完全使用,主要是希望能多一些自定義view(繪製)相關的代碼,以輔助初學者練習繪製。
TankCombat
在項目中,不僅可以瞭解到繪製相關的知識,還可以對Flutter的mixin、接口、抽象類、工廠構造函數、extension等語言特性,以及一些簡單地設計模式有個初步的認識。
下面對各節點做一個簡單地介紹。
繪製
因為之前做過相關係列的介紹,這裏不做贅述,可以參考之前的文章以及代碼註釋。Flutter&Flame——TankCombat遊戲開發(一) - 掘金 (juejin.cn)
Mixin
在入口類TankGame中,我們可以看到混入和複合混入的應用。
ps: 複合混入,我自己編的名字。 :D
複合混入:
接口和繼承
在tank_model.dart等文件中我們可以看到抽象類和接口的簡單應用。
設計模式
在混入類TankTheater中,通過跟蹤電腦tank的生成,我們可以瞭解到代理,工廠,Builder等設計模式的簡單應用。
代理類ComputerTankSpawner的結構圖。
工廠構造函數和extension
工廠構造函數
為了方便標準化對象的創建,如:
坦克發射過程中,一輛坦克可以發射n枚炮彈,所以我們需要創建位置相對獨立但與坦克相關的拷貝對象
///抽象類
abstract class BaseBullet extends WindowComponent{
///...其他構造函數
///抽象
BaseBullet copyWith({
int? tankId,
Size? activeSize,
Offset? position,
double? angle,
BulletStatus status = BulletStatus.none,
});
///.....其它代碼
}
///實現類之一
/// * 玩家炮彈
class PlayerBullet extends BaseBullet{
PlayerBullet({required int tankId, required Size activeSize})
: super(tankId: tankId, activeSize: activeSize);
///...其他代碼
@override
PlayerBullet copyWith({int? tankId, Size? activeSize, Offset? position, double? angle, BulletStatus status = BulletStatus.none}) {
final PlayerBullet pb = PlayerBullet(tankId: tankId ?? this.tankId, activeSize: activeSize ?? this.activeSize);
pb.position = position ?? Offset.zero;
pb.angle = angle ?? 0;
pb.status = status;
return pb;
}
}
extension
通過對List使用extension,可以簡化對List<componenet>的遍歷調用:
extension GameExtension<T extends WindowComponent> on List<T>{
void onGameResize(Vector2 canvasSize) {
forEach((element) => element.onGameResize(canvasSize));
}
void render(Canvas canvas) {
forEach((element) => element.render(canvas));
}
void update(double dt) {
forEach((element) => element.update(dt));
}
}
至此,主要節點的介紹便完結了,更多實現細節可以在項目中瞭解,謝謝閲讀。