Qt:有效处理具有“大量像素图”的QGraphicsItems? (RTS)

时间:2011-04-04 17:54:58

标签: c++ qt qgraphicsview

我目前正在构建一个小型的实时策略2D引擎。并且我想知道如何处理最终会破坏我的屏幕的许多不断变化的精灵

仅供参考,我不是针对任何AAA级别的,我只是想尝试实现一些机器学习方法。因此,我选择了魔兽争霸II放弃软件ISOs,无耻地拍摄了一些图形,并且在第一期出现了问题。

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

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

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

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

感谢任何输入! (RTS引擎教程,复杂性等等......)

1 个答案:

答案 0 :(得分:15)

(我先从一般的想法开始,然后会有一个可能的Qt实现)

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

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

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

你可以看到“空闲”动画是由3帧构成的,这些子节点与精灵表中的前3帧相匹配。与子矩阵相关联,此示例中还有两个信息:

  • 持续时间:在移动到下一个之前,框架应显示多长时间?
  • origin:框架的锚点是什么?

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

动画的描述文件格式完全取决于您,但我推荐一些层次结构文件格式。由于Qt提供了XML解析器,因此它可以是完美的。如果您习惯于提升并喜欢像JSon这样的轻量级格式,那么可以使用boost.ptree来解析无关紧要的XML / JSon文件,并使用通用接口从中提取数据。

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

  • QPixmap :我们将从中绘制匹配动画帧的子矩形,
  • 一个 QTimer 来更新你的精灵,
  • 你自己的显示类,比如 AnimatedSprite (从 QGraphicsObject 派生,你需要信号/插槽支持),
  • 和支持类,例如 TimerProxy (派生自 QObject )。

我将从描述 TimerProxy 角色开始。它的作用是发送时间更新消息。它在这里是因为Qt Graphics对象不提供任何类型的“定时”更新(即 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 持有spritesheet,
  • 和动画说明

在运行时,更新方法如下所示:

  • 一个私人 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);
}

希望有所帮助(并且不会太大,哇哇哇哇)