Qt5 C++14教程----突围游戏

Qt5中的突围游戏
 

在Qt5教程的这一部分,我们创建一个简单的Breakout游戏克隆。

《突围》是由Atari公司开发的一款街机游戏。该游戏创建于1976年。在这个游戏中,玩家移动一个桨,弹出一个球。我們的目標是摧毀磚塊在窗口的頂部。

开发过程
在我们的游戏中,我们有一个桨,一个球和三十块砖。一个计时器被用来创建一个游戏周期。我们不使用角度,我们只是改变方向:上、下、左、右。代码的灵感来自于PyBreakout游戏,它是由Nathan Dawson用PyGame库开发的。

这个游戏有意地简单。没有奖金、关卡或分数。这样就更容易理解了。

Qt5库是为创建计算机应用程序而开发的。尽管如此,它也可以用来创建游戏。开发一个电脑游戏是了解Qt5的一个好方法。

paddle.h
#pragma once

#include 
#include 

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
#include 
#include "paddle.h"

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;
}



桨叶可以向右或向左移动。

Paddle::Paddle() {
    
  dx = 0;    
  image.load("paddle.png");

  rect = image.rect();
  resetState();
}
在构造函数中,我们启动了dx变量并加载了paddle图像。我们得到图像的矩形,并将图像移动到其初始位置。

void Paddle::move() {
    
    int x = rect.x() + dx;
    int y = rect.top();
    
    rect.moveTo(x, y);
}
移动方法移动桨叶的矩形。移动的方向是由dx变量控制的。

void Paddle::resetState() {
    
  rect.moveTo(INITIAL_X, INITIAL_Y);
}
resetState将桨叶移动到它的初始位置。

brick.h
#pragma once

#include 
#include 

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;
};



这是砖块对象的头文件。如果一个砖块被销毁,销毁的变量被设置为真。

brick.cpp
#include 
#include "brick.h"

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类代表砖块对象。

Brick::Brick(int x, int y) {
    
  image.load("brickie.png");
  destroyed = false;
  rect = image.rect();
  rect.translate(x, y);
}
砖块的构造函数加载它的图像,启动销毁标志,并将图像移动到它的初始位置。

bool Brick::isDestroyed() {
    
  return destroyed;
}


砖块有一个销毁的标志。如果销毁标志被设置,该砖块就不会被画在窗口上。

ball.h
#pragma once

#include 
#include 

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
#include 
#include "ball.h"

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;
}


球类代表了球的对象。

xdir = 1;
ydir = -1;
开始时,球在东北方向移动。

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
#pragma once

#include 
#include 
#include "ball.h"
#include "brick.h"
#include "paddle.h"

class Breakout : public QWidget {
    
  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;
};


这是闯关对象的头文件。

void keyPressEvent(QKeyEvent *);
void keyReleaseEvent(QKeyEvent *);
桨是用光标键控制的。在游戏中,我们监听按键和按键释放事件。

int x;
int timerId;
x变量存储桨的当前x位置。timerId用于识别定时器对象。这在我们暂停游戏时是必要的。

static const int N_OF_BRICKS = 30;
N_OF_BRICKS常数存储游戏中砖块的数量。

static const int DELAY = 10;
DELAY常数控制游戏的速度。

static const int BOTTOM_EDGE = 400;
当球通过底边时,游戏就结束了。

Ball *ball;
Paddle *paddle;
Brick *bricks[N_OF_BRICKS];


游戏由一个球、桨和一个砖块阵列组成。

bool gameOver;
bool gameWon;
bool gameStarted;
bool paused;
这四个变量代表游戏的各种状态。

breakout.cpp
#include 
#include 
#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; isetFont(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; iisDestroyed()) {
      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; isetDestroyed(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; iisDestroyed()) {
      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; igetRect()).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文件中,我们有游戏逻辑。

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对象的构造函数中,我们实例化了三十块砖。

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的变量,我们或者用一条消息结束游戏,或者在窗口上画出游戏对象。

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方法在窗口中央画一个最后的消息。它是 "游戏失败 "或 "胜利"。QFontMetrics的宽度被用来计算字符串的宽度。

void Breakout::drawObjects(QPainter *painter) {
    
  painter->drawImage(ball->getRect(), ball->getImage());
  painter->drawImage(paddle->getRect(), paddle->getImage());

  for (int i=0; i     if (!bricks[i]->isDestroyed()) {
      painter->drawImage(bricks[i]->getRect(), bricks[i]->getImage() );
    }
  }      
}
drawObjects方法在窗口上画出游戏的所有对象:球、桨和砖块。这些对象由图像表示,drawImage方法将它们画在窗口上。

void Breakout::timerEvent(QTimerEvent *e) {
    
  Q_UNUSED(e);  
    
  moveObjects();
  checkCollision();
  repaint();
}
在timerEvent中,我们移动物体,检查球是否与球拍或砖块相撞,并产生一个油漆事件。

void Breakout::moveObjects() {

  ball->autoMove();
  paddle->move();
}
moveObjects方法移动了球和桨的对象。他们自己的移动方法正在被调用。

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变量设置为0。结果是,桨叶停止移动。

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变量设置为零。因此,桨叶停止移动。

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键退出应用程序。

void Breakout::startGame() {
     
  if (!gameStarted) {
    ball->resetState();
    paddle->resetState();

    for (int i=0; i       bricks[i]->setDestroyed(false);
    }
    
    gameOver = false; 
    gameWon = false; 
    gameStarted = true;
    timerId = startTimer(DELAY);  
  }      
}
startGame方法重置了球和桨的对象;它们被移动到初始位置。在for循环中,我们将每个砖块的销毁标志重置为false,从而将它们全部显示在窗口中。gameOver、gameWon和gameStarted变量得到它们的初始布尔值。最后,用startTimer方法启动定时器。

void Breakout::pauseGame() {
    
  if(paused) {
      
    timerId = startTimer(DELAY);
    paused = false;
  } else {
      
    paused = true;
    killTimer(timerId); 
  }        
}
pauseGame用于暂停和启动暂停的游戏。状态是由paused变量控制的。我们还存储了定时器的Id。为了暂停游戏,我们用killTimer方法杀死定时器。为了重新启动它,我们调用startTimer方法。

void Breakout::stopGame() {
    
  killTimer(timerId);    
  gameOver = true;      
  gameStarted = false;
}
在stopGame方法中,我们杀死定时器并设置相应的标志。

void Breakout::checkCollision() {
  
  if(ball->getRect().bottom() > BOTTOM_EDGE) {
    stopGame();
  }
...
}
在checkCollision方法中,我们为游戏进行碰撞检测。如果球撞到了底边,游戏就结束了。

for (int i=0, j=0; iisDestroyed()) {
    j++;
  }
    
  if (j == N_OF_BRICKS) {
    victory();
  }
}


我们检查有多少块砖被摧毁。如果我们摧毁了所有的砖块,我们就赢得了游戏。

if (ballLPos < first) {
  ball->setXDir(-1);
  ball->setYDir(-1);
}


如果球打到桨的第一部分,我们就把球的方向改为西北方向。

如果(bricks[i]->getRect().包含(pointTop)) {
  球->setYDir(1);

如果球打到了砖块的底部,我们改变球的Y方向;它就会往下走。

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


这是主文件。

Qt5 C++14教程----突围游戏_第1张图片

你可能感兴趣的:(Qt,qt,c++)