本文的俄罗斯方块实例官方的一个经典实例。
俄罗斯方块这个游戏就是从顶部落下方块到底部,把每行都填充满。当一行被填充满,这行就会被移除,玩家就会获取分数。顶部的方块都会依次落下,如果有多行被填充满了,那么会移除多行,获取对应的分数。
键盘左键控制方块向左移动,键盘右键控制方块向右移动,键盘上键使得方块逆时针旋转90°,键盘下键使得方块顺时针旋转90°。
键盘D键使得方块加速落下,键盘空格使得方块立即落下。
本俄罗斯方块实例由3个类构成:
TetrixWindow:构造游戏的整体界面,也就是肉眼看到的东西(不含游戏逻辑)都是这个类负责。
TetrixBoard:包含游戏逻辑、键盘、展示方块、游戏区域。
TetrixPiece:统计分数信息等。
从中可以知道,本实例最复杂的类就是TetrixBoard,包含游戏逻辑和一些界面渲染。TetrixWindow和TetrixPiece就比较简单了。
TetrixWindow类定义
TetrixWindow类用于展示游戏信息并且画出游戏区域:
class TetrixWindow : public QWidget
{
Q_OBJECT
public:
TetrixWindow();
private:
QLabel *createLabel(const QString &text);
TetrixBoard *board;
QLabel *nextPieceLabel;
QLCDNumber *scoreLcd;
QLCDNumber *levelLcd;
QLCDNumber *linesLcd;
QPushButton *startButton;
QPushButton *quitButton;
QPushButton *pauseButton;
};
在类的private中有几个成员变量,包括前端画线,各种挂机和按钮,按钮有开始游戏,暂停当前游戏和退出。
TetrixWindow继承了QWidget,但QWidget父类不能构造自己想创建的结构,所以,一般用上面这种方式创建程序员想要的界面。
TetrixWindow类声明
构造函数为游戏创建元素:
TetrixWindow::TetrixWindow()
{
board = new TetrixBoard;
在构造函数里面创建TetrixBoard的实例,用于画出游戏区域,标签,下一个方块。标签初始为空。
3个QLCDNumber对象用于展示分数、等级、移除了多少行。程序运行时给他们一个初始值,当游戏开始时,再给数据进行填充。
scoreLcd = new QLCDNumber(5);
scoreLcd->setSegmentStyle(QLCDNumber::Filled);
创建了3个按钮并且绑定了快捷键,用于开始新游戏,暂停当前游戏,退出应用:
startButton = new QPushButton(tr("&Start"));
startButton->setFocusPolicy(Qt::NoFocus);
quitButton = new QPushButton(tr("&Quit"));
quitButton->setFocusPolicy(Qt::NoFocus);
pauseButton = new QPushButton(tr("&Pause"));
pauseButton->setFocusPolicy(Qt::NoFocus);
上面的代码说明3个按钮不接受键盘的聚焦;但他需要和TetrixBoard示例的槽函数关联。虽然设置了Qt::NoFocus,但是如果带上键盘Alt的按键,仍然会收到。
按钮的clicked()信号关联TextrixBoard的Start和Pause,并且关联QCoreApplication::quit()
connect(startButton, &QPushButton::clicked, board, &TetrixBoard::start);
connect(quitButton , &QPushButton::clicked, qApp, &QApplication::quit);
connect(pauseButton, &QPushButton::clicked, board, &TetrixBoard::pause);
#if __cplusplus >= 201402L
connect(board, &TetrixBoard::scoreChanged,
scoreLcd, qOverload<int>(&QLCDNumber::display));
connect(board, &TetrixBoard::levelChanged,
levelLcd, qOverload<int>(&QLCDNumber::display));
connect(board, &TetrixBoard::linesRemovedChanged,
linesLcd, qOverload<int>(&QLCDNumber::display));
#else
connect(board, &TetrixBoard::scoreChanged,
scoreLcd, QOverload<int>::of(&QLCDNumber::display));
connect(board, &TetrixBoard::levelChanged,
levelLcd, QOverload<int>::of(&QLCDNumber::display));
connect(board, &TetrixBoard::linesRemovedChanged,
linesLcd, QOverload<int>::of(&QLCDNumber::display));
#endif
board中有些信号关联了LCD关键的一些槽函数,用于更新分数、等级、游戏区域移除了多少行。
将标签、LCD挂件、board、一些使用createLabel函数创建的label放到QGridLayout:
QGridLayout *layout = new QGridLayout;
layout->addWidget(createLabel(tr("NEXT")), 0, 0);
layout->addWidget(nextPieceLabel, 1, 0);
layout->addWidget(createLabel(tr("LEVEL")), 2, 0);
layout->addWidget(levelLcd, 3, 0);
layout->addWidget(startButton, 4, 0);
layout->addWidget(board, 0, 1, 6, 1);
layout->addWidget(createLabel(tr("SCORE")), 0, 2);
layout->addWidget(scoreLcd, 1, 2);
layout->addWidget(createLabel(tr("LINES REMOVED")), 2, 2);
layout->addWidget(linesLcd, 3, 2);
layout->addWidget(quitButton, 4, 2);
layout->addWidget(pauseButton, 5, 2);
setLayout(layout);
setWindowTitle(tr("Tetrix"));
resize(550, 370);
}
最后将grid放到widget中,并设置程序的标题,应用程序的大小。
createLabel()这个函数在堆区创建label,并且将label居中,最后返回