关于qt5:Qt5之QGraphicsItem编写Tetris俄罗斯方块游戏

5次阅读

共计 4010 个字符,预计需要花费 11 分钟才能阅读完成。

背景

应用 Qt5.12.9 的 QGraphicsItem 来实现俄罗斯方块,当初是 C ++ 版本,下来还会有 python 版本,以及不便的接口,来接入算法,由机器人玩俄罗斯方块。

思路

  • CustomGraphBase 类继承自 QGraphicsObject,提供必要的虚函数。
  • CustomGraphTetrisBlock 类继承自 CustomGraphBase,实现最小方块,分边框类型(0)与方块类型(1)。
  • CustomGraphTetrisText 类继承自 CustomGraphBase,显示文字,类型为 5。
  • Tetris 类组合 CustomGraphTetrisBlock,显示俄罗斯方块。
  • Game 类为游戏逻辑管制类。

    该游戏传统的编程形式,是用一个二维数组来管制游戏空间,相似迷宫的形式。其实抉择 QGraphicsItem 来实现就是一种很另类的抉择,其实用 gdi 来做更不便,这种规模,QGraphicsItem 没有劣势,只是集体学习摸索的抉择。
    我没有用二维数组来管制游戏空间,而是在边际上用了一圏 CustomGraphTetrisBlock 来定义游戏空间,因为所有的 items 都能不便的在 scene 上检索到,所以看一个方块是否能挪动,就须要检索本人的四周是否曾经被其它方块占据。这里有一点,在方块进行旋转的时候,就要判断辨别组成本人的 block 和他人的方块。

效果图

要害代码剖析

性能尽量内聚,类 CustomGraphTetrisBlock 封装小方块,Tetris 类组合了 Block,封装了俄罗斯方块的绝大部分操作,类 Game 游戏的整体流程。

CustomGraphBase 自定义图元基类

class CustomGraphBase : public QGraphicsObject
{
    Q_OBJECT
public:
    CustomGraphBase();
public:
    virtual QRectF boundingRect() const = 0;  // 占位区域,必须精确,能力很好的显示与革除
    virtual int type() const = 0;             
    virtual void relocate() = 0;              // 挪动,重定位
    virtual bool isActive() { return false;};// 未落地的方块
    virtual int getBlockType() { return 0;}; // 方块类型,次要区别边际方块
};

CustomGraphTetrisBlock 最小方块,组成俄罗斯方块的根本元素

paint 重绘操作,须要操作边际方块,边际方块只占位,不显示。要留神 prepareGeometryChange() 函数的应用,不能放在这个函数中,不然会不停的重绘,占用大量 CPU 资源。具体原理我还没钻研透,我将其放到 relocateb 函数中了。

void CustomGraphTetrisBlock::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget /*= nullptr*/)
{if (blockType) {
        painter->drawRoundedRect(
            0,
            0,
            BLOCKSIDEWIDTH,
            BLOCKSIDEWIDTH,
            2, 2
        );
    }
    //prepareGeometryChange();}

relocate 元素重定位,只需将其放到 scene 上正确的坐标

void CustomGraphTetrisBlock::relocate()
{this->setPos(pos * BLOCKSIDEWIDTH);
    prepareGeometryChange();}

Tetris 类,俄罗斯方块类

七类方块的定义

QVector<QVector<int>> SHAPES = {{1, 1, 1, 1},
    {0, 1, 1, 1, 0 , 1},
    {1, 1, 1, 0, 0, 0, 1},
    {0, 1, 1, 0, 0, 1, 1},
    {1, 1, 0, 0, 0, 1, 1},
    {0, 1, 1, 0, 1, 1},
    {0, 1, 0, 0, 1, 1, 1} 
};

俄罗斯方块的构建

QVector<int> curShape = SHAPES[shape % SHAPES.size()];
    for (int i = 0; i < curShape.size(); i++) {if (curShape[i]) {data[1 + i / sideLen][i % sideLen] = true;
            CustomGraphTetrisBlock* block = new CustomGraphTetrisBlock(pos + QPoint(i % sideLen, 1 + i / sideLen), 2, shape);
            blocks.push_back(block);   // 存储组成该方块的所有元素,在落到底之前须要由 Tetris 类管制其静止
            MainWindow::GetApp()->GetScene()->addItem(block);                                   // 退出 block 到 scene,显示方块
        }
    }

hasTetrisBlock 函数检测地位上是否有方块

CustomGraphTetrisBlock* Tetris::hasTetrisBlock(int x, int y)
{auto items = MainWindow::GetApp()->GetScene()->items(QPointF((x + 0.5) * BLOCKSIDEWIDTH, (y + 0.5) * BLOCKSIDEWIDTH));
    foreach (auto al , items)
    {if (!(((CustomGraphBase*)al)->isActive()) && (((CustomGraphBase*)al)->type()) == TETRISBLOCKTYPE) {      // 要区别组合俄罗斯方块自身的 block 与其它的 block
            return (CustomGraphTetrisBlock*)al;  // 返回方块,提供给革除行操作用
        }
    }
    return nullptr;
}

rotate 函数进行俄罗斯方块的旋转

bool Tetris::rotate()
{
    int i, j, t, lenHalf = sideLen / 2, lenJ;
    for (i = 0; i < lenHalf; i++)
    {
        lenJ = sideLen - i - 1;
        for (j = i; j < lenJ; j++)
        {        // 后行判断是否能旋转,要挪动的点不为 0 时,判断指标点是否曾经有 block 存在
            int lenI = sideLen - j - 1;
            if (data[i][j] && this->hasTetrisBlock(pos.x() + lenJ, pos.y() + j) ||
                data[lenI][i] && this->hasTetrisBlock(pos.x() + j, pos.y() + i) ||
                data[lenJ][lenI] && this->hasTetrisBlock(pos.x() + i, pos.y() + lenI) ||
                data[j][lenJ] && this->hasTetrisBlock(pos.x() + lenI, pos.y() + lenJ)){return false;}
        }
    }
    for (i = 0; i < lenHalf; i++)
    {       // 抉择了顺时针 90 度旋转,应用了螺旋挪动算法,网上能够容易搜寻到阐明。lenJ = sideLen - i - 1;
        for (j = i; j < lenJ; j++)
        {
            int lenI = sideLen - j - 1;
            t = data[i][j];
            data[i][j] = data[lenI][i];
            data[lenI][i] = data[lenJ][lenI];
            data[lenJ][lenI] = data[j][lenJ];
            data[j][lenJ] = t;
        }
    }
    this->relocate();
    return true;
}

cleanRow 函数实现行革除

int Tetris::cleanRow()
{       // 该革除算法效率不高,是以一行来解决的,这块当前能够优化。int h = 19, levelCount = 0;
    while (h >= 0) {
        int count = 0;
        for (int i = 0; i < 10; i++) {  // 判断是否行满
            if (!this->hasTetrisBlock(i, h)) {count++;}
        }
        if (count == 0) {               // 行满,须要革除并整体下移
            int level = h;
            levelCount++;
            bool first = true;
            while (level >= 0) {
                int ct = 0;
                for (int j = 0; j < 10; j++) {if(first)      // 第一个外循环删除满行上的图元,前面是整体下移
                        this->erase(j, level);
                    CustomGraphTetrisBlock* block = this->hasTetrisBlock(j, level - 1);
                    if (!block) {ct++;}
                    else {block->relocate(QPoint(j, level));     // 下移一个地位
                    }
                }
                first = false;
                if (ct == 10) {           // 一行上都没有图元,工作实现,提前结束
                    break;
                }
                else {level--;}
            }
        }
        else if (count == 10) {break;}
        else {h--;}
    }
    return levelCount;
}

源代码及运行办法

我的项目采纳 cmake 组织,请装置 cmake3.10 以上版本。

cmake -Bbuild .
cd build
cmake --build . --config Release

注:本我的项目采纳计划能跨平台运行,曾经适配过 windows,linux,mac。

源代码:

https://gitee.com/zhoutk/qtetris.git

https://gitee.com/zhoutk/qtdemo/tree/master/tetrisGraphicsItem

https://github.com/zhoutk/qtDemo/tree/master/tetrisGraphicsItem
正文完
 0