【俄罗斯方块】单机游戏-微信小程序项目开发入门

news2025/7/17 14:49:58

这是一个仿俄罗斯方块小游戏的微信小程序,只需要写一小段代码就实现出来了,有兴趣的同学完全可以自己动手开发,来看看实现过程是怎样的呢,边写边做,一起来回忆小时候玩过的经典俄罗斯方块游戏吧。

文章目录

  • 创建小程序
    • 页面设计
    • 页面逻辑
    • 初始化
    • 定时刷新
    • 完善细节
  • 运行小程序

💡 给读者一个小小提示
需要有HTML和CSS基础
还有JavaScript基础
电脑有安装好微信小程序开发工具

满足以上三个条件就能看懂接下来的一些内容了

创建小程序

首先,打开微信小程序开发工具,如下图所示,

新建一个小程序项目minigame-tetris,就选择小程序,注意不要选择使用云服务和模板,然后点确定即可,
在这里插入图片描述

这时项目在开发者工具中自动创建好了,会发现生成的文件不会太多,这是为方便精简项目源码,避免新建的项目自动生成一大堆对新手来说不懂的东西,此项目开发对新手非常友好的!

页面设计

在项目根目录下,打开一个页面布局文件,文件路径为pages/index/index.wxml,如下图所示,这是我们要做的页面显示效果,准备编写布局标签实现它吧
在这里插入图片描述
可能新手想不太明白,那么说下原理,整个页面的布局只用到了一个画布Canvas组件和两个图片image,然后设置css样式层层叠加放置就可以实现效果,布局文件内容参考如下,简单就好!

<!--index.wxml-->
<view class="container">
  <view class="canvas-box">
    <image src="{{bgImg}}" class="canvas-bg" />
    <image src="{{foreImg}}" class="canvas-bg" />
    <canvas type="2d" id="canv" class="canvas"></canvas>
  </view>
  <view class="footer">
    <view class="btngroup">
    	<!-- 此处省略更多 -->
    </view>
  </view>
</view>

页面逻辑

接下来,想到需要编写游戏的逻辑,这下会不会感到难写了,以我们对俄罗斯方块游戏互动逻辑的了解,似乎实现起来不太难吧,别担心,站在巨人的肩膀上,看看怎么写得呢,

打开文件路径为pages/index/index.js,开始编写代码,如下所示,理清一下逻辑,一边写一边思考

// index.js
const canvasId = 'canv';
//...

Page({
  data:{
    context:{},//存放其它的值
    bgImg:'',//背景图
    //...
  },
  //页面加载时,从此处开始执行
  onLoad(){
  	//先获取canvas画布组件 大小size 和 节点node
    wx.createSelectorQuery().in(this).select(`#${canvasId}`).fields({
      size:true, node:true
    },res=>{
      const canvas = res.node;
      //这里需要设置一下画布的宽高,不然画布实际大小会与实际不一致
      canvas.width = res.width;
      canvas.height = res.height;
      //获取绘制2d画布的工具集合对象ctx
      const ctx = canvas.getContext('2d');
      //设置到页面的局部变量context里去,方便在其它方法中使用
      Object.assign(this.data.context,{ canvas, ctx });
      const base64 = this.initGrid();
      this.initTimer();
      //页面显示背景图
      this.setData({
        bgImg: base64
      });
    }).exec()
  },
  //页面卸载时
  onUnload(){
    //...
  },
  //初始化网格,或绘制游戏背景图
  initGrid(){
    //...
  },
  //初始化定时器
  initTimer(){
    //...
  },
  //用户按下按键,或点击的事件
  onclickkey(e){
    //...
  },
  //用户松开按键事件
  onclickend(){
    //...
  }
})

初始化

初始化方法initGrid(),这里面做的是初始化一些属性,和绘制背景图,还有网格数据,大致的实现逻辑如下,可以这样理解,游戏中的一些方块都是有自己位置的,需要网络来作为参照物,定方位

// index.js
import Tetris from '../../static/utils/tetris';//导入一个模块

Page({
  data:{
  	context:{},
    tetris:null,//方块相关的对象
    //...
  },
  //...
  //初始化网格,或绘制游戏背景图
  initGrid(){
		const { ctx, canvas } = this.data.context;
	    const cols = 28;//这里设定每一行内的方块数量
	    const gs = Math.trunc(canvas.width/cols);
	    const rows = Math.trunc(canvas.height/gs);
	    const padding = Math.trunc(canvas.width%cols/2);
	    //计算好了一些属性,如果要用到,就在局部变量context中存上去
	    Object.assign(this.data.context,{ padding, gs, rows, cols });
	    //这里我们用到了一个模块,这是对处理方块相关的封装,使用前需要先导入它
	    const tetris = new Tetris({rows,cols});
	    //先保存一下对画布的设置
	    ctx.save();
	    ctx.fillStyle='#000000';
	    ctx.strokeStyle='#fff';
	    ctx.rect(0,0,canvas.width,canvas.height);
	    ctx.fill();
	    ctx.fillStyle='#fff';
	    ctx.strokeStyle='#000';
	    ctx.beginPath();
	    // 这两行是绘制网格显示的,在测试中可以参照,游戏中我们会将它隐藏起来
	    // const grids = tetris.getGrids();
	    // grids.forEach(g=>this.drawGrid(g));
	    ctx.rect(padding,0,cols*gs,rows*gs);
	    ctx.fill();
	    ctx.stroke();
	    //绘制完,可以恢复设置了
	    ctx.restore();
	    this.data.tetris = tetris;
	    const data = canvas.toDataURL();
	    ctx.clearRect(0,0,canvas.width,canvas.height);//需要清除了,下次绘制用
	    return data;//绘制好了,返回图片信息
	},
	//...
}

定时刷新

初始化定时器方法initTimer(),这方法做的是让游戏运动起来的意思,因为是用定时器实现刷新游戏状态的,如果对此不太了解,那么实现过程会是难以理解的,这个实现逻辑是较复杂的,大致代码如下

const TIME = 900;
//...
Page({
  data:{ 
    foreImg:'',//前置图,在背景图上一层
    tetris:null,//方块相关的对象
    timer:null,//定时器
    isContinue:true,//游戏开始或暂停
    downKey:null,//按下按键的信息
    speed:1,//刷新速度调节阈值
    score:0,//游戏得分
    time:0,//游戏时长
  	//...
  },
  //游戏更新逻辑
  refresh(offset = 0){
    //...
  },
  //游戏结束
  gameEnd(){
    //...
  },
  //画方块(格子)
  drawGrid(g,fill){
    //...
  },
  //初始化定时器
  initTimer(){
    const { tetris } = this.data;
    //使用模块对象的方法 这里先创建一个随机方块
    let tetri = tetris.createReadomTetris();
    //定义一个重绘的方法,实现它的逻辑
    const redraw=()=>{
      const { ctx, canvas } = this.data.context;
      const { isContinue, downKey, speed, score, time } = this.data;
      //如果游戏可以继续  并且  在没有按下键时
      if(isContinue && !downKey){
        let offset = 1;//向下移动1格
        //方块在下落了,然后刷新显示,最后判断下落是否触底
        if(this.refresh(offset)){
          //方块无法向下移动 就判断游戏结束
          if(tetri.gt==0){
            this.gameEnd();
            return;
          }
          //将方块加入到网格集合中,就是合并入,装下去的意思 
          tetris.joinGrids(tetri);
          //接着尝试处理清理逻辑,会自动判断底下的所有方块是否装满,如果装满了一行会自动消除,返回成功消除后的得分
          let scope = tetris.clearFillGrids();
          //重新创建一个随机方块
          tetri = tetris.createReadomTetris();
          //获取网格集合
          let grids = tetris.getGrids(true);
          //清空画布
          ctx.clearRect(0,0,canvas.width,canvas.height);
          //将网格集合中所有方块绘制出来
          grids.forEach(g=>this.drawGrid(g,true));
          //绘制好了,弄成一个图片数据,放在页面前置背景图中显示
          this.setData({
            foreImg: canvas.toDataURL(),
            score: score+scope,//更新显示得分加分
          });
          ctx.clearRect(0,0,canvas.width,canvas.height);
        }else{
          //方块下落一格
          tetri.gt+=offset;
        }
      }
      //需要调用这个方法,在不需要的时候会自动挂起,节省CPU处理开销
      canvas.requestAnimationFrame(()=>{
        let t = Math.trunc(TIME*speed);
        //等待下一次重绘
        this.data.timer = setTimeout(redraw,t);
        this.setData({
          time:time+t
        })
      });
    };
	//开始重绘
    redraw();
  },
  //...
 }

完善细节

上面讲得一些的方法都是比较详细的,应该能看得明白吧,对了,那个模块文件只需要实现一些方法就可以了,文件路径为/static/utils/tetris.js,具体怎样写得,参考代码大致如下

//定义一个基本方块宽度,所占格数
const COLS = 4;
//定义一些基本方块,形状各不相同
const RetrisMini = [
  [0,1,5,9],[0,1,2,5],[0,4,8,12],[0,1,4,5],[2,4,5,6],[1,4,5,9],[0,1,2,3],[0,4,8,9],[1,4,5,6],[0,1,2,4],[0,4,5,8]
];
/**
* 方块方法封装对象类
**/
export default class Tetris{
  //一些私有属性
  #grids;
  #cols;
  #rows;
  #coordis;
  #tetri;

  constructor(config){
    const { rows, cols } = config;//传参数两个属性,分别为行数和列数
    const grids = new Array(rows*cols);
    for(let i=0; i<rows; i++){
      for(let j=0; j<cols; j++){
        grids[i*cols+j]={ l:j, t:i };
      }
    }
    this.#grids=grids;//初始化一些属性
    this.#cols=cols;
    this.#rows=rows;
    this.#coordis=[];
  }

  moveTetriAtHorizontal(l,success){
    //...左右移动方块
  }

  getGrids(filter){
    //...获取那些方块占用的网格集合
  }

  getGridsIndex(l,t){
    //...根据表格坐标计,算出一格的索引值返回
  }

  joinGrids(titri,l,t){
    //...将一个方块并入网格集合中
  }

  clearFillGrids(){
    let scope=0;
    //...如果有,清理填平的一行方块,加分
    return scope;
  }

  isIntoTetris(g){
    //...判断方块位置是否与其它实物发生重叠,也就是说是否发生碰撞
    return true;
  }

  isTouchTetris(titri,l,t){
    //...判断方块位置是否触底,也就是说是否还能往下落
  }
  
  createCoordis(cs){
    //...给方块创建一个位置坐标
  }

  createReadomTetris(){
    //...创建一个随机的方块
  }

  getTetris(){
    return this.#tetri;
  }

  rotateRightAngle(success){
    //...方块顺时针方向旋转角度
  }
}

💡一个问题
可能有注意到了,代码中的RetrisMini 是什么东西,看不懂正常,来解说一下,第一个[0,1,5,9],表格形式填充如下,仔细看一看,好像是一个方块形状的图案

ABCD
01xx
x5xx
x9xx

这就是基本方块形状,方便后面绘制的,

还有一些实现细节,剩下的方法这里就不讲了,由于篇幅有限,没法在这里将每个实现细节都讲得清楚,项目代码写得是不多,就讲到这了,请自己动手完善,相信自己,努力就会有收获。

如果想要看完整项目源码的请"点击这里",点击进去展开资源一栏往下仔细找一找就有了。

运行小程序

下面是游戏项目源码运行的效果动图,是否心动了呢,欢迎尝试,项目源码完整,运行无问题,请放心下载学习,谢谢!
请添加图片描述

💡关于游戏剧情
俄罗斯方块游戏原本是没有剧情的,让本人想起来了,跟两个神话故事有异曲同工之妙呢,分别是《精卫填海》和《女娲补天》故事,这故事来做游戏剧情好像可以,还有其它剧情吗,这里不多说了,剧情怎么写,请自己脑补。

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/108865.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

certbot生成证书,配置nginx,利用脚本自动续期

踩了大量坑&#xff0c;做下记录。以下适用于博主本人&#xff0c;但是未必会适用于所有人 单域名与泛域名证书生成 sudo certbot certonly --standalone --email 邮箱 -d 域名# 单域名certbot certonly --preferred-challenges dns --manual -d *.baidu.com(修改这里) --ser…

【檀越剑指大厂—Springboot】Springboot高阶

一.整体介绍 1.什么是 Springboot? Springboot 是一个全新的框架&#xff0c;简化 Spring 的初始搭建和开发过程&#xff0c;使用了特定的方式来进行配置&#xff0c;让开发人员不再需要定义样板化的配置。此框架不需要配置 xml&#xff0c;依赖于 maven 这样的构建系统。 …

嵌入式分享合集125

一、多层板PCB设计中电源平面相对地平面要进行内缩&#xff1f; 有一些人绘制的PCB&#xff0c;在GND层和电源层会进行一定程度的内缩设计&#xff0c;那么大家有没有想过为什么要内缩呢。 需要搞清楚这个问题&#xff0c;我们需要来先了解一个知识点&#xff0c;那就是“20H”…

matlab 功率谱分析

谱分析介绍 谱分析是一种用于研究函数的数学方法。在数学中&#xff0c;谱分析的基本概念是将函数分解成不同的频率成分&#xff0c;以便更好地理解其行为。这些频率成分可以表示为正弦或余弦函数的级数和&#xff0c;称为谱线。 谱分析常用于信号处理、音频信息处理和图像处…

Windows系统增强优化工具

计算机系统优化的作用很多&#xff0c;它可以清理WINDOWS临时文件夹中的临时文件&#xff0c;释放硬盘空间&#xff1b;可以清理注册表里的垃圾文件&#xff0c;减少系统错误的产生&#xff1b;它还能加快开机速度&#xff0c;阻止一些程序开机自动执行&#xff1b;还可以加快上…

数据也能开口说话?这次汇报,老板疯狂给我点赞

年底了&#xff0c;大家的工作汇报进行得怎么样了&#xff1f; 是不是少不了各种数据&#xff1f;饼图、柱形图、条形图、折线图、散点图有没有充斥在你的 PPT 中&#xff1f; 我们出版社的数据统计一般截止到 12 月中下旬&#xff0c;所以前两天&#xff0c;我已经做完了年终…

白话说Java虚拟机原理系列【第三章】:类加载器详解

文章目录jvm.dllBootstrapLoader&#xff1a;装载系统类ExtClassLoader&#xff1a;装载扩展类AppClassLoader&#xff1a;装载自定义类双亲委派模型类加载器加载类的方式类加载器特性类加载器加载字节码到JVM的过程自定义/第三方类加载器类加载器加载字节码到哪&#xff1f;Cl…

浅谈冯诺依曼体系,操作系统和进程概念

文章目录浅谈冯诺依曼体系结构和操作系统冯诺依曼体系结构冯诺依曼体系结构图操作系统进程task_struct内容分类进程内核数据结构&#xff08;task_struct)进程对应的磁盘代码查看进程ps 列出系统中运行的进程ps ajx 查看系统中所有运行的进程ps ajx | grep 程序名 &#xff1a;…

【Linux操作系统】——在Ubuntu20.04上安装MySQL数据库

在Ubuntu上安装MySQL MySQL是一个开源数据库管理系统&#xff0c;通常作为流行的LAMP&#xff08;Linux&#xff0c;Apache&#xff0c;MySQL&#xff0c;PHP / Python / Perl&#xff09;堆栈的一部分安装。它使用关系数据库和SQL&#xff08;结构化查询语言&#xff09;来管…

类美团外卖、骑手、类快递取餐柜、整合菜品供应商、前厅、后厨、配送、智能厨电设备的智慧餐饮业务

一种商业模型之类美团外卖、骑手、类快递取餐柜、整合前厅、后厨、智能厨电设备智慧餐饮业务架构 涉及到&#xff1a; 0、基础数据管理 1、菜谱创错 2、菜谱编译 3、菜谱商业化 4、厨电管理 5、后厨管理 6、前厅管理 …

【Call for papers】SIGKDD-2023(CCF-A/数据挖掘/2023年2月2日截稿)

29TH ACM SIGKDD CONFERENCE ON KNOWLEDGE DISCOVERY AND DATA MINING. 文章目录1.会议信息2.时间节点3.论文主题1.会议信息 会议介绍&#xff1a; 29TH ACM SIGKDD CONFERENCE ON KNOWLEDGE DISCOVERY AND DATA MINING. 会议全称&#xff1a; ACM Knowledge Discovery and D…

为什么 APISIX Ingress 是比 Traefik 更好的选择?

本文可以为正在选型 Kubernetes Ingress Controller 产品的用户提供一些帮助。 作者张晋涛&#xff0c;API7.ai 云原生专家&#xff0c;Apache APISIX Committer、Kubernetes Ingress Nginx Reviewer Apache APISIX Ingress Apache APISIX Ingress 是一个使用 Apache APISIX 作…

FrameLayout布局案例

框架布局-FrameLayout 1.FrameLayout简介 1.简介&#xff1a;白话&#xff0c;墙角堆砌东西 就是开辟一个巨大的空间控件的位置不能够指定&#xff0c;默认就是左上角后面对挡住前面的2.属性 属性名称 对应方法 说明 android:foreground setForeground(Drawable) 设置绘制…

【408篇】C语言笔记-第十四章( 二叉树的建树和遍历考研真题实战)

文章目录第一节&#xff1a;冒泡排序1. 排序2. 冒泡排序第二节&#xff1a;冒泡排序实战1. 步骤2. 代码3. 时间复杂度与空间复杂度第三节&#xff1a;快速排序原理与实战1. 基本思想2. 快速排序实战3. 时间复杂度与空间复杂度第四节&#xff1a;插入排序原理及实战1. 插入排序原…

HSF 实现原理

HSF 实现原理 提供服务的流程 - server启动时候向ConfigServer注册 - client启动时候向ConfigServer请求list - client缓存list&#xff0c;发现不可用的server&#xff0c;从缓存中remove - ConfigServer通过心跳包维护可用server的list - list有更新的时候&#xff0c;…

单片机——LED

0. 单片机编程的一般步骤 目标分析&#xff1a;点亮开发板上的LED灯 电路原理图分析&#xff1a;相关器件的工作原理 数据手册分析&#xff1a;IO端口控制 代码编写、编译 下载与调试 1. LED简介 Led&#xff1a;即发光二极管&#xff0c;具有单向导通性&#xff0c;一般…

验证码、通知短信API常见使用问题

如今短信应用于我们生活工作的方方面面&#xff0c;注册或者登录一个应用可以用短信验证码快速登录&#xff0c;支付可以使用短信验证码&#xff1b;商家搞促销活动可以发送通知短信给客户&#xff0c;会员到期了商家可以发送告警短信给会员用户…可见验证码短信API和通知短信A…

JavaFX爱好者看过来,这款工具值得拥有

前言 各位CSDN的博友们&#xff0c;随着各地政策的放开&#xff0c;大伙现在是在水深火热当中呢&#xff1f;还是天选打工人-安然无羊。在这里&#xff0c;希望阳了的朋友&#xff0c;赶紧恢复健康&#xff0c;早日康复。希望没有阳的朋友们&#xff0c;继续坚持&#xff0c;万…

聊聊设计模式-解释器模式?

简介 解释器模式属于行为型模式。它是指给定一门语言&#xff0c;定义它的文法的一种表示&#xff0c;并定义一个解释器&#xff0c;该解释器使用该表示来解释语言中的句子。是一种按照规定的语法进行解析的模式 编译器可以将源码编译解释为机器码&#xff0c;让CPU能进行识别并…

C++调用matlab引擎画三维图

VS2012设置 项目–项目属性–配置属性–VC目录–包含目录 D:\MATLAB\R2016a\extern\include 项目–项目属性–配置属性–VC目录–库目录 D:\MATLAB\R2016a\extern\lib\win64\microsoft 添加依赖项有两种方法&#xff1a; 方法一&#xff1a;项目中设置 项目–项目属性–配置属…