Qt : 有效处理QGraphics有"lots of pixmaps"的项目?(即时战略)

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

本文关键字:项目 即时战略 pixmaps lots 有效 处理 QGraphics Qt of      更新时间:2023-10-16

我目前正在构建一个小型的实时策略2D引擎。我想知道如何处理许多不断变化的精灵,这些精灵最终会使我的屏幕变得混乱。

仅供参考,我的目标不是任何AAA级别,我只是试图实现一些机器学习方法。因此,我选择了《魔兽争霸2》的废弃iso,无耻地截取了一些图像,并在第一批问题上摔倒了。

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

正如你在上面所看到的,即使是《魔兽争霸2》中简单的男仆也有大约50个精灵用于其动画。这是很多。它会经常改变精灵。(黑线只是检查我的alpha通道是否正确)

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

我只是重载paint()方法的QGraphicsPixmapItem和不断改变屏幕上使用的像素图?它会导致口吃吗?我听说有时候,创建一个所有像素图,隐藏它们,并在需要时复制它们是明智的/可能的。(复制比其他操作更便宜)还有其他聪明的主意吗?

谢谢你的建议!

(我将首先从总体思路开始,随后将可能实现Qt)

我不知道WCII精灵是如何存储的,但你应该使用一个精灵表(如果需要的话,自己构建)。与此表格相关联,你将拥有一些精灵的描述,至少包含动画列表,对于每个动画,它的标识符/名称,以及帧列表。

描述这些动画帧的细节级别取决于你,但必须至少包含要显示的精灵表的矩形。

作为一个例子,看看这个精灵表(显然没有优化,但作为一个例子,它是可以的:))。下面是相关的动画描述(第12到39行)。所有的动画都不包括在内,但你会明白的。

你可以看到"空闲"动画是由3帧组成的,它的子矩形与精灵表中的前3帧相匹配。与子rect相关联,本示例中还有2个信息:

  • 持续时间:在移动到下一个帧之前应该显示多长时间?
  • origin:帧的锚点在哪里?

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

动画的描述文件格式完全取决于您,但我建议使用一些层次文件格式。由于Qt提供了XML解析器,所以它可以是完美的。如果您习惯于boost,并且更喜欢JSon这样的轻量级格式,那么您可以使用boost.ptree来解析XML/JSon文件,并拥有一个通用接口来从中提取数据。

对于图形表示,您必须使用一些类:

  • QPixmap:我们将从中绘制匹配动画帧的子矩形,
  • a QTimer来更新你的精灵,
  • 你自己的显示类,比如AnimatedSprite(来自于QGraphicsObject,你需要信号/插槽支持),
  • 和一个支持类,比如TimerProxy(来源于QObject)。
我将从描述TimerProxy角色开始。它的作用是发送时间更新消息。这是因为Qt图形对象不提供任何类型的"定时"更新(例如,update(float dt),其中dt是你最喜欢的时间单位)。您可能想知道为什么要使用代理类来处理时间。这是为了限制活动QTimer的数量;如果你有一个这样的AnimatedSprite,你可能最终有大量的计时器活动,这显然是一个大的禁止。

所以它扮演了两个角色:

  • 在场景构建时:所有AnimatedSprite将自己注册到它提供的信号,让我们将其命名为updateTime(int msecs)
  • 在场景运行时,它将启动一个QTimer,超时时间设置为您需要的粒度(您可以继续使用16ms以获得大约60 fps)。QTimer的信号timeout()将关联到一个私有槽,该槽将触发updateTime(int msecs),其中msecs被设置为之前设置的计时器粒度。
现在,对于解决方案的核心:AnimatedSprite。该类具有以下角色:
  • 阅读,存储所需的动画描述,
  • 启动,更新&停止动画
  • QGraphicScene
  • 上绘制活动精灵的帧

在初始化时,你应该给它以下信息:

  • 一个TimerProxy的实例(由你的场景或拥有场景的类拥有)。当提供这个实例时,你只需要将TimerProxy::updateTime(int)信号连接到一个私有槽,它将更新当前动画,
  • QPixmap按住精灵表,
  • 和动画描述

运行时,更新方法看起来像:

  • 一个私有的timeUpdated(int)插槽,它将检查当前动画的帧是否应该更新,并相应地更新它,
  • 公共动画方法,如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)