背景
收到一個遊戲開發的需求,因為雙端上線所以需要支援iOS Android兩端.
3個Android RD,沒有接觸過遊戲開發,快速學習上手Flutter支援雙端.
計劃遊戲使用Flame框架,先大題熟悉下環境然後在開發的過程中學習和解決問題.
知識點
GameLoop是什麼
遊戲迴圈就是搭建一個遊戲的腳手架.大部分遊戲只需要2個方法即:
- render方法 用來繪製
- update方法 用來更新繪製引數. update當前幀和上一幀的差異.
比如表現一個正在下墜的圓 圓的座標用y表示. render方法負責拿著y值去繪製圓, update方法負責更新y的變化. 互相配合完成了一個圓的下墜動畫
如何把一個Flutter元件展示在Frame引擎上
利用 HasWidgetsOverlay 介面.
- 用
removeWidgetOverlay
方法移出Flutter元件 - 用
addWidgetOverlay
方法新增Flutter元件
class MyGame extends BaseGame with HasWidgetsOverlay, TapDetector {
Size size;
SpriteC spriteC;
bool isPaused = false;
MyGame(this.size) {
spriteC = SpriteC.create();
}
@override
void onTap() {
super.onTap();
if (isPaused) {
removeWidgetOverlay('pausemenu');
isPaused = false;
} else {
addWidgetOverlay(
'pausemenu',
Center(
child: Container(
width: 100,
height: 100,
color: Colors.blue[300],
child: const Center(child: const Text('Pause')),
),
));
isPaused = true;
}
}
}
複製程式碼
如何把遊戲展示在Flutter的一個元件上
Game提供一個Widget物件,可以直接獲取最為Flutter的一個Widget使用.
final game = MyGame(size);
runApp(game.widget);
複製程式碼
Debug功能是什麼
@override
bool debugMode() => true;
@override
bool recordFps() => true;
複製程式碼
開啟Debug.
Debug就是允許除錯,會自動列印元件的位置.
recordFps就是輸出當前執行遊戲的FPS.
元件(Components)
必要性: 就跟在Android/iOS上糊頁面一樣. 所有的頁面,動畫,特效都可以用Canvas來畫, 實際上開發一般情況下用的卻是ImageView,TextView,ListView這樣的元件來完成. 沒有人喜歡刀耕火種
元件重要方法:
除了render 和 update這種通用方法.
// 初始化
@override
void onMount() {
super.onMount();
}
// 回收前
@override
void onDestroy() {
super.onDestroy();
}
// 還沒搞清楚...? 好像在說繪製的位置跟裝置之間的關係
@override
bool isHud() {
return true;
}
// 標記可回收
@override
bool destroy() {
return false;
}
複製程式碼
怎麼畫幀動畫
即把
變成遊戲裡面的人都是會動的,所以這是一個重要知識點.
在遊戲裡面展示 AnimationComponent
const textureWidth = 96.0;
const textureHeight = 96.0;
add(AnimationComponent.sequenced(
textureWidth * 2,
textureHeight * 2,
'minotaur.png',
19,
textureWidth: textureWidth,
textureHeight: textureHeight,
loop: true,
stepTime: 0.15,
));
複製程式碼
其中函式 AnimationComponent.sequenced 的引數說明如下:
AnimationComponent.sequenced(
double width, // 展示的寬度
double height, // 展示的高度
String imagePath,
int amount, { // Sprit Sheet的幀數
int amountPerRow, // 多行切圖 兩行之間的間隔
double textureX = 0.0, // 展示的偏移值
double textureY = 0.0,
double textureWidth, // 切圖一幀的寬度
double textureHeight, // 切圖一幀的高度
double stepTime, // 播放間隔時間
bool loop = true, // 是否迴圈播放
this.destroyOnFinish = false, // 播放完了是否銷燬
})
複製程式碼
提供給Flutter元件展示 AnimationWidget
await Flame.images.load('minotaur.png');
final _animationSpriteSheet = SpriteSheet(
imageName: 'minotaur.png',
columns: 19,
rows: 1,
textureWidth: 96,
textureHeight: 96,
);
_animation = _animationSpriteSheet.createAnimation(
0,
stepTime: 0.2,
to: 5,
);
// 使用
Container(
width: 200,
height: 200,
child: AnimationWidget(animation: _animation),
),
複製程式碼
怎麼畫SVG動畫 SvgComponent
Svg svg = Svg('android.svg');
SvgComponent android = SvgComponent.fromSvg(100, 100, svg);
android.x = 100;
android.y = 100;
複製程式碼
怎麼畫組合控制元件 ComposedComponent
和原生組合控制元件相似的做法.
class GameOverPanel extends PositionComponent
with Resizable, HasGameRef, Tapable, ComposedComponent {
GameOverPanel(Image spriteImage) : super() {
gameOverText = GameOverText(spriteImage);
gameOverRestart = GameOverRestart(spriteImage);
components..add(gameOverText)..add(gameOverRestart);
}
bool visible = false;
GameOverText gameOverText;
GameOverRestart gameOverRestart;
@override
void render(Canvas canvas) {
if (visible) {
super.render(canvas);
}
}
}
class GameOverText extends SpriteComponent with Resizable {
GameOverText(Image spriteImage)
: super.fromSprite(
GameOverConfig.textWidth,
GameOverConfig.textHeight,
Sprite.fromImage(
spriteImage,
x: 955.0,
y: 26.0,
width: GameOverConfig.textWidth,
height: GameOverConfig.textHeight,
),
);
@override
void resize(Size size) {
if (width > size.width * 0.8) {
width = size.width * 0.8;
}
y = size.height * .25;
x = (size.width / 2) - width / 2;
}
}
class GameOverRestart extends SpriteComponent with Resizable {
GameOverRestart(Image spriteImage)
: super.fromSprite(
GameOverConfig.restartWidth,
GameOverConfig.restartHeight,
Sprite.fromImage(
spriteImage,
x: 2.0,
y: 2.0,
width: GameOverConfig.restartWidth,
height: GameOverConfig.restartHeight,
),
);
@override
void resize(Size size) {
y = size.height * .75;
x = (size.width / 2) - GameOverConfig.restartWidth / 2;
![](https://user-gold-cdn.xitu.io/2020/6/19/172c89c8d5d4aff0?w=304&h=640&f=gif&s=3257550)
}
}
複製程式碼
怎麼繪製視差動畫 ParallaxComponent
簡單來說就是怎麼把
組合起來生成
final images = [
ParallaxImage("bg.png"),
ParallaxImage("mountain-far.png"),
ParallaxImage("mountains.png"),
ParallaxImage("trees.png"),
ParallaxImage("foreground-trees.png"),
];
final parallaxComponent = ParallaxComponent(images,
baseSpeed: const Offset(20, 0), layerDelta: const Offset(100, 0));
複製程式碼
月亮&天空離我們遠所以移動得慢一些
樹木離我們近所以移動得快一些
組合起來很神奇,這個效果我很喜歡.TOT
怎麼畫可拉伸圖片(Android的.9.png圖) NineTileBox
如下例:
// 定義
final sprite = Sprite('nine-box.png');
nineTileBox = NineTileBox(sprite, tileSize: 8, destTileSize: 24);
// 使用
nineTileBox.draw(canvas, x, y, 200, 500);
複製程式碼
兩個角各佔4個畫素,合起來就是8個畫素, 圖片總共24個畫素. 這就是上面得引數得含義.
UE切圖的話需要保持圖片尺寸是3的倍數,另外這裡比起.9.png有個缺點是他必須保證4個角是對稱的. 對於非對稱,比如上面8個畫素 下面只需要4個畫素的圖片不支援.
物理引擎 Box2DComponent
Flame支援Box2D.即元件 Box2DComponent.我理解就是可以實現物理體積 和 碰撞的能力.
這是一個很Powerful得元件,應該會有些深度,需要後面花時間仔細研究下.這裡先熟悉他的作用範圍就行.
網站:box2d.org/
Box2D是一款免費的開源二維物理引擎,由Erin Catto使用C++編寫,在zlib授權下發布。它已被用於蠟筆物理學、憤怒的小鳥、地獄邊境、Rolando、Fantastic ...