Qt5 C++14教程----贪吃蛇游戏

在Qt5教程的这一部分,我们创建了一个贪吃蛇游戏。

贪吃蛇
贪吃蛇是一个古老的经典视频游戏。它是在70年代末首次创建的。后来,它被带到了PC上。在这个游戏中,玩家控制一条蛇。游戏的目的是吃尽可能多的苹果。每当蛇吃下一个苹果,它的身体就会增长。蛇必须避开墙壁和自己的身体。这个游戏有时被称为Nibbles。

发展
蛇的每个关节的大小为10px。蛇是用光标键控制的。最初,蛇有三个关节。如果游戏结束,"游戏结束 "的信息会显示在棋盘的中间。

snake.h
#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坐标。

snake.cpp
#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方法,随机定位一个新的苹果对象。

在移动方法中,我们有这个游戏的关键算法。为了理解它,看看蛇是如何移动的。我们控制蛇的头部。我们可以用光标键改变它的方向。其余的关节在链条上移动一个位置。第二个关节移动到第一个关节的位置,第三个关节移动到第二个关节的位置,等等。

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;
    }
}
如果蛇用头撞到它的一个关节,游戏就结束了。

如果(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。这个变量在移动函数中被用来改变蛇对象的坐标。还要注意的是,当蛇向右移动时,我们不能立即向左转。

main.cpp
#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();
}


这是主类。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值