QT实现俄罗斯方块游戏

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模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击莬费领取↓↓ 

猜你喜欢

转载自blog.csdn.net/QtCompany/article/details/132910695