🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# Qt4 中的打砖块游戏 > 原文: [http://zetcode.com/gui/qt4/breakoutgame/](http://zetcode.com/gui/qt4/breakoutgame/) 在 Qt4 教程的这一部分中,我们创建一个简单的打砖块游戏克隆。 打砖块是 Atari Inc.开发的一款街机游戏。该游戏创建于 1976 年。在该游戏中,玩家移动桨叶并弹跳球。 目的是销毁窗口顶部的砖块。 可以在此处下载游戏图像。 ## 开发 在我们的游戏中,我们只有一个桨,一个球和三十个砖头。 计时器用于创建游戏周期。 我们不使用角度,而是仅更改方向:顶部,底部,左侧和右侧。 该代码的灵感来自 Nathan Dawson 在 PyGame 库中开发的 PyBreakout 游戏。 游戏是故意简单的。 没有奖金,等级或分数。 这样更容易理解。 Qt4 库是为创建计算机应用而开发的。 但是,它也可以用于创建游戏。 开发计算机游戏是了解 Qt4 的好方法。 `paddle.h` ```cpp #pragma once #include <QImage> #include <QRect> class Paddle { public: Paddle(); ~Paddle(); public: void resetState(); void move(); void setDx(int); QRect getRect(); QImage & getImage(); private: QImage image; QRect rect; int dx; static const int INITIAL_X = 200; static const int INITIAL_Y = 360; }; ``` 这是桨对象的头文件。 `INITIAL_X`和`INITIAL_Y`是代表桨状对象的初始坐标的常数。 `paddle.cpp` ```cpp #include "paddle.h" #include <iostream> Paddle::Paddle() { dx = 0; image.load("paddle.png"); rect = image.rect(); resetState(); } Paddle::~Paddle() { std::cout << ("Paddle deleted") << std::endl; } void Paddle::setDx(int x) { dx = x; } void Paddle::move() { int x = rect.x() + dx; int y = rect.top(); rect.moveTo(x, y); } void Paddle::resetState() { rect.moveTo(INITIAL_X, INITIAL_Y); } QRect Paddle::getRect() { return rect; } QImage & Paddle::getImage() { return image; } ``` 桨板可以向右或向左移动。 ```cpp Paddle::Paddle() { dx = 0; image.load("paddle.png"); rect = image.rect(); resetState(); } ``` 在构造器中,我们启动`dx`变量并加载桨图像。 我们得到图像矩形并将图像移动到其初始位置。 ```cpp void Paddle::move() { int x = rect.x() + dx; int y = rect.top(); rect.moveTo(x, y); } ``` `move()`方法移动桨的矩形。 移动方向由`dx`变量控制。 ```cpp void Paddle::resetState() { rect.moveTo(INITIAL_X, INITIAL_Y); } ``` `resetState()`将拨片移动到其初始位置。 `brick.h` ```cpp #pragma once #include <QImage> #include <QRect> class Brick { public: Brick(int, int); ~Brick(); public: bool isDestroyed(); void setDestroyed(bool); QRect getRect(); void setRect(QRect); QImage & getImage(); private: QImage image; QRect rect; bool destroyed; }; ``` 这是砖对象的头文件。 如果销毁了积木,则`destroyed`变量将设置为`true`。 `brick.cpp` ```cpp #include "brick.h" #include <iostream> Brick::Brick(int x, int y) { image.load("brickie.png"); destroyed = false; rect = image.rect(); rect.translate(x, y); } Brick::~Brick() { std::cout << ("Brick deleted") << std::endl; } QRect Brick::getRect() { return rect; } void Brick::setRect(QRect rct) { rect = rct; } QImage & Brick::getImage() { return image; } bool Brick::isDestroyed() { return destroyed; } void Brick::setDestroyed(bool destr) { destroyed = destr; } ``` `Brick`类代表砖对象。 ```cpp Brick::Brick(int x, int y) { image.load("brickie.png"); destroyed = false; rect = image.rect(); rect.translate(x, y); } ``` 砖的构造器加载其图像,启动`destroyed`标志,然后将图像移至其初始位置。 ```cpp bool Brick::isDestroyed() { return destroyed; } ``` 砖块具有`destroyed`标志。 如果设置了`destroyed`标志,则不会在窗口上绘制砖块。 `ball.h` ```cpp #pragma once #include <QImage> #include <QRect> class Ball { public: Ball(); ~Ball(); public: void resetState(); void autoMove(); void setXDir(int); void setYDir(int); int getXDir(); int getYDir(); QRect getRect(); QImage & getImage(); private: int xdir; int ydir; QImage image; QRect rect; static const int INITIAL_X = 230; static const int INITIAL_Y = 355; static const int RIGHT_EDGE = 300; }; ``` 这是球形对象的头文件。 `xdir`和`ydir`变量存储球的运动方向。 `ball.cpp` ```cpp #include "ball.h" #include <iostream> Ball::Ball() { xdir = 1; ydir = -1; image.load("ball.png"); rect = image.rect(); resetState(); } Ball::~Ball() { std::cout << ("Ball deleted") << std::endl; } void Ball::autoMove() { rect.translate(xdir, ydir); if (rect.left() == 0) { xdir = 1; } if (rect.right() == RIGHT_EDGE) { xdir = -1; } if (rect.top() == 0) { ydir = 1; } } void Ball::resetState() { rect.moveTo(INITIAL_X, INITIAL_Y); } void Ball::setXDir(int x) { xdir = x; } void Ball::setYDir(int y) { ydir = y; } int Ball::getXDir() { return xdir; } int Ball::getYDir() { return ydir; } QRect Ball::getRect() { return rect; } QImage & Ball::getImage() { return image; } ``` `Ball`类表示球对象。 ```cpp xdir = 1; ydir = -1; ``` 开始时,球向东北方向移动。 ```cpp void Ball::autoMove() { rect.translate(xdir, ydir); if (rect.left() == 0) { xdir = 1; } if (rect.right() == RIGHT_EDGE) { xdir = -1; } if (rect.top() == 0) { ydir = 1; } } ``` 在每个游戏周期都会调用`autoMove()`方法来在屏幕上移动球。 如果它破坏了边界,球的方向就会改变。 如果球越过底边,则球不会反弹回来-游戏结束。 `breakout.h` ```cpp #pragma once #include <QWidget> #include <QKeyEvent> #include "ball.h" #include "brick.h" #include "paddle.h" class Breakout : public QWidget { Q_OBJECT public: Breakout(QWidget *parent = 0); ~Breakout(); protected: void paintEvent(QPaintEvent *); void timerEvent(QTimerEvent *); void keyPressEvent(QKeyEvent *); void keyReleaseEvent(QKeyEvent *); void drawObjects(QPainter *); void finishGame(QPainter *, QString); void moveObjects(); void startGame(); void pauseGame(); void stopGame(); void victory(); void checkCollision(); private: int x; int timerId; static const int N_OF_BRICKS = 30; static const int DELAY = 10; static const int BOTTOM_EDGE = 400; Ball *ball; Paddle *paddle; Brick *bricks[N_OF_BRICKS]; bool gameOver; bool gameWon; bool gameStarted; bool paused; }; ``` 这是突破对象的头文件。 ```cpp void keyPressEvent(QKeyEvent *); void keyReleaseEvent(QKeyEvent *); ``` 使用光标键控制桨。 在游戏中,我们监听按键和按键释放事件。 ```cpp int x; int timerId; ``` `x`变量存储桨的当前 x 位置。 `timerId`用于识别计时器对象。 当我们暂停游戏时,这是必需的。 ```cpp static const int N_OF_BRICKS = 30; ``` `N_OF_BRICKS`常数存储游戏中的积木数量。 ```cpp static const int DELAY = 10; ``` `DELAY`常数控制游戏的速度。 ```cpp static const int BOTTOM_EDGE = 400; ``` 当球通过底边时,比赛结束。 ```cpp Ball *ball; Paddle *paddle; Brick *bricks[N_OF_BRICKS]; ``` 游戏包括一个球,一个球拍和一系列砖块。 ```cpp bool gameOver; bool gameWon; bool gameStarted; bool paused; ``` 这四个变量代表游戏的各种状态。 `breakout.cpp` ```cpp #include <QPainter> #include <QApplication> #include "breakout.h" Breakout::Breakout(QWidget *parent) : QWidget(parent) { x = 0; gameOver = false; gameWon = false; paused = false; gameStarted = false; ball = new Ball(); paddle = new Paddle(); int k = 0; for (int i=0; i<5; i++) { for (int j=0; j<6; j++) { bricks[k] = new Brick(j*40+30, i*10+50); k++; } } } Breakout::~Breakout() { delete ball; delete paddle; for (int i=0; i<N_OF_BRICKS; i++) { delete bricks[i]; } } void Breakout::paintEvent(QPaintEvent *e) { Q_UNUSED(e); QPainter painter(this); if (gameOver) { finishGame(&painter, "Game lost"); } else if(gameWon) { finishGame(&painter, "Victory"); } else { drawObjects(&painter); } } void Breakout::finishGame(QPainter *painter, QString message) { QFont font("Courier", 15, QFont::DemiBold); QFontMetrics fm(font); int textWidth = fm.width(message); painter->setFont(font); int h = height(); int w = width(); painter->translate(QPoint(w/2, h/2)); painter->drawText(-textWidth/2, 0, message); } void Breakout::drawObjects(QPainter *painter) { painter->drawImage(ball->getRect(), ball->getImage()); painter->drawImage(paddle->getRect(), paddle->getImage()); for (int i=0; i<N_OF_BRICKS; i++) { if (!bricks[i]->isDestroyed()) { painter->drawImage(bricks[i]->getRect(), bricks[i]->getImage()); } } } void Breakout::timerEvent(QTimerEvent *e) { Q_UNUSED(e); moveObjects(); checkCollision(); repaint(); } void Breakout::moveObjects() { ball->autoMove(); paddle->move(); } void Breakout::keyReleaseEvent(QKeyEvent *e) { int dx = 0; switch (e->key()) { case Qt::Key_Left: dx = 0; paddle->setDx(dx); break; case Qt::Key_Right: dx = 0; paddle->setDx(dx); break; } } void Breakout::keyPressEvent(QKeyEvent *e) { int dx = 0; switch (e->key()) { case Qt::Key_Left: dx = -1; paddle->setDx(dx); break; case Qt::Key_Right: dx = 1; paddle->setDx(dx); break; case Qt::Key_P: pauseGame(); break; case Qt::Key_Space: startGame(); break; case Qt::Key_Escape: qApp->exit(); break; default: QWidget::keyPressEvent(e); } } void Breakout::startGame() { if (!gameStarted) { ball->resetState(); paddle->resetState(); for (int i=0; i<N_OF_BRICKS; i++) { bricks[i]->setDestroyed(false); } gameOver = false; gameWon = false; gameStarted = true; timerId = startTimer(DELAY); } } void Breakout::pauseGame() { if (paused) { timerId = startTimer(DELAY); paused = false; } else { paused = true; killTimer(timerId); } } void Breakout::stopGame() { killTimer(timerId); gameOver = true; gameStarted = false; } void Breakout::victory() { killTimer(timerId); gameWon = true; gameStarted = false; } void Breakout::checkCollision() { if (ball->getRect().bottom() > BOTTOM_EDGE) { stopGame(); } for (int i=0, j=0; i<N_OF_BRICKS; i++) { if (bricks[i]->isDestroyed()) { j++; } if (j == N_OF_BRICKS) { victory(); } } if ((ball->getRect()).intersects(paddle->getRect())) { int paddleLPos = paddle->getRect().left(); int ballLPos = ball->getRect().left(); int first = paddleLPos + 8; int second = paddleLPos + 16; int third = paddleLPos + 24; int fourth = paddleLPos + 32; if (ballLPos < first) { ball->setXDir(-1); ball->setYDir(-1); } if (ballLPos >= first && ballLPos < second) { ball->setXDir(-1); ball->setYDir(-1*ball->getYDir()); } if (ballLPos >= second && ballLPos < third) { ball->setXDir(0); ball->setYDir(-1); } if (ballLPos >= third && ballLPos < fourth) { ball->setXDir(1); ball->setYDir(-1*ball->getYDir()); } if (ballLPos > fourth) { ball->setXDir(1); ball->setYDir(-1); } } for (int i=0; i<N_OF_BRICKS; i++) { if ((ball->getRect()).intersects(bricks[i]->getRect())) { int ballLeft = ball->getRect().left(); int ballHeight = ball->getRect().height(); int ballWidth = ball->getRect().width(); int ballTop = ball->getRect().top(); QPoint pointRight(ballLeft + ballWidth + 1, ballTop); QPoint pointLeft(ballLeft - 1, ballTop); QPoint pointTop(ballLeft, ballTop -1); QPoint pointBottom(ballLeft, ballTop + ballHeight + 1); if (!bricks[i]->isDestroyed()) { if(bricks[i]->getRect().contains(pointRight)) { ball->setXDir(-1); } else if(bricks[i]->getRect().contains(pointLeft)) { ball->setXDir(1); } if(bricks[i]->getRect().contains(pointTop)) { ball->setYDir(1); } else if(bricks[i]->getRect().contains(pointBottom)) { ball->setYDir(-1); } bricks[i]->setDestroyed(true); } } } } ``` 在`breakout.cpp`文件中,我们有游戏逻辑。 ```cpp int k = 0; for (int i=0; i<5; i++) { for (int j=0; j<6; j++) { bricks[k] = new Brick(j*40+30, i*10+50); k++; } } ``` 在打砖块对象的构造器中,我们实例化了三十个砖块。 ```cpp void Breakout::paintEvent(QPaintEvent *e) { Q_UNUSED(e); QPainter painter(this); if (gameOver) { finishGame(&painter, "Game lost"); } else if(gameWon) { finishGame(&painter, "Victory"); } else { drawObjects(&painter); } } ``` 根据`gameOver`和`gameWon`变量,我们要么用消息结束游戏,要么在窗口上绘制游戏对象。 ```cpp void Breakout::finishGame(QPainter *painter, QString message) { QFont font("Courier", 15, QFont::DemiBold); QFontMetrics fm(font); int textWidth = fm.width(message); painter->setFont(font); int h = height(); int w = width(); painter->translate(QPoint(w/2, h/2)); painter->drawText(-textWidth/2, 0, message); } ``` `finishGame()`方法在窗口中心绘制一条最终消息。 它是`"Game Over"`或`"Victory"`。 `QFontMetrics'` `width()`用于计算字符串的宽度。 ```cpp void Breakout::drawObjects(QPainter *painter) { painter->drawImage(ball->getRect(), ball->getImage()); painter->drawImage(paddle->getRect(), paddle->getImage()); for (int i=0; i<N_OF_BRICKS; i++) { if (!bricks[i]->isDestroyed()) { painter->drawImage(bricks[i]->getRect(), bricks[i]->getImage()); } } } ``` `drawObjects()`方法在窗口上绘制游戏的所有对象:球,球拍和砖头。 这些对象由图像表示,`drawImage()`方法将它们绘制在窗口上。 ```cpp void Breakout::timerEvent(QTimerEvent *e) { Q_UNUSED(e); moveObjects(); checkCollision(); repaint(); } ``` 在`timerEvent()`中,我们移动对象,检查球是否与桨或砖相撞,并生成绘图事件。 ```cpp void Breakout::moveObjects() { ball->autoMove(); paddle->move(); } ``` `moveObjects()`方法移动球和桨对象。 他们自己的移动方法被调用。 ```cpp void Breakout::keyReleaseEvent(QKeyEvent *e) { int dx = 0; switch (e->key()) { case Qt::Key_Left: dx = 0; paddle->setDx(dx); break; case Qt::Key_Right: dx = 0; paddle->setDx(dx); break; } } ``` 当播放器释放`左`光标键或`右`光标键时,我们将板的`dx`变量设置为零。 结果,桨停止运动。 ```cpp void Breakout::keyPressEvent(QKeyEvent *e) { int dx = 0; switch (e->key()) { case Qt::Key_Left: dx = -1; paddle->setDx(dx); break; case Qt::Key_Right: dx = 1; paddle->setDx(dx); break; case Qt::Key_P: pauseGame(); break; case Qt::Key_Space: startGame(); break; case Qt::Key_Escape: qApp->exit(); break; default: QWidget::keyPressEvent(e); } } ``` 在`keyPressEvent()`方法中,我们监听与游戏相关的按键事件。 `左`和`右`光标键移动桨状对象。 他们设置`dx`变量,该变量随后添加到桨的 x 坐标中。 `P` 键暂停游戏,`空格键`启动游戏。 `Esc` 键退出应用。 ```cpp void Breakout::startGame() { if (!gameStarted) { ball->resetState(); paddle->resetState(); for (int i=0; i<N_OF_BRICKS; i++) { bricks[i]->setDestroyed(false); } gameOver = false; gameWon = false; gameStarted = true; timerId = startTimer(DELAY); } } ``` `startGame()`方法重置球和桨对象; 他们被转移到他们的初始位置。 在`for`循环中,我们将每个积木的`destroyed`标志重置为`false`,从而将它们全部显示在窗口中。 `gameOver`,`gameWon`和`gameStarted`变量获得其初始布尔值。 最后,使用`startTimer()`方法启动计时器。 ```cpp void Breakout::pauseGame() { if (paused) { timerId = startTimer(DELAY); paused = false; } else { paused = true; killTimer(timerId); } } ``` `pauseGame()`用于暂停和开始暂停的游戏。 状态由`paused`变量控制。 我们还存储计时器的 ID。 为了暂停游戏,我们使用`killTimer()`方法终止计时器。 要重新启动它,我们调用`startTimer()`方法。 ```cpp void Breakout::stopGame() { killTimer(timerId); gameOver = true; gameStarted = false; } ``` 在`stopGame()`方法中,我们终止计时器并设置适当的标志。 ```cpp void Breakout::checkCollision() { if (ball->getRect().bottom() > BOTTOM_EDGE) { stopGame(); } ... } ``` 在`checkCollision()`方法中,我们对游戏进行碰撞检测。 如果球撞到底边,则比赛结束。 ```cpp for (int i=0, j=0; i<N_OF_BRICKS; i++) { if (bricks[i]->isDestroyed()) { j++; } if (j == N_OF_BRICKS) { victory(); } } ``` 我们检查了多少砖被破坏了。 如果我们摧毁了所有积木,我们将赢得这场比赛。 ```cpp if (ballLPos < first) { ball->setXDir(-1); ball->setYDir(-1); } ``` 如果球碰到了桨的第一部分,我们会将球的方向更改为西北。 ```cpp if(bricks[i]->getRect().contains(pointTop)) { ball->setYDir(1); } ``` 如果球撞击砖的底部,我们将改变球的 y 方向; 它下降了。 `main.cpp` ```cpp #include <QApplication> #include "breakout.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); Breakout window; window.resize(300, 400); window.setWindowTitle("Breakout"); window.show(); return app.exec(); } ``` 这是主文件。 ![The Breakout game](https://img.kancloud.cn/94/12/941207514269d2abca85a3993eb61ee3_302x426.jpg) 图:打砖块游戏 这是 Qt4 中的打砖块游戏。