Flutter&Flame仿微信飞机大战

一. 游戏介绍

使用 Flutter & Flame 模仿微信飞机大战.

源码: https://github.com/Flame-CN/biubiubiu.git

预览: 飞机大战体验地址

image

二.创建项目

创建项目


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 可看到如下效果:

image

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可看到如下效果:

image

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 {



  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中调用add方法添加我们的Player时,BiuBiuGame会将BiuBiuGame的引用赋值给 HasGameRef中的gameRef.这样我们可以很方便的在 Player类中使用BiuBiuGame.

​ 因为我们需要根据BiuBiuGame 中的size 来计算Player的大小,所以需要在在onMount()方法中初始化Player.

我们的BiubiuGame继承了BaseGame ,当使用add方法添加 component组件时,会对mixinHasGameRefResizable等组件进行处理,然后调用 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)));

    ....

  }

}

篇幅原因会在已有的类中新添加内容时使用...包裹新添加的内容,详细代码参照源码.

运行游戏可以看到下面画面:

image

控制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了:

image

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 {

  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 {

  ...

  BulletFactory _bulletFactory;

  ...



  @override

  void onMount() {

  ...

    _bulletFactory = BulletFactory(limit: 0.3);

    gameRef.add(_bulletFactory);

  ...

  }

}

现在我们的player可以发射子弹了:

image

优化:

​ 可以看到我们的bullet是从plaer的上面发射出去的,我们可以重写int priority()方法,指定 Player的渲染顺序,返回的数值越大,越靠近上层。这里,我们的Player返回了100.

Bullet中添加:


class Bullet extends SpriteComponent {

    ...

    @override

  int priority() => 10;

    ...

}

Player中添加:


class Player extends PositionComponent with HasGameRef {

  ...

  @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 {

  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 {

  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);

    ...

  }

}

运行程序可以看到如下界面:

image

8.添加碰撞

./lib/biu_biu_game.dart中添加检测碰撞方法:


class BiuBiuGame extends BaseGame with PanDetector {



    @override

    void update(double t) {

        ...

        //碰撞检测

        collide();

        ...

    }



    ...

    void collide() {

    var bullets = components.whereType().toList();

    components.whereType().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;

        }

      });

    });

  }

    ...

}

运行程序可以看到如下界面:

image

在游戏中我们会发现,有时我们的player并没有和enemy发生接触却被判定游戏失败.这是因为在碰撞检测中,我们使用的是PositionComponentRect toRect()方法返回的矩形进行判断的,这导致我们的碰撞判定范围大于我们的图片显示范围.

开启 debugMode 我们可以清楚的看到原因:

./lib/biu_biu_game.dart添加:


class BiuBiuGame extends BaseGame with PanDetector, HasWidgetsOverlay {

    ...

    @override

bool debugMode() => true;

    ...

}

重启游戏我们看到:

image

碰撞优化:

这里我们进行一个简单的优化,使用Rect中的deflate()方法来缩小用于碰撞检测的矩形 .

​ 修改./lib/biu_biu_game.dart中的collide()方法:


void collide() {

    var bullets = components.whereType().toList();

    components.whereType().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().toList();

    components.whereType().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().toList();

    components.whereType().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()]));

运行游戏可以看到:

image

显示游戏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);

  }

你可能感兴趣的:(Flutter&Flame仿微信飞机大战)