Qt 项目实战 | 俄罗斯方块

Qt 项目实战 | 俄罗斯方块

  • Qt 项目实战 | 俄罗斯方块
    • 游戏架构
    • 实现游戏逻辑
      • 游戏流程
      • 实现基本游戏功能
        • 设计小方块
        • 设计方块组
        • 添加游戏场景
        • 添加主函数
      • 测试
        • 踩坑点1:rotate 失效
        • 踩坑点2:items 方法报错
        • 踩坑点3:setCodecForTr 失效
        • 踩坑点4:不要在中文路径下运行 Qt 项目
        • 踩坑点5:multiple definition of `qMain(int, char**)'
        • 测试效果
    • 游戏优化
      • 添加满行销毁动画
      • 添加游戏级别设置
      • 添加游戏控制按钮和面板
        • 踩坑点1:error: no matching function for call to 'QGraphicsTextItem::QGraphicsTextItem(int, QGraphicsScene*&)'
        • 踩坑点2:error: no matching function for call to 'QGraphicsScene::items(int, int, int, int, Qt::ItemSelectionMode)'
      • 添加背景音乐和音效
      • 添加程序启动画面
    • 运行效果
    • 资源下载

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

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

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

Qt 项目实战 | 俄罗斯方块

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

在这里插入图片描述

游戏架构

在这个游戏中,有一个区域用来摆放方块,该区域宽为10,高为20,以小正方形为单位,它可以看作是拥有20行10列的一个网格。标准的游戏中一共有7种方块,它们都是由4个小正方形组成的规则图形,依据形状分别用字母I、J、L、O、S、T和Z来命名。

这里使用图形视图框架来实现整个游戏的设计。小正方形由OneBox来表示,它继承自QGraphicsObject类,之所以继承自这个类,是因为这样就可以使用信号和槽机制,话可以使用属性动画。小正方形就是一个宽和高都为20像素的正方形图形项。游戏中的方块游戏由方块组BoxGroup类来实现,继承自QObject和QGraphicsItemGroup类,这样该类也可以使用信号和槽机制。方块组是一个宽和高都是80像素的图形项组,其中包含了4个小方块,通过设置小方块的位置来实现7种标准的方块图形。它们的形状和位置如下图,在BoxGroup类中实现了方块图形的创建、移动和碰撞检测。

本项目由三个类构成:

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

整个游戏场景宽800像素,高500像素。方块移动区域宽200像素,高400像素,纵向每20个像素被视作一行,共有20行;横行也是每20个像素视作一列,所以共有10列,该区域可以看作一个由20行10列20×20像素的方格组成的网格。方块组在方块移动区域的初始位置为上方正中间,但方块组的最上方一行小正方形在方块移动区域以外,这样可以保证方块组完全出现在移动区域的最上方,方块组每移动一次,就是移动一个方格的位置。场景还设置了下一个要出现方块的提示方块、游戏暂停等控制按钮和游戏分数级别的显示文本。

游戏场景示意图:

在这里插入图片描述

实现游戏逻辑

当游戏开始后,首先创建一个新的方块组,并将其添加到场景中的方块移动区域上方。然后进行碰撞检测,如果这时已经发生了碰撞,那么游戏结束;如果没有发生碰撞,就可以使用键盘的方向键对其进行旋转变形或者左右移动。当到达指定事件时方块组会自动下移一个方格,这时再次判断是否发生碰撞,如果发生了碰撞,先消除满行的方格,然后出现新的方块组,并继续进行整个流程。其中方程块的移动、旋转、碰撞检测等都在BoxGroup类中进行;游戏的开始、结束、出现新的方程组、消除满行等都在MyView类中进行。

游戏流程

游戏流程图:

在这里插入图片描述

七种方块图形:

在这里插入图片描述

方块组的左移、右移、下移和旋转都是先进行该操作,然后判断是否发生碰撞,比如发生了碰撞就再进行反向操作。比如,使用方向键左移方块组,那么就先将方块组左移一格,然后进行碰撞检测,看是否与边界线或者其他方块碰撞了,如果发生了碰撞,那么就再移过来,即右移一个。

方块组移动和旋转:

在这里插入图片描述

  • 碰撞检测:对于方块组的碰撞检测,其实是使用方块组中的4个小方块来进行的,这样就不用再为每个方块图形都设置一个碰撞检测时使用的形状。要进行碰撞检测时,对每一个小方块都使用函数来获取与它们碰撞的图形项的数目,因为现在小方块在方块组中,所以应该只有方块组与它们碰撞了(由于我们对小方块的形状进行了设置,所以挨着的四个小方块相互间不会被检测出发生了碰撞),也就是说与它们碰撞的图形项数目应该不会大于1,如果有哪个小方块发现与它碰撞的图形项的数目大于1,那么说明已经发生了碰撞。

  • 游戏结束:当一个新的方块组出现时,就立即对齐进行碰撞检测,如果它一出现就与其他方块发生了碰撞,说明游戏已经结束了,这时由方块组发射游戏结束信号。

  • 消除满行:游戏开始后,每当出现一个新的方块以前,都判断游戏移动区域的每一行是否已经拥有10个小方块。如果有一行已经拥有了10个小方块,说明改行已满,那么就销毁该行的所有小方块,然后让该行上面的所有小方块都下移一格。

实现基本游戏功能

新建空的 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 重复了,删掉一个即可。

测试效果

在这里插入图片描述

游戏优化

添加满行销毁动画

在 myview.cpp 中添加头文件:

#include <QPropertyAnimation>
#include <QGraphicsBlurEffect>
#include <QTimer>

修改 clearFullRows() 函数:

void MyView::clearFullRows()
{// 获取比一行方格较大的矩形中包含的所有小方块for (int y = 429; y > 50; y -= 20){// QList<QGraphicsItem*> list = scene()->items(199, y, 202, 22, Qt::ContainsItemShape);QList<QGraphicsItem*> list = scene()->items(199, y, 202, 22, Qt::ContainsItemShape, Qt::AscendingOrder);// 如果该行已满if (list.count() == 10){foreach (QGraphicsItem* item, list){OneBox* box = (OneBox*)item;// box->deleteLater();QGraphicsBlurEffect* blurEffect = new QGraphicsBlurEffect;box->setGraphicsEffect(blurEffect);QPropertyAnimation* animation = new QPropertyAnimation(box, "scale");animation->setEasingCurve(QEasingCurve::OutBounce);animation->setDuration(250);animation->setStartValue(4);animation->setEndValue(0.25);animation->start(QAbstractAnimation::DeleteWhenStopped);connect(animation, SIGNAL(finished()), box, SLOT(deleteLater()));}// 保存满行的位置rows << y;}}// 如果有满行,下移满行上面的各行再出现新的方块组if (rows.count() > 0){// moveBox();QTimer::singleShot(400, this, SLOT(moveBox()));}else  // 如果没有满行,则直接出现新的方块组{boxGroup->createBox(QPointF(300, 70), nextBoxGroup->getCurrentShape());// 清空并销毁提示方块组中的所有小方块nextBoxGroup->clearBoxGroup(true);nextBoxGroup->createBox(QPointF(500, 70));}
}

为小方块设置模糊效果,为小方块添加先放大再缩小的属性动画。

使用了只执行一次的定时器,其目的是等待所有小方块都销毁后再移动满行上面的小方块。

添加游戏级别设置

在 myview.h 的 private 中添加两个变量:

QGraphicsTextItem* gameScoreText;
QGraphicsTextItem* gameLevelText;

在myview.cpp 的 initView() 函数中调用 startGame() 槽前添加代码:

// 得分文本
gameScoreText = new QGraphicsTextItem(0, scene);
gameScoreText->setFont(QFont("Times", 20, QFont::Bold));
gameScoreText->setPos(650, 350);// 级别文本
gameLevelText = new QGraphicsTextItem(0, scene);
gameLevelText->setFont(QFont("Times", 30, QFont::Bold));
gameLevelText->setPos(20, 150);

注意,上面是书中的代码,是错的,修改为下面代码:

// 得分文本
gameScoreText = new QGraphicsTextItem();
gameScoreText->setFont(QFont("Times", 20, QFont::Bold));
gameScoreText->setPos(650, 350);// 级别文本
gameLevelText = new QGraphicsTextItem();
gameLevelText->setFont(QFont("Times", 30, QFont::Bold));
gameLevelText->setPos(20, 150);scene->addItem(gameLevelText);
scene->addItem(gameScoreText);

再到initGame() 中添加代码:

scene()->setBackgroundBrush(QPixmap(":/images/background01.png"));
gameScoreText->setHtml(tr("<font color=red>0</font>"));
gameLevelText->setHtml(tr("<font color=white>第<br>一<br>幕</font>"));

最后到 updateScore() 函数中添加代码:

// 更新分数
void MyView::updateScore(const int fullRowNum)
{int score = fullRowNum * 100;int currentScore = gameScoreText->toPlainText().toInt();currentScore += score;// 显示当前分数gameScoreText->setHtml(tr("<font color=red>%1</font>").arg(currentScore));// 判断级别if (currentScore < 500){// 第一级,什么都不用做}else if (currentScore < 1000){  // 第二级gameLevelText->setHtml(tr("<font color=white>第<br>二<br>幕</font>"));scene()->setBackgroundBrush(QPixmap(":/images/background02.png"));gameSpeed = 300;boxGroup->stopTimer();boxGroup->startTimer(gameSpeed);}else{// 添加下一个级别的设置}
}

测试:

在这里插入图片描述

添加游戏控制按钮和面板

在 myview.h 添加私有槽:

void restartGame();
void finishGame();
void pauseGame();
void returnGame();

在 myview.h 添加私有变量:

QGraphicsWidget *maskWidget; // 遮罩面板
// 各种按钮
QGraphicsWidget *startButton;
QGraphicsWidget *finishButton;
QGraphicsWidget *restartButton;
QGraphicsWidget *pauseButton;
QGraphicsWidget *optionButton;
QGraphicsWidget *returnButton;
QGraphicsWidget *helpButton;
QGraphicsWidget *exitButton;
QGraphicsWidget *showMenuButton;
// 各种文本
QGraphicsTextItem *gameWelcomeText;
QGraphicsTextItem *gamePausedText;
QGraphicsTextItem *gameOverText;

在 myview.cpp 添加头文件:

#include <QPushButton>
#include <QGraphicsProxyWidget>
#include <QApplication>
#include <QLabel>
#include <QFileInfo>

在 initGame() 函数中,删除代码:

startGame();

添加代码:

/*****************下面是2-4中添加的代码,部分代码在书中省略了*************/// 设置初始为隐藏状态
topLine->hide();
bottomLine->hide();
leftLine->hide();
rightLine->hide();
gameScoreText->hide();
gameLevelText->hide();// 黑色遮罩
QWidget *mask = new QWidget;
mask->setAutoFillBackground(true);
mask->setPalette(QPalette(QColor(0, 0, 0, 80)));
mask->resize(900, 600);
maskWidget = scene->addWidget(mask);
maskWidget->setPos(-50, -50);
// 设置其Z值为1,这样可以处于Z值为0的图形项上面
maskWidget->setZValue(1);// 选项面板
QWidget *option = new QWidget;
QPushButton *optionCloseButton = new QPushButton(tr("关   闭"), option);
QPalette palette;
palette.setColor(QPalette::ButtonText, Qt::black);
optionCloseButton->setPalette(palette);
optionCloseButton->move(120, 300);
connect(optionCloseButton, SIGNAL(clicked()), option, SLOT(hide()));
option->setAutoFillBackground(true);
option->setPalette(QPalette(QColor(0, 0, 0, 180)));
option->resize(300, 400);
QGraphicsWidget *optionWidget = scene->addWidget(option);
optionWidget->setPos(250, 50);
optionWidget->setZValue(3);
optionWidget->hide();// 帮助面板
QWidget *help = new QWidget;
QPushButton *helpCloseButton = new QPushButton(tr("关   闭"), help);
helpCloseButton->setPalette(palette);
helpCloseButton->move(120, 300);
connect(helpCloseButton, SIGNAL(clicked()), help, SLOT(hide()));
help->setAutoFillBackground(true);
help->setPalette(QPalette(QColor(0, 0, 0, 180)));
help->resize(300, 400);
QGraphicsWidget *helpWidget = scene->addWidget(help);
helpWidget->setPos(250, 50);
helpWidget->setZValue(3);
helpWidget->hide();QLabel *helpLabel = new QLabel(help);
helpLabel->setText(tr("<h1><font color=white>yafeilinux作品""<br>www.yafeilinux.com</font></h1>"));
helpLabel->setAlignment(Qt::AlignCenter);
helpLabel->move(30, 150);// 游戏欢迎文本
gameWelcomeText = new QGraphicsTextItem(0, scene);
gameWelcomeText->setHtml(tr("<font color=white>MyBox方块游戏</font>"));
gameWelcomeText->setFont(QFont("Times", 30, QFont::Bold));
gameWelcomeText->setPos(250, 100);
gameWelcomeText->setZValue(2);// 游戏暂停文本
gamePausedText = new QGraphicsTextItem(0, scene);
gamePausedText->setHtml(tr("<font color=white>游戏暂停中!</font>"));
gamePausedText->setFont(QFont("Times", 30, QFont::Bold));
gamePausedText->setPos(300, 100);
gamePausedText->setZValue(2);
gamePausedText->hide();// 游戏结束文本
gameOverText = new QGraphicsTextItem(0, scene);
gameOverText->setHtml(tr("<font color=white>游戏结束!</font>"));
gameOverText->setFont(QFont("Times", 30, QFont::Bold));
gameOverText->setPos(320, 100);
gameOverText->setZValue(2);
gameOverText->hide();// 游戏中使用的按钮QPushButton *button1 = new QPushButton(tr("开    始"));
QPushButton *button2 = new QPushButton(tr("选    项"));
QPushButton *button3 = new QPushButton(tr("帮    助"));
QPushButton *button4 = new QPushButton(tr("退    出"));
QPushButton *button5 = new QPushButton(tr("重新开始"));
QPushButton *button6 = new QPushButton(tr("暂    停"));
QPushButton *button7 = new QPushButton(tr("主 菜 单"));
QPushButton *button8 = new QPushButton(tr("返回游戏"));
QPushButton *button9 = new QPushButton(tr("结束游戏"));connect(button1, SIGNAL(clicked()), this, SLOT(startGame()));
connect(button2, SIGNAL(clicked()), option, SLOT(show()));
connect(button3, SIGNAL(clicked()), help, SLOT(show()));
connect(button4, SIGNAL(clicked()), qApp, SLOT(quit()));
connect(button5, SIGNAL(clicked()), this, SLOT(restartGame()));
connect(button6, SIGNAL(clicked()), this, SLOT(pauseGame()));
connect(button7, SIGNAL(clicked()), this, SLOT(finishGame()));
connect(button8, SIGNAL(clicked()), this, SLOT(returnGame()));
connect(button9, SIGNAL(clicked()), this, SLOT(finishGame()));startButton = scene->addWidget(button1);
optionButton = scene->addWidget(button2);
helpButton = scene->addWidget(button3);
exitButton = scene->addWidget(button4);
restartButton = scene->addWidget(button5);
pauseButton = scene->addWidget(button6);
showMenuButton = scene->addWidget(button7);
returnButton = scene->addWidget(button8);
finishButton = scene->addWidget(button9);startButton->setPos(370, 200);
optionButton->setPos(370, 250);
helpButton->setPos(370, 300);
exitButton->setPos(370, 350);
restartButton->setPos(600, 150);
pauseButton->setPos(600, 200);
showMenuButton->setPos(600, 250);
returnButton->setPos(370, 200);
finishButton->setPos(370, 250);startButton->setZValue(2);
optionButton->setZValue(2);
helpButton->setZValue(2);
exitButton->setZValue(2);
restartButton->setZValue(2);
returnButton->setZValue(2);
finishButton->setZValue(2);restartButton->hide();
finishButton->hide();
pauseButton->hide();
showMenuButton->hide();
returnButton->hide();/*****************上面是2-4中添加的代码,部分代码在书中省略了*************/

在 startGame() 中调用 initGame() 函数前添加代码:

gameWelcomeText->hide();
startButton->hide();
optionButton->hide();
helpButton->hide();
exitButton->hide();
maskWidget->hide();

在 initGame() 函数的最后添加代码:

restartButton->show();
pauseButton->show();
showMenuButton->show();
gameScoreText->show();
gameLevelText->show();
topLine->show();
bottomLine->show();
leftLine->show();
rightLine->show();
// 可能以前返回主菜单时隐藏了boxGroup
boxGroup->show();

修改 gameOver() 槽:

// 游戏结束
void MyView::gameOver()
{pauseButton->hide();showMenuButton->hide();maskWidget->show();gameOverText->show();restartButton->setPos(370, 200);finishButton->show();
}

添加其他槽:

// 重新开始游戏
void MyView::restartGame()
{maskWidget->hide();gameOverText->hide();finishButton->hide();restartButton->setPos(600, 150);// 销毁提示方块组和当前方块移动区域中的所有小方块nextBoxGroup->clearBoxGroup(true);boxGroup->clearBoxGroup();boxGroup->hide();foreach (QGraphicsItem* item, scene()->items(199, 49, 202, 402, Qt::ContainsItemShape, Qt::AscendingOrder)){// 先从场景中移除小方块,因为使用deleteLater()是在返回主事件循环后才销毁// 小方块的,为了在出现新的方块组时不发生碰撞,所以需要先从场景中移除小方块scene()->removeItem(item);OneBox* box = (OneBox*)item;box->deleteLater();}initGame();
}// 结束当前游戏
void MyView::finishGame()
{gameOverText->hide();finishButton->hide();restartButton->setPos(600, 150);restartButton->hide();pauseButton->hide();showMenuButton->hide();gameScoreText->hide();gameLevelText->hide();topLine->hide();bottomLine->hide();leftLine->hide();rightLine->hide();nextBoxGroup->clearBoxGroup(true);boxGroup->clearBoxGroup();boxGroup->hide();foreach (QGraphicsItem* item, scene()->items(199, 49, 202, 402, Qt::ContainsItemShape)){OneBox* box = (OneBox*)item;box->deleteLater();}// 可能是在进行游戏时按下“主菜单”按钮maskWidget->show();gameWelcomeText->show();startButton->show();optionButton->show();helpButton->show();exitButton->show();scene()->setBackgroundBrush(QPixmap(":/images/background.png"));
}// 暂停游戏
void MyView::pauseGame()
{boxGroup->stopTimer();restartButton->hide();pauseButton->hide();showMenuButton->hide();maskWidget->show();gamePausedText->show();returnButton->show();
}// 返回游戏,处于暂停状态时
void MyView::returnGame()
{returnButton->hide();gamePausedText->hide();maskWidget->hide();restartButton->show();pauseButton->show();showMenuButton->show();boxGroup->startTimer(gameSpeed);
}
踩坑点1:error: no matching function for call to ‘QGraphicsTextItem::QGraphicsTextItem(int, QGraphicsScene*&)’

报错信息:

error: no matching function for call to 'QGraphicsTextItem::QGraphicsTextItem(int, QGraphicsScene*&)'gameWelcomeText = new QGraphicsTextItem(0, scene);
                                                 ^

将下面代码:

// 游戏欢迎文本
gameWelcomeText = new QGraphicsTextItem(0, scene);
gameWelcomeText->setHtml(tr("<font color=white>MyBox方块游戏</font>"));
gameWelcomeText->setFont(QFont("Times", 30, QFont::Bold));
gameWelcomeText->setPos(250, 100);
gameWelcomeText->setZValue(2);// 游戏暂停文本
gamePausedText = new QGraphicsTextItem(0, scene);
gamePausedText->setHtml(tr("<font color=white>游戏暂停中!</font>"));
gamePausedText->setFont(QFont("Times", 30, QFont::Bold));
gamePausedText->setPos(300, 100);
gamePausedText->setZValue(2);
gamePausedText->hide();// 游戏结束文本
gameOverText = new QGraphicsTextItem(0, scene);
gameOverText->setHtml(tr("<font color=white>游戏结束!</font>"));
gameOverText->setFont(QFont("Times", 30, QFont::Bold));
gameOverText->setPos(320, 100);
gameOverText->setZValue(2);
gameOverText->hide();

修改为:

// 游戏欢迎文本
gameWelcomeText = new QGraphicsTextItem();
gameWelcomeText->setHtml(tr("<font color=white>MyBox方块游戏</font>"));
gameWelcomeText->setFont(QFont("Times", 30, QFont::Bold));
gameWelcomeText->setPos(250, 100);
gameWelcomeText->setZValue(2);// 游戏暂停文本
gamePausedText = new QGraphicsTextItem();
gamePausedText->setHtml(tr("<font color=white>游戏暂停中!</font>"));
gamePausedText->setFont(QFont("Times", 30, QFont::Bold));
gamePausedText->setPos(300, 100);
gamePausedText->setZValue(2);
gamePausedText->hide();// 游戏结束文本
gameOverText = new QGraphicsTextItem();
gameOverText->setHtml(tr("<font color=white>游戏结束!</font>"));
gameOverText->setFont(QFont("Times", 30, QFont::Bold));
gameOverText->setPos(320, 100);
gameOverText->setZValue(2);
gameOverText->hide();scene->addItem(gameWelcomeText);
scene->addItem(gamePausedText);
scene->addItem(gameOverText);

为了进行游戏时总是当前方块组获得焦点,我们要重写视图的键盘按下事件处理函数。

myview.h 新增代码:

protected:void keyPressEvent(QKeyEvent* event);

然后到 myview.cpp 添加定义:

// 如果正在进行游戏,当键盘按下时总是方块组获得焦点
void MyView::keyPressEvent(QKeyEvent* event)
{if (pauseButton->isVisible())boxGroup->setFocus();elseboxGroup->clearFocus();QGraphicsView::keyPressEvent(event);
}
踩坑点2:error: no matching function for call to ‘QGraphicsScene::items(int, int, int, int, Qt::ItemSelectionMode)’

报错信息:

error: no matching function for call to 'QGraphicsScene::items(int, int, int, int, Qt::ItemSelectionMode)'foreach (QGraphicsItem* item, scene()->items(199, 49, 202, 402, Qt::ContainsItemShape))

原因与之前的踩坑点2:items 方法报错相同。

错误代码:

foreach (QGraphicsItem *item, scene()->items(199, 49, 202, 402, Qt::ContainsItemShape))
{OneBox *box = (OneBox *)item;box->deleteLater();
}

修改为:

foreach (QGraphicsItem *item, scene()->items(199, 49, 202, 402, Qt::ContainsItemShape, Qt::AscendingOrder))
{OneBox *box = (OneBox *)item;box->deleteLater();
}

添加背景音乐和音效

在 myGame.pro 添加代码:

QT += phonon

报错:

Project ERROR: Unknown module(s) in QT: phonon

因为 Qt5 不支持 phonon,所以此部分省略。

添加程序启动画面

在 main.cpp 中添加头文件:

#include <QSplashScreen>

在主函数创建 view 对象前添加代码:

QPixmap pix(":/images/logo.png");
QSplashScreen splash(pix);
splash.resize(pix.size());
splash.show();
app.processEvents();

在调用 show() 函数后添加代码:

splash.finish(&view);

运行效果

运行程序,主界面出现前会在屏幕的中心出现启动画面:

在这里插入图片描述

开始界面:

在这里插入图片描述

点击“帮助”:

在这里插入图片描述

游戏界面:

在这里插入图片描述

暂停:

在这里插入图片描述

游戏结束:

在这里插入图片描述

资源下载

GitHub:

CSDN:

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux的账号管理

本章的学习感觉如果不做系统管理员&#xff0c;作为简单了解就可以了 前面介绍了&#xff0c;用户&#xff0c;组&#xff0c;other三个角色&#xff0c; 每个用户创建都会有uid与之对应&#xff0c;创建的用户基本信息在一下两个文件中&#xff0c;也是我们要介绍的内容&…

模板引擎技术---FreeMarker

什么是模板引擎 模板引擎是一种用于生成动态内容的工具&#xff0c;它将数据和静态模板结合起来&#xff0c;生成最终的文档&#xff08;通常是HTML、XML、JSON等格式&#xff09;&#xff0c;这些文档可以被浏览器或其他客户端解析并展示给用户。模板引擎的主要目的是将数据和…

Node学习笔记之跨域

1.跨域是什么&#xff1f; 跨域&#xff0c;是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的&#xff0c;是浏览器对JavaScript实施的安全限制。 同源策略限制了一下行为&#xff1a; Cookie无法读取DOM 和 JS 对象无法获取Ajax请求发送不出去 同源是指&#…

摩尔斯电码加密字符串

摩尔斯电码滴答“.-”&#xff0c;加密字符串。 (本笔记适合熟悉循环和列表的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 python 完全自学教程》&#xff0c;不仅仅是基础那么简单………

用前端框架Bootstrap的AdminLTE模板和Django实现后台首页的页面

承接博文 用前端框架Bootstrap和Django实现用户注册页面 继续开发实现 后台首页的页面。 01-下载 AdminLTE-3.1.0-rc 并解压缩 以下需要的四个文件夹及里面的文件百度网盘下载链接&#xff1a; https://pan.baidu.com/s/1QYpjOfSBJPmjmVuFZdSgFQ?pwdo9ta 下载 AdminLTE-3.1…

代码随想录二刷Day 56

1143.最长公共子序列 本题和动态规划&#xff1a;718. 最长重复子数组 (opens new window)区别在于这里不要求是连续的了&#xff0c;但要有相对顺序&#xff0c;即&#xff1a;"ace" 是 "abcde" 的子序列&#xff0c;但 "aec" 不是 "abcd…

重温云栖,分享十年成长:我和云栖的故事

文章目录 前言活动背景我和云栖的交际历届峰会主题2009201020112012201320142015201620172018202120222023 技术带来的变化工作生活关注的领域 后记 前言 云栖大会&#xff0c;前身可追溯到2009年的地方网站峰会&#xff0c;2011年演变为阿里云开发者大会&#xff0c;2015年正式…

rcore 笔记 批处理系统 邓氏鱼

批处理系统 批处理系统 (Batch System) &#xff0c;它可用来管理无需或仅需少量用户交互即可运行的程序&#xff0c;在资源允许的情况下它可以自动安排程序的执行&#xff0c;这被称为“批处理作业”。 特权机制 实现特权级机制的根本原因是应用程序运行的安全性不可充分信…

修改element组件库的el-input-number的图标

官方样式&#xff1a; 我希望组件的图标改成一对上下是三角形的图标&#xff1a; 直接复制以下代码&#xff1a; ::v-deep .el-icon-arrow-down:before {content: "\e790"; } ::v-deep .el-icon-arrow-up:before {content: "\e78f"; } 完成&#xff01…

【高光谱与多光谱:空间-光谱双优化模型驱动】

A Spatial–Spectral Dual-Optimization Model-Driven Deep Network for Hyperspectral and Multispectral Image Fusion &#xff08;一种用于高光谱与多光谱图像融合的空间-光谱双优化模型驱动深度网络&#xff09; 深度学习&#xff0c;特别是卷积神经网络&#xff08;CNN…

uniapp开发小程序无法上传图片的解决方法

登录小程序后台&#xff0c;第一步菜单栏 设置 第二步&#xff0c;用户隐私保护 更新 第三步 选2 第四步 勾选需要的接口&#xff0c;并说明 等审核通过后&#xff0c;一会就能正常上传图片。

php实现关键词过滤

1. 构建关键词库 首先&#xff0c;你需要构建一个包含敏感关键词的库。你可以将这些敏感关键词存储在一个数组中&#xff0c;或者将它们存储在数据库中。 $keywords array(敏感词1,敏感词2,敏感词3,// 其他敏感词 ); 2. 实现关键词过滤函数 接下来&#xff0c;你需要实现一个…