Как бороться с произвольно большими изображениями в 2D играх?

13

Вопрос

Что делать, если у вас есть 2D-игра, в которой используются действительно большие изображения (больше, чем может поддерживать ваше оборудование)? Может быть, есть какое-то интересное решение этой проблемы, которое мне никогда не приходило в голову.

Я работаю над создателем графических приключенческих игр. Моя целевая аудитория - люди, у которых практически нет опыта разработки игр (и программирования). Если бы вы работали с таким приложением, разве вы не предпочли бы, чтобы оно обрабатывало проблему внутренне, а не говорило вам «разделить ваши изображения»?

контекст

Хорошо известно, что графические карты накладывают ряд ограничений на размер текстур, которые они могут использовать. Например, при работе с XNA максимальный размер текстуры ограничен 2048 или 4096 пикселями в зависимости от выбранного профиля.

Обычно это не представляет большой проблемы в 2D играх, потому что большинство из них имеют уровни, которые могут быть построены из более мелких отдельных частей (например, плитки или преобразованных спрайтов).

Но подумайте о нескольких классических графических приключенческих играх point'n'click (например, Monkey Island). Комнаты в графических приключенческих играх обычно разрабатываются целиком, с небольшими или отсутствующими повторяемыми участками, как художник, работающий над пейзажем. Или, возможно, такая игра, как Final Fantasy 7, в которой используются большие предварительно отрендеренные фоны.

Некоторые из этих комнат могут быть довольно большими, часто занимая три или более экрана шириной. Теперь, если мы рассмотрим эти фоны в контексте современной игры высокой четкости, они легко превысят максимальный поддерживаемый размер текстуры.

Теперь очевидным решением этого является разделение фона на более мелкие участки, которые соответствуют требованиям, и их рисование рядом. Но должен ли ты (или твой художник) быть тем, кто разделяет все твои текстуры?

Если вы работаете с высокоуровневым API, разве он не должен быть готов к тому, чтобы справиться с этой ситуацией и выполнить расщепление и сборку текстур внутри себя (или в качестве предварительной обработки, такой как XNA Content Pipeline или во время выполнения)?

редактировать

Вот то, о чем я думал, с точки зрения реализации XNA.

Мой 2D движок требует только небольшого подмножества функций Texture2D. Я действительно могу уменьшить его до этого интерфейса:

public interface ITexture
{
    int Height { get; }
    int Width { get; }
    void Draw(SpriteBatch spriteBatch, Vector2 position, Rectangle? source, Color  color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth);
}

Единственный внешний метод, который я использую, который опирается на Texture2D, это SpriteBatch.Draw (), поэтому я мог бы создать мост между ними, добавив метод расширения:

    public static void Draw(this SpriteBatch spriteBatch, ITexture texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float layerDepth)
    {
        texture.Draw(spriteBatch, position, sourceRectangle, color, rotation, origin, scale, effects, layerDepth);
    }

Это позволило бы мне использовать ITexture взаимозаменяемо всякий раз, когда я использовал Texture2D раньше.

Тогда я мог бы создать две разные реализации ITexture, например RegularTexture и LargeTexture, где RegularTexture просто обернет обычный Texture2D.

С другой стороны, LargeTexture будет хранить список Texture2D, каждый из которых соответствует одному из разделенных фрагментов полного изображения. Наиболее важной частью является то, что вызов Draw () в LargeTexture должен иметь возможность применить все преобразования и нарисовать все части, как если бы они были только одним непрерывным изображением.

Наконец, чтобы сделать весь процесс прозрачным, я бы создал унифицированный класс загрузчика текстур, который будет действовать как фабричный метод. Он будет принимать путь текстуры в качестве параметра и будет возвращать ITexture, которая будет либо RegularTexture, либо LargeTexture, в зависимости от размера изображения, превышающего ограничение, или нет. Но пользователю не нужно было знать, какой это был.

Немного перебор или это звучит нормально?

Дэвид Гувея
источник
1
Если вы хотите разделить изображение, расширьте конвейер содержимого, чтобы сделать это программно. Не все функции существуют для охвата каждого отдельного сценария, и в какой-то момент вам придется расширять его самостоятельно. Личное разделение звучит как простейшее решение, так как вы можете обрабатывать изображения, подобные плиткам, на которых есть множество ресурсов.
DMan
Делать это на конвейере контента - самый разумный подход. Но в моем случае мне нужно было загружать изображения во время выполнения, и мне уже удалось найти решение для выполнения (именно поэтому я задал этот вопрос вчера). Но настоящая красота заключается в том, что вы берете эту «карту тайлов» и заставляете ее вести себя как обычная текстура. Т.е. вы все еще можете передать его в spritebatch для рисования, и вы все равно можете указать положение, вращение, масштаб, прямоугольники src / dest и т. Д. Я сейчас на полпути :)
Дэвид Гувейя
Но, по правде говоря, я уже нахожусь в точке, где я задаюсь вопросом, действительно ли все это действительно того стоит, или я должен просто показать предупреждение пользователю, говоря ему, что максимальный поддерживаемый размер изображения составляет 2048x2048 и с этим покончено.
Дэвид Гувейя
Это действительно зависит от вас. Если вы закончили проект и не можете прокрутить комнаты, лучше будет не законченный редактор, который позволит вам прокручивать большие текстуры: D
Aleks
Сокрытие этого в API звучит как хороший способ облегчить жизнь авторам игр. Вероятно, я бы реализовал это в классах Polygon (sprite) и Texture, а не в особых случаях для небольших изображений, а просто сделал бы их плиткой размером 1x1. Таким образом, класс текстуры - это группа текстур, описывающая все плитки и количество строк / столбцов. Класс Polygon затем просматривает эту информацию, чтобы разделить себя и нарисовать каждую плитку с соответствующей текстурой. Таким образом, он является тот же класс. Бонусные баллы, если ваш класс спрайтов рисует только видимые плитки, а группы текстур не загружают текстуры, пока не понадобятся.
Uliwitness

Ответы:

5

Я думаю, что вы на правильном пути. Вы должны разделить изображения на более мелкие кусочки. Поскольку вы создаете игру, вы не можете использовать конвейер контента XNA. Если ваш производитель больше не похож на движок, который все еще будет требовать от разработчиков использовать Visual Studio. Поэтому вам нужно создать свои собственные процедуры, которые будут выполнять разбиение. Вы даже должны рассмотреть возможность потоковой передачи фрагментов до того, как они станут видимыми, чтобы вы по-прежнему не ограничивали объем видеопамяти игроков.

Есть несколько трюков, которые вы можете сделать специально для приключенческих игр. Я думаю, что, как и во всех классических приключенческих играх, у вас будет несколько слоев этих текстур, чтобы ... ваш персонаж мог ходить за деревом или зданием ...

Если вы хотите, чтобы эти слои имели эффект паралаксинга, тогда, когда вы разделяете свои плитки, просто пропустите все полупрозрачные один раз. Здесь нужно подумать о размере плитки. Меньшие плитки добавят управление накладными расходами и вызовами отрисовки, но будут меньше данных, когда большие плитки будут более дружественными к графическому процессору, но будут иметь большую прозрачность.

Если у вас нет эффекта паралаксинга, тогда вместо того, чтобы покрывать всю сцену 2-4 гигантскими изображениями с глубиной 32 бита, вы можете создать только одну 32-битную глубину. И у вас может быть слой трафарета или глубины, который будет только 8 бит на пиксель.

Я знаю, что это не похоже на управление активами сторонними разработчиками, но на самом деле это не обязательно. Например, если у вас есть улица с 3 деревьями, и игрок идет за 2 из них, перед третьим деревом и остальным фоном ... Просто сделайте макет сцены так, как вы хотите в редакторе, а затем в сделав шаг от своего создателя игры, вы можете испечь эти гигантские изображения (ну, маленькие плитки большого изображения).

О том, как классические игры делали это. Я не уверен, что они, вероятно, делали одинаковые трюки, но вы должны помнить, что в то время, когда экран был 320х240, игры были с 16 цветами, что при использовании палатализированных текстур позволяет кодировать 2 пикселя в одном байте. Но это не так, как нынешние архитектуры работают так: D

Удачи

Aleks
источник
Спасибо, здесь очень много интересных идей! Я на самом деле видел технику слоя трафарета, которая использовалась ранее в Adventure Game Studio. Что касается моего приложения, я делаю вещи немного по-другому. Номера не обязательно имеют одно фоновое изображение. Вместо этого есть палитра изображений, которые пользователь импортировал, и слой фона / переднего плана. Пользователь может свободно перетаскивать кусочки в комнату, чтобы создать его. Поэтому, если бы он захотел, он мог бы также создавать его из более мелких частей, не создавая игровых объектов без интерактивности. Там нет параллакса, хотя, поскольку есть только эти два слоя.
Дэвид Гувейя
И я посмотрел файлы данных для Monkey Island Special Edition (римейк HQ). Каждый фон делится на ровно 1024x1024 фрагмента (даже если большая часть изображения не используется). В любом случае, проверьте мои изменения, чтобы сделать весь процесс прозрачным.
Дэвид Гувейя
Я бы, наверное, выбрал что-то более разумное, например, 256x256. Таким образом, я не буду тратить ресурсы впустую, и если позже решу реализовать потоковую передачу, это будет загружать более разумные куски. Может быть, я недостаточно хорошо это объяснил, но я предложил вам испечь все эти маленькие статические игровые объекты в большую текстуру. Что вы сохраните, так это, если у вас большая фоновая текстура, все пиксели покрыты статическими объектами сверху.
Алекс
Я вижу, так что объедините все статические объекты в фон, затем разделите и создайте текстурный атлас. Я мог также испечь все остальное в текстурный атлас, следуя этому ходу мыслей. Это тоже хорошая идея. Но я оставлю это на потом, потому что сейчас у меня нет никакого шага «сборки», то есть и редактор, и игровой клиент работают поверх одного и того же движка и того же формата данных.
Дэвид Гувейя
Мне нравится трафаретный подход, но если я создаю редактор приключенческих игр, я, вероятно, не буду его использовать ... Возможно, потому что, если я создаю такой инструмент, я буду предназначен для разработчиков игр. Ну, у него есть свои плюсы и минусы. Например, вы можете использовать другое изображение, на котором биты могут определять триггеры на пиксель, или, если у вас есть анимация воды, вы можете легко ее трафаретить. Примечание. Одно странное несвязанное предложение - посмотрите на диаграммы состояния / активности UML для вашего сценария.
Алекс
2

Разрежьте их, чтобы они больше не были сколь угодно большими, как вы говорите. Там нет магии. Те старые 2D-игры либо делали это, либо воспроизводились в программном обеспечении, и в этом случае аппаратные ограничения не превышали объем оперативной памяти. Следует отметить, что многие из этих 2D-игр на самом деле не имели огромного фона, они просто медленно прокручивались, чтобы чувствовать себя огромными.

Что вы хотите сделать в своем создателе графических приключенческих игр, так это предварительно обработать эти большие изображения во время какого-то этапа «сборки» или «интеграции» перед тем, как игра будет упакована для запуска.

Если вы хотите использовать существующий API и библиотеку вместо того, чтобы писать свой собственный, я бы посоветовал вам конвертировать эти большие изображения в плитки во время этой «сборки» и использовать механизм 2D-плиток, которых много.

Если вы хотите использовать свои собственные 3D-методы, вы все равно можете разделить их на фрагменты и использовать хорошо известный и хорошо спроектированный 2D-API, чтобы написать для этого свою собственную 3D-библиотеку, таким образом вы гарантированно начнете с хороший дизайн API и меньше дизайна требуется с вашей стороны.

Патрик Хьюз
источник
Делать это во время сборки было бы идеально ... Но мой редактор также использует XNA для рендеринга, это не просто движок. Это означает, что, как только пользователь импортирует изображение и перетаскивает его в комнату, XNA уже сможет его отрендерить. Поэтому моя большая дилемма - делать ли разделение в памяти при загрузке контента или на диске при импорте контента. Кроме того, физическое разбиение изображения на несколько файлов означало бы, что мне понадобится способ хранить их по-разному, тогда как выполнение этого в памяти во время выполнения не потребует каких-либо изменений в файлах данных игры.
Дэвид Гувея
1

Если вы знакомы с тем, как выглядит квадри в визуальной практике, я использовал метод, который разрезает детализированные большие изображения на меньшие, относительно похожие на то, как выглядит доказательство / визуализация квадродерева. Тем не менее, мои были сделаны таким образом, чтобы вырезать области, которые могут быть закодированы или сжаты по-разному в зависимости от цвета. Вы можете использовать этот метод и делать то, что я описал, поскольку это также полезно.

Во время выполнения их обрезка временно потребует больше памяти и замедлит время загрузки. Хотя я бы сказал, что это зависит от того, заботитесь ли вы больше о физической памяти или времени загрузки пользовательской игры, созданной вашим редактором.

Время загрузки / прогона: способ загрузки - просто разрезать ее на части пропорционального размера. Если учитывать нечетное количество пикселей, учитывайте один пиксель справа, никому не нравится, когда его изображения обрезают, особенно неопытным пользователям, которые могут просто не знать, что это не совсем их дело. Сделайте их наполовину ширины ИЛИ наполовину высоты (или 3-х, 4-х и т. Д.), Причина не в квадратах или 4-х секциях (2 строки 2 столбца) в том, что это уменьшит размер мозаичной памяти, поскольку вы не будете беспокоиться о x и y в то же время (и в зависимости от того, сколько изображений такого размера, это может быть очень важно)

До руки / автоматически: в этом случае мне нравится идея предоставить пользователю возможность сохранить его в одном изображении или сохранить в виде отдельных фрагментов (одно изображение должно быть по умолчанию). Сохранение изображения в формате .gif будет работать отлично. Изображение будет в одном файле, и вместо того, чтобы каждый кадр был анимацией, он был бы больше похож на сжатый архив одного и того же изображения (что, по сути, в любом случае представляет собой .gif). Это позволило бы упростить физическую передачу файлов, облегчить загрузку:

  1. Это один файл
  2. Все файлы будут иметь практически одинаковое форматирование
  3. Вы можете легко выбрать, когда и если разрезать его на отдельные текстуры
  4. Возможно, будет проще получить доступ к отдельным изображениям, если они уже находятся в одном загруженном файле .gif, поскольку это не потребует большого количества файлов изображений в памяти.

Да, и если вы используете метод .gif, вы должны изменить расширение файла на что-то другое (.gif -> .kittens) ради меньшей путаницы, когда ваш .gif анимирует, как будто это поврежденный файл. (не беспокойтесь о законности этого, .gif это открытый формат)

Джошуа Хеджес
источник
Здравствуйте и спасибо за ваш комментарий! Извините, если я неправильно понял, но разве вы не написали в первую очередь о сжатии и хранении? В этом случае все, что меня действительно беспокоит, так это то, что пользователь моего приложения может импортировать любое изображение, а движок (в XNA) способен загружать и отображать любое изображение во время выполнения . Мне удалось решить эту проблему, просто используя GDI для считывания различных разделов изображения в отдельные текстуры XNA и оборачивая их в класс, который ведет себя как обычная текстура (т.е. поддерживает рисование и преобразования в целом).
Дэвид Гувея
Но из любопытства, не могли бы вы рассказать подробнее о том, какие критерии вы использовали, чтобы выбрать, какая область изображения входила в каждый раздел?
Дэвид Гувейя
Некоторые из них могут быть у вас над головой, если вы менее опытны в обработке изображений. Но по сути, я описывал разделение изображения на сегменты. И да. Точка .gif описывала как хранение, так и загрузку, но она предназначена для того, чтобы вы могли разрезать изображение, а затем сохранить его на компьютере таким, как оно есть. Если вы сохраняете это как gif, я говорил, что вы можете разрезать его на отдельные изображения, а затем сохранить как анимированный gif, при этом каждый фрагмент изображения сохраняется как отдельные кадры анимации. Это работает хорошо, и я даже добавил другие функции, которые могут помочь.
Джошуа Хеджес
Система quadtree, как я описал, полностью указана в качестве справочной, поскольку именно там я и получил представление. Хотя вместо того, чтобы собирать вопросы, которые могут быть полезны читателям, у которых могут быть похожие проблемы, мы должны использовать личные сообщения, если вы хотите узнать больше.
Джошуа Хеджес
Роджер, я получил часть об использовании GIF-кадров в качестве своего рода архива. Но мне также интересно, не является ли GIF индексированным форматом с ограниченной цветовой палитрой? Могу ли я просто сохранить все изображения RGBA 32bpp в каждом кадре? И я представляю, что процесс его загрузки стал еще более сложным, поскольку XNA ничего не поддерживает.
Дэвид Гувейя
0

Это будет звучать очевидно, но почему бы просто не разделить вашу комнату на более мелкие части? Выберите произвольный размер, который не столкнется с любыми видеокартами (например, 1024x1024 или больше), и просто разбейте свои комнаты на несколько частей.

Я знаю, что это позволяет избежать проблемы, и, честно говоря, это очень интересная проблема для решения. В любом случае, это своего рода тривиальное решение, которое может работать для вас время от времени.

ashes999
источник
Я думаю, что мог бы упомянуть причину где-то в этой теме, но так как я не уверен, я повторюсь. В основном потому, что это не игра, а скорее производитель игр для широкой публики (подумайте о том же, что и производитель RPG), поэтому вместо принудительного ограничения размера текстуры при импорте пользователями своих собственных ресурсов, было бы неплохо справиться разделение и сшивание автоматически за кулисами, не отвлекая их техническими деталями ограничений размера текстур.
Дэвид Гувейя
Хорошо, это имеет смысл. Извините, я думаю, что пропустил это.
ashes999