• 用Qt图形视图框架开发拼图游戏


    用Qt的图形视图框架(Graphics View Framework)做了一个拼图游戏DEMO,演示了:

    • QGraphicsView、QGraphicsScene、QGraphicsItem的基本用法
    • drag && drop
    • 自定义QGraphicsItem

    先来看看效果吧:

    现在,来看下代码了。

    项目说明

    如上图所示,项目名称为qPuzzle,三个源文件,main.cpp是入口,imageitem.h和imageitem.cpp实现了:

    • PuzzleImageItem,就是界面左上侧那两个可以拖动的碎片,支持拖动
    • PuzzlePart,用于接受拖放的item
    • PuzzlePartManager,管理可拖放的PuzzleImageItem,拖放到位后从QGraphicsScene中移除PuzzleImageItem

    项目还有几个图片资源,model.png是带有拼图区域的房子图片,mode_1.png和model_2.png是房子上扣出来的小图片。

    源码说明

    分开来说吧,main()、PuzzleImageItem和PuzzlePartItem。

    入口函数main

    先看main.cpp吧:

    class GraphicsView : public QGraphicsView
    {
    public:
        GraphicsView(QGraphicsScene *scene) : QGraphicsView(scene)
        {
        }
    
    protected:
        virtual void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE
        {
        }
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        QGraphicsScene scene(0, 0, 480, 360);
        PuzzleImageItem *image1 = new PuzzleImageItem(":/model_1.png", 60, 60, 1);
        image1->setPos(4, 4);
        scene.addItem(image1);
        PuzzleImageItem *image2 = new PuzzleImageItem(":/model_2.png", 60, 60, 2);
        image2->setPos(4, 70);
        scene.addItem(image2);
    
        QGraphicsPixmapItem *model = new QGraphicsPixmapItem(QPixmap(":/model.png"));
        scene.addItem(model);
        model->setPos(100, 100);
    
        PuzzlePartManager mgr(&scene);
        mgr.addSourceItems(1, image1);
        mgr.addSourceItems(2, image2);
    
        PuzzlePart *part1 = new PuzzlePart(&mgr, 60, 60, 1);
        part1->setPos(261, 149);
        part1->setZValue(2);
        scene.addItem(part1);
    
        PuzzlePart *part2 = new PuzzlePart(&mgr, 60, 60, 2);
        part2->setPos(231, 199);
        part2->setZValue(2);
        scene.addItem(part2);
    
        GraphicsView view(&scene);
        view.setRenderHint(QPainter::Antialiasing);
        view.setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
        view.setBackgroundBrush(QColor(230, 200, 167));
        view.setWindowTitle("House Puzzle");
        view.show();
    
        return a.exec();
    }

    main()方法创建了QGraphicsScene实例,构造各种item并添加到场景中,将QGraphicsView与QGraphicsScene关联起来,代码很直接,不多说了。

    PuzzleImageItem

    再来看看支持拖动的PuzzleImageItem的实现:

    class PuzzleImageItem : public QGraphicsObject
    {
    public:
        PuzzleImageItem(const QString & imagePath, int w, int h, int partId);
        QRectF boundingRect() const Q_DECL_OVERRIDE;
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) Q_DECL_OVERRIDE;
    
    protected:
        void mousePressEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE;
        void mouseMoveEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE;
        void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) Q_DECL_OVERRIDE;
    
    protected:
        QImage m_image;
        int m_width;
        int m_height;
        int m_partId;
    };

    PuzzleImageItem代表完整图片的一部分,它从QGraphicsObject继承而来,持有一个图片、宽、高以及图片的id(m_partId)。其中m_partId是碎片索引,在拖放到位后,PuzzlePartManager通过它来将界面左上角的碎片从视图中移除。

    重写了boundingRect和paint方法,这是自定义QGraphicsItem时通常都需要做的。

    重写了mousePressEvent,在它里面将鼠标形状修改为抓紧的小手:

    void PuzzleImageItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
    {
        setCursor(Qt::ClosedHandCursor);
    }

    重写了mouseMoveEvent,在它里面组装QDrag和QMimeData:

    void PuzzleImageItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
    {
        if (QLineF(event->screenPos(), event->buttonDownScreenPos(Qt::LeftButton))
            .length() < QApplication::startDragDistance()) {
            return;
        }
    
        QDrag *drag = new QDrag((QObject*)event->widget());
        QMimeData *mime = new QMimeData;
        mime->setImageData(m_image);
        mime->setData(QString(QMetaType::typeName(QMetaType::Int)), QString("%1").arg(m_partId).toLatin1());
        drag->setMimeData(mime);
    
        drag->setPixmap(QPixmap::fromImage(m_image));
        drag->setHotSpot(QPoint(15, 30));
    
        drag->exec();
        setCursor(Qt::OpenHandCursor);
    }

    注意我在这里用QMimeData传递m_partId给接受拖放的PuzzlePartItem,这样可以区分碎片该放到哪个目标区域。在PuzzlePartItem的dragEnterEvent方法中有用到,代码如下:

    void PuzzlePart::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
    {
        const QMimeData *mime = event->mimeData();
        int partId = mime->data(QMetaType::typeName(QMetaType::Int)).toInt();
        if(mime->hasImage() && partId == m_partId)
        {
            event->setAccepted(true);
            m_dragOver = true;
            update();
        }
        else
        {
            event->setAccepted(false);
        }
    }

    这里可以留意一下使用QMimeData传递非典型类型数据的做法。

    重写了mouseReleaseEvent,在它里面重新设置鼠标形状为打开的小手:

    void PuzzleImageItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
    {
        setCursor(Qt::OpenHandCursor);
    }

    PuzzlePartItem

    PuzzlePartItem定义了一个区域,用于接受拖放。它同样从QGraphicsObject继承,声明如下:

    class PuzzlePart : public QGraphicsObject
    {
    public:
        PuzzlePart(PuzzlePartManager *mgr, int w, int h, int partId, QGraphicsItem *parent = 0);
    
        QRectF boundingRect() const Q_DECL_OVERRIDE;
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) Q_DECL_OVERRIDE;
    
    protected:
        void dragEnterEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE;
        void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE;
        void dropEvent(QGraphicsSceneDragDropEvent *event) Q_DECL_OVERRIDE;
    
        PuzzlePartManager *m_sourcePartManager;
        int m_width;
        int m_height;
        int m_partId;
        QImage m_image;
        bool m_dragOver;
    };

    这个类也保留了一个id(m_partId),可以用来决定接受哪个PuzzleImageItem。前面贴出来的dragEnterEvent方法的代码里就用到了。dragEnterEvent方法在PuzzleImageItem被拖到PuzzlePartItem所在区域时触发。

    当释放鼠标时,会触发dropEvent,代码如下:

    void PuzzlePart::dropEvent(QGraphicsSceneDragDropEvent *event)
    {
        m_dragOver = false;
        const QMimeData *mime = event->mimeData();
        int partId = mime->data(QMetaType::typeName(QMetaType::Int)).toInt();
        if(mime->hasImage() && partId == m_partId)
        {
            m_image = qvariant_cast<QImage>(event->mimeData()->imageData());
            m_sourcePartManager->removeItem(partId);
        }
        update();
    }

    我们在这里接收QMimeData里的图片,触发重新绘制,还调用PuzzlePartManager的removeItem将源item(PuzzleImageItem)从视图中移除。

    重写dragLeaveEvent是为了在鼠标拖着源object离开自己时重绘。


    好啦,这个demo基本就这样了。

    需要项目源码的,可以关注我的订阅号“程序视界”,回复“qPuzzle”获取下载地址。

  • 相关阅读:
    mtd-utils 工具的使用
    nand flash坏块管理OOB,BBT,ECC
    Ubifs Support
    linux 关闭显示器命令
    玩转shell之符号篇
    shell中的cut命令
    【详解】如何编写Linux下Nand_Flash驱动_v1.2
    Nand 的几个名词:oob,bbt,ecc
    mtd-utils 及 ubi-utils 交叉编译
    UBIFS分区制作及UBIFS烧写和启动
  • 原文地址:https://www.cnblogs.com/hehehaha/p/6147402.html
Copyright © 2020-2023  润新知