一. 游戏介绍 使用 Flutter & Flame 模仿微信飞机大战.
源码: https://github.com/Flame-CN/biubiubiu.git
预览: 飞机大战体验地址
二.创建项目 创建项目
1 flutter create biubiubiu
1.添加依赖 在pubspec.yaml
文件中添加:
1 2 flame: ^0.24.0 flame_scrolling_sprite: ^0.0.2
flame 游戏引擎 flame_scrolling_sprite 图片滚屏组件
2.添加资源 根目录创建assets
文件夹,将资源文件放入其中:
1 2 3 4 ├─assets │ ├─audio │ ├─font │ └─images
在 pubspec.yaml
中配置资源文件:
1 2 3 4 5 6 assets: - assets/audio/ - assets/images/ - assets/images/ui/
3.初始化项目 删除test
文件夹下内容, 清空 ./lib/main.dart
内容并添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 = await Flame.util.initialDimensions(); runApp(BiuBiuGame(size).widget); }
BiuBiuGame().widget
就是一个Flutter的widget,因此你可以把它放在Fluuter中的任何地方.
3.创建Game类 创建文件./lib/biu_biu_game.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import 'dart:ui' ;import 'package:flame/game.dart' ;class BiuBiuGame extends BaseGame { double tileSize; BiuBiuGame(Size size) { resize(size); } @override void resize(Size size) { tileSize = size.width / 9 ; super .resize(size); } @override Color backgroundColor() => Color(0xffc3c8c9 ); }
这里重写backgroundColor()方法 设置背景色为0xffc3c8c9
,与随后要使用的背景图片基色一致. 如果不这样设置,图片滚动时图片连接处会有一条缝隙.
运行 app 可看到如下效果:
4.创建背景 在./lib创建文件夹component
;然后在component文件夹下创建background.dart
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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, 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中:
1 2 3 4 5 6 7 BiuBiuGame(Size size) { this .size = size; add(Background(size)); }
运行app可看到如下效果:
5.创建Player 创建./lib/component/player.dart
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 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; bool onMove = false ; int life = 1 ; Player({this .life = 1 }); @override void onMount() { width = gameRef.tileSize; height = 126 / 102 * gameRef.tileSize; _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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 class BiuBiuGame extends BaseGame { ... Player player; ... BiuBiuGame(Size size) { .... add(player = Player() ..anchor = Anchor.center ..setByPosition(Position(size.width / 2 , size.height * 0.75 ))); .... } }
篇幅原因会在已有的类中新添加内容时使用...
包裹新添加的内容,具体代码参照源码.
运行游戏可以看到下面画面:
控制player
的移动:
在Player
中添加void move(Offset offset)
方法:
1 2 3 4 5 6 7 8 9 10 void move(Offset offset) { x += offset.dx; x = max(0.0 , x); x = min(gameRef.size.width, x); y += offset.dy; y = max(0.0 , y); y = min(gameRef.size.height, y); }
在BiuBiuGame
中添加拖动的手势控制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class BiuBiuGame extends BaseGame with PanDetector { ... @override void onPanStart(DragStartDetails details) { if (player.toRect().contains(details.globalPosition)) { player.onMove = true ; } } @override void onPanUpdate(DragUpdateDetails details) { if (player.onMove) { 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
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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 用来产生子弹:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 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
中添加:
1 2 3 4 5 6 class Bullet extends SpriteComponent { ... @override int priority() => 10 ; ... }
在Player
中添加:
1 2 3 4 5 6 class Player extends PositionComponent with HasGameRef <BiuBiuGame > { ... @override int priority() => 100 ; ... }
7.创建敌人 创建./lib/component/enemy/enemy.dart
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 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() { 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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() { 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 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() { 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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
中生成敌人
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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
中添加检测碰撞方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 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) { if (enemy.state != EnemyState.DESTROY && player.life > 0 && player.toRect().overlaps(enemy.toRect())) { enemy.hurt(enemy.life); player.hurt(player.life); return ; } bullets.forEach((bullet) { 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
添加:
1 2 3 4 5 6 class BiuBiuGame extends BaseGame with PanDetector , HasWidgetsOverlay { ... @override bool debugMode() => true ; ... }
重启游戏我们看到:
碰撞优化:
这里我们进行一个简单的优化,使用Rect
中的缩小碰撞的矩形
9.记录分数 在./lib/biu_biu_game.dart
中添加 score
用于记录分数,添加TextComponent
用于显示结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 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.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) { if (enemy.state != EnemyState.DESTROY && player.life > 0 && player.toRect().overlaps(enemy.toRect())) { enemy.hurt(enemy.life); player.hurt(player.life); return ; } bullets.forEach((bullet) { if (enemy.state != EnemyState.DESTROY && bullet.toRect().overlaps(enemy.toRect())) { enemy.hurt(bullet.power); bullet.isDestroy = true ; if (enemy.state == EnemyState.DESTROY) { score += enemy.score; } } }); }); }
10.游戏结果展示 player
销毁后展示玩家最终得分并启动一个计时器3秒后重新开始游戏.
把./lib/biu_biu_game.dart
构造方法中的内容提取到init()
方法中,添加一个Timer restarTimer
.
完整的./lib/biu_biu_game.dart
文件中的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 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' ;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) { if (enemy.state != EnemyState.DESTROY && player.life > 0 && player.toRect().overlaps(enemy.toRect())) { enemy.hurt(enemy.life); player.hurt(player.life); gameOver(); return ; } bullets.forEach((bullet) { if (enemy.state != EnemyState.DESTROY && player.life > 0 && bullet.toRect().overlaps(enemy.toRect())) { enemy.hurt(bullet.power); bullet.isDestroy = true ; 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.
1 Stack(children: [widget.gameChild, ..._overlays.values.toList()]));
运行游戏可以看到:
显示游戏FPS值:
BaseGame
中提供了double fps()
这个方法获取fps值的方法,但是需要bool recordFps()
方法返回true
才进行记录 :
在./lib/biu_biu_game.dart
添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void main() async { WidgetsFlutterBinding.ensureInitialized(); if (!kIsWeb) { await Flame.util.fullScreen(); await Flame.util.setPortraitDownOnly(); } final Size size = await Flame.util.initialDimensions(); runApp(BiuBiuGame(size).widget); }
修改./lib/biu_biu_game.dart
:
1 2 3 4 5 6 @override void resize(Size size) { tileSize = min(size.width / 9 , 50.0 ); super .resize(size); }