Qt: Эффективно обрабатываете QGraphicsItems, которые имеют "много растровых изображений"? (РТС)

В настоящее время я создаю небольшой движок Real Time Strategy 2D. И мне интересно, как справиться со многими постоянно меняющимися спрайтами, которые в конечном итоге засорят мой экран.

К вашему сведению, я не стремлюсь ни к какому уровню ААА, я просто пытаюсь реализовать некоторые методы машинного обучения. Таким образом, я выбрал бессмысленные ISO-образы Warcraft II, взял бесстыдную графику и столкнулся с первыми проблемами.

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

Как вы можете видеть выше, даже простой лакей Warcraft II получил около 50 спрайтов для своей анимации. Что очень много. И это очень часто меняет спрайты. (Черная линия просто проверяла, был ли мой альфа-канал правильным)

Итак, последний вопрос: как эффективно реализовать объект QGraphicsObject, который постоянно меняется? Как эффективно реализовать QGraphicsItem, который неоднократно меняет свой внешний вид?

Я просто перегружаю paint() метод QGraphicsPixmapItem и продолжать изменять Pixmap, используемый на экране? Это вызовет некоторое "заикание"? Я слышал, что иногда разумно / можно создать все растровые изображения, спрятать их и дублировать, когда это необходимо. (Копирование обходится дешевле, чем другие операции) Есть ли еще какая-нибудь разумная идея?

Спасибо за любой вклад! (учебник по двигателям RTS, сложностям и т. д.)

1 ответ

Решение

(Сначала я начну с общей идеи, возможная реализация Qt последует)

Я не знаю, как хранятся спрайты WCII, но вы должны использовать лист спрайтов (создайте его самостоятельно, если это необходимо). С этим листом у вас будет некоторое описание спрайта, содержащего как минимум список анимаций, а для каждой анимации - его идентификатор / имя, а также список фреймов.

Уровень детализации, который вы вкладываете в описание кадров этой анимации, зависит от вас, но должен содержать как минимум прямоугольник листа спрайта для отображения.

В качестве примера взгляните на этот лист спрайтов (явно не оптимизированный, но для примера, все в порядке:)). Вот соответствующие описания анимации (строки с 12 по 39). Все анимации не включены, но вы поймете идею.

Вы можете видеть, что анимация "в режиме ожидания" состоит из 3 кадров, суб-которые соответствуют первым 3 кадрам в листе спрайта. В этом образце есть еще две информации, связанные с подправкой:

  • длительность: как долго должен отображаться кадр, прежде чем переходить к следующему?
  • Происхождение: какова точка привязки кадра?

Теперь, как бы вы реализовали это в Qt?

Формат файла описания анимации полностью зависит от вас, но я рекомендую использовать некоторый формат файла иерархии. Поскольку Qt предоставляет синтаксический анализатор XML, он может быть идеальным. Если вы привыкли повышать и предпочитаете легкий формат, такой как JSon, вы можете использовать boost.ptree, чтобы безразлично анализировать файлы XML/JSon и иметь общий интерфейс для извлечения данных из них.

Для графического представления вам нужно будет использовать несколько классов:

  • QPixmap: из которого мы будем рисовать под прямоугольники, соответствующие кадрам анимации,
  • QTimer для обновления ваших спрайтов,
  • ваш собственный класс отображения, скажем, AnimatedSprite (производный от QGraphicsObject, вам понадобится поддержка сигнала / слотов),
  • и класс поддержки, скажем, TimerProxy (производный от QObject).

Я начну с описания роли TimerProxy. Его роль - отправлять сообщения об обновлении времени. Это происходит потому, что объекты Qt Graphics не предоставляют какого-либо "временного" обновления (т. Е. Update(float dt), где dt указывается как ваша любимая единица времени). Вы можете удивиться, почему мы используем прокси-класс для обработки времени. Это ограничивает количество активных QTimer; если у вас есть один из них на AnimatedSprite, вы можете в конечном итоге иметь множество активных таймеров, что, безусловно, является большим нет-нет.

Таким образом, он выполняет 2 роли:

  • во время создания сцены: все AnimatedSprite зарегистрируются в сигнале, который он предоставляет, назовем его updateTime(int msecs),
  • во время выполнения сцены он запускает QTimer, время ожидания которого установлено на нужную вам степень детализации (вы можете продолжить с 16 мс для приблизительных 60 кадров в секунду). Тайм- аут сигнала QTimer () будет связан с частными слотами, которые будут запускать 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);
}

Надеюсь, что это помогает (и не слишком большой, вау: с)

Другие вопросы по тегам