如何在 QML 中处理大量的瓦片?

Posted

技术标签:

【中文标题】如何在 QML 中处理大量的瓦片?【英文标题】:How to handle a huge amout of tiles in QML? 【发布时间】:2016-08-31 08:54:55 【问题描述】:

想象一个巨大的矩形网格,里面装满了瓷砖。单个图块不是很复杂,它们是包含少量形状的 svg 图像。

不同类型的瓷砖数量不是很多,我估计在几百个。但是,网格可能会变得非常大,因此总图块的数量非常庞大(至少数万,也许更多)。

我必须能够平滑地水平和垂直滚动网格,以及平滑地放大和缩小。我还必须能够跳到特定的位置。

如果我可以异步填充它也会很好,首先是实际可见的元素,然后是其余的。这意味着我首先必须在循环中添加行和列的表格处理类不是最佳解决方案,因为起始位置不一定是左上角。

缩放可以简单地通过将图块中项目的所有widthheight 属性指定为缩放因子的倍数来实现。 svg 应该不是问题,因为不同图像的数量并不多,它应该可以被缓存。在不太可能的情况下svg 成为瓶颈,我可以使用不同分辨率的不同pngs 集。

我尝试(或考虑)了以下方法:

    使用SameGame example的方法,动态创建QML对象(Component.createObject)。如果对象的数量很少,这很有效,但是对于大量的对象来说非常慢。即使对象完全为空,此方法也会采用very long time。

    Flickable 中使用RepeaterFlickable 包含一个 Grid,它由 Repater 填充。当然,Grid 是巨大的。 这种方法比动态创建对象要快,但随着瓦片数量的增加,效率仍然很低。 QML 引擎会跟踪每个项目,即使是那些不可见的项目。缩放也很慢,因为每个项目的属性都会重新计算,而不仅仅是可见的。

    使用GridView。乍一看,这似乎是完美的解决方案。 GridView 继承 Flickable,并且它还注意只渲染视图范围内的内容。即使是包含数百万个 svg 图像的测试用例也运行得相当快,并且它可以平滑地滚动和调整大小。只有一个问题:GridView 只能水平或垂直滑动,但不能同时滑动。自 2012 年以来,关于此问题的 feature request 一直存在,但似乎仍被忽略。

    直接使用QGraphicsView。它is capable 用于显示、滚动和缩放所需数量的元素,但它不是基于 QML 的。我的 GUI 的其余部分在 QML 中,我只读过关于结合 QMLQGraphicsView 的恐怖故事。我从未见过任何合理的例子。

还有哪些其他解决方案?在 Flickable 中移动时,使用 javascript 添加和删除简单 GridLayout 的行和列(仅比可见区域大几行和列)的一些可怕的技巧?还是只是嵌入一个 OpenGL 窗口并手动绘制所有内容?

我希望这不应该是一项不可能完成的任务。 20 多年前为 DOS 和 Windows 95 编写的策略游戏可以处理这么多的图块,同时还具有纹理和动画。

【问题讨论】:

与 DOS 和 Windows 95 的比较没有任何意义,因为底层技术和方法截然不同。为什么不使用自定义 QtQuick 元素来满足您的需求? @peppe :我的观点不是系统本身,而是可以用 200 倍的计算能力来做到这一点。 “自定义 QtQuick 元素”是指我应该从头开始实现我自己的 2d 图形引擎?是的,这是可能的,但这种方法类似于 OpenGL 绘图解决方案。 GridView 的优势在于知道加载和卸载的内容和时间。通过从头开始做所有事情,我必须实现自己的内存管理、可见性计算等等。如果我必须从头开始实现所有内容,为什么还要使用 Qt? 即使在今天你也完全可以,但是具有 10000 个元素的中继器是错误的方法。 QML 缺乏这样一个内置的东西并不意味着它不可能实现。 当您知道要显示的区域的大小和图块的大小时,可见性计算应该不难实现。然后使用带有 Loader 的中继器作为委托,并为匹配可见性约束的“活动”属性设置绑定(取决于索引)。 查看github.com/bjorn/tiled 来源也许你会在那里找到有用的东西 【参考方案1】:

是的,Qt 多年来一直非常擅长忽略社区的建议,即使它们非常有用、被认为很重要,并且恰好是投票最多的,例如 zip support。

我个人不会费心“修复”GridView,而是用 C++ 从头开始​​实现适合我特定要求的东西,这样它既快速又高效。如果你的瓷砖是统一的正方形,这将非常容易,听起来你可以逃脱,即使里面的实际图像不是正方形的。这将使以编程方式确定它们的位置变得非常容易,并且还可以确定左上角的瓦片、每行有多少瓦片以及后续行的步幅。然后随着可见性矩形移动,您迭代容器并发出信号,为那些进入可见性的元素创建 QML 元素。轻松愉快。

你不需要任何花哨的东西,只需继承QObject,将类型注册到 QML,然后去填充它的内部“模型”。您绝对不希望将所有对象都保存在内存中,即使场景图足够智能而不会渲染它们,它仍然会处理它们,我怀疑您的 FPS 下降不是 GPU 的产物,而是 CPU 瓶颈的产物.

实际的网格对象可以使用其数据Q_SIGNAL void create(x, y, imgPath); 发出创建和销毁信号,因此您可以在 QML 端绑定自定义处理程序,这将为您提供灵活性和易用性,例如轻松指定“委托”对象,它会比在 C++ 中进行实际的创建/销毁更优雅。您可以在 QML 端对少数可见的项目使用绑定,以便在它们离开屏幕自毁时进行跟踪,这样可以最大限度地降低复杂性,因为您不必跟踪所有“活”对象。

Component 
   id: objComponent
   Image 
      property bool isVisible:  is in grid.visibleRect ??? 
      onIsVisibleChanged: if (!isVisible) destroy()
   


MyGrid 
  id: grid
  contentX: flickable.contentX
  contentY: flickable.contentY

  onCreate: objComponent.createObject(flickable.contentItem, "x" : x, "y" : y, "source" : imgPath)


Flickable 
   id: flickable
   contentWidth: grid.contentWidth
   contentHeight: grid.contentHeight

通常,当用户有一个重要到足以提供赏金的问题时,我会生成工作代码,但不幸的是我目前太忙了。这个概念虽然很简单,但实施起来应该不会有太大问题。

【讨论】:

GridView 用 C++ 实现的。 @DavidK.Hess - 还有,谁声称不是?

以上是关于如何在 QML 中处理大量的瓦片?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 QML/QtLocation 模块在地图上显示大量离线数据?

如何在 QML 中处理 Stylus/Pen 输入

PyQt 和 QML:如何在一个插槽或函数中处理多个信号

如何在 Qt QML 中处理 mac 集成 About MenuBar 项?

如何在 QML 中处理来自父 Flickable 的 gridview contentY 和 Y 位置

如何有条件地禁用 QML 绑定到 C++ 后端?