使用Flutter Widget开发游戏”是男人就坚持100秒“,一套代码横跨6端~

4,762 阅读3分钟

前言

之前使用Flutter里的游戏引擎Flare已经开发了一版这个游戏,原文地址:juejin.cn/post/693906… 。在文章里我说要用Widget再来做一次。现在兑现我的承诺,并且上周日在B站直播了整个开发过程

📺点击查看直播视频

在Flutter里展示Sprite动画

请看这篇文章《手写一个在Flutter里展示”精灵图“的Widget》

飞机的移动

首先将飞机放置在画面正中,由于Widget的原点统一为左上角,所以要减去飞机图像宽和高的一半。

//获得画布的宽高
Size screenSize = window.physicalSize/window.devicePixelRatio;

//将飞机的x,y坐标设定为画面中心
playerLeft = screenSize.width/2-66/2;
playerTop = screenSize.height/2-82/2;

飞机我们需要捕获到用户的手势事件,使用GestureDetector这个Widget来拖动飞机。

GestureDetector( 
  onPanUpdate: (DragUpdateDetails details) {
    setState(() {
      playerLeft += details.delta.dx;
      playerTop += details.delta.dy;
    });
  },
  child://飞机的Widget
}

2021-04-12 16_29_13.gif

设定FPS

由于没有使用游戏引擎,所以只好自己通过定时器来实现。比如我们要实现60FPS的刷新率,可以将定时器设置为17毫秒,这样的话刷新率约等于59fps。当然可以更精确一些,但没有那个必要。

Timer.periodic(Duration(milliseconds: 17), (timer) {
  gameloop();
});

gameloop(){
    setState(() {
        //触发build方法
    });
}

不过我这里建议设置为每20毫秒刷新一次,原因在后面会讲。

添加子弹

我们建立一个子弹管理数组,将所有子弹的数据都放在数组中

List bulletsData = [];

addBullet(){
    double bulletX;
    double bulletY;

    if (Random().nextBool()) {
      bulletX = Random().nextDouble() * (screenSize.width + bulletSize.width) -
          bulletSize.width;
      bulletY = Random().nextBool() ? -bulletSize.height : screenSize.height;
    } else {
      bulletX = Random().nextBool() ? -bulletSize.width : screenSize.width;
      bulletY = Random().nextDouble() * (screenSize.height + bulletSize.height) -
          bulletSize.height;
    }

    bulletsData.add({
      "x":bulletX,
      "y":bulletY,
      "speed": (1+gameTime/10) + Random().nextDouble()*3,
      "angle": atan2(((bulletY + bulletSize.height/2) - (playerTop + playerHeight / 2)),
          ((bulletX + bulletSize.width) - (playerLeft + playerWidth / 2)))
    });
}

子弹移动

gameloop中遍历数组对子弹进行移动。

for (int i = bulletsData.length - 1; i >= 0; i--) {
      var bulletItem = bulletsData[i];

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

      bulletItem["x"] -= cos(angle) * speed;
      bulletItem["y"] -= sin(angle) * speed;

      if (isHitPlayer(bulletItem["x"], bulletItem["y"])) {
        print("gameOver");
        gameOver();
      }

      if (isNotInScreen(bulletItem["x"], bulletItem["y"])) {
        print("bullet removed");
        bulletsData.removeAt(i);
        continue;
      }
    }
}

子弹展示

上述代码完成后,我们的子弹在数据中就存在了。但是你看不见它们,因为他们没有被绘制到画面中。我们需要利用StackPositioned控件来展示它们。

Stack(
  children: getBulletsWidget(),
)

getBulletsWidget(){
    List<Positioned> bullets = [];

    for(int i = 0;i<bulletsData.length;i++){
      var bulletItem = bulletsData[i];
      // print(bulletItem);
      var bulletWidget = Positioned(
        left: bulletItem["x"],
        top: bulletItem["y"],
        child: bulletImage
      );

      bullets.add(bulletWidget);
    }

    return bullets;
}

2021-04-12 16_42_24.gif

按秒计时

既然游戏标题叫“是男人就坚持100秒”,那游戏中肯定需要一个按秒的计时器。还记得前面为什么我建议将计时器的刷新频率设置为20毫秒吗?这样的话,我们每刷新50次是不是就是1秒钟呢?

Timer.periodic(Duration(milliseconds: 20), (timer) {
  if(timer.tick%50==0){
    gameSeconds+=1;
    //seconds
  }

  loop();
});

在Flutter里我们可以这样做,timer里的tick是一个计时器的执行计数,会不断累计,所以我们只需要对50取余,每次整除50的时候就是1秒钟啦~

跨端

借助Flutter强大的跨端能力,这个游戏我们可以...

运行在Mac桌面

flutter run -d macOS

2021-04-12 11_45_01.gif

运行在浏览器

flutter run -d Chrome

2021-04-12 16_54_04.gif

运行在iOS

flutter run -d 模拟器ID

2021-04-12 17_05_27.gif

还有Linux,Windows,Android我就不一一给大家截图了

项目已开源,请自行运行吧!

源码仓库

github.com/ezshine/flu…

关注大帅吧

来一波赞和关注可好?