使用Flutter开发俄罗斯方块小游戏

2023-11-05

一、本篇文章主要是来讲解下俄罗斯方块游戏的开发思路(当然可能不是最好的思路),博客文章顶部有代码(仅供参考)

二、效果图

视频效果图地址

三、UI页面思路拆解

  • 游戏的主界面两部分组成,上面为15*10的格子用来放置方块,下面为操作按钮和显示当前分数(也就是消失了多少行方块)
  • 每个方块的大小计算:根据当前屏幕的宽度、显示的列数、方块之间的间隙
 Size _calcRectSize(double screenWidth, int count) {
    double remainderWidth = screenWidth - ((count - 1) * widget.gap);
    return Size.square(remainderWidth / count);
  }

计算出每个方块的大小,也就可以计算出格子所占的高度了,接下来通过CustomPaint进行绘制游戏背景即可,如下

class GameBgWidget extends StatelessWidget {
  ///省略部分代码...
  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: Size.fromHeight(height),
      painter: _GameBgPainter(parent: this, size: rectSize),
    );
  }
}
  • 同理,绘制游戏过程中显示的UI也应当是和背景一模一样的大小,最后将这两个上下层叠 就达到了方块在格子中移动的效果,整体UI布局如下:
class GameWidget extends StatefulWidget {
  final int colCount;
  final int rowCount;
  final double gap;

  const GameWidget({
    Key? key,
    required this.colCount,
    required this.rowCount,
    required this.gap,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => _GameWidgetState();
}

class _GameWidgetState extends BaseState<GameWidget> {

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        LayoutBuilder(
          builder: (_, constrains) {
            //计算方块的大小
            final size = _calcRectSize(constrains.maxWidth, widget.colCount);
            //计算所占的高度
            final height = _calcCanvasHeight(size);
            return Stack(
              children: [
              	//游戏背景组件
                GameBgWidget(
                  colCount: widget.colCount,
                  rowCount: widget.rowCount,
                  gap: widget.gap,
                  rectSize: size,
                  height: height,
                ),
                //游戏进行中的数据组件
                GameDataWidget(
                  colCount: widget.colCount,
                  rowCount: widget.rowCount,
                  gap: widget.gap,
                  rectSize: size,
                  height: height,
                  scoreCallback: (line) {
                  },
                ),
              ],
            );
          },
        ),
        Expanded(
          child: Container(
            color: Colors.blueGrey,
            ///省略操作按钮代码...
            ),
        ),
      ],
    );
  }

四、接下来就是重点了游戏逻辑的开发思路,一个怎样的方法会比较好处理数据,下面将为大家说说我的思路

1、 这里可以将整个游戏界面(格子)看成一个15*10的二维数组,当某一个格子内有方块的时候,那么对应的二维数组位置就不为空,如下表示:

  • 当假设开始加入一个“O”型方块的时候,就会变成如下这样

还有一点:这里为什么二维数组里面装的是Color、null而不是0、1呢,原因就是每个方块的颜色会随机生成,同时当方块消失的时候上面的方块要进行下移,所以就需要知道每个格子需要绘制什么颜色的方块

2、 游戏中会产生的所有方块类型如下:

可以讲如上七种方块大致形象称为"O,Z,S,T,J,L,I"类型

  • 那现在的重点就是又该如何表示这些方块了?其实和上面同理也可以使用一个二维数组来进行表示,当某个位置没有方块则为0有则为1如下:
    • “O” 2*2
    • “Z” 2*3
    • “S” 2*3
    • “T” 2*3
    • “J” 3*2
    • “L” 3*2
    • “I” 4*1

3、现在就可以抽象出一个方块的模板来了

abstract class BaseBlock {
  List<Color> allColors = [
    Colors.amber,
    Colors.lightBlue,
    Colors.red,
    Colors.blue,
    Colors.pink,
    Colors.lightGreen,
    Colors.purpleAccent,
  ];

  ///方块数据
  List<List<int>> block;

  ///方向
  BlocDirection direction;

  ///颜色
  late Color color;
  
  BaseBlock({
    required this.block,
    this.direction = BlocDirection.top,
  }) {
    color = allColors[Random().nextInt(allColors.length)];
  }

  ///宽度
  int get width => block.first.length;

  ///高度
  int get height => block.length;

  ///旋转
  void rotate() {
    int nextIndex = (direction.index + 1) % BlocDirection.values.length;
    direction = BlocDirection.values[nextIndex];
  }
}
  • 有了模板实现起来就很快了,举几个例子
    • "O"型方块
    class OBlock extends BaseBlock {
      OBlock()
          : super(block: [
              [1, 1],
              [1, 1]
            ]);
    }
    
    • "T"型方块
    class TBlock extends BaseBlock {
      TBlock()
          : super(block: [
              [1, 1, 1],
              [0, 1, 0]
            ]);
    
      @override
      void rotate() {
        var currDirection = direction;
        super.rotate();
        if (currDirection == BlocDirection.top) {
          block = [
            [0, 1],
            [1, 1],
            [0, 1]
          ];
        } else if (currDirection == BlocDirection.right) {
          block = [
            [0, 1, 0],
            [1, 1, 1]
          ];
        } else if (currDirection == BlocDirection.bottom) {
          block = [
            [1, 0],
            [1, 1],
            [1, 0]
          ];
        } else {
          block = [
            [1, 1, 1],
            [0, 1, 0]
          ];
        }
      }
    }
    

    因为每一个方块可以进行无限制旋转,所以T型方块重写rotate函数进行实现,也就是将旋转后将新block数据进行更新即可。

五、剩下的就是对方块进行向下移动,左移、右移动同时需要进行判断是否可以向左、向右、向下移动

  • 对于生成的方块我们可以使用(x,y)来进行标记位置,向下移动y+1,向左移动x-1,向右移动x+1,同时对边界进行处理,也要判断要移动的位置是否已经有方块了
  • 最后对于某一行是否填满可以进行消除的判断就更简单些了,需要判断每一行是否都不为null,然后对应的行删除,然后在 在0下标插入一行,这样就完成了消除同时保证了二维数组的正确性
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用Flutter开发俄罗斯方块小游戏 的相关文章

随机推荐

  • 如何使用 WSL 在 Windows 上安装 Linux-官方流程

    前提条件 安装 WSL 命令 更改默认安装的 Linux 发行版 设置 Linux 用户信息 设置和最佳实践 检查你正在运行的 WSL 版本 从 WSL 1 升级到 WSL 2 使用 WSL 运行多个 Linux 发行版的方法 想体验最新的
  • 刷脸支付服务商推广进程已逐渐深入

    2019年是刷脸支付的商用元年 刷脸支付推广进程已逐渐深入 移动支付领域巨头间的竞争越发激烈 在支付宝与微信支付之后 银联也正式加入 近日 中国银联旗下云闪付APP正式推出刷脸支付服务 目前已经在宁波 长沙 杭州 嘉兴 合肥 广州 武汉七个
  • docker容器里设置中文时区

    本文讨论docker容器里中文时区的问题 总所周知docker hub上的镜像默认都是英文时区的 在国人使用过程当中需要将时区设置成中文 我原来光配置 etc localtime了date显示的时间也对 但是tomcat日志里输出的时间还是
  • git 基本使用

    git和svn的区别 svn的特点是集中式 工程文件全部放在中央服务器的一个唯一库上 管理员对开发者的权限有掌控 每个人都只能拉取和开发属于自己的模块 且提交按照文件进行存储 有网络要求 git的特点是分布式 每个开发者都能把全部工程文件拉
  • 学习STM32(一)之芯片类型,内核架构的关系

    原文 https blog csdn net qlexcel article details 79299970 ARM内核和架构都是什么意思 它们到底是什么关系 1 ARM内核 从ARM7 ARM9到Cortex A7 A8 A9 A12
  • [开发

    Ngrok是一个开源的 跨平台的 用于将本地服务器 如Web服务器 映射到公共Internet上的工具 它允许你在没有公共IP地址或域名的情况下 将你本地开发环境暴露给其他人 方便测试 演示和与他人共享工作进展 官网地址 官方文档 官方下载
  • IDEA插件的在线离线安装

    插件的使用 插件的设置 在 IntelliJ IDEA 的安装讲解中我们其实已经知道 IntelliJ IDEA 本身很多功能也都是通过插件的方式来实现的 只是 IntelliJ IDEA 本身就是它自己的插件平台最大的开发者而已 开发了很
  • 关闭、开启 hype-v

    情景 在使用docker之后 需要使用虚拟机 直接开启虚拟机会报错 VMware Workstation 不支持在此主机上使用虚拟化性能计数器 有关更多详细信息 请参阅 VMware 知识库文章 81623 模块 VPMC 启动失败 未能启
  • JS 使用DES加密解密

    1 安装插件 npm install crypto js 2 使用 import CryptoJS from crypto js const key abcdefg const keyHex CryptoJS enc Utf8 parse
  • shardingsphere的sharding jdbc报类型转换异常问题

    shardingsphere的sharding jdbc报类型转换异常问题 根据官网的解释 在4 1 1的版本中是不支持数据库的原生native sql的 所有的sql都会被转换校验一次之后才会进入mybatis进行解析 如果你的sql中使
  • get 和 post 俩种提交表单的方式

    get 和 post 俩种提交表单的方式 自动提交表单的数据 启用表单的自动提交方式时 我们需要添加上这一句 eg action Main GetData method post action Main GetData 这是所对应的路径 m
  • 【华为OD机试python】阿里巴巴找黄金宝箱(IV)【2023 B卷

    题目描述 一贫如洗的樵夫阿里巴巴在去砍柴的路上 无意中发现了强盗集团的藏宝地 藏宝地有编号从0 N的箱子 每个箱子上面有一个数字 箱子排列成一个环 编号最大的箱子的下一个是编号为0的箱子 请输出每个箱子贴的数字之后的第一个比它大的数 如果不
  • JetBrains软件(Idea、Pycharm等)模板设置

    以Idea为例 一 打开File中的Settings 二 打开Editor中的File and Code Templates 找到对应的文件修改即可 三 修改后的状态
  • CCF/CSP 201409-3 字符串匹配(满分题解Java版)

    此题虽然放在了第三题 但是如果对Java的API了解的比较好的同学 解这道题一点都不难 比前几题都要简单一些 题目描述 官方题目地址 读题请点击 Java满分题解 import java util Scanner next 与 nextLi
  • Java 图形用户界面 复习题

    题目 编写一个包含主方法main的公共类 访问权限为public的类 该类继承自窗体类JFrame 并且 该类实现了接口ActionListener 实现接口ActionListener的方法actionPerformed 需要实现的界面
  • 工程师如何提高写作修养

    昨天非常有幸参加了电子工业出版社博文视点专业出版高峰论坛 在 写作精进 分论坛上 我受邀做了主题为 工程师如何提高写作修养 的分享 昨天现场的同学的不多 而这个主题估计很多都会有兴趣 发布在这里 供大家参考 在此 再次感谢电子工业出版社博文
  • 字符串前面补零的简单写法

    include
  • 代理模式--静态代理

    明确AOP之前首先要对代理模式进行深刻的学习 代理模式分为静态代理 和动态代理 动态代理又包括JDK代理和Cglib 本文主要学习静态代理 代理模式 从生活出发 我是一个要租房子的人 我要租房子 要找房屋中介 房源多 我不会去找房东 因为很
  • 2022亚太E题——How Many Nuclear Bombs can Destroy the Earth?(思路)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 2022亚太E题 How Many Nuclear Bombs can Destroy the Ear
  • 使用Flutter开发俄罗斯方块小游戏

    一 本篇文章主要是来讲解下俄罗斯方块游戏的开发思路 当然可能不是最好的思路 博客文章顶部有代码 仅供参考 二 效果图 视频效果图地址 三 UI页面思路拆解 游戏的主界面两部分组成 上面为15 10的格子用来放置方块 下面为操作按钮和显示当前