Qt 项目实战 | 俄罗斯方块

news/2025/2/21 7:21:25/

Qt 项目实战 | 俄罗斯方块

  • Qt 项目实战 | 俄罗斯方块
    • 游戏架构
    • 实现游戏逻辑
      • 游戏流程
      • 实现基本游戏功能
        • 设计小方块
        • 设计方块组
        • 添加游戏场景
        • 添加主函数
      • 测试
        • 踩坑点1:rotate 失效
        • 踩坑点2:items 方法报错
        • 踩坑点3:setCodecForTr 失效
        • 踩坑点4:不要在中文路径下运行 Qt 项目
        • 踩坑点5:multiple definition of `qMain(int, char**)'
        • 测试效果

官方博客:https://www.yafeilinux.com/

Qt开源社区:https://www.qter.org/

参考书:《Qt 及 Qt Quick 开发实战精解》

Qt 项目实战 | 俄罗斯方块

开发环境:Qt Creator 4.6.2 Based on Qt 5.9.6

在这里插入图片描述

游戏架构

本项目由三个类构成:

  • OneBox 类:继承自 QGraphicsObject 类。表示小正方形,可以使用信号与槽机制和属性动画。
  • BoxGroup 类:继承自 QObject 类和 QGraphicsItemGroup 类。表示游戏中的方块图形,可以使用信号与槽机制,实现了方块图形的创建、移动和碰撞检测。
  • MyView 类:实现了游戏场景。

游戏场景示意图:

在这里插入图片描述

实现游戏逻辑

游戏流程

游戏流程图:

在这里插入图片描述

七种方块图形:

在这里插入图片描述

方块组移动和旋转:

在这里插入图片描述

  • 碰撞检测:对每一个小方块都使用函数来获取与它们碰撞的图形项的数目,如果数目大于 1,说明已经发生了碰撞。
  • 游戏结束:当一个新的方块组出现时,就立即对它进行碰撞检测,如果发现了碰撞,说明游戏结束,这时由方块组发射游戏结束信号。
  • 消除满行:游戏开始后,每当出现新的方块前,都判断游戏移动区域的每一行是否已经拥有了满行的小方块。若满行,则销毁该行的所有小方块,然后让该行上面的方块都下移一格。

实现基本游戏功能

新建空的 Qt 项目,项目名 myGame。

myGame.pro 中新增代码:

QT += widgetsTARGET = myGame

这也是个踩坑点,在这里提前说了。

添加资源文件,名称为 myImages,添加图片:

在这里插入图片描述

设计小方块

新建 mybox.h,添加 OneBox 类的定义:

#ifndef MYBOX_H
#define MYBOX_H#include <QGraphicsItemGroup>
#include <QGraphicsObject>// 小方块类
class OneBox : public QGraphicsObject
{
private:QColor brushColor;public:OneBox(const QColor& color = Qt::red);QRectF boundingRect() const;void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget);QPainterPath shape() const;
};#endif  // MYBOX_H

新建 mybox.cpp,添加 OneBox 类的实现代码:

#include "mybox.h"#include <QPainter>OneBox::OneBox(const QColor& color) { brushColor = color; }QRectF OneBox::boundingRect() const
{qreal penWidth = 1;return QRectF(-10 - penWidth / 2, -10 - penWidth / 2, 20 + penWidth, 20 + penWidth);
}void OneBox::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{// 为小方块使用贴图painter->drawPixmap(-10, -10, 20, 20, QPixmap(":/images/box.gif"));painter->setBrush(brushColor);QColor penColor = brushColor;// 将颜色的透明度降低penColor.setAlpha(20);painter->setPen(penColor);painter->drawRect(-10, -10, 20, 20);
}QPainterPath OneBox::shape() const
{QPainterPath path;// 形状比边框矩形小 0.5 像素,这样方块组中的小方块才不会发生碰撞path.addRect(-9.5, -9.5, 19, 19);return path;
}
设计方块组

在 mybox.h 中添加头文件:

#include <QGraphicsItemGroup>

再添加 BoxGroup 类的定义:

// 方块组类
class BoxGroup : public QObject, public QGraphicsItemGroup
{Q_OBJECT
private:BoxShape currentShape;QTransform oldTransform;QTimer* timer;protected:void keyPressEvent(QKeyEvent* event);public:enum BoxShape{IShape,JShape,LShape,OShape,SShape,TShape,ZShape,RandomShape};BoxGroup();QRectF boundingRect() const;bool isColliding();void createBox(const QPointF& point = QPointF(0, 0), BoxShape shape = RandomShape);void clearBoxGroup(bool destroyBox = false);BoxShape getCurrentShape() { return currentShape; }signals:void needNewBox();void gameFinished();public slots:void moveOneStep();void startTimer(int interval);void stopTimer();
};

到 mybox.cpp 中添加头文件:

#include <QKeyEvent>
#include <QTimer>

添加 BoxGroup 类的实现代码:

// 方块组类void BoxGroup::keyPressEvent(QKeyEvent* event)
{switch (event->key()){case Qt::Key_Down:moveBy(0, 20);if (isColliding()){moveBy(0, -20);// 将小方块从方块组中移除到场景中clearBoxGroup();// 需要显示新的方块emit needNewBox();}break;case Qt::Key_Left:moveBy(-20, 0);if (isColliding())moveBy(20, 0);break;case Qt::Key_Right:moveBy(20, 0);if (isColliding())moveBy(-20, 0);break;case Qt::Key_Up:rotate(90);if (isColliding())rotate(-90);break;// 空格键实现坠落case Qt::Key_Space:moveBy(0, 20);while (!isColliding()){moveBy(0, 20);}moveBy(0, -20);clearBoxGroup();emit needNewBox();break;}
}BoxGroup::BoxGroup()
{setFlags(QGraphicsItem::ItemIsFocusable);// 保存变换矩阵,当 BoxGroup 进行旋转后,可以使用它来进行恢复oldTransform = transform();timer = new QTimer(this);connect(timer, SIGNAL(timeout()), this, SLOT(moveOneStep()));currentShape = RandomShape;
}QRectF BoxGroup::boundingRect() const
{qreal penWidth = 1;return QRectF(-40 - penWidth / 2, -40 - penWidth / 2, 80 + penWidth, 80 + penWidth);
}// 碰撞检测
bool BoxGroup::isColliding()
{QList<QGraphicsItem*> itemList = childItems();QGraphicsItem* item;// 使用方块组中的每一个小方块来进行判断foreach (item, itemList){if (item->collidingItems().count() > 1)return true;}return false;
}// 创建方块
void BoxGroup::createBox(const QPointF& point, BoxShape shape)
{static const QColor colorTable[7] ={QColor(200, 0, 0, 100),QColor(255, 200, 0, 100),QColor(0, 0, 200, 100),QColor(0, 200, 0, 100),QColor(0, 200, 255, 100),QColor(200, 0, 255, 100),QColor(150, 100, 100, 100)};int shapeID = shape;if (shape == RandomShape){// 产生 0-6 之间的随机数shapeID = qrand() % 7;}QColor color = colorTable[shapeID];QList<OneBox*> list;//恢复方块组的变换矩阵setTransform(oldTransform);for (int i = 0; i < 4; ++i){OneBox* temp = new OneBox(color);list << temp;addToGroup(temp);}switch (shapeID){case IShape:currentShape = IShape;list.at(0)->setPos(-30, -10);list.at(1)->setPos(-10, -10);list.at(2)->setPos(10, -10);list.at(3)->setPos(30, -10);break;case JShape:currentShape = JShape;list.at(0)->setPos(10, -10);list.at(1)->setPos(10, 10);list.at(2)->setPos(-10, 30);list.at(3)->setPos(10, 30);break;case LShape:currentShape = LShape;list.at(0)->setPos(-10, -10);list.at(1)->setPos(-10, 10);list.at(2)->setPos(-10, 30);list.at(3)->setPos(10, 30);break;case OShape:currentShape = OShape;list.at(0)->setPos(-10, -10);list.at(1)->setPos(10, -10);list.at(2)->setPos(-10, 10);list.at(3)->setPos(10, 10);break;case SShape:currentShape = SShape;list.at(0)->setPos(10, -10);list.at(1)->setPos(30, -10);list.at(2)->setPos(-10, 10);list.at(3)->setPos(10, 10);break;case TShape:currentShape = TShape;list.at(0)->setPos(-10, -10);list.at(1)->setPos(10, -10);list.at(2)->setPos(30, -10);list.at(3)->setPos(10, 10);break;case ZShape:currentShape = ZShape;list.at(0)->setPos(-10, -10);list.at(1)->setPos(10, -10);list.at(2)->setPos(10, 10);list.at(3)->setPos(30, 10);break;default: break;}// 设置位置setPos(point);// 如果开始就发生碰撞,说明已经结束游戏if (isColliding()){stopTimer();emit gameFinished();}
}// 删除方块组中的所有小方块
void BoxGroup::clearBoxGroup(bool destroyBox)
{QList<QGraphicsItem*> itemList = childItems();QGraphicsItem* item;foreach (item, itemList){removeFromGroup(item);if (destroyBox){OneBox* box = (OneBox*)item;box->deleteLater();}}
}// 向下移动一步
void BoxGroup::moveOneStep()
{moveBy(0, 20);if (isColliding()){moveBy(0, -20);// 将小方块从方块组中移除到场景中clearBoxGroup();emit needNewBox();}
}// 开启定时器
void BoxGroup::startTimer(int interval) { timer->start(interval); }// 停止定时器
void BoxGroup::stopTimer() { timer->stop(); }
添加游戏场景

新建一个 C++ 类,类名为 MyView,基类为 GraphicsView,继承自 QWidget:

在这里插入图片描述

更改 myview.h:

#ifndef MYVIEW_H
#define MYVIEW_H#include <QGraphicsView>
#include <QWidget>class BoxGroup;class MyView : public GraphicsView
{
private:BoxGroup* boxGroup;BoxGroup* nextBoxGroup;QGraphicsLineItem* topLine;QGraphicsLineItem* bottomLine;QGraphicsLineItem* leftLine;QGraphicsLineItem* rightLine;qreal gameSpeed;QList<int> rows;void initView();void initGame();void updateScore(const int fullRowNum = 0);public:explicit MyView(QWidget* parent = 0);public slots:void startGame();void clearFullRows();void moveBox();void gameOver();
};#endif  // MYVIEW_H

更改 myview.cpp:

#include "myview.h"#include <QIcon>#include "mybox.h"// 游戏的初始速度
static const qreal INITSPEED = 500;// 初始化游戏界面
void MyView::initView()
{// 使用抗锯齿渲染setRenderHint(QPainter::Antialiasing);// 设置缓存背景,这样可以加快渲染速度setCacheMode(CacheBackground);setWindowTitle(tr("MyBox方块游戏"));setWindowIcon(QIcon(":/images/icon.png"));setMinimumSize(810, 510);setMaximumSize(810, 510);// 设置场景QGraphicsScene* scene = new QGraphicsScene;scene->setSceneRect(5, 5, 800, 500);scene->setBackgroundBrush(QPixmap(":/images/background.png"));setScene(scene);// 方块可移动区域的四条边界线topLine = scene->addLine(197, 47, 403, 47);bottomLine = scene->addLine(197, 453, 403, 453);leftLine = scene->addLine(197, 47, 197, 453);rightLine = scene->addLine(403, 47, 403, 453);// 当前方块组和提示方块组boxGroup = new BoxGroup;connect(boxGroup, SIGNAL(needNewBox()), this, SLOT(clearFullRows()));connect(boxGroup, SIGNAL(gameFinished()), this, SLOT(gameOver()));scene->addItem(boxGroup);nextBoxGroup = new BoxGroup;scene->addItem(nextBoxGroup);startGame();
}// 初始化游戏
void MyView::initGame()
{boxGroup->createBox(QPointF(300, 70));boxGroup->setFocus();boxGroup->startTimer(INITSPEED);gameSpeed = INITSPEED;nextBoxGroup->createBox(QPointF(500, 70));
}// 更新分数
void MyView::updateScore(const int fullRowNum) {}MyView::MyView(QWidget* parent) : QGraphicsView(parent) { initView(); }// 开始游戏
void MyView::startGame() { initGame(); }// 清空满行
void MyView::clearFullRows()
{// 获取比一行方格较大的矩形中包含的所有小方块for (int y = 429; y > 50; y -= 20){QList<QGraphicsItem*> list = scene()->items(199, y, 202, 22, Qt::ContainsItemShape);// 如果该行已满if (list.count() == 10){foreach (QGraphicsItem* item, list){OneBox* box = (OneBox*)item;box->deleteLater();}// 保存满行的位置rows << y;}}if (rows.count() > 0){// 如果有满行,下移满行上面的各行再出现新的方块组moveBox();}else  // 如果没有满行,则直接出现新的方块组{boxGroup->createBox(QPointF(300, 70), nextBoxGroup->getCurrentShape());// 清空并销毁提示方块组中的所有小方块nextBoxGroup->clearBoxGroup(true);nextBoxGroup->createBox(QPointF(500, 70));}
}// 下移满行上面的所有小方块
void MyView::moveBox()
{// 从位置最靠上的满行开始for (int i = rows.count(); i > 0; i--){int row = rows.at(i - 1);foreach (QGraphicsItem* item, scene()->items(199, 49, 202, row - 47, Qt::ContainsItemShape)){item->moveBy(0, 20);}}// 更新分数updateScore(rows.count());// 将满行列表清空为 0rows.clear();// 等所有行下移以后再出现新的方块组boxGroup->createBox(QPointF(300, 70), nextBoxGroup->getCurrentShape());nextBoxGroup->clearBoxGroup(true);nextBoxGroup->createBox(QPointF(500, 70));
}// 游戏结束
void MyView::gameOver() {}
添加主函数

新建 main.cpp,添加代码:

#include <QApplication>
#include <QTextCodec>
#include <QTime>#include "myview.h"int main(int argc, char* argv[])
{QApplication app(argc, argv);QTextCodec::setCodecForTr(QTextCodec::codecForLocale());// 设置随机数的初始值qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));MyView view;view.show();return app.exec();
}

测试

运行程序。

果不其然的报错了。

主要是一些 Qt4 和 Qt5 的差别带来的问题。

踩坑点1:rotate 失效

函数 void BoxGroup::keyPressEvent(QKeyEvent* event) 原代码:

void BoxGroup::keyPressEvent(QKeyEvent *event)
{switch (event->key()){case Qt::Key_Down :moveBy(0, 20);if (isColliding()) {moveBy(0, -20);// 将小方块从方块组中移除到场景中clearBoxGroup();// 需要显示新的方块emit needNewBox();}break;case Qt::Key_Left :moveBy(-20, 0);if (isColliding())moveBy(20, 0);break;case Qt::Key_Right :moveBy(20, 0);if (isColliding())moveBy(-20, 0);break;case Qt::Key_Up :rotate(90);if(isColliding())rotate(-90);break;// 空格键实现坠落case Qt::Key_Space :moveBy(0, 20);while (!isColliding()) {moveBy(0, 20);}moveBy(0, -20);clearBoxGroup();emit needNewBox();break;}
}

其中的 rotate 函数失效。

在 Qt5 中,QGraphicsItem::rotate 已经不再使用,而是使用 setRotation。

修改为:

void BoxGroup::keyPressEvent(QKeyEvent* event)
{qreal oldRotate;switch (event->key()){// 下移case Qt::Key_Down:moveBy(0, 20);if (isColliding()){moveBy(0, -20);// 将小方块从方块组中移除到场景中clearBoxGroup();// 需要显示新的方块emit needNewBox();}break;// 左移case Qt::Key_Left:moveBy(-20, 0);if (isColliding())moveBy(20, 0);break;// 右移case Qt::Key_Right:moveBy(20, 0);if (isColliding())moveBy(-20, 0);break;// 旋转case Qt::Key_Up:// 在 Qt5 中,QGraphicsItem::rotate 已经不再使用,而是使用 setRotation/* old code *///    rotate(90);//   if (isColliding())//       rotate(-90);//    break;/* old code */oldRotate = rotation();if (oldRotate >= 360){oldRotate = 0;}setRotation(oldRotate + 90);if (isColliding()){setRotation(oldRotate - 90);}break;// 空格键实现坠落case Qt::Key_Space:moveBy(0, 20);while (!isColliding()){moveBy(0, 20);}moveBy(0, -20);clearBoxGroup();emit needNewBox();break;}
}

参考博客:Qt及Qt Quick开发实战精解项目二俄罗斯方块 rotate失效方法报错

踩坑点2:items 方法报错

在 void MyView::clearFullRows() 函数里有这样一行代码:

QList<QGraphicsItem*> list = scene()->items(199, y, 202, 22, Qt::ContainsItemShape);

报错信息:

myview.cpp:75:47: error: no matching member function for call to 'items'
qgraphicsscene.h:158:28: note: candidate function not viable: requires at most 4 arguments, but 5 were provided
qgraphicsscene.h:159:28: note: candidate function not viable: requires at most 4 arguments, but 5 were provided
qgraphicsscene.h:160:28: note: candidate function not viable: requires at most 4 arguments, but 5 were provided
qgraphicsscene.h:161:28: note: candidate function not viable: requires at most 4 arguments, but 5 were provided
qgraphicsscene.h:175:35: note: candidate function not viable: requires at least 6 arguments, but 5 were provided
qgraphicsscene.h:156:28: note: candidate function not viable: allows at most single argument 'order', but 5 arguments were provided

大概意思是参数不匹配。

修改为:

QList<QGraphicsItem*> list = scene()->items(199, y, 202, 22, Qt::ContainsItemShape, Qt::AscendingOrder);

新增的一项 Qt::AscendingOrder 的意思是对 QList 的内容正序排序。

参考博客:Qt及Qt Quick开发实战精解项目二俄罗斯方块 items方法报错

踩坑点3:setCodecForTr 失效

在 main.cpp 中有这样一行代码:

QTextCodec::setCodecForTr(QTextCodec::codecForLocale());

这行代码主要解决 Qt 中文乱码的问题。

但是在 Qt5 中 setCodecForTr 函数已经失效了,我们改成:

QTextCodec::setCodecForLocale(QTextCodec::codecForName("utf-8"));

这个视个人电脑使用的编码决定。

踩坑点4:不要在中文路径下运行 Qt 项目

就是这样,喵~

踩坑点5:multiple definition of `qMain(int, char**)’

报错信息:

error: multiple definition of `qMain(int, char**)'

这是在 pro 文件中出的问题,频繁的添加以及移除文件,导致 HEADERS 以及 SOURCES 中会重复添加。

在这里插入图片描述

这里 main.cpp 重复了,删掉一个即可。

测试效果

在这里插入图片描述


http://www.ppmy.cn/news/1189421.html

相关文章

LeetCode 2401.最长优雅子数组 ----双指针+位运算

数据范围1e5 考虑nlog 或者n的解法&#xff0c;考虑双指针 因为这里要求的是一段连续的数组 想起我们的最长不重复连续子序列 然后结合一下位运算就好了 是一道双指针不错的题目 class Solution { public:int longestNiceSubarray(vector<int>& nums) {int n nums…

云原生安全日志审计

记得添加&#xff0c;把配置文件挂载进去 - mountPath: /etc/kubernetes/auditname: audit-policyreadOnly: true.....- hostPath:path: /etc/kubernetes/audit/type: DirectoryOrCreatename: audit-policy/etc/kubernetes/manifests/kube-apiserver.yaml 具体配置文件如下 a…

【案例教程】基于最新导则下生态环评报告编制技术暨报告篇、制图篇、指数篇、综合应用篇系统性实践技能提升

根据生态环评内容庞杂、综合性强的特点&#xff0c;依据生态环评最新导则&#xff0c;将内容分为4大篇章(报告篇、制图篇、指数篇、综合篇)、10大专题(生态环评报告编制、土地利用图的制作、植被类型及植被覆盖度图的制作、物种适宜生境分布图的制作、生物多样性测定、生物量及…

githu访问慢解决方法-mac系统

1.访问链接&#xff1a;https://site.ip138.com/github.global.ssl.fastly.net/ 2.分别输入github.com&#xff0c;github.global.ssl.fastly.net进行ip解析 3.打开host文件 sudo vim /etc/hosts4.将在2步骤中的信息添加到host文件中 20.205.243.166 gith…

实时视频混合和映射:Resolume Arena 6(专业的VJ)中文

Resolume Arena 6是一款专业的实时视频混合和映射软件&#xff0c;广泛应用于音乐表演、舞台演出、俱乐部活动等场合&#xff0c;打造令人惊叹的视觉效果和图像投影。它支持实时混合多个视频源&#xff0c;拥有视频映射功能以及提供多样实时效果和过渡效果&#xff0c;让用户能…

【蓝桥杯选拔赛真题07】C++小球自由落体 青少年组蓝桥杯C++选拔赛真题 STEMA比赛真题解析

目录 C/C++小球自由落体 一、题目要求 1、编程实现 2、输入输出 二、算法分析

.net core iis 发布后登入的时候请求不到方法报错502

.net core iis 发布后登入的时候请求不到方法报错502 502 bad gateway 502 - Web 服务器在作为网关或代理服务器时收到了无效响应。 您要查找的页面有问题&#xff0c;无法显示。当 Web 服务器(作为网关或代理)与上游内容服务器联系时&#xff0c;收到来自内容服务器的无效…

企业级JAVA、数据库等编程规范之命名风格 —— 超详细准确无误

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信你对这两篇博客也感兴趣o (ˉ▽ˉ&#xff1b;) &#x1f4dc; 表白墙/留言墙 —— 初级SpringBoot项目&#xff0c;练手项目前后端开发(带完整源码) 全方位全步骤手把手教学 &#x1f4dc; 用户登录前后端…