1. 程序简介:
俄罗斯方块是一款大家都熟系的小游戏,这里给大家一步一步的详细介绍如何用QT开发这个游戏,并通过这款游戏的开发练习,进一步熟系"qvector.h","qpoint.h", "qmap.h","qpainter.h", QTime, KeyEvent,QMediaPlaylist,QMediaPlayer 等的用法。
2. 程序说明:
2.1 程序界面:分游戏区,提示区,控制区
本文福利,莬费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QSS,OpenCV,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击莬费领取↓↓
2.2 程序实现功能:
1) 绘制游戏区域;
2) 按开始按钮开始游戏;
3) 按方向键实现方块的左移,右移,翻转;
4) 按空格键实现方块移动到最底部;
5) 当一行排满之后,自动消行。消行后,自动记分。
6) 每满1000分后,自动升到下一级,每升一级,方块向下移动的速度加快;
7) 记录游戏时间;
8) 按暂停按钮暂停游戏;
9) 按结束按钮结束游戏;
10)单击音乐按扭打开/关闭背景音乐;
3. 程序设计
3. 1 创建项目
3.1.1 新建一个以QMainWindow 为基类的Qt Widgets Application,取名 Teris;
3.1.2. 构建项目
1)单击项目模式,在弹出的窗口中选择构建套件,后按Configure Project 按扭。
构建完成后,程序处于可编译状态。
3.2 UI设计
3.2.1 移除窗口中的菜单栏,状态栏;
3.2.2 按下面示意图来设计窗体。
注意:
a. 主窗体大小设置为1000*800;
b. widgetGameArea 窗体大小设为600*800;
c. 先设计排版好控件后,再按Gridlayout;
d. 3 个Pushbutton 和CheckBox 的Focus Policy 设置为NoFocus;
3.2.3 在mainwindow.cpp 中填加如下语句:
setFixedSize(1000,800); //设置窗体为固定大小
setWindowTitle(tr("俄罗斯方块"));//设置窗体标题
3.2.4 运行效果如下
3.3 创建方块类:
3.3.1 新建一个C++ Class 文件,取名为Item, 并勾选#include Widget, #include QObject , Add Q_OBJECT.
3.3.2 在item.h中添加头文件 ,定义方块类形枚举变量,在GameArea 类体中添加 public 变量,成员函数;
#ifndef ITEM_H
#define ITEM_H
#include <QObject>
#include <QWidget>
#include "qvector.h"
#include "qpoint.h"
#include "qpainter.h"
enum ITEM_TYPE{
ITEM_1 = 0, //长条
ITEM_2, //山字形
ITEM_3, //手枪形1
ITEM_4, //手枪形2
ITEM_5, //田
ITEM_6, //Z字形1
ITEM_MAX,
};
class Item
{
public:
Item(){}
Item(ITEM_TYPE t,int nShape = 0); //类型和形状构造
~Item(void){};
void InitNew(int nSeed = 0); //根据随机因子nSeed, 初始化方块类形的种类及形状
void InitItem(ITEM_TYPE t,int nShape = 0); //根据方块类形的种类及形状,初始化方块类形的坐标
void ChangeShape(int nAdd = 1); //改变文块类形形状, 默认按顺续改变形状
void AddPoints(QVector<QPoint>& points); //添加方块格子坐标
void Move(int x,int y); //横向移动x格,竖向移动y格
void MoveTo(int x,int y); //移动到位置(x,y)格
void MoveDown(int nRow,int y); //第nRow行以上的部分下移y行,用在消格之后
void Draw(QPainter& painter,int nStartX,int nStartY,int nW,int nH); //根据方块格子的起始位置座标,长,宽,画出方块格子
void DeleteRow(int y); //删除第y行
public:
QVector<QPoint> mPoints; //方块类型4个方块格子的坐标
QPoint mPos; //方块类型原点
ITEM_TYPE mType; //方块类型,有6种类型
int mShape; //方块形状,每个类型方块有1~4个形状态,
};
#endif // ITEM_H
3.3.3 在item.cpp 中实现成员函数。
#include "item.h"
#include <QTime>
Item::Item(ITEM_TYPE t,int nShape) //类型和形状构造
{
mPos=QPoint(0,0); // 初始化方块类型的原点坐标
InitItem(t,nShape); // 根据方块类形的种类及形状,初始化方块类形的坐标
}
void Item::InitNew(int nSeed)
{
if(nSeed == 0)
{
//如果没有随机因子,就使用当前时间作随机因子
qsrand(QTime::currentTime().msec()); //qsrand, 使用Seed 生成随机数。后面的Qrand 使用相同的随机数序列
}
else
{
//传入随机因子
qsrand(nSeed);
}
ITEM_TYPE t = (ITEM_TYPE)(qrand()%ITEM_MAX); // 产生随机的方块类型
int s = qrand()%4; // 产生随机的形状
//qDebug()<<"t="<<t<<"s="<<s;
InitItem(t,s); //构造方块类型的坐标
}
void Item::InitItem(ITEM_TYPE t,int nShape) //根据方块类形的种类及形状,初始化方块类形的坐标
{
mPoints.clear(); //坐标初始化
mType = t; //初始化Item类体变量mType
mShape = nShape; //初始化Item类体变量mShape
switch (t)
{
case ITEM_1: //如果是长条形
{
if (nShape%2 == 0) //如果形状是0或2
{
for (int i = 0; i < 4; i++)
{
mPoints.append(mPos + QPoint( i,2));//QPoint 加法重载 ,(0,2),(1,2),(2,2),(3,2)
}
}
else if (nShape%2 == 1) //如果形状是1或3
{
for (int i = 0; i < 4; i++)
{
mPoints.append(mPos + QPoint( 2,i));//(2,0),(2,1),(2,2),(2,3)
}
}
break;
}
case ITEM_2: // 如果是山字形
{
if (nShape == 0) //如果形状是0
{
mPoints.append(mPos + QPoint( 1,0));
mPoints.append(mPos + QPoint( 0,1));
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 2,1));
}
else if (nShape == 1) //如果形状是1
{
mPoints.append(mPos + QPoint( 1,0));
mPoints.append(mPos + QPoint( 1,2));
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 2,1));
}
else if (nShape == 2) //如果形状是2
{
mPoints.append(mPos + QPoint( 0,1));
mPoints.append(mPos + QPoint( 1,2));
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 2,1));
}
else if (nShape == 3) //如果形状是3
{
mPoints.append(mPos + QPoint( 1,0));
mPoints.append(mPos + QPoint( 0,1));
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 1,2));
}
break;
}
case ITEM_3: // 手枪形1
{
if (nShape == 0)
{
mPoints.append(mPos + QPoint( 1,0));
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 1,2));
mPoints.append(mPos + QPoint( 2,2));
}
else if (nShape == 1)
{
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 2,1));
mPoints.append(mPos + QPoint( 3,1));
mPoints.append(mPos + QPoint( 1,2));
}
else if (nShape == 2)
{
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 2,1));
mPoints.append(mPos + QPoint( 2,2));
mPoints.append(mPos + QPoint( 2,3));
}
else if (nShape == 3)
{
mPoints.append(mPos + QPoint( 2,1));
mPoints.append(mPos + QPoint( 2,2));
mPoints.append(mPos + QPoint( 1,2));
mPoints.append(mPos + QPoint( 0,2));
}
break;
}
case ITEM_4: // 手枪形2
{
if (nShape == 0)
{
mPoints.append(mPos + QPoint( 2,0));
mPoints.append(mPos + QPoint( 2,1));
mPoints.append(mPos + QPoint( 2,2));
mPoints.append(mPos + QPoint( 1,2));
}
else if (nShape == 1)
{
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 1,2));
mPoints.append(mPos + QPoint( 2,2));
mPoints.append(mPos + QPoint( 3,2));
}
else if (nShape == 2)
{
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 2,1));
mPoints.append(mPos + QPoint( 1,2));
mPoints.append(mPos + QPoint( 1,3));
}
else if (nShape == 3)
{
mPoints.append(mPos + QPoint( 0,1));
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 2,1));
mPoints.append(mPos + QPoint( 2,2));
}
break;
}
case ITEM_5://田字形
{
mPoints.append(mPos + QPoint( 0,0));
mPoints.append(mPos + QPoint( 0,1));
mPoints.append(mPos + QPoint( 1,0));
mPoints.append(mPos + QPoint( 1,1));
break;
}
case ITEM_6: //z字形
{
if (nShape == 0)
{
mPoints.append(mPos + QPoint( 1,0));
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 2,1));
mPoints.append(mPos + QPoint( 2,2));
}
else if (nShape == 1)
{
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 2,1));
mPoints.append(mPos + QPoint( 0,2));
mPoints.append(mPos + QPoint( 1,2));
}
else if (nShape == 2)
{
mPoints.append(mPos + QPoint( 2,0));
mPoints.append(mPos + QPoint( 2,1));
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 1,2));
}
else if (nShape == 3)
{
mPoints.append(mPos + QPoint( 0,1));
mPoints.append(mPos + QPoint( 1,1));
mPoints.append(mPos + QPoint( 1,2));
mPoints.append(mPos + QPoint( 2,2));
}
break;
}
}
}
void Item::ChangeShape(int nAdd) //改变方块类型形状
{
mShape=(mShape+nAdd)%4; //重新产生方块类形形状态
InitItem(mType,mShape); //更新方块类型坐标
}
void Item::AddPoints(QVector<QPoint> &points) //添加方块格子坐标。size, contains, append 者是Vector 类的函数。
{
for(int i=0;i<points.size();i++) // 遍历Points
{
if(!mPoints.contains(points[i])) //如果mPoints中没有
mPoints.append(points[i]); //添加进mPoints
}
}
void Item::Move(int x, int y) //横向移动x格,竖向移动y格
{
for(int i=0;i<mPoints.size();i++) //遍历mPoints ( 方块类形坐标容噐)
{
int x1=mPoints[i].x()+x; //横坐标加x;
int y1=mPoints[i].y()+y; //横坐标加y;
mPoints[i].setX(x1); //新的x回写
mPoints[i].setY(y1); //新的y回写
}
mPos=mPos+QPoint(x,y); //原点位置回写
}
void Item::MoveTo(int x, int y) //移动到位置(x,y),即原点移动到(x,y)
{
for(int i=0;i<mPoints.size();i++) //遍历mPoints ( 方块类形坐标容噐)
{
int x1=mPoints[i].x()+(x-mPos.x()); //移动后的x坐标;
int y1=mPoints[i].y()+(y-mPos.y()); //移动后的y坐标;
mPoints[i].setX(x1); //x回写
mPoints[i].setY(y1); //y回写
}
mPos=QPoint(x,y); //原点位置回写
}
void Item::MoveDown(int nRow, int y) //第nRow行以上的部分下移y行,用在消格之后
{
for(int i=0;i<mPoints.size();i++) //遍历mPoints ( 方块类形坐标容噐)
{
if(mPoints[i].y()<nRow) //行坐标小于nRow 的
mPoints[i].setY(mPoints[i].y()+y); //行标加y,后回写
}
}
void Item::DeleteRow(int y) //删除第y行
{
QVector<QPoint> newPoints; //新建一个新的容器
for(int i=0;i<mPoints.size();i++) //遍历mPoints ( 方块类形坐标容噐)
{
if(mPoints[i].y()!=y) //如果行坐标不等于y,
newPoints.append(mPoints[i]); //添加到新的点容器
}
mPoints=newPoints; //新的容器回写到mPoints. (Vector 类=号重载)
}
void Item::Draw(QPainter &painter, int nStartX, int nStartY, int nW, int nH) //根据方块格子的起始位置座标,长,宽,画出方块格子
{
for (int i = 0; i< mPoints.size(); i++) //遍历mPoints
{
QPoint pt = mPoints[i];
//drawRect :画正方形,并填充
painter.drawRect(QRect(nStartX + pt.x() * nW,nStartY + pt.y() * nH,nW,nH)); //QRect( X,Y,width,height), 构造一个以(x,y) 为左上角, 长为width, 高为height 的长方形
}
}
3.4 游戏区程序设计, 实现开始,暂停,结束游戏,产生方块,方块移动,消格,记示游戏等级,分数,时间, 游戏背景绘制,方块绘制等功能
3.4.1 新建一个C++ Class 文件, 取名为GameArea, 并勾选#include Widget, #include QObject , Add Q_OBJECT.
3.4.2 在gamearea.h中添加头文件, 在类体中添加成员函数,变量及信号;
#ifndef GAMEAREA_H
#define GAMEAREA_H
#include <QObject>
#include <QWidget>
#include <QTime>
#include "item.h"
class GameArea : public QWidget
{
Q_OBJECT
public:
explicit GameArea(QWidget *parent = nullptr); //构造函数
void DrawBKRects(); //画用作背景的方块
void DrawFixedRects(); //画下落后已固定不动的方块
void DrawCurItem(); //画当前下落中的方块
void NewGame(); //开始游戏
void KeyPressed(int key); //识别按键,操作游戏
bool HitSide(); //判断当前下落方块是否超左右边界
bool HitBottom(); //判断当前下落方块是否达到底部
bool HitTop(); //判断当前下落方块是否达到顶部
void AddToFixedRects(); //把当前方块加入到 固定方块
void DeleteFullRows(); //删除完整的行
int GetLevelTime(int level); //获取不同等级关卡对应的定时器时间,关卡越高,时间越快(短)。比如1关=1s,2关=900ms,3关=800ms
void PauseGame(bool Gamestatus); //暂停游戏
void StopGame(); //停止游戏
signals:
void sigUpdateNextItem(ITEM_TYPE t,int nShape); //产生下一个方块类型信号
void sigUpdateScore(int nScore); //游戏分数信号
void sigUpdateLevel(int nSpeed); //游戏等极信号
void sigUdateTimeEslape(int tPlayed); //游戏时间信息
protected:
void paintEvent(QPaintEvent *); //绘图事件
void timerEvent(QTimerEvent*); //定时事件
private:
Item mFixItems; //已落下、固定住了的方块们
Item mCurItem; //当前移动中的方块
Item mNextItem; //下一个方块
int mTimerID; //定时器ID
int mScore; //得分
int mLevel; //关卡
QTime playTime; //游戏时间
};
#endif
3.4.3 在gamearea.cpp 中实现成员函数
#include "gamearea.h"
#include <QTimerEvent>
#include <QMessageBox>
#include <QKeyEvent>
#include <QTime>
#define RECT_COLUMES 15 //游戏区15个小格子宽
#define RECT_ROWS 20 //游戏区20个小格子高
#define RECT_WIDTH 40 //每个小格子40个像素宽
#define RECT_HEIGHT 40 //每个小格子40个像素高
//默认出生点 x方向
#define DEFAULT_BORN_POS_X (RECT_COLUMES / 2 - 1)
GameArea::GameArea(QWidget *parent) : QWidget(parent) //构造函数
{
mScore = 0;
mLevel = 1;
setMinimumSize(RECT_COLUMES*RECT_WIDTH, RECT_ROWS*RECT_HEIGHT);
}
void GameArea::NewGame() //开始游戏
{
mFixItems.mPoints.clear(); //清除已落下固定的格子的座标集
//mCurItem 和 mNextItem 使用不同随机因子,避免初始一样
mCurItem.InitNew(QTime::currentTime().msec()); //产生当前方块的座标集
mCurItem.MoveTo(DEFAULT_BORN_POS_X, 1);//移动当前方块到初始位置
mNextItem.InitNew(QTime::currentTime().second());//产生下一个方块的座标集
emit sigUpdateNextItem(mNextItem.mType,mNextItem.mShape);//传送下一个方块的消息
mScore = 0; //初始化
mLevel = 1; //初始化
emit sigUpdateScore(mScore); //更新MainWindow界面得分
emit sigUpdateLevel(mLevel); //更新MainWindow界面等级
mTimerID = startTimer( GetLevelTime(mLevel) ); //创建定时器
playTime.start(); //游戏开始计时
}
void GameArea::paintEvent(QPaintEvent *)//绘图事件,当窗体发生变更时,自动执行
{
//绘制左侧游戏区域
DrawBKRects(); //绘制游戏区被景
DrawFixedRects(); //绘制固定方块
DrawCurItem(); //绘制当前方块
update(); //更新窗体
}
void GameArea::DrawBKRects()
{
QPainter painter(this); //定义一个本窗体的画笔
painter.setBrush(QColor("#696969")); //定义填冲的颜色或格式
painter.setPen(Qt::NoPen); //定义边框的颜色和类型
for(int i=0;i<RECT_COLUMES;i++) //遍历游戏区域
{
for(int j=0;j<RECT_ROWS;j++)
{
//第0列和最后一列和第0行和最后一行
if(i==0||i==RECT_COLUMES-1||j==0||j==RECT_ROWS-1)
//drawRect(左上角x座标,左上角y座标,方格宽,方格高)
painter.drawRect(i*RECT_HEIGHT,j*RECT_WIDTH,RECT_WIDTH,RECT_HEIGHT);
}
}
}
void GameArea::DrawFixedRects() //画下落后已固定不动的方块
{
QPainter painter(this); //定义一个画笔
painter.setBrush(QColor("#D3D3D3"));//定义填冲的颜色或格式
painter.setPen(QPen(QColor(Qt::black),1)); //定义边框的颜色和类型
mFixItems.Draw(painter,0,0,RECT_WIDTH,RECT_HEIGHT);
}
void GameArea::DrawCurItem() //画当前下落中的方块
{
QPainter painter(this);
painter.setBrush(QColor("#FFDEAD"));
painter.setPen(QPen(QColor(Qt::black),1));
mCurItem.Draw(painter,0,0,RECT_WIDTH,RECT_HEIGHT);
}
bool GameArea::HitSide() //判断当前下落方块是否超左右边界
{
for (int i = 0; i<mCurItem.mPoints.size(); i++) //遍历当前下落方块的坐标
{
QPoint pt = mCurItem.mPoints[i];
if (pt.x() <= 0 || pt.x() >= RECT_COLUMES - 1)//如果X坐标小于等于0或大于等于RECT_COLUMES - 1,则撞到边界了
{
return true; //返回true
}
}
return false;
}
bool GameArea::HitTop() //判断当前下落方块是否超上边界
{
for (int i=0;i<mCurItem.mPoints.size();i++) //遍历当前下落方块的坐标
{
QPoint pt=mCurItem.mPoints[i];
if(pt.y()<=1) //如果y座标小于等于1, 则表示撞上边界了
return true;
}
return false;
}
bool GameArea::HitBottom() //判断当前下落方块是否撞下边界
{
for (int i=0;i<mCurItem.mPoints.size();i++)
{
QPoint pt=mCurItem.mPoints[i];
if(pt.y()>=RECT_ROWS-1) //如果y座标大于等于RECT_ROWS-1,则表示撞上下边界了
return true;
if(mFixItems.mPoints.contains(pt))// 如果固定不动的方块的座标包含当前下落方块的座标,则表示下到底了
return true;
}
return false;
}
void GameArea::KeyPressed(int key) //按键按压事件
{
int x = 0;
int y = 0;
switch(key)
{
case Qt::Key_Left: //左移
{
x = -1;
break;
}
case Qt::Key_Up://改变形状态
{
mCurItem.ChangeShape();
if(HitSide() || HitBottom())
{
mCurItem.ChangeShape(-1);
}
return;
}
case Qt::Key_Right: //右移
{
x = 1;
break;
}
case Qt::Key_Down://下移
{
y = 1;
break;
}
case Qt::Key_Space:
{
//空格键直接移到底部
while(1)
{
mCurItem.Move(0,1); //下移一行
if(HitBottom()) //如果撞到下边界或固定区域方块
{
mCurItem.Move(0,-1); //上移一行
break; //退出循环
}
}
return;//返回
}
}
mCurItem.Move(x,y); //移动x列,y行
if (HitSide() || HitBottom()) //如果撞到边界,下边界或固定区域方块
{
mCurItem.Move(-x,-y); //退回x列,y行
}
}
void GameArea::AddToFixedRects() //把当前方块座标加入到固定方块
{
mFixItems.AddPoints(mCurItem.mPoints);
}
void GameArea::DeleteFullRows() //删除完整的行
{
int nRowsDeleted = 0; //记录消的行数
for (int i = 1; i<RECT_ROWS-1; i++) //遍历游戏区座标
{
int nCount = 0;
for (int j = 1; j<RECT_COLUMES-1; j++)
{
if (mFixItems.mPoints.contains(QPoint(j,i))) //判定每行有多少位置有方块
{
nCount++; // 每行方块的个数
}
}
if (nCount == RECT_COLUMES-2)// 如果每行的个数等于Rect_Columes-2, 则代表是满的
{
mFixItems.DeleteRow(i);
mFixItems.MoveDown(i,1); //消除行之上的内容下移一个单位
nRowsDeleted++;
}
}
//一次元素落下,最多可能消4行
//一次消除的越多,得分越多
if (nRowsDeleted == 1)
{
mScore += 100;
}
else if (nRowsDeleted == 2)
{
mScore += 300;
}
else if (nRowsDeleted == 3)
{
mScore += 500;
}
else if (nRowsDeleted == 4)
{
mScore += 700;
}
emit sigUpdateScore(mScore); //更新MainWindow界面得分
//粗略使用每1000分一关
if(mScore >= 1000 * mLevel)
{
mLevel++;
//随关卡增加下落速度,即把定时器加快
killTimer(mTimerID);
mTimerID = startTimer( GetLevelTime(mLevel) );
emit sigUpdateLevel(mLevel); //更新MainWindow界面关卡
}
}
int GameArea::GetLevelTime(int level)
{
//第1关=1000ms,第2关=900ms 越来越快 第8关=300ms
//关卡>8后,速度不再增加,保持200ms
if (level > 0 && level<=8)
{
return (11 - level) * 100;
}
else
{
return 200;
}
}
void GameArea::timerEvent(QTimerEvent* e) //定时事件
{
if (e->timerId() == mTimerID)
{
mCurItem.Move(0,1);//向下移一格
if (HitBottom()) //如果撞到下边界或固定方块区,
{
mCurItem.Move(0,-1);//向上移一格
AddToFixedRects(); //添加当前下落方块到固定方块
DeleteFullRows(); //判定是否有满格行,若有,则删行
if (HitTop()) //如果撞到上边界
{
killTimer(mTimerID); //结束定时器,提醒Game over.
QMessageBox::information(NULL, "GAME OVER", "GAME OVER", QMessageBox::Yes , QMessageBox::Yes);
// NewGame();
return;
}
mCurItem = mNextItem; //产生新的当前方块类形,直接用Vector 值赋值Vector 变量
mCurItem.MoveTo(DEFAULT_BORN_POS_X, 1); //移到中间位置
mNextItem.InitNew(); //产生新的下一个方块类形
emit sigUpdateNextItem(mNextItem.mType,mNextItem.mShape); //发送下一个方块类型信号
}
emit sigUdateTimeEslape(playTime.elapsed()); //发送游戏时间信号
}
}
void GameArea::PauseGame(bool gamestatus) //暂停游戏
{
if(gamestatus)
killTimer(mTimerID); //停止定时器
else
{
mTimerID=startTimer(GetLevelTime(mLevel)); //开始定时器
playTime.restart(); //重新计时
}
}
void GameArea::StopGame() //停止游戏
{
mCurItem.mPoints.clear(); //Vector 的clear 函数,清除座标
mNextItem.mPoints.clear();
mFixItems.mPoints.clear();
killTimer(mTimerID);// 停止定时器
}
3.4.4 在mainwindow.h 添加槽的声明。
private slots:
void slotUpdateScore(int nScore);
void slotUpdateLevel(int nSpeed);
void slotUpdateTime(int tPlayed);
3.4.5 在mainwindow. cpp 中关联头文件 #include "gamearea.h",在构造函数中创建信号和槽的连接 及关联
GameArea通过信号sigUpdateScore、sigUpdateLevel 通知MainWindow更新界面的得分和关卡
GameArea通过信号sigUpdateTimeEslape 通知 MainWindow 更新游戏时间
connect(ui->widgetGameArea,&GameArea::sigUpdateScore,this,&MainWindow::slotUpdateScore);
connect(ui->widgetGameArea,&GameArea::sigUpdateLevel,this,&MainWindow::slotUpdateLevel);
connect(ui->widgetGameArea,&GameArea::sigUdateTimeEslape,this,&MainWindow::slotUpdateTime);
3.4.6 在mainwindow.cpp 中实现槽函数,slotUpdateScoe,slotUpdateLevel,slotUpdateTime
void MainWindow::slotUpdateScore(int nScore)
{
ui->labelScore->setText(QString::number(nScore));
}
void MainWindow::slotUpdateLevel(int nSpeed)
{
ui->labelSpeed->setText(QString::number(nSpeed));
}
void MainWindow::slotUpdateTime(int tplayed)
{
int sec=tplayed/1000;
ui->labelTime->setText(QString::number(sec)+mTime);
}
3.4.7 把Widgetgamearea 窗体提升为自定义类GameArea
3.5 提示区设计, 提示区主要包含一个窗体,用于产生和提示下一个方块。
3.5.1 新建一个C++ Clarss 文件 , 取名为NextArea , 并勾选#include Widget, #include QObject , Add Q_OBJECT
3.5.2 在nextarea.h 中 添加 "item.h" 头文件, 并在类体中添加如下代码
class NextArea
{
Q_OBJECT
public:
explicit NextArea(QWidget *parent = nullptr); //构造函数
protected:
void paintEvent(QPaintEvent *); //绘图事件
public slots:
void slotUpdateNextItem(ITEM_TYPE t,int nShape); //产生下一个方块的槽函数
private:
Item mItem; //item 类变量
};
3.5.3 在nextarea.cpp 中添加类的成员函数
void NextArea::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setBrush(QColor("#FFDEAD"));
painter.setPen(QPen(QColor(Qt::black),1));
int xStart = 80; //为了绘制在显示下一个方块区域的中部
int yStart = 15;
int w = 20;
int h = 20;
foreach (QPoint pt, mItem.mPoints)
{
int x = xStart + pt.x() * w;
int y = yStart + pt.y() * h;
painter.drawRect(x, y, w, h);
}
update();
}
void NextArea::slotUpdateNextItem(ITEM_TYPE t, int nShape)
{
mItem.InitItem(t,nShape);
}
3.5.3 在maiwindow.cpp 中添加头文件 nextarea.h, 在构造函数中添加如下代码
//GameArea通过信号sigUpdateNextItem 通知 NextArea 刷新下一个元素
connect(ui->widgetGameArea,&GameArea::sigUpdateNextItem,ui->widgetNextArea,&NextArea::slotUpdateNextItem);
3.5.4 提升nextarea 窗口为NextArea 类
3.6 操作区程序设计:
3.6.1 背景音乐:
3.6.1.1 在程序目录下,创建一个文件夹,命名为media, 拷备3个mp3 音乐到此文件夹下,并改名子为1.mp3, 2.mp3, 3.mp3
3.6.1.2 在teris.pro 添加 QT +=multimedia
3.6.1.3 在mainwindow.h 中添加头文件 QMediaPlaylist 和 QMediaPlayer. 并定义私有变量
QMediaPlaylist *Playlist;
QMediaPlayer *player;
3.6.1.4 在Mainwindow.cpp 的构造函数中, 添加如下代码
Playlist =new QMediaPlaylist; //创建新的播放清单
player=new QMediaPlayer; //创建新的播放器
Playlist->addMedia(QUrl::fromLocalFile("D:/MyQT/Teris/media/1.mp3")); //往播放清单中添加音乐文件
Playlist->addMedia(QUrl::fromLocalFile("D:/MyQT/Teris/media/2.mp3"));
Playlist->addMedia(QUrl::fromLocalFile("D:/MyQT/Teris/media/3.mp3"));
Playlist->setCurrentIndex(0); //设置默认播放开始位置
Playlist->setPlaybackMode(QMediaPlaylist::Loop); //设置循环播放
player->setPlaylist(Playlist); //播放清单导入播放器
player->play(); //播放器开播
3.6.1.5 单击Checkbox , 添加槽,
void MainWindow::on_checkBox_stateChanged(int arg1)
{
if(arg1)
player->play();
else
player->stop();
}
3.6.2 控制按键设计:
3.6.2.1 在mainwindow.h 中添加私有变量:
bool Gamestatus; //记录游戏状态
int mTime; //记录游戏时间
3.6.2.2 在mainwindow.cpp 的构造函数添加语名,初始化按键和游戏状态
ui->pbPause->setEnabled(false);
ui->pbStop->setEnabled(false);
ui->checkBox->setChecked(true);
Gamestatus=true;
3.6.2.3 分别添加3个按键的槽函数
void MainWindow::on_pbStart_clicked()
{
Gamestatus=true;
ui->widgetGameArea->NewGame();
ui->pbStart->setEnabled(false);
ui->pbPause->setEnabled(true);
ui->pbStop->setEnabled(true);
}
void MainWindow::on_pbPause_clicked()
{
ui->widgetGameArea->PauseGame(Gamestatus);
bool ok;
if(Gamestatus)
{
ui->pbPause->setText(tr("重新开始"));
Gamestatus=false;
mTime=(ui->labelTime->text()).toInt(&ok,10);
}
else
{
ui->pbPause->setText(tr("暂停"));
Gamestatus=true;
}
}
void MainWindow::on_pbStop_clicked()
{
ui->widgetGameArea->StopGame();
ui->pbStop->setEnabled(false);
ui->pbStart->setEnabled(true);
ui->pbPause->setEnabled(false);
}
3.6.2.4 添加键盘事件, 响应按键动作, 在mainwindow.h 中添加头文件KeyEvent 和KeypressEvent
protected:
void keyPressEvent(QKeyEvent *e);
3.6.2.5 在mainwindow.cpp , 实现函数
void MainWindow::keyPressEvent(QKeyEvent *e)
{
ui->widgetGameArea->KeyPressed(e->key());
QMainWindow::keyPressEvent(e);
}
介绍完成,运行程序玩起
文章转自博客园(江心秋月白1):QT实现俄罗斯方块游戏 - 江心秋月白1 - 博客园
本文福利,莬费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QSS,OpenCV,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击莬费领取↓↓