使用 Flutter Widget 开发游戏”是男人就坚持 100 秒“,一套代码横跨 6 端~ - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
ezshine
V2EX    分享创造

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

  •  
  •   ezshine
    ezshine 2021-04-28 12:02:41 +08:00 3748 次点击
    这是一个创建于 1632 天前的主题,其中的信息可能已经有所发展或是发生改变。

    是男人就坚持 100 秒

    前言

    之前使用 Flutter 里的游戏引擎 Flare 已经开发了一版这个游戏。在文章里我说要用 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控件来展示它们。

    注意:这里我是故意为之,如果真的做游戏那么子弹粒子最好使用 CustomPainter 来实现

    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 我就不一一给大家截图了

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

    源码仓库

    https://github.com/ezshine/fluttergame-keepalive100s

    15 条回复    2021-05-06 13:39:00 +08:00
    dingwen07
        1
    dingwen07  
       2021-04-28 13:08:56 +08:00 via iPhone
    想到之前有个 v 站用户做的一个网页版的游戏,有点像
    James369
        2
    James369  
       2021-04-28 13:39:08 +08:00
    这个游戏暴露年龄了,哈哈
    RookiePG
        3
    RookiePG  
       2021-04-28 13:44:31 +08:00
    @James369 哈哈哈你也暴露年龄了
    kylix
        4
    kylix  
       2021-04-28 13:46:18 +08:00
    这个。。。算了,到我的收藏夹吃灰去吧
    hronro
        5
    hronro  
       2021-04-28 13:49:31 +08:00
    有没有打包好的二进制下载呢?我就想体验一下现在 Flutter 做的桌面 App 到底效果如何
    ezshine
        6
    ezshine  
    OP
       2021-04-28 14:03:14 +08:00
    @hronro 没有打包好的,我个人感受是效果挺不错的。或者说”无感“,flutter2.0 发布后的将 desktop 的支持带到了 stable 版,编译打包非常简单。
    IvanLi127
        7
    IvanLi127  
       2021-04-28 16:20:26 +08:00
    这个好诶
    randm
        8
    randm  
       2021-04-28 18:05:16 +08:00
    原来是大神,膜拜学习
    ezshine
        9
    ezshine  
    OP
      &nbs;2021-04-28 18:10:30 +08:00
    @randm flutter 我也刚学一段时间,我看你文章也在要学这个,一起交流学习呀。
    wipbssldo
        10
    wipbssldo  
       2021-04-28 18:32:14 +08:00
    Flutter 的思路和游戏引擎差不多,你用 Unity 或者其他游戏引擎开发个这样的游戏一样可以「一套代码横跨 6 端」。
    但是如果真的去做桌面 App,特别是强交互,内容显示类的,不同端的体验上面你会发现很多问题。
    ezshine
        11
    ezshine  
    OP
       2021-04-28 18:35:29 +08:00
    @wipbssldo 是的,我也是这么认为的。我视频里有说
    lancelock
        12
    lancelock  
       2021-04-28 18:44:52 +08:00
    正常引擎不都能跨端吗
    ezshine
        13
    ezshine  
    OP
       2021-04-28 20:26:15 +08:00
    @lancelock 对,正常游戏引擎都能跨端。我视频里也是这么说的
    justin2018
        14
    justin2018  
       2021-04-29 15:06:34 +08:00
    Dark 语法 为啥我总觉得怪怪的
    linktom
        15
    linktom  
       2021-05-06 13:39:00 +08:00
    star 一下,吃灰去吧
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2886 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 14:10 PVG 22:10 LAX 07:10 JFK 10:10
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86