21xrx.com
2025-03-22 18:24:10 Saturday
文章检索 我的文章 写文章
使用Qt开发舒尔格游戏的C++教程
2023-07-01 12:21:23 深夜i     22     0
Qt 舒尔格游戏 C++ 教程 开发

舒尔格游戏(Schulte Grid)是一个测试反应力和注意力的小游戏,其原理是在一个方阵中随机排列数字,并要求玩家按照从小到大的顺序依次点击。在开发这个小游戏时,我们可以选择使用Qt作为开发工具,下面是一份使用Qt开发舒尔格游戏的C++教程,供大家参考。

1. 创建一个Qt项目

首先,我们需要在Qt中创建一个项目。选择File > New Project,然后在弹出的对话框中选择Qt Widgets Application。

2. 创建一个QWidget

在Qt中,我们可以将舞台理解为QWidget,因此我们需要创建一个QWidget,并设置其大小和背景。我们可以在头文件(.h)中定义一个新的类TowerDefenseWidget,并在实现文件(.cpp)中定义其具体的实现:

// TowerDefenseWidget.h
#ifndef TOWERDEFENSEWIDGET_H
#define TOWERDEFENSEWIDGET_H
#include <QWidget>
class TowerDefenseWidget : public QWidget
{
  Q_OBJECT
public:
  explicit TowerDefenseWidget(QWidget *parent = nullptr);
protected:
  void paintEvent(QPaintEvent *event) override;
private:
  void drawGrid(QPainter &painter, int size);
  void drawText(QPainter &painter, int fontSize, const QString &text,
         const QPointF &point, Qt::AlignmentFlag flags);
  int m_gridSize;
};
#endif // TOWERDEFENSEWIDGET_H
// TowerDefenseWidget.cpp
#include "TowerDefenseWidget.h"
#include <QPainter>
#include <QFont>
TowerDefenseWidget::TowerDefenseWidget(QWidget *parent)
  : QWidget(parent), m_gridSize(30)
{
  setFixedSize(m_gridSize * 5, m_gridSize * 5);
  setStyleSheet("background-color: white;");
  setWindowTitle(tr("Schulte Grid"));
}
void TowerDefenseWidget::paintEvent(QPaintEvent *event)
{
  Q_UNUSED(event);
  QPainter painter(this);
  painter.setRenderHint(QPainter::Antialiasing);
  drawGrid(painter, m_gridSize);
  drawText(painter, 20, "Schulte Grid", QPointF(width() / 2.0, 25), Qt::AlignHCenter);
}
void TowerDefenseWidget::drawGrid(QPainter &painter, int size)
{
  painter.save();
  painter.setPen(QPen(Qt::black, 1));
  for (int x = 0; x < 5; x++) {
    for (int y = 0; y < 5; y++) {
      QRectF rect(x * size, y * size, size, size);
      painter.drawRect(rect);
    }
  }
  painter.restore();
}
void TowerDefenseWidget::drawText(QPainter &painter, int fontSize, const QString &text,
                 const QPointF &point, Qt::AlignmentFlag flags)
{
  painter.save();
  QFont font = painter.font();
  font.setPixelSize(fontSize);
  painter.setFont(font);
  QRectF textRect = painter.boundingRect(QRectF(), flags, text);
  QPointF textPos = point - QPointF(textRect.width() / 2.0, textRect.height() / 2.0);
  painter.drawText(textPos, text);
  painter.restore();
}

在这里,我们定义了一个QWidget,并设置了其大小为150x150,背景颜色为白色,并在顶部居中显示了文字“Schulte Grid”。

3. 渲染数字方格

接下来,我们需要在QWidget上渲染数字方格。每个方格都由一个数字表示,玩家需要按照数字从小到大的顺序依次点击方格。我们可以在头文件(.h)中添加一些类变量和方法,方便我们在实现文件(.cpp)中渲染方格:

// TowerDefenseWidget.h
#ifndef TOWERDEFENSEWIDGET_H
#define TOWERDEFENSEWIDGET_H
#include <QWidget>
#include <vector>
class TowerDefenseWidget : public QWidget
{
  Q_OBJECT
public:
  explicit TowerDefenseWidget(QWidget *parent = nullptr);
protected:
  void paintEvent(QPaintEvent *event) override;
  void mousePressEvent(QMouseEvent *event) override;
private:
  void drawGrid(QPainter &painter, int size);
  void drawText(QPainter &painter, int fontSize, const QString &text,
         const QPointF &point, Qt::AlignmentFlag flags);
  int m_gridSize;
  std::vector<int> m_gridValues;
  std::vector<int> m_gridOrder;
};
#endif // TOWERDEFENSEWIDGET_H

在实现文件(.cpp)中,我们可以添加以下代码:

// TowerDefenseWidget.cpp
#include "TowerDefenseWidget.h"
#include <QPainter>
#include <QFont>
#include <algorithm>
#include <random>
TowerDefenseWidget::TowerDefenseWidget(QWidget *parent)
  : QWidget(parent), m_gridSize(30)
{
  setFixedSize(m_gridSize * 5, m_gridSize * 5);
  setStyleSheet("background-color: white;");
  setWindowTitle(tr("Schulte Grid"));
  // 随机生成25个数字
  for (int i = 1; i <= 25; ++i) {
    m_gridValues.push_back(i);
  }
  std::random_device rd;
  std::mt19937 g(rd());
  std::shuffle(m_gridValues.begin(), m_gridValues.end(), g);
  // 计算25个数字的顺序,并打乱
  for (int i = 0; i < 25; ++i) {
    m_gridOrder.push_back(i);
  }
  std::shuffle(m_gridOrder.begin(), m_gridOrder.end(), g);
}
void TowerDefenseWidget::paintEvent(QPaintEvent *event)
{
  Q_UNUSED(event);
  QPainter painter(this);
  painter.setRenderHint(QPainter::Antialiasing);
  drawGrid(painter, m_gridSize);
  for (int i = 0; i < 25; ++i) {
    int value = m_gridValues[m_gridOrder[i]];
    QPointF pos(i % 5, i / 5);
    QRectF rect(pos * m_gridSize, QSizeF(m_gridSize, m_gridSize));
    drawText(painter, 20, QString::number(value), rect.center(), Qt::AlignCenter);
  }
}
void TowerDefenseWidget::mousePressEvent(QMouseEvent *event)
{
  if (event->button() == Qt::LeftButton) {
    int index = (event->pos().y() / m_gridSize) * 5 + (event->pos().x() / m_gridSize);
    if (index == m_gridOrder.front()) {
      m_gridOrder.erase(m_gridOrder.begin());
      if (m_gridOrder.empty()) {
        QMessageBox::information(this, tr("Game Over"), tr("Congratulations!"));
      } else {
        update();
      }
    } else {
      QMessageBox::warning(this, tr("Wrong"), tr("Please click the next number!"));
    }
  }
}
void TowerDefenseWidget::drawGrid(QPainter &painter, int size)
{
  painter.save();
  painter.setPen(QPen(Qt::black, 1));
  for (int x = 0; x < 5; x++) {
    for (int y = 0; y < 5; y++) {
      QRectF rect(x * size, y * size, size, size);
      painter.drawRect(rect);
    }
  }
  painter.restore();
}
void TowerDefenseWidget::drawText(QPainter &painter, int fontSize, const QString &text,
                 const QPointF &point, Qt::AlignmentFlag flags)
{
  painter.save();
  QFont font = painter.font();
  font.setPixelSize(fontSize);
  painter.setFont(font);
  QRectF textRect = painter.boundingRect(QRectF(), flags, text);
  QPointF textPos = point - QPointF(textRect.width() / 2.0, textRect.height() / 2.0);
  painter.drawText(textPos, text);
  painter.restore();
}

在这里,我们添加了两个变量:m_gridValues和m_gridOrder。m_gridValues数组中保存了所有需要渲染的数字,m_gridOrder数组中保存了所有数字的顺序,并进行了打乱处理,以便让游戏更具挑战性。

在paintEvent方法中,我们使用循环遍历m_gridOrder数组,并使用其值作为索引从m_gridValues数组中获取相应的数字,然后绘制在对应的方格中。

在mousePressEvent方法中,我们首先通过计算鼠标点击位置所在的方格位置,并与m_gridOrder.front()进行比较来判断是否点击了正确的数字。如果点击了正确的数字,则将m_gridOrder.front()删除,并调用update()方法进行重新绘制。如果m_gridOrder为空,则表示游戏结束,弹出恭喜消息框。如果点击了错误的数字,则弹出错误消息框。

4. 增加游戏开始按钮

为了让游戏更加友好,我们可以添加一个按钮,让玩家可以随时开始游戏。我们可以在头文件(.h)中添加一个QPushButon对象,并在实现文件(.cpp)中创建该对象,设置其位置和大小以及样式表:

// TowerDefenseWidget.h
#ifndef TOWERDEFENSEWIDGET_H
#define TOWERDEFENSEWIDGET_H
#include <QWidget>
#include <vector>
class QPushButton;
class TowerDefenseWidget : public QWidget
{
  Q_OBJECT
public:
  explicit TowerDefenseWidget(QWidget *parent = nullptr);
protected:
  void paintEvent(QPaintEvent *event) override;
  void mousePressEvent(QMouseEvent *event) override;
private:
  void drawGrid(QPainter &painter, int size);
  void drawText(QPainter &painter, int fontSize, const QString &text,
         const QPointF &point, Qt::AlignmentFlag flags);
  int m_gridSize;
  std::vector<int> m_gridValues;
  std::vector<int> m_gridOrder;
  QPushButton *m_startButton;
};
#endif // TOWERDEFENSEWIDGET_H
// TowerDefenseWidget.cpp
#include "TowerDefenseWidget.h"
#include <QPainter>
#include <QFont>
#include <QHBoxLayout>
#include <QMessageBox>
#include <algorithm>
#include <random>
TowerDefenseWidget::TowerDefenseWidget(QWidget *parent)
  : QWidget(parent), m_gridSize(30)
{
  setFixedSize(m_gridSize * 5, m_gridSize * 5);
  setStyleSheet("background-color: white;");
  setWindowTitle(tr("Schulte Grid"));
  QHBoxLayout *layout = new QHBoxLayout(this);
  m_startButton = new QPushButton(tr("Start"), this);
  layout->addWidget(m_startButton, 0, Qt::AlignCenter);
  setLayout(layout);
  connect(m_startButton, &QPushButton::clicked, [this](){
    std::random_device rd;
    std::mt19937 g(rd());
    // 随机生成25个数字
    for (int i = 1; i <= 25; ++i) {
      m_gridValues.push_back(i);
    }
    std::shuffle(m_gridValues.begin(), m_gridValues.end(), g);
    // 计算25个数字的顺序,并打乱
    for (int i = 0; i < 25; ++i) {
      m_gridOrder.push_back(i);
    }
    std::shuffle(m_gridOrder.begin(), m_gridOrder.end(), g);
    m_startButton->hide();
    update();
  });
  m_startButton->setStyleSheet("font-size: 20px; padding: 10px;");
}
void TowerDefenseWidget::paintEvent(QPaintEvent *event)
{
  Q_UNUSED(event);
  QPainter painter(this);
  painter.setRenderHint(QPainter::Antialiasing);
  drawGrid(painter, m_gridSize);
  if (!m_gridOrder.empty()) {
    for (int i = 0; i < 25; ++i) {
      int value = m_gridValues[m_gridOrder[i]];
      QPointF pos(i % 5, i / 5);
      QRectF rect(pos * m_gridSize, QSizeF(m_gridSize, m_gridSize));
      drawText(painter, 20, QString::number(value), rect.center(), Qt::AlignCenter);
    }
  } else {
    drawText(painter, 20, "Game Over", QPointF(width() / 2.0, height() / 2.0), Qt::AlignHCenter | Qt::AlignVCenter);
  }
}
void TowerDefenseWidget::mousePressEvent(QMouseEvent *event)
{
  if (!m_gridOrder.empty() && event->button() == Qt::LeftButton) {
    int index = (event->pos().y() / m_gridSize) * 5 + (event->pos().x() / m_gridSize);
    if (index == m_gridOrder.front()) {
      m_gridOrder.erase(m_gridOrder.begin());
      if (m_gridOrder.empty()) {
        QMessageBox::information(this, tr("Game Over"), tr("Congratulations!"));
      } else {
        update();
      }
    } else {
      QMessageBox::warning(this, tr("Wrong"), tr("Please click the next number!"));
    }
  }
}
void TowerDefenseWidget::drawGrid(QPainter &painter, int size)
{
  painter.save();
  painter.setPen(QPen(Qt::black, 1));
  for (int x = 0; x < 5; x++) {
    for (int y = 0; y < 5; y++) {
      QRectF rect(x * size, y * size, size, size);
      painter.drawRect(rect);
    }
  }
  painter.restore();
}
void TowerDefenseWidget::drawText(QPainter &painter, int fontSize, const QString &text,
                 const QPointF &point, Qt::AlignmentFlag flags)
{
  painter.save();
  QFont font = painter.font();
  font.setPixelSize(fontSize);
  painter.setFont(font);
  QRectF textRect = painter.boundingRect(QRectF(), flags, text);
  QPointF textPos = point - QPointF(textRect.width() / 2.0, textRect.height() / 2.0);
  painter.drawText(textPos, text);
  painter.restore();
}

在这里,我们首先在头文件(.h)中添加了一个QPushButton指针,在实现文件(.cpp)中创建了该按钮,并设置了其样式表。在按钮被点击时,我们通过std::random_device和std::mt19937实现了随机生成数字,并打乱数字的顺序的功能。点击开始按钮后,我们将其隐藏,并调用update()方法进行重新绘制。

5. 总结

使用Qt开发舒尔格游戏是一项有趣而且又具挑战的任务。通过这个教程,我们学会了如何使用Qt绘制数字方格、随机生成数字并打乱顺序、处理鼠标事件和添加按钮等功能。学完本教程后,你可以进一步挑战自己,尝试添加计时器、音效和排行榜等功能,让游戏更加完善。

  
  

评论区