这还能叫Flutter?用它复原一个叫《是男人就坚持100秒》的游戏|技术点评

·  阅读 2911
这还能叫Flutter?用它复原一个叫《是男人就坚持100秒》的游戏|技术点评

前言

我说Flutter的跨端一致性并非首创

但凡你用过Unity3D,Unreal等游戏引擎,你就会发现 这些不都是跨端的吗?不都是开局一块画布,剩下随便整么?

既然和游戏引擎作对比

那么我们真的拿Flutter来做个游戏吧

FLAME引擎

image.png 这里我用了一款基于的Flutter的游戏引擎,名为FLAME。使用它开发游戏,基本用不到Flutter里的那些Widget,所以就有了标题里的提问

一个Flutter应用不使用Flutter的Widget,那还叫Flutter吗?

不管了,先把这篇文章写完,抽空再用widget重做一次这个游戏

游戏引擎基础

引入Flame

dependencies:
  flame: ^1.0.0-rc5
复制代码

开发游戏,最基础就是这三件事

  • 刷新机制(update)
  • 渲染画布(renderer)
  • 输入事件(events)

Flame中,一个基础的游戏框架代码如下

class MyGameSubClass extends Game {
  @override
  void render(Canvas canvas) {
    // TODO: implement render
  }

  @override
  void update(double t) {
    // TODO: implement update
  }
}
    
  
main() {
  runApp(
    GameWidget(
      game: MyGameSubClass(),
    )
  );
}
复制代码

刷新,渲染都有了,事件呢,在本游戏中我们加入拖动事件

class MyGameSubClass extends BaseGame with PanDetector
复制代码

渲染图形元素

  • 飞机

image.png

飞机的图形素材是一个精灵图,其中前面两帧是飞行状态,后面三帧是爆炸状态

playerSpriteSheet = SpriteSheet(
  image: await images.load('player.png'),
  srcSize: playerSize,
);

//运行状态下的精灵图动画
player = SpriteAnimationComponent(
  size: playerSize,
  animation: playerSpriteSheet.createAnimation(row: 0, stepTime: .1, to: 2),
);
复制代码

然后我们通过add方法,将图形元素展示到画面中

add(player);

//放到x:100,y:100的坐标,大家感受一下坐标系
player.x=player.y=100;
复制代码

image.png

  • 子弹

image.png

bulletImage = await images.load('bullet.png');
复制代码

一颗子弹的出现逻辑以及生命周期是这样的

  1. 从屏幕外生成
  2. 向着飞机当前坐标的方向移动
  3. 移出屏幕即销毁
//添加一颗子弹
void addBullet() {
final bullet = SpriteComponent.fromImage(bulletImage, size: bulletSize);

double bulletX;
double bulletY;

//随机在显示区四周的矩形边上生成
if (Random().nextBool()) {
  bulletX = Random().nextDouble() * (screenSize.width + bulletSize.x) -
      bulletSize.x;
  bulletY = Random().nextBool() ? -bulletSize.y : screenSize.height;
} else {
  bulletX = Random().nextBool() ? -bulletSize.x : screenSize.width;
  bulletY = Random().nextDouble() * (screenSize.height + bulletSize.y) -
      bulletSize.y;
}

//给与子弹初始坐标
bullet.x = bulletX;
bullet.y = bulletY;

//添加到场景中
add(bullet);

//加入bullet管理数组,稍后用于更新子弹飞行坐标以及碰撞检测
bullets.add({
  "component": bullet,//子弹控件实例
  "speed": (1+gameTime/10) + Random().nextDouble()*3,//飞行速度
  "angle": atan2(((bulletY + bulletSize.y/2) - (player.y + playerSize.y / 2)),
      ((bulletX + bulletSize.x) - (player.x + playerSize.x / 2)))
});//向量角度
}
复制代码

通过调用这个addBullet的方法,我们就能在屏幕中朝着飞机所在位置发射一颗子弹了

2021-03-13 16_36_49.gif

为了发射多颗,我们再新建一个函数addGroupBullet

//添加一组子弹
void addGroupBullet() {
    int groupCount = 10+Random().nextInt(gameTime+1);
    for (int i = 0; i < groupCount; i++) {
      addBullet();
    }
}
复制代码

刷新机制

Flame提供的update方法中我们需要更新所有子弹的位置,因为子弹不是静止的,需要一直移动呀!

@override
void update(double dt) {
    super.update(dt);
    
    //遍历子弹数组,对所有子弹进行更新
    for (int i = bullets.length - 1; i >= 0; i--) {
          var bulletItem = bullets[i];
          
          //获得子弹实例
          SpriteComponent bullet = bulletItem["component"] as SpriteComponent;

          double angle = bulletItem["angle"];
          double speed = bulletItem["speed"];

          //让子弹根据发射时的向量角度进行移动
          bullet.x -= cos(angle) * speed;
          bullet.y -= sin(angle) * speed;
          
          //当子弹移动到屏幕外时销毁它
          if (isNotInScreen(bullet.x, bullet.y)) {
                print("bullet removed");
                remove(bullet);
                bullets.removeAt(i);
                continue;
          }
    }
}
复制代码

开始有点带感了吧?

2021-03-13 16_47_52.gif

飞机的移动

void onPanUpdate(DragUpdateDetails details) {
    super.onPanUpdate(details);

    if (!isGameStart) return;

    //由于飞机对象的坐标点在左上角,所以移动是要注意偏移一下
    player.x = details.globalPosition.dx - playerSize.x / 2;
    player.y = details.globalPosition.dy - playerSize.y / 2;
}
复制代码

2021-03-13 16_52_06.gif

碰撞检测

在每次刷新子弹坐标的时候,去检测一下是否和当前飞机的坐标重合了,重合了游戏就结束啦

if (isHitPlayer(bullet.x, bullet.y)) {
    gameOver();
}
复制代码

这里用了一个简单的判断机制,并没有精确到飞机的外轮廓。一是精确到外轮廓将带来更大的运算量,也比较复杂。二是那样的游戏体验不见得好,毕竟在手机玩的话,手指要挡住大部分飞机的图形

//飞机和子弹碰撞判断
bool isHitPlayer(double x, double y) {
    double _x = (x + bulletSize.x / 2 - (player.x + playerSize.x / 2)).abs();
    double _y = (y + bulletSize.y / 2 - (player.y + playerSize.y / 2)).abs();

    //求出子弹和飞机坐标的直线距离
    double distance = sqrt(_x * _x + _y * _y);

    //当直线距离小于判定为碰撞的距离时则返回true/false
    if (distance <= hitDistance) return true;
    return false;
}
复制代码

游戏结束

void gameOver() {
    isGameStart = false;
    
    //取消定时发射子弹的定时器
    if(timer!=null)timer.cancel();

    //播放飞机爆炸动画
    player.animation = playerSpriteSheet.createAnimation(
        row: 0, stepTime: .1, loop: false, from: 2, to: 6);

    print("game over");
}
复制代码

代码仓库

github.com/ezshine/flu…

后话

这么写,从代码层面看,和Flutter关系不大,但利用Flutter的跨端能力,我们可以很容易的开发一个横跨Mac,Linux,Windows,Web,iOS,Android端的小游戏。如果你不会用那些开发游戏的IDE,比如Unity3D等,那Flutter也是一个不错的选择。

关注大帅

一个设计师出身的老程序猿,希望写代码到70岁


近期文章(感谢掘友的鼓励与支持🌹🌹🌹)


本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情

分类:
前端
标签: