c++ - Qt : Efficiently handle QGraphicsItems that have "lots of pixmaps"? (RTS)

标签 c++ qt qgraphicsview

我目前正在构建一个小型实时策略 2D 引擎。和 我想知道如何处理最终会使我的屏幕变得困惑的许多不断变化的 Sprite .

仅供引用,我的目标不是 AAA 级,我只是想实现一些机器学习方法。因此,我选择了魔兽争霸 II 废弃的 ISO,无耻地拍摄了一些图形,并且在第一个问题上遇到了麻烦。

http://img263.imageshack.us/img263/1480/footman.png

正如您在上面看到的,即使是魔兽争霸 II 的简单仆从也有大约 50 个 Sprite 用于动画。这是很多。它会经常改变 Sprite 。 (黑线只是检查我的 alpha channel 是否正确)

因此,最后一个问题:如何有效地实现不断变化的 QGraphicsObject?如何有效地实现重复改变其外观的 QGraphicsItem?

我是否只是让 paint() 过载? QGraphicsPixmapItem 的方法并继续更改屏幕上使用的像素图?会不会引起一些“口吃”?
我听说有时,明智/可能将所有像素图创建一个,隐藏它们,并在需要时复制它们。 (复制比其他操作便宜)
有没有其他聪明的想法?

感谢您提供任何意见! (RTS 引擎教程、复杂性内容等...)

最佳答案

(我会先从总体思路开始,接下来会是一个可能的 Qt 实现)

我不知道 WCII Sprite 是如何存储的,但您应该使用 Sprite 表(如果需要,请自行构建)。与此表相关联,您将获得一些对 Sprite 的描述,其中至少包含动画列表,对于每个动画,它的标识符/名称以及帧列表。

描述这些动画帧的详细程度由您决定,但必须至少包含要显示的 Sprite 表的矩形。

举个例子,看看this sprite sheet (显然没有优化,但举个例子,没关系:))。这是相关的 animations descriptions (第 12 至 39 行)。不包括所有动画,但你会明白的。

您可以看到“空闲”动画由 3 帧组成,这些子矩形与 Sprite 表中的前 3 帧匹配。与 sub-rect 相关联,此示例中还有 2 个信息:

  • 持续时间:在继续下一个框架之前应该显示多长时间?
  • origin:框架的 anchor 是什么?

  • 现在,你将如何在 Qt 中实现它?

    动画的描述文件格式完全取决于你,但我推荐一些层次文件格式。由于 Qt 提供了 XML 解析器,所以它可以是完美的。如果你习惯于 boost 并且喜欢像 JSon 这样的轻量级格式,你可以使用 boost.ptree 来无差别地解析 XML/JSon 文件,并有一个通用的接口(interface)来从中提取数据。

    对于图形表示,您必须使用一些类:
  • QPixmap:从中我们将绘制匹配动画帧的子矩形,
  • 一个 QTimer 来更新你的 Sprite ,
  • 你自己的显示类,比如 AnimatedSprite(派生自 QGraphicsObject,你需要信号/插槽支持),
  • 和一个支持类,比如 TimerProxy(从 QObject 派生)。

  • 我将首先描述 TimerProxy 角色。它的作用是发送时间更新消息。这是因为 Qt Graphics 对象不提供任何类型的“定时”更新(即 update(float dt) 其中 dt 是您最喜欢的时间单位)。您可能想知道为什么我们使用代理类来处理时间。这是为了限制事件QTimer的数量;如果您为每个 AnimatedSprite 设置了其中一个,您最终可能会有大量计时器处于事件状态,这显然是一个很大的禁忌。

    所以它扮演了两个角色:
  • 在场景构建时:所有 AnimatedSprite 都会将自己注册到它提供的信号中,我们将其命名为 updateTime(int msecs),
  • 在场景运行时,它会启动一个 QTimer,它的超时设置为您需要的粒度(您可以继续使用 16ms 以获得大约 60 fps)。 QTimer 的信号 timeout() 将与一个私有(private)插槽相关联,该插槽将触发 updateTime(int msecs),其中 msecs 设置为您之前设置的计时器粒度。

  • 现在,对于解决方案的核心:AnimatedSprite。这个类有以下作用:
  • 阅读和存储它需要的动画描述,
  • 启动、更新和停止动画
  • 在 QGraphicScene 上绘制事件 Sprite 的框架

  • 在初始化时,你应该给它以下信息:
  • TimerProxy 的实例(由您的场景或拥有场景的类拥有)。当提供此实例时,您只需将 TimerProxy::updateTime(int) 信号连接到将更新当前动画的专用插槽,
  • 持有 Sprite 表的 QPixmap,
  • 和动画描述

  • 在运行时,更新方法将如下所示:
  • 一个私有(private)的 timeUpdated(int) 槽,它将检查当前动画的帧是否应该更新,并相应地更新它,
  • 公共(public)动画方法,如 startAnim(const QString &animName),它将改变当前动画,重置耗时计数器,并更新当前子矩形以匹配新动画的第一帧。

  • 在 timeUpdated(int) 槽中,您想更新耗时,并检查它是否应该使动画进入下一帧。如果是这样,只需将当前帧指针更新为新帧。

    最后,要渲染,您只需重新实现 QGraphicsItem::paint(...) 方法来绘制当前子矩形,它可能如下所示:
    void AnimatedSprite::paint(QPainter *painter,
                               const QStyleOptionGraphicsItem * /*option*/,
                               QWidget * /*widget*/)
    {
        painter->drawImage(mCurrentAnim.mCurrentFrame->mOrigin,
                           mSpriteSheet,
                           mCurrentAnim.mCurrentFrame->mSubRect);
    }
    

    希望有帮助(而且不是太大,哇:s)

    关于c++ - Qt : Efficiently handle QGraphicsItems that have "lots of pixmaps"? (RTS),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5542352/

    相关文章:

    c++ - 在 C++ 中使用类的链表

    c++ - Qt——将事件传递给多个对象?

    c++ - 在 Python 中使用自定义 Qt 子类

    c++ - GraphicsScene 中的轴承公式计算产生不稳定的结果

    c++ - QPainterPath 形状 union (单笔划)

    c++ - 派生类中的运算符<< C++

    c++ - 使用了危险的魔数(Magic Number) N

    c++ - 在模板参数中通过 SFINAE 选择构造函数

    qt - 我可以在 QGraphicsItem 中获取鼠标事件吗?

    Qt - 如何将 QGraphicsScene 缩放到 QGraphicsView