C

Qt Quick Ultralite 象棋示例

展示如何在 Qt Quick Ultralite 中使用 C++ 单例对象。

概述

这是一个简单的示例,展示了如何将 C++ 对象与 Qt Quick Ultralite 集成。它实现了一个简化的棋盘游戏,故意不遵守所有规则。两名玩家可以进行移动并捕获对方的棋子。游戏在右上角指示玩家的回合,并显示自上次移动以来的时间。

游戏逻辑在 C++ 单例对象中实现,以将单例的一些属性和方法暴露给 QML 代码。QML 代码通过将鼠标事件连接到单例来显示棋盘的当前状态。

项目结构

CMake 项目文件

在 QML 文件中使用 C++ 单例对象的属性和方法之前,告诉 qmlinterfacegenerator 工具将其暴露给 QML。这是通过在 QmlProject 中使用 InterfaceFiles.files 属性来完成的,该属性在头文件上运行该工具。

...
        InterfaceFiles {
                files: ["chessmodel.h"]
        }
...
单例对象

ChessModel 类负责管理棋盘。它通过继承 Qul::Singleton 来公开 QML 中的公共方法、信号和属性。

    ...
    Qul::Property<bool> whiteTurn;
    Qul::Property<int> secondsSinceMove;
    Qul::Signal<void(int col, int row)> validMove;
    Qul::Signal<void()> invalidMove;
    ...
    /// Return the current column position of a specific piece
    /// (or -1 if it is not in the chess board).
    /// This function is public and can be called from the QML
    int col(int piece) { return position[piece].value().first; }

    /// Return the current row position of a specific piece
    /// (or -1 if it is not in the chess board).
    /// This function is public and can be called from the QML
    int row(int piece) { return position[piece].value().second; }

    /// Return true if it is allowed to drop the piece on thie case
    /// This function is public and can be called from the QML
    bool canDrop(int col, int row)
    {
        if (col < 0 || col > 7 || row < 0 || row > 7)
            return false;
        return squares[col][row].value().canDrop;
    }

    /// Set the current active piece
    /// (if piece is negative, there shall not be any active piece)
    /// This function is public and can be called from the QML
    void setActivePiece(int piece)
    ...
    void release(int piece, int col, int row)
    ...
应用程序 UI

chess.qml 文件定义了棋盘和棋子。它使用两个 Repeaters 使用 Rectangle 项构建棋盘,并在其上面放置棋子。

负责棋盘的 Reapeator
    ...
    // The chess board: simply 64 squares of different colors
    Repeater {
        model: 64

        Rectangle {
            property int row: Math.floor(index/8);
            property int col: index % 8;
            z: 0;
            x: col * squareSize
            y: (7 - row) * squareSize
            height: squareSize
            width: squareSize
            color: {
                var even = ((row + col) % 2) == 0;
                if (!ChessModel.canDrop(col, row))
                    return even ? "#d18b47" : "#ffce9e";
                if (row == hoverRow && col == hoverCol)
                    return even ? "#d18bff" : "#ffceff";
                return even ? "#ff8b47" : "#ffaa88";
            }
        }
    }
    ...

区域颜色绑定到 ChessModel,使用 canDrop() 方法。它突出显示了特定棋子的可能移动。每次 canDrop() 对于一个棋子返回的值发生变化时,颜色绑定都会重新评估。

负责棋子的 Reapeator

它使用 ChessModel 获取每个棋子的位置。它还通过调用 setActivePiece()release() 方法通知模型任何用户操作。

    ...
    // The chess pieces: There are 32 chess pieces, the first 16 are white, and the
    // last 16 are black.
    Repeater {
        model: 32
        Item {
            id: pieceText;
            visible: ChessModel.col(modelData) >= 0;
            x: squareSize * ChessModel.col(modelData);
            y: squareSize * (7 - ChessModel.row(modelData));
            // Note: with QUL, the item in a repeater might not get the same order relative to
            // the item, so we use the z order to ensure that pieces are on top
            z: 1
            height: squareSize
            width: squareSize

            Text {
                color: index < 16 ? "#eee" : "#444"
                text: {
                    var p = index % 16;
                    switch (p) {
                    case 0:
                        return "♚";
                    case 1:
                        return "♛";
                    case 2:
                    case 3:
                        return "♜";
                    case 4:
                    case 5:
                        return "♝";
                    case 6:
                    case 7:
                        return "♞";
                    }
                    return "♟";
                }
                x: (pieceTouch.pressed ? pieceTouch.mouseX - pieceTouch.pressedX : 0);
                y: (pieceTouch.pressed ? pieceTouch.mouseY - pieceTouch.pressedY : 0);
                height: squareSize
                width: squareSize
                horizontalAlignment: Text.AlignHCenter
                verticalAlignment: Text.AlignVCenter
            }
            MouseArea {
                id: pieceTouch;
                anchors.fill: pieceText;
                property real pressedX: 0
                property real pressedY: 0
                onPressed: {
                    pressedX = mouse.x
                    pressedY = mouse.y
                }
                onPressedChanged: {
                    if (pressed) {
                        ChessModel.setActivePiece(modelData);
                    } else {
                        ChessModel.release(modelData, hoverCol,  hoverRow);
                        ChessModel.setActivePiece(-1);
                    }
                }

                onMouseXChanged: hoverCol = (pieceText.x + pieceTouch.mouseX - pieceTouch.pressedX + squareSize / 2) / squareSize;
                onMouseYChanged: hoverRow = 7 - (pieceText.y + pieceTouch.mouseY - pieceTouch.pressedY  - squareSize / 3) / squareSize;
            }
        }
    }
    ...

它还将 ChessModel 操作绑定到 invalidMovevalidMove 信号。

    ...
    ChessModel.onInvalidMove: invalidLabel.visible = true
    ChessModel.onValidMove: {
        invalidLabel.visible = false;
        console.log("valid move ", col, row);
    }

文件

在特定 Qt 许可下可用。
了解更多。