手撸俄罗斯方块(四)——渲染与交互

news2025/4/6 16:29:30

手撸俄罗斯方块(四)——渲染与交互

如何渲染游戏界面

我们知道,当我们看到页面先呈现图像时,实际上看到的是一张图片,多张图片按照一定的刷新频率进行切换,则变成了动态的视频。当刷新频率超过24Hz时,人眼不会察觉到卡顿情况。

因此,一个简单的方案呼之欲出,我们只需要按照1000 / 24 = 41.7ms的时间间隔整体刷新一下频率即可。
在这里插入图片描述

对于俄罗斯方块而言,从上到下,我们可以将整个操作界面分为如下区域:

  • 外框区域: 包括外框的颜色和整体的背景,以及外框的样式;

  • 分数区域: 用于显示分数;

  • 当前图形: 显示当前正在移动的方块;

  • 下一图形: 显示接下来要出现的方块;

  • 游戏状态: 显示当前游戏的状态,如: 游戏暂停;

  • 已填充图形: 显示已填充的图形;

我们有两种方式进行处理:

  1. 每次刷新时重新渲染所有部分。该方法处理逻辑简单,清空全局区域,渲染有效区域。

  2. 每次刷新时仅渲染更新区域。该方法能减少页面整体重绘,提交渲染效率,但相对来说处理复杂,控制逻辑较多。

但是,有些场景却无法使用局部刷新,如控制台渲染。

下面我分别以控制台渲染和DOM渲染为例,分别讲述如何实现渲染。

控制台渲染

按照上文描述,我们将渲染过程进行了抽象,包括了首次渲染和更新渲染,如下:

import { Canvas } from '@shushanfx/teris-core';
class ConsoleCanvas extends Canvas {
  render(): void {

  }
  update(): void {

  }
}

对于控制台而言,每次更新实际上也是渲染全部区域,因此我们可以定义update为:

class ConsoleCanvas extends Canvas {
  update(): void {
    this.render();
  }
}

那么接下来的问题就是如何实现render方法。

render实现

render为全局渲染,在渲染之前需要首先清除控制台。我们可以使用如下代码表示渲染逻辑:

render() {
  const printArray = [];
  // 1. 清空可视区域
  console.clear();
  // 2. array填充
  // ...
  // 3. 打印array
  print(printArray);
}

array的填充

将整个渲染区域分解成一个个字符,多个字符组合组装成画面。这个是控制台游戏的特点。

我们俄罗斯方块游戏的特点:

  1. 第一行显示为外边框的上边框;

  2. 第二行显示为分数;

  3. 第三行为内边框的上边框;

  4. 游戏核心区域渲染,包括填充方块、下一个方块、游戏帮助、游戏状态等字符;

  5. 内边框的下边框;

  6. 外边框的下边框。

  7. 渲染外边框的上边框时:

const outLine1 = this.getOutterLine(
  this.leftTopChar +
  this.createChar(xSize + 2 + this.rightWidth, this.horizonalChar) +
  this.rightTopChar
);
printArray.push(outLine1);

包括leftTopCharhorizonalCharrightTopChar

  1. 渲染分数:
// 2. 渲染score
const scoreText = this.theme.scoreTemplate(score);
const scoreConsoleChar = ConsoleChar.create(scoreText);
this.theme.scoreStyle(scoreConsoleChar);
// 计算左侧需要补充的空格
const leftSpace = this.rightWidth - scoreText.length - 3;
// 右侧需要补充的空格
const rightSpace = 3;
let scoreLine =
  this.getOutterLine(this.verticalChar) +
  this.createChar(xSize + 2 + leftSpace) +
  scoreConsoleChar.ch +
  this.createChar(rightSpace) +
  this.getOutterLine(this.verticalChar);
printArray.push(scoreLine);
  • 1~3行,用于生成score的文本和样式,后续在theme中会描述。
  • 5行,计算左侧补充的空格数
  • 7行,右侧补充的空格数
  • 添加scoreLine,包括verticalChar,左侧空格,分数,右侧空格,verticalChar。
  1. 渲染内边框的上边框
// 3. 渲染内边框的上边框
let line1 =
  this.getOutterLine(this.verticalChar) +
  this.getInnerLine(this.leftTopChar);
for (let x = 0; x < xSize; x++) {
  const oneBlockItem = current?.points.find(item => item.x === x);
  if (oneBlockItem) {
    line1 += this.getInnerLine(bold(this.horizonalChar))
  } else {
    line1 += this.getInnerLine(this.horizonalChar)
  }
}
line1 +=
  this.getInnerLine(this.rightTopChar) +
  this.createChar(this.rightWidth) +
  this.getOutterLine(this.verticalChar);
printArray.push(line1);

内边框包括,verticalCharleftTopCharhorizonalChar、空格和verticalChar

  1. 核心区域渲染

游戏核心区域主要是一些方块,包括当前方块currentBlock,下一个方块nextBlock,已经落地的方块以及左右边框。

基本思路为:

4.1. 对于游戏区域,按照xSize x ySize维度,遍历每个点

* 如果当前点存在points(已经固定的点)中,则渲染固定点;
* 如果当前点包含在currentBlock中,则渲染当前活动的block。
* 否则渲染空点

```javascript
for (let y = 0; y < ySize; y++) {
  let rowLength = 2;
  let row = this.getOutterLine(this.verticalChar)
    + this.getInnerLine(this.verticalChar);
  for (let x = 0; x < xSize; x++) {
    const point = stage.points[y][x];
    const currentPoint = current
      ? current?.points.find((p) => p.x === x && p.y === y)
      : null;
    if (currentPoint || !point.isEmpty) {
      let consoleChar = new ConsoleChar(this.blockChar);
      this.theme.blockPointStyle(consoleChar, currentPoint || point);
      row += consoleChar.ch;
    } else {
      row += " ";
    }
  }
  rowLength += xSize;
  row += this.getInnerLine(this.verticalChar);
  rowLength += 1;

  // 渲染其他点位

  // 扣除末尾的结束符号
  const leftLength = outLength - rowLength - 1;
  if (leftLength > 0) {
    row += new Array(leftLength).fill(" ").join("");
  }
  row += this.getOutterLine(this.verticalChar);
  printArray.push(row);
}
```

从上述代码来看,渲染逻辑很简单,先是渲染左侧外边框`verticalChar`、内边框`verticalChar`,之后渲染具体的方块,即如果方块包含在currentBlock或者不为空,则渲染方块,否则渲染空字符。之后,渲染内边框`verticalChar`,补充空格和外边框`verticalChar`。

4.2. 渲染nextBlock

从游戏区第一行开始,我们需要渲染`nextBlock`部分,因此实现逻辑如下:

```javascript
// drawNext
if (y >= 0 && y <= 4) {
  row += this.createChar(1);
  rowLength += 1;
  if (next) {
    let xStart = 0;
    let xEnd = 0;
    next.points.forEach((point) => {
      if (xStart === 0 || point.x < xStart) {
        xStart = point.x;
      }
      if (xEnd === 0 || point.x > xEnd) {
        xEnd = point.x;
      }
    });
    for (let x = xStart; x <= xEnd; x++) {
      const point = next.points.find((p) => p.x === x && p.y === y);
      let consoleChar: ConsoleChar | null = point
        ? new ConsoleChar(this.blockChar)
        : null;
      if (point) {
        this.theme.nextPointStyle(consoleChar, point);
      }
      row += consoleChar ? consoleChar.ch : " ";
      rowLength += 1;
    }
  }
}
```
需要说名的是`nextBlock`的x坐标并不是从0开始,需要先找到x坐标的最小值和最大值,然后再依次渲染对应的行。

比如Block T,如果形状如下:

```javascript
// 口
// 口口
// 口
```

那么,`xStart`和`xEnd`的差值为1。y只有0-2是有效的行,其余均渲染为空格。

4.3. 渲染游戏状态

第7行,渲染游戏状态

```javascript
else if (y === 6) {
  const { status } = game;
  if (status === GameStatus.PAUSE) {
    row += this.createChar(1) + this.getStatusLine("游戏暂停");
    rowLength += 5;
  } else if (status === GameStatus.OVER) {
    row += this.createChar(1) + this.getStatusLine("游戏结束");
    rowLength += 5;
  } else if (status === GameStatus.STOP) {
    row += this.createChar(1) + this.getStatusLine("游戏停止");
    rowLength += 5;
  }
}
```

4.4. 渲染游戏帮助

从第9行开始,渲染游戏的帮助信息:

```javascript
else if (y >= 8) {
  const messsage = this.createChar(1) + this.getHelpMessage(y - 8);
  row += messsage;
  rowLength += messsage.length;
}
```
  1. 渲染内边框的下边框
let line2 =
  this.getOutterLine(this.verticalChar) +
  this.getInnerLine(this.leftBottomChar);
for (let x = 0; x < xSize; x++) {
  const oneBlockItem = current?.points.find(item => item.x === x);
  if (oneBlockItem) {
    line2 += this.getInnerLine(bold(this.horizonalChar))
  } else {
    line2 += this.getInnerLine(this.horizonalChar)
  }
}
line2 += this.getInnerLine(this.rightBottomChar) +
  this.createChar(this.rightWidth) +
  this.getOutterLine(this.verticalChar);
printArray.push(line2);

逻辑与渲染内边框的上边类似。

  1. 渲染外边框的下边框
const outLine2 = this.getOutterLine(
  this.leftBottomChar +
  this.createChar(xSize + 2 + this.rightWidth, this.horizonalChar) +
  this.rightBottomChar
);
printArray.push(outLine2);

小结

本章主要讲述如何将游戏数据渲染成画面,以及渲染的一些通用的原理。渲染的本质是绘图,即告诉显示器如何绘制图像,通过不停的更新绘图实现动态的交互的效果。

详细内容可以关注在git上查看: https://github.com/shushanfx/tetris
也可以关注我的git账号: https://github.com/shushanfx

接下来我将从如下几个方面来阐述:

  • 手撸俄罗斯方块——游戏主题

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

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

相关文章

PDF 分割拆分 API 数据接口

PDF 分割拆分 API 数据接口 文件处理&#xff0c;PDF 高效的 PDF 分割工具&#xff0c;高效处理&#xff0c;可永久存储。 1. 产品功能 高效处理大文件&#xff1b;支持多语言字符识别&#xff1b;支持 formdata 格式 PDF 文件流传参&#xff1b;支持设置每个 PDF 文件的页数…

Java面试八股之Redis单线程为什么性能高

Redis单线程为什么性能高 1.内存数据库特性 要点&#xff1a;Redis是一个内存数据库&#xff0c;其数据主要存储在内存中&#xff0c;而非磁盘。内存访问的速度远超磁盘&#xff0c;通常可达纳秒级别&#xff0c;这使得Redis在处理数据时几乎不受I/O瓶颈的影响。由于数据操作…

【机器学习理论基础】回归模型定义和分类

定义 回归分析是研究自变量与因变量之间数量变化关系的一种分析方法&#xff0c;它主要是通过因变量 Y Y Y与影响它的自变量 X i X_i Xi​ 之间的回归模型&#xff0c;衡量自变量 X i X_i Xi​ 对因变量 Y Y Y 的影响能力的&#xff0c;进而可以用来预测因变量Y的发展趋势。…

7.10日学习打卡----初学Redis(五)

7.10日学习打卡 目录&#xff1a; 7.10日学习打卡一. redis功能流水线pipeline什么是流水线&#xff1f;pipeline实现使用pipeline 发布与订阅Redis的发布与订阅发布订阅命令行实现 慢查询Redis命令执行的整个过程如何进行配置实践建议 二 . redis的持久化机制RDB持久化机制触发…

[ACM独立出版]2024年虚拟现实、图像和信号处理国际学术会议(ICVISP 2024)

最新消息ICVISP 2024-已通过ACM出版申请投稿免费参会&#xff0c;口头汇报或海报展示(可获得相应证明证书) ————————————————————————————————————————— [ACM独立出版]2024年虚拟现实、图像和信号处理国际学术会议&#xff08;ICVI…

传感器标定(三)激光雷达外参标定(lidar2ins)

一、数据采集 1、LiDAR 传感器的 LiDAR PCD 数据 2、来自 IMU 传感器的姿势文件 3、手动测量传感器之间外部参数初始值并写入的 JSON 文件 二、下载标定工具 //总的git地址&#xff1a; https://github.com/PJLab-ADG/SensorsCalibration git地址&#xff1a; https://githu…

人工智能算法工程师(中级)课程4-sklearn机器学习之回归问题与代码详解

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下人工智能算法工程师(中级)课程4-sklearn机器学习之回归问题与代码详解。回归分析是统计学和机器学习中的一种重要方法&#xff0c;用于研究因变量和自变量之间的关系。在机器学习中&#xff0c;回归算法被广泛应用于…

从零开始的python学习生活2

接上封装 class Phone:__volt0.5def __keepsinglecore(self):print("让cpu以单核运行")def if5G(self):if self.__volt>1:print("5G通话已开启")else:self.__keepsinglecore()print("电量不足&#xff0c;无法使用5G通话&#xff0c;已经设置为单…

【NLP学习笔记】transformers中的tokenizer切词时是否返回token_type_ids

结论 先说结论&#xff1a; 是否返回token_type_ids&#xff0c;可以在切词时通过 return_token_type_idsTrue/False指定&#xff0c;指定了True就肯定会返回&#xff0c;指定False&#xff0c;不一定就不返回。 分析 Doc地址 https://huggingface.co/docs/transformers/main…

MATLAB | 如何使用MATLAB优雅的推公式,全网最全MATLAB符号表达式使用教程

HEY&#xff0c; 各位这次是真的好久不见&#xff0c;本期推送来教大家如何使用MATLAB推公式并使用推出来的结果。 本文说白了就是讲符号表达式这个东西咋用&#xff0c;所使用最重要的函数就是syms&#xff0c;在开始前&#xff0c;首先要保证自己的MATLAB安装了Symbolic Mat…

【Pytorch】RNN for Image Classification

文章目录 1 RNN 的定义2 RNN 输入 input, h_03 RNN 输出 output, h_n4 多层5 小试牛刀 学习参考来自 pytorch中nn.RNN()总结RNN for Image Classification(RNN图片分类–MNIST数据集)pytorch使用-nn.RNN 1 RNN 的定义 nn.RNN(input_size, hidden_size, num_layers1, nonlinea…

特斯拉的人形机器人最新展示,穿戴遥操作示教的机器人学习!

在机器人领域&#xff0c;特斯拉的人形机器人一直备受关注。2021 年&#xff0c;在「特斯拉 AI 日」上&#xff0c;马斯克发布了特斯拉的通用机器人计划&#xff0c;并用图片展示了人形机器人 Tesla Bot 的大致形态。但当时的 Tesla Bot 只是个概念&#xff0c;动作展示部分是由…

C++基础学习笔记

1.命名空间(namespace) 1.什么是命名空间&命名空间的作用 1.在C/C中&#xff0c;变量、函数、类都是大量存在的&#xff0c;这些变量等的名称将都存在于全局作用域中&#xff0c;就会导致很多的命名冲突等。使用命名空间的目的就是对标识符的名称进行本地化&#xff0c;以…

springboot中通过jwt令牌校验以及前端token请求头进行登录拦截实战

前言 大家从b站大学学习的项目侧重点好像都在基础功能的实现上&#xff0c;反而一个项目最根本的登录拦截请求接口都不会写&#xff0c;怎么拦截&#xff1f;为什么拦截&#xff1f;只知道用户登录时我后端会返回一个token&#xff0c;这个token是怎么生成的&#xff0c;我把它…

YOLOv10改进 | Conv篇 | 全新的SOATA轻量化下采样操作ADown(参数量下降百分之二十,附手撕结构图)

一、本文介绍 本文给大家带来的改进机制是利用2024/02/21号最新发布的YOLOv9其中提出的ADown模块来改进我们的Conv模块&#xff0c;其中YOLOv9针对于这个模块并没有介绍&#xff0c;只是在其项目文件中用到了&#xff0c;我将其整理出来用于我们的YOLOv10的项目&#xff0c;经…

【大模型】微调实战—使用 ORPO 微调 Llama 3

ORPO 是一种新颖微调&#xff08;fine-tuning&#xff09;技术&#xff0c;它将传统的监督微调&#xff08;supervised fine-tuning&#xff09;和偏好对齐&#xff08;preference alignment&#xff09;阶段合并为一个过程。这减少了训练所需的计算资源和时间。此外&#xff0…

【计算机毕业设计】012基于微信小程序的科创微应用平台

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

华为ensp实现防火墙的区域管理与用户认证

实验环境 基于该总公司内网&#xff0c;实现图片所在要求 后文配置请以本图为准 接口配置与网卡配置 1、创建vlan 2、防火墙g0/0/0与云页面登录 登录admin,密码Admin123&#xff0c;自行更改新密码 更改g0/0/0口ip&#xff0c;敲下命令service-manage all permit 网卡配置…

彩虹小插画:成都亚恒丰创教育科技有限公司

彩虹小插画&#xff1a;色彩斑斓的梦幻世界 在繁忙的生活节奏中&#xff0c;总有一抹温柔的色彩能悄然触动心弦&#xff0c;那就是彩虹小插画带来的梦幻与宁静。彩虹&#xff0c;这一自然界的奇迹&#xff0c;被艺术家们巧妙地融入小巧精致的插画之中&#xff0c;不仅捕捉了瞬…

3D线上展示技术如何应用到汽车营销中?有哪些优势?

传统的汽车销售主要是通过实体店面展示汽车&#xff0c;但这样的展示方式成本高昂&#xff0c;而且还有空间限制。近年来&#xff0c;随着互联网的不断发展&#xff0c;线上看车逐渐成为当下年轻消费群体的看车新选择&#xff0c;并且线上看车正在从2D平面转向3D立体体验。 一、…