一. 游戏介绍
使用 Flutter & Flame 模仿微信飞机大战.
源码: https://github.com/Flame-CN/biubiubiu.git
预览: 飞机大战体验地址
二.创建项目
创建项目
flutter create biubiubiu
1. 在pubspec.yaml
文件中添加依赖
flame: ^0.24.0
flame_scrolling_sprite: ^0.0.2
flame 游戏引擎
flame_scrolling_sprite 图片滚屏组件
2.添加资源
根目录创建assets
文件夹,将资源文件放入其中:
├─assets
│ ├─audio
│ ├─font
│ └─images
在 pubspec.yaml
中配置资源文件:
#~~~
assets:
- assets/audio/
- assets/images/
- assets/images/ui/
#~~~
3.初始化项目
删除test
文件夹下内容, 清空 ./lib/main.dart
内容并添加以下内容:
import 'package:flame/flame.dart';
import 'package:flutter/material.dart';
import 'biu_biu_game.dart';
void main() async{
WidgetsFlutterBinding.ensureInitialized();
//设置全屏
await Flame.util.fullScreen();
//设置屏幕方向只能竖屏显示
await Flame.util.setPortraitDownOnly();
//获取屏幕size
Size size = await Flame.util.initialDimensions();
//稍后创建BiuBiuGame类
runApp(BiuBiuGame(size).widget);
}
BiuBiuGame().widget
就是一个Flutter的widget,因此你可以把它放在Fluuter中的任何地方.
3.创建Game类
创建文件./lib/biu_biu_game.dart
import 'dart:ui';
import 'package:flame/game.dart';
class BiuBiuGame extends BaseGame {
// 适配屏幕 精灵大小的基本单位
double tileSize;
BiuBiuGame(Size size) {
resize(size);
}
@override
void resize(Size size) {
// 屏幕可显示9个精灵
tileSize = size.width / 9;
super.resize(size);
}
@override
Color backgroundColor() => Color(0xffc3c8c9);
}
这里重写backgroundColor()方法 设置背景色为
0xffc3c8c9
,与随后要使用的背景图片基色一致. 如果不这样设置,图片滚动时图片连接处会有一条缝隙.
运行 app 可看到如下效果:
4.创建背景
在./lib创建文件夹component
;然后在component文件夹下创建background.dart
import 'dart:ui';
import 'package:flame_scrolling_sprite/flame_scrolling_sprite.dart';
class Background extends ScrollingSpriteComponent {
Background(Size size, {double speed = 30, x = 0.0, y = 0.0})
: super(
x: x, //图片x方向偏移距离
y: y, //图片y方向偏移距离
scrollingSprite: ScrollingSprite(
spritePath: "background.png",
spriteWidth: 480,
spriteHeight: 700,
width: size.width,
height: size.height,
verticalSpeed: speed,
),
);
}
ScrollingSprite中用到的属性解释:
- spritePath: 背景图片地址
- spriteWidth: 背景图片的宽度
- spriteHeight: 背景图片的高度
- width: 滚动区域宽度
- height: 滚动区域高度
- verticalSpeed: 垂直滚动速度
在 ./lib/biu_biu_game.dart
中将 背景组件加入gmae中:
///...
BiuBiuGame(Size size) {
this.size = size;
//添加背景组件
add(Background(size));
}
///...
运行app可看到如下效果:
5.创建Player
创建./lib/component/player.dart
文件
import 'dart:ui';
import 'package:flame/animation.dart';
import 'package:flame/components/animation_component.dart';
import 'package:flame/components/component.dart';
import 'package:flame/components/mixins/has_game_ref.dart';
import 'package:flame/sprite.dart';
import '../biu_biu_game.dart';
class Player extends PositionComponent with HasGameRef<BiuBiuGame> {
Animation _live;
//player是否在移动
bool onMove = false;
//生命值
int life = 1;
Player({this.life = 1});
@override
void onMount() {
//player图片宽高为102*126 设置player 宽度为 1 tileSize
width = gameRef.tileSize;
height = 126 / 102 * gameRef.tileSize;
//设置player动画
_live = Animation.spriteList(
[
Sprite("me1.png"),
Sprite("me2.png"),
],
stepTime: 0.2,
);
}
@override
void render(Canvas c) {
prepareCanvas(c);
if (life >= 0) {
_live.getSprite().render(c,
width: width, height: height);
}
}
@override
void update(double t) {
super.update(t);
if (life >= 0) {
_live.update(t);
}
}
}
创建Player
类 , Player
继承PositionComponent
类,用来记录Player的位置,Plaer mixin
HasGameRef<BiuBiuGame>
,这样当我们在 BiuBiuGame
中调用add
方法添加我们的Player时,BiuBiuGame
会将BiuBiuGame
的引用赋值给 HasGameRef
中的gameRef
.这样我们可以很方便的在 Player
类中使用BiuBiuGame
.
因为我们需要根据BiuBiuGame
中的size
来计算Player的大小,所以需要在在onMount()
方法中初始化Player
.
我们的
BiubiuGame
继承了BaseGame
,当使用add
方法添加component
组件时,会对mixin
了HasGameRef<T>
和Resizable
等组件进行处理,然后调用component
组件的onMount()
方法.
在BiuBiuGame
中添加一个Player
:
class BiuBiuGame extends BaseGame {
...
Player player;
...
BiuBiuGame(Size size) {
....
add(player = Player()
..anchor = Anchor.center//设置player的中心点
//设置player的位置在宽度的中心,高度的1/4处
..setByPosition(Position(size.width / 2, size.height * 0.75)));
....
}
}
篇幅原因会在已有的类中新添加内容时使用
...
包裹新添加的内容,详细代码参照源码.
运行游戏可以看到下面画面:
控制player
的移动:
在Player
中添加void move(Offset offset)
方法:
void move(Offset offset) {
x += offset.dx;
//限制x轴移动距离防止超出屏幕
x = max(0.0, x);
x = min(gameRef.size.width, x);
y += offset.dy;
//限制y轴移动距离防止超出屏幕
y = max(0.0, y);
y = min(gameRef.size.height, y);
}
在BiuBiuGame
中添加拖动的手势控制:
class BiuBiuGame extends BaseGame with PanDetector {
...
@override
void onPanStart(DragStartDetails details) {
//拖动的起始点如果在player上,改变player移动状态
if (player.toRect().contains(details.globalPosition)) {
player.onMove = true;
}
}
@override
void onPanUpdate(DragUpdateDetails details) {
if (player.onMove) {
//拖动更新 移动player
player.move(details.delta);
}
}
@override
void onPanEnd(DragEndDetails details) {
//拖动结束时
if (player.onMove) {
onPanCancel();
}
}
@override
void onPanCancel() {
if (player.onMove) {
player.onMove = false;
}
}
...
}
运行游戏我们可以控制我们的player
了:
6. 发射子弹
创建./lib/component/bullet.dart
文件
import 'dart:ui';
import 'package:biubiubiu/biu_biu_game.dart';
import 'package:flame/anchor.dart';
import 'package:flame/components/component.dart';
import 'package:flame/components/mixins/has_game_ref.dart';
import 'package:flame/position.dart';
import 'package:flame/sprite.dart';
import 'package:flame/time.dart';
import 'package:flutter/cupertino.dart';
import 'player.dart';
class Bullet extends SpriteComponent {
//子弹的速度
double speed;
//子弹的伤害
double power;
//是否销毁
bool isDestroy = false;
Bullet({Position position, this.speed = 300.0, this.power = 1.0,String img="bullet1.png"}) {
setByPosition(position);
width = 5.0;
height = 11.0;
sprite = Sprite(img);
anchor = Anchor.center;
}
@override
void update(double dt) {
super.update(dt);
y -= speed * dt;
//子弹超出屏幕销毁
if (y < 0) {
isDestroy = true;
}
}
@override
bool destroy() => isDestroy;
}
在./lib/component/bullet.dart
中添加一个BulletFactory
class 用来产生子弹:
class BulletFactory extends Component with HasGameRef<BiuBiuGame> {
Player player;
Timer _timer;
double limit;
BulletFactory({this.limit = 1});
@override
void onMount() {
_timer = Timer(limit, repeat: true, callback: () {
gameRef.addLater(Bullet(position: gameRef.player.toPosition()));
});
_timer.start();
}
@override
void render(Canvas c) {}
@override
void update(double t) {
_timer.update(t);
}
}
flame提供了
Timer
类来执行定时任务,Timer
类接收三个参数:
limit
:必填参数 任务间隔时间 单位 秒repeat
: 可选默认false
callback
: 可选 需要执行的任务(回调函数)
给plaer装备武器系统--在./lib/component/player.dart
中添加 BulletFactory
:
class Player extends PositionComponent with HasGameRef<BiuBiuGame> {
...
BulletFactory _bulletFactory;
...
@override
void onMount() {
...
_bulletFactory = BulletFactory(limit: 0.3);
gameRef.add(_bulletFactory);
...
}
}
现在我们的player可以发射子弹了:
优化:
可以看到我们的bullet
是从plaer的上面发射出去的,我们可以重写int priority()
方法,指定 Player
的渲染顺序,返回的数值越大,越靠近上层。这里,我们的Player
返回了100.
在Bullet
中添加:
class Bullet extends SpriteComponent {
...
@override
int priority() => 10;
...
}
在Player
中添加:
class Player extends PositionComponent with HasGameRef<BiuBiuGame> {
...
@override
int priority() => 100;
...
}
7.创建敌人
创建./lib/component/enemy/enemy.dart
文件:
import 'dart:ui';
import 'package:flame/animation.dart';
import 'package:flame/components/component.dart';
import 'package:flame/components/mixins/has_game_ref.dart';
import '../../biu_biu_game.dart';
enum EnemyState { LIVING, HIT, DESTROY }
class Enemy extends PositionComponent with HasGameRef<BiuBiuGame> {
Animation livingAnimation;
Animation hitAnimation;
Animation destroyAnimation;
EnemyState state = EnemyState.LIVING;
int life;
int power;
double speed;
int score;
bool isDestroy = false;
Enemy({this.life = 1, this.power = 1, this.speed = 150, this.score = 1});
@override
void render(Canvas c) {
prepareCanvas(c);
switch (state) {
case EnemyState.LIVING:
livingAnimation?.getSprite()?.render(c, width: width, height: height);
break;
case EnemyState.HIT:
hitAnimation?.getSprite()?.render(c, width: width, height: height);
break;
case EnemyState.DESTROY:
destroyAnimation?.getSprite()?.render(c, width: width, height: height);
break;
}
}
@override
void update(double dt) {
super.update(dt);
switch (state) {
case EnemyState.LIVING:
livingAnimation?.update(dt);
break;
case EnemyState.HIT:
hitAnimation?.update(dt);
if (hitAnimation!=null&&hitAnimation.done()) {
state = EnemyState.LIVING;
}
break;
case EnemyState.DESTROY:
destroyAnimation?.update(dt);
if (destroyAnimation.done()) {
isDestroy = true;
}
break;
}
//战机生成后向下移动
y += speed * dt;
//超出屏幕销毁
if (y > gameRef.size.height + height) {
isDestroy = true;
}
}
void hurt(int power) {
life -= power;
if (life > 0) {
state = EnemyState.HIT;
} else {
state = EnemyState.DESTROY;
}
}
@override
bool destroy() => isDestroy;
@override
int priority() => 2;
}
敌机设计:
小飞机 | 战斗机 | 飞船 | |
---|---|---|---|
生命值 | 1 | 3 | 5 |
移动速度 | 5 | 3 | 2 |
分数 | 10 | 50 | 100 |
MiniPlane
./lib/component/enemy/mini_plane.dart
import 'package:flame/animation.dart';
import 'package:flame/sprite.dart';
import 'enemy.dart';
class MiniPlane extends Enemy {
MiniPlane({int score = 10}) : super(life: 1, score: score);
@override
void onMount() {
//57*43
width = gameRef.tileSize;
height = 43 / 57 * width;
//速度
speed = 5 * gameRef.tileSize;
livingAnimation = Animation.spriteList(
[
Sprite("enemy1.png"),
],
stepTime: 0.2,
loop: true,
);
destroyAnimation = Animation.spriteList(
[
Sprite("enemy1_down1.png"),
Sprite("enemy1_down2.png"),
Sprite("enemy1_down3.png"),
Sprite("enemy1_down4.png"),
],
stepTime: 0.2,
loop: false,
);
}
}
Warplane
./lib/component/enemy/warplane.dart
import 'package:flame/animation.dart';
import 'package:flame/sprite.dart';
import 'enemy.dart';
class Warplane extends Enemy {
Warplane({int score = 50}) : super(life: 3, score: score);
@override
void onMount() {
//69*95
width = 1.5 * gameRef.tileSize;
height = 95 / 69 * width;
speed = 3 * gameRef.tileSize;
livingAnimation = Animation.spriteList(
[
Sprite("enemy2.png"),
],
stepTime: 0.2,
loop: true,
);
hitAnimation = Animation.spriteList(
[
Sprite("enemy2_hit.png"),
],
stepTime: 0.2,
loop: false,
);
destroyAnimation = Animation.spriteList(
[
Sprite("enemy2_down1.png"),
Sprite("enemy2_down2.png"),
Sprite("enemy2_down3.png"),
Sprite("enemy2_down4.png"),
],
stepTime: 0.2,
loop: false,
);
}
}
ShipEnemy
./lib/component/enemy/ship_enemy.dart
import 'package:flame/animation.dart';
import 'package:flame/sprite.dart';
import 'enemy.dart';
class ShipEnemy extends Enemy {
ShipEnemy({int score = 100}) : super(life: 5, score: score);
@override
void onMount() {
//165*260
width = 3 * gameRef.tileSize;
height = 260 / 165 * width;
speed = 2 * gameRef.tileSize;
livingAnimation = Animation.spriteList(
[
Sprite("enemy3_n1.png"),
Sprite("enemy3_n2.png"),
],
stepTime: 0.2,
loop: true,
);
hitAnimation = Animation.spriteList(
[
Sprite("enemy3_hit.png"),
],
stepTime: 0.2,
loop: false,
);
destroyAnimation = Animation.spriteList(
[
Sprite("enemy3_down1.png"),
Sprite("enemy3_down2.png"),
Sprite("enemy3_down3.png"),
Sprite("enemy3_down4.png"),
Sprite("enemy3_down5.png"),
Sprite("enemy3_down6.png"),
],
stepTime: 0.2,
loop: false,
);
}
}
EnemyFactory
./lib/component/enemy/enemy_factory.dart
import 'dart:math';
import 'package:flame/components/mixins/has_game_ref.dart';
import 'package:flame/position.dart';
import 'package:flame/time.dart';
import 'package:flutter/cupertino.dart';
import '../../biu_biu_game.dart';
import 'enemy.dart';
import 'mini_plane.dart';
import 'ship_enemy.dart';
import 'warplane.dart';
class EnemyFactory with HasGameRef<BiuBiuGame> {
Timer _timer;
Random _random = Random();
EnemyFactory({@required BiuBiuGame game, double limit = 1}) {
gameRef = game;
_timer = Timer(limit, repeat: true, callback: () {
gameRef.addLater(generate());
});
_timer.start();
}
void update(double dt) {
_timer.update(dt);
}
Enemy generate() {
switch (_random.nextInt(3)) {
case 1:
return MiniPlane()..setByPosition(randomPosition(gameRef.tileSize, 43 / 57 * gameRef.tileSize));
break;
case 2:
return Warplane()..setByPosition(randomPosition(1.5 * gameRef.tileSize, (95 / 69) * 1.5 * gameRef.tileSize));
break;
default:
return ShipEnemy()..setByPosition(randomPosition(3 * gameRef.tileSize, (260 / 165) * 3 * gameRef.tileSize));
break;
}
}
//随机生成位置
Position randomPosition(double width, height) {
return Position(_random.nextDouble() * (gameRef.size.width - width), -height);
}
}
在./lib/biu_biu_game.dart
中生成敌人
class BiuBiuGame extends BaseGame with PanDetector {
...
EnemyFactory _enemyFactory;
...
BiuBiuGame(Size size) {
...
//添加敌人生产工厂组件
_enemyFactory = EnemyFactory(game: this);
...
}
@override
void update(double t) {
...
_enemyFactory?.update(t);
...
}
}
运行程序可以看到如下界面:
8.添加碰撞
在./lib/biu_biu_game.dart
中添加检测碰撞方法:
class BiuBiuGame extends BaseGame with PanDetector {
@override
void update(double t) {
...
//碰撞检测
collide();
...
}
...
void collide() {
var bullets = components.whereType<Bullet>().toList();
components.whereType<Enemy>().forEach((enemy) {
// player 和 enemy 之间的碰撞检测
if (enemy.state != EnemyState.DESTROY && player.life > 0 && player.toRect().overlaps(enemy.toRect())) {
//碰撞全部销毁
enemy.hurt(enemy.life);
player.hurt(player.life);
return;
}
// enemy 和 bullet 之间的碰撞检测
bullets.forEach((bullet) {
//当player生命值大于0,enemy状态不为DESTROY时,才进行bullet和enemy的碰撞检测
if (enemy.state != EnemyState.DESTROY && player.life > 0 && bullet.toRect().overlaps(enemy.toRect())) {
enemy.hurt(bullet.power);
bullet.isDestroy = true;
}
});
});
}
...
}
运行程序可以看到如下界面:
在游戏中我们会发现,有时我们的player并没有和enemy发生接触却被判定游戏失败.这是因为在碰撞检测中,我们使用的是PositionComponent
的 Rect toRect()
方法返回的矩形进行判断的,这导致我们的碰撞判定范围大于我们的图片显示范围.
开启 debugMode
我们可以清楚的看到原因:
在./lib/biu_biu_game.dart
添加:
class BiuBiuGame extends BaseGame with PanDetector, HasWidgetsOverlay {
...
@override
bool debugMode() => true;
...
}
重启游戏我们看到:
碰撞优化:
这里我们进行一个简单的优化,使用Rect
中的deflate()
方法来缩小用于碰撞检测的矩形 .
修改./lib/biu_biu_game.dart
中的collide()
方法:
void collide() {
var bullets = components.whereType<Bullet>().toList();
components.whereType<Enemy>().forEach((enemy) {
// player 和 enemy 之间的碰撞检测
if (enemy.state != EnemyState.DESTROY &&
player.life > 0 &&
player.toRect().deflate(0.1 * player.width).overlaps(enemy.toRect().deflate(0.1 * enemy.width))) {
//碰撞全部销毁
enemy.hurt(enemy.life);
player.hurt(player.life);
gameOver();
return;
}
// enemy 和 bullet 之间的碰撞检测
bullets.forEach((bullet) {
//当player生命值大于0,enemy状态不为DESTROY时,才进行bullet和enemy的碰撞检测
if (enemy.state != EnemyState.DESTROY && player.life > 0 && bullet.toRect().overlaps(enemy.toRect())) {
enemy.hurt(bullet.power);
bullet.isDestroy = true;
//当enemy被击毁时,计算得分
if (enemy.state == EnemyState.DESTROY) {
score += enemy.score;
}
}
});
});
}
9.记录分数
在./lib/biu_biu_game.dart
中添加 score
用于记录分数,添加TextComponent
用于显示结果:
class BiuBiuGame extends BaseGame with PanDetector {
...
int score = 0;
TextComponent scoreComponent;
...
...
BiuBiuGame(Size size) {
...
scoreComponent = TextComponent("SCORE $score", config: TextConfig(color: Color(0xffffffff)))
..x = 10
..y = 10;
...
}
...
@override
void render(Canvas canvas) {
super.render(canvas);
//最后渲染scoreComponent,使其在最上层
scoreComponent.render(canvas);
}
@override
void update(double t) {
super.update(t);
//生成敌人
_enemyFactory?.update(t);
//检测碰撞
collide();
//计算分数
scoreComponent.text = "SCORE $score";
}
void collide() {
var bullets = components.whereType<Bullet>().toList();
components.whereType<Enemy>().forEach((enemy) {
// player 和 enemy 之间的碰撞检测
if (enemy.state != EnemyState.DESTROY && player.life > 0 && player.toRect().overlaps(enemy.toRect())) {
//碰撞全部销毁
enemy.hurt(enemy.life);
player.hurt(player.life);
return;
}
// enemy 和 bullet 之间的碰撞检测
bullets.forEach((bullet) {
if (enemy.state != EnemyState.DESTROY && bullet.toRect().overlaps(enemy.toRect())) {
enemy.hurt(bullet.power);
bullet.isDestroy = true;
//当enemy被击毁时,计算得分
if (enemy.state == EnemyState.DESTROY) {
score += enemy.score;
}
}
});
});
}
10.游戏结果展示
player
销毁后展示玩家最终得分并启动一个计时器3秒后重新开始游戏.
把./lib/biu_biu_game.dart
构造方法中的内容提取到init()
方法中,添加一个Timer restarTimer
.
完整的./lib/biu_biu_game.dart
文件中的内容:
import 'dart:ui';
import 'package:flame/anchor.dart';
import 'package:flame/components/text_component.dart';
import 'package:flame/game.dart';
import 'package:flame/gestures.dart';
import 'package:flame/position.dart';
import 'package:flame/text_config.dart';
import 'package:flame/time.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'component/background.dart';
import 'component/bullet.dart';
import 'component/enemy/enemy.dart';
import 'component/enemy/enemy_factory.dart';
import 'component/player.dart';
// mixin HasWidgetsOverlay 可以添加flutter的widget.
class BiuBiuGame extends BaseGame with PanDetector, HasWidgetsOverlay {
double tileSize;
Player player;
EnemyFactory _enemyFactory;
int score;
TextComponent scoreComponent;
Timer restartTimer;
BiuBiuGame(Size size) {
resize(size);
init();
}
void init() {
components.clear();
score=0;
//添加背景组件
add(Background(size));
add(player = Player()
..anchor = Anchor.center
..setByPosition(Position(size.width / 2, size.height * 0.75)));
//添加敌人生产工厂组件
_enemyFactory = EnemyFactory(game: this);
scoreComponent = TextComponent("SCORE $score", config: TextConfig(color: Color(0xffffffff)))
..x = 10
..y = 10;
restartTimer = Timer(3.0, callback: () {
removeWidgetOverlay("gameOver");
init();
});
}
void gameOver() {
restartTimer.start();
addWidgetOverlay(
"gameOver",
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"您的最终分数:$score",
style: TextStyle(
color: Color(0xffffffff),
fontSize: 24,
fontWeight: FontWeight.w700,
),
),
],
),
));
}
@override
void resize(Size size) {
tileSize = size.width / 9;
super.resize(size);
}
@override
void render(Canvas canvas) {
super.render(canvas);
scoreComponent.render(canvas);
}
@override
void update(double t) {
super.update(t);
//生成敌人
_enemyFactory?.update(t);
restartTimer.update(t);
//检测碰撞
collide();
//计算分数
scoreComponent.text = "SCORE $score";
}
@override
void onPanUpdate(DragUpdateDetails details) {
if (player.toRect().contains(details.globalPosition)) {
player.move(details.delta);
}
}
void collide() {
var bullets = components.whereType<Bullet>().toList();
components.whereType<Enemy>().forEach((enemy) {
// player 和 enemy 之间的碰撞检测
if (enemy.state != EnemyState.DESTROY && player.life > 0 && player.toRect().overlaps(enemy.toRect())) {
//碰撞全部销毁
enemy.hurt(enemy.life);
player.hurt(player.life);
gameOver();
return;
}
// enemy 和 bullet 之间的碰撞检测
bullets.forEach((bullet) {
//当player生命值大于0,enemy状态不为DESTROY时,才进行bullet和enemy的碰撞检测
if (enemy.state != EnemyState.DESTROY && player.life > 0 && bullet.toRect().overlaps(enemy.toRect())) {
enemy.hurt(bullet.power);
bullet.isDestroy = true;
//当enemy被击毁时,计算得分
if (enemy.state == EnemyState.DESTROY) {
score += enemy.score;
}
}
});
});
}
@override
Color backgroundColor() => Color(0xffc3c8c9);
@override
bool debugMode() => false;
}
BiuBiuGame类mixin了 HasWidgetsOverlay.这个mixin类让我们可以方便的添加Flutter的widget到Game类中. Flame底层使用了
Stack
来展示addWidgetOverlay
添加的widget.Stack(children: [widget.gameChild, ..._overlays.values.toList()]));
运行游戏可以看到:
显示游戏FPS值:
BaseGame
中提供了double fps()
这个方法获取fps值的方法,但是需要bool recordFps()
方法返回true
才进行记录 :
在./lib/biu_biu_game.dart
添加:
class BiuBiuGame extends BaseGame with PanDetector, HasWidgetsOverlay {
...
@override
void update(double t) {
...
if(recordFps()){
scoreComponent.text+="\nFPS ${fps().toStringAsFixed(2)}";
}
...
}
@override
bool recordFps() =>true;
...
}
11.适配web
因为web端还未在Flutter正式版支持,需要切换到Flutter beta 版本,参考文章使用 Flutter 构建 Web 应用将Flutter切换到 beta版然后改造我们的项目:
修改./lib/main.dart
:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
//设置全屏 屏幕方向这些操作web端不支持需要进行判断
if (!kIsWeb) {
//设置全屏
await Flame.util.fullScreen();
//设置屏幕方向只能竖屏显示
await Flame.util.setPortraitDownOnly();
}
//获取屏幕size
final Size size = await Flame.util.initialDimensions();
//稍后创建BiuBiuGame类
runApp(BiuBiuGame(size).widget);
}
修改./lib/biu_biu_game.dart
:
@override
void resize(Size size) {
//tileSize 设定一个最大值50.0
tileSize = min(size.width / 9, 50.0);
super.resize(size);
}