Qt5 里的贪吃蛇
最后修改于 2023 年 10 月 18 日
在本 Qt5 教程中,我们创建一个贪吃蛇游戏的克隆版。
贪吃蛇
贪吃蛇 是一款经典的旧视频游戏。它最初于 70 年代末被创造出来。后来它被移植到 PC 上。在这个游戏中,玩家控制一条蛇。目标是吃掉尽可能多的苹果。每次蛇吃掉一个苹果,它的身体就会变长。蛇必须避开墙壁和自己的身体。这个游戏有时也被称为 Nibbles。
开发
蛇的每个关节的大小是 10 像素。蛇用光标键控制。最初,蛇有三个关节。如果游戏结束,"Game Over" 消息将显示在游戏板的中间。
#pragma once #include <QWidget> #include <QKeyEvent> class Snake : public QWidget { public: Snake(QWidget *parent = nullptr); protected: void paintEvent(QPaintEvent *); void timerEvent(QTimerEvent *); void keyPressEvent(QKeyEvent *); private: QImage dot; QImage head; QImage apple; static const int B_WIDTH = 300; static const int B_HEIGHT = 300; static const int DOT_SIZE = 10; static const int ALL_DOTS = 900; static const int RAND_POS = 29; static const int DELAY = 140; int timerId; int dots; int apple_x; int apple_y; int x[ALL_DOTS]; int y[ALL_DOTS]; bool leftDirection; bool rightDirection; bool upDirection; bool downDirection; bool inGame; void loadImages(); void initGame(); void locateApple(); void checkApple(); void checkCollision(); void move(); void doDrawing(); void gameOver(QPainter &); };
这是头文件。
static const int B_WIDTH = 300; static const int B_HEIGHT = 300; static const int DOT_SIZE = 10; static const int ALL_DOTS = 900; static const int RAND_POS = 29; static const int DELAY = 140;
B_WIDTH
和 B_HEIGHT
常量确定游戏板的大小。DOT_SIZE
是苹果和蛇的点的尺寸。ALL_DOTS
常量定义了游戏板上可能的最大点数(900 = (300*300)/(10*10))。RAND_POS
常量用于计算苹果的随机位置。DELAY
常量决定了游戏的速度。
int x[ALL_DOTS]; int y[ALL_DOTS];
这两个数组保存了蛇的所有关节的 x 和 y 坐标。
#include <QPainter> #include <QTime> #include "snake.h" Snake::Snake(QWidget *parent) : QWidget(parent) { setStyleSheet("background-color:black;"); leftDirection = false; rightDirection = true; upDirection = false; downDirection = false; inGame = true; setFixedSize(B_WIDTH, B_HEIGHT); loadImages(); initGame(); } void Snake::loadImages() { dot.load("dot.png"); head.load("head.png"); apple.load("apple.png"); } void Snake::initGame() { dots = 3; for (int z = 0; z < dots; z++) { x[z] = 50 - z * 10; y[z] = 50; } locateApple(); timerId = startTimer(DELAY); } void Snake::paintEvent(QPaintEvent *e) { Q_UNUSED(e); doDrawing(); } void Snake::doDrawing() { QPainter qp(this); if (inGame) { qp.drawImage(apple_x, apple_y, apple); for (int z = 0; z < dots; z++) { if (z == 0) { qp.drawImage(x[z], y[z], head); } else { qp.drawImage(x[z], y[z], dot); } } } else { gameOver(qp); } } void Snake::gameOver(QPainter &qp) { QString message = "Game over"; QFont font("Courier", 15, QFont::DemiBold); QFontMetrics fm(font); int textWidth = fm.horizontalAdvance(message); qp.setFont(font); int h = height(); int w = width(); qp.translate(QPoint(w/2, h/2)); qp.drawText(-textWidth/2, 0, message); } void Snake::checkApple() { if ((x[0] == apple_x) && (y[0] == apple_y)) { dots++; locateApple(); } } void Snake::move() { for (int z = dots; z > 0; z--) { x[z] = x[(z - 1)]; y[z] = y[(z - 1)]; } if (leftDirection) { x[0] -= DOT_SIZE; } if (rightDirection) { x[0] += DOT_SIZE; } if (upDirection) { y[0] -= DOT_SIZE; } if (downDirection) { y[0] += DOT_SIZE; } } void Snake::checkCollision() { for (int z = dots; z > 0; z--) { if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) { inGame = false; } } if (y[0] >= B_HEIGHT) { inGame = false; } if (y[0] < 0) { inGame = false; } if (x[0] >= B_WIDTH) { inGame = false; } if (x[0] < 0) { inGame = false; } if(!inGame) { killTimer(timerId); } } void Snake::locateApple() { QTime time = QTime::currentTime(); qsrand((uint) time.msec()); int r = qrand() % RAND_POS; apple_x = (r * DOT_SIZE); r = qrand() % RAND_POS; apple_y = (r * DOT_SIZE); } void Snake::timerEvent(QTimerEvent *e) { Q_UNUSED(e); if (inGame) { checkApple(); checkCollision(); move(); } repaint(); } void Snake::keyPressEvent(QKeyEvent *e) { int key = e->key(); if ((key == Qt::Key_Left) && (!rightDirection)) { leftDirection = true; upDirection = false; downDirection = false; } if ((key == Qt::Key_Right) && (!leftDirection)) { rightDirection = true; upDirection = false; downDirection = false; } if ((key == Qt::Key_Up) && (!downDirection)) { upDirection = true; rightDirection = false; leftDirection = false; } if ((key == Qt::Key_Down) && (!upDirection)) { downDirection = true; rightDirection = false; leftDirection = false; } QWidget::keyPressEvent(e); }
在 snake.cpp
文件中,我们有游戏的逻辑。
void Snake::loadImages() { dot.load("dot.png"); head.load("head.png"); apple.load("apple.png"); }
在 loadImages
方法中,我们获取游戏的图像。ImageIcon
类用于显示 PNG 图像。
void Snake::initGame() { dots = 3; for (int z = 0; z < dots; z++) { x[z] = 50 - z * 10; y[z] = 50; } locateApple(); timerId = startTimer(DELAY); }
在 initGame
方法中,我们创建蛇,随机地在游戏板上定位一个苹果,并启动计时器。
void Snake::checkApple() { if ((x[0] == apple_x) && (y[0] == apple_y)) { dots++; locateApple(); } }
如果苹果与蛇头碰撞,我们增加蛇的关节数量。我们调用 locateApple
方法,该方法随机定位一个新的苹果对象。
在 move
方法中,我们有游戏的关键算法。要理解它,请看蛇是如何移动的。我们控制蛇的头部。我们可以用光标键改变它的方向。其余的关节沿链向上移动一个位置。第二个关节移动到第一个关节的位置,第三个关节移动到第二个关节的位置,以此类推。
for (int z = dots; z > 0; z--) { x[z] = x[(z - 1)]; y[z] = y[(z - 1)]; }
此代码沿链移动关节。
if (leftDirection) { x[0] -= DOT_SIZE; }
此行将头部向左移动。
在 checkCollision
方法中,我们确定蛇是否撞到了自己或墙壁之一。
for (int z = dots; z > 0; z--) { if ((z > 4) && (x[0] == x[z]) && (y[0] == y[z])) { inGame = false; } }
如果蛇用它的头撞到了它的一个关节,游戏就结束了。
if (y[0] >= B_HEIGHT) { inGame = false; }
如果蛇撞到了游戏板的底部,游戏就结束了。
void Snake::timerEvent(QTimerEvent *e) { Q_UNUSED(e); if (inGame) { checkApple(); checkCollision(); move(); } repaint(); }
timerEvent
方法形成一个游戏循环。假设游戏尚未结束,我们执行碰撞检测并进行移动。repaint
会导致窗口被重绘。
if ((key == Qt::Key_Left) && (!rightDirection)) { leftDirection = true; upDirection = false; downDirection = false; }
如果我们按下左光标键,我们将 leftDirection
变量设置为 true。此变量用于 move
函数以更改蛇对象的坐标。还要注意,当蛇向右移动时,我们不能立即向左转。
#include <QApplication> #include "snake.h" int main(int argc, char *argv[]) { QApplication app(argc, argv); Snake window; window.setWindowTitle("Snake"); window.show(); return app.exec(); }
这是主类。

这是在 Qt5 中开发的贪吃蛇游戏。