QT实现俄罗斯方块游戏
1. 程序简介:
俄罗斯方块是一款大家都熟系的小游戏,这里给大家一步一步的详细介绍如何用QT开发这个游戏,并通过这款游戏的开发练习,进一步熟系"qvector.h","qpoint.h", "qmap.h","qpainter.h", QTime, KeyEvent,QMediaPlaylist,QMediaPlayer 等的用法。
2. 程序说明:
2.1 程序界面:分游戏区,提示区,控制区

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 中填加如下语句:
1 setFixedSize(1000,800); //设置窗体为固定大小 2 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); }
介绍完成,运行程序玩起

浙公网安备 33010602011771号