Expose 2D C++ Game Board to QML using QAbstractItemModel

这一生的挚爱 提交于 2019-12-11 02:09:22

问题


I'm writing a simple Snake game with a game board model in C++ holding a two-dimensional vector of states (std::vector<std::vector<board::state>>). Now I want to expose this board to QML so that it's basically some sort of grid/chess board with access to the states of the model. I've read up a lot on this topic, but still wasn't able to understand the mechanics enough to solve my problem. Applying the docs, tutorials and blog entries to my problem is my hurdle here.

I subclassed QAbstractItemModel for my game board model and implemented the necessary functions. Now I want to make the step into QML and use/show the contents of my model there.

Here's my code:

board.h

#pragma once

#include <vector>

#include <QAbstractItemModel>

class board : public QAbstractItemModel
{
  Q_OBJECT

public:
  enum class state
  {
    empty,
    snake,
    fruit
  };

  board(int x, int y);

  state get_state(int x, int y) const;
  void  set_state(int x, int y, state state);

  QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;
  QModelIndex parent(const QModelIndex& index) const;
  int rowCount(const QModelIndex& parent = QModelIndex()) const;
  int columnCount(const QModelIndex& parent = QModelIndex()) const;
  QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;

private:
  std::vector<std::vector<state>> m_board;
};

Q_DECLARE_METATYPE(board::state)

board.cpp

#include "board.h"

board::board(int x, int y) :
  m_board(x, std::vector<board::state>(y, board::state::empty))
{
}

//----------------------------------------------------------------------------------------------------------------------

board::state board::get_state(int x, int y) const
{
  if((size_t) x >= m_board.size() || x < 0)
    return board::state::empty;

  if((size_t) y >= m_board.at(0).size() || y < 0)
    return board::state::empty;

  return m_board.at(x).at(y);
}

//----------------------------------------------------------------------------------------------------------------------

void board::set_state(int x, int y, state state)
{
  if(get_state(x, y) == state)
    return;

  m_board.at(x).at(y) = state;
}

//----------------------------------------------------------------------------------------------------------------------

QModelIndex board::index(int row, int column, const QModelIndex&) const
{
  if((size_t) row >= m_board.size() || row < 0)
    return QModelIndex();

  if((size_t) column >= m_board.at(0).size() || column < 0)
    return QModelIndex();

  return createIndex(row, column);
}

//----------------------------------------------------------------------------------------------------------------------

QModelIndex board::parent(const QModelIndex& index) const
{
  if((size_t) index.row() >= m_board.size() || index.row() < 0)
    return QModelIndex();

  if((size_t) index.column() >= m_board.at(0).size() || index.column() < 0)
    return QModelIndex();

  return createIndex(index.row(), index.column());
}

//----------------------------------------------------------------------------------------------------------------------

int board::rowCount(const QModelIndex&) const
{
  return m_board.size();
}

//----------------------------------------------------------------------------------------------------------------------

int board::columnCount(const QModelIndex&) const
{
  return m_board.at(0).size();
}

//----------------------------------------------------------------------------------------------------------------------

QVariant board::data(const QModelIndex& index, int role) const
{
  if(!index.isValid())
    return QVariant();

  if(role != Qt::DisplayRole)
    return QVariant();

  if((size_t) index.row() >= m_board.size() || index.row() < 0)
    return QVariant();

  if((size_t) index.column() >= m_board.at(0).size() || index.column() < 0)
    return QVariant();

  return qVariantFromValue(get_state(index.row(), index.column()));
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickView>

#include <board.h>

int main(int argc, char *argv[])
{
  QGuiApplication app(argc, argv);



  board game_board(10, 10);
  game_board.set_state(4, 9, board::state::snake);
  game_board.set_state(3, 10, board::state::fruit);

  QQuickView view;
  view.setResizeMode(QQuickView::SizeRootObjectToView);
  QQmlContext *ctxt = view.rootContext();
  ctxt->setContextProperty("myGameBoard", &game_board);

  QQmlApplicationEngine engine;
  engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

  return app.exec();
}

main.qml

import QtQuick 2.6
import QtQuick.Window 2.2

Window {
  visible: true

  MouseArea {
    anchors.fill: parent
    onClicked: {
      Qt.quit();
    }
  }

  Text {
    text: qsTr("Hello World")
    anchors.centerIn: parent
  }

  GridView {
    model: myGameBoard
    delegate: Rectangle {
      width: 30
      height: 30
      color: blue
    }
  }
}

I'm sure there's a lot of stuff that I'm missing or is just blatantly wrong. Also I'm aware that exposing C++ models to QML is frequently asked and well covered, but I still couldn't manage.

To state a few more specific questions:

  • How do I display the data from the model in QML? I've seen this being done with role names, but my rows don't have role names for their columns
  • Is my model correctly implemented?
  • What QML component do I need to use to have a tile/chess board and if I need a custom component, which properties does it need to have to read data from the model?

Thanks for having a look!


回答1:


  1. QtQuick, the main UI framework using QML, basically only deals with list models, thus using roles to simulate tables when e.g. using a TableView

  2. The parent() method is wrong as it basically returns the same index again. That doesn't cause any problem in your case since you have a table and not a tree.

Recommendation: if you only need a table model, derive from QAbstractTableModel and let it take care of index() and parent()

  1. There is no standard QtQuick element that can deal with a table model, so you will either have to build your own or use a list model just arrange the "list items" in a grid.

In case of a custom item, you can either build one that works with your model or even works on the data directly, without the need for a model.

If you use the model and want to instantiate the model from QML, then your need a property of "pointer to your model class" or "pointer to QAbstractItemModel.

If you don't want to use a model or don't need to instantiate it from QML, then you don't need any specific property at all.

In either case your custom item can use one of the following approaches:

  1. it can draw everything itself, i.e. the grid of tile and the tiles itself
  2. just provide the grid and use delegates to draw the different type of tiles, i.e. like ListView allows to set delegates for the list elements, header, footer, etc.



回答2:


I have a solution that works for me. Here is what I did and some code:

  • I used Q_ENUM for the state enum to make it available in QML
  • Connected the rootContext to the QML engine
  • Wrote a custom QML component consisting of a column with a repeater containing a row with a repeater
  • Saved the index of the outer repeater and inner repeater to compile an index for accessing the data
  • Some cleanup

Code

Board.qml

import QtQuick 2.0

Column {
  Repeater {
    model: myGameBoard.columnCount()

    Row {
      property int y_pos: index

      Repeater {
        id: repeatr
        model: myGameBoard.rowCount()

        Cell {
          x_cord: index
          y_cord: y_pos

          size: 20
        }
      } //Repeater
    } //Row
  } //Repeater
} //Column

Cell.qml

import QtQuick 2.0

Rectangle {
  id: cell
  property int x_cord: 0
  property int y_cord: 0
  property int size: 10

  width: size
  height: size

  color: getCorrectColor()

  Connections {
    target: myGameBoard
    onDataChanged: {
      cell.color = cell.getCorrectColor()
    }
  }

  function getCorrectColor() {
    switch(myGameBoard.data(myGameBoard.index(cell.x_cord, cell.y_cord)) + 0) {
    case 0 :
      return "honeydew"
    case 1 :
      return "black"
    case 2 :
      return "orangered"
    default:
      return "yellow"
    }
  }
}

C++ side remained basically the same, except for using Q_ENUM on the state enum on board.h.

Thanks for helping and the hints to the repeater!



来源:https://stackoverflow.com/questions/39707951/expose-2d-c-game-board-to-qml-using-qabstractitemmodel

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!