Карта с 20 миллионами плиток делает игру нехваткой памяти, как мне избежать этого?

11

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

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

UPD2: Сначала я пошел и назначил текстуру каждому новому экземпляру класса плиток, и это заняло так много памяти (также время загрузки). Теперь это занимает примерно в четыре раза меньше места. Спасибо всем, теперь я могу запускать огромные карты, даже не думая разбивать их на куски.

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

user1306322
источник
8
Загружайте только области вокруг игроков.
MichaelHouse
4
20 миллионов плиток по 4 байта на плитку занимают всего около 80 МБ, так что кажется, что ваши плитки не такие маленькие. Мы не можем волшебным образом дать вам больше памяти, поэтому нам нужно решить, как сделать ваши данные меньше. Итак, покажите нам, что находится в этих плитках.
Kylotan
Что делает метод загрузки? Почтовый индекс, вы используете конвейер контента? Загружает ли он текстуры в память или просто метаданные? Какие метаданные? Типы содержимого XNA обычно ссылаются на неуправляемые вещи DirectX, поэтому управляемые объекты очень малы, но на неуправляемой стороне может быть множество вещей, которые вы не увидите, если не используете также профилировщик DirectX. stackoverflow.com/questions/3050188/…
Оскар Дюверборн
2
Если система выдает исключение «Недостаточно памяти», значит, недостаточно свободной памяти для использования, несмотря на то, что вы думаете. Там не будет какой-то секретной строки кода, которую мы можем дать вам, чтобы включить дополнительную память. Содержание каждой плитки и то, как вы их распределяете, очень важно.
Kylotan
3
Что заставляет вас думать, что у вас есть свободная память? Вы запускаете 32-битный процесс внутри 64-битной ОС?
Далин Сейврайт

Ответы:

58

приложение падает, когда оно достигает 1,5 ГБ.

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

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

Взять, к примеру, Террарию. Самый маленький мир Terraria занимает 4200x1200 плиток, что составляет 5 миллионов плиток. Теперь, сколько памяти нужно, чтобы представить этот мир?

Итак, у каждой плитки есть слой переднего плана, фоновый слой (фоновые стены), «слой проволоки», куда идут провода, и «слой мебели», куда идут предметы мебели. Сколько памяти занимает каждая плитка? Опять же, мы просто говорим концептуально, а не визуально.

Плитка переднего плана может быть легко сохранена в неподписанном коротком. Существует не более 65536 типов листов переднего плана, поэтому нет смысла использовать больше памяти, чем эта. Фоновые листы могут легко находиться в байте без знака, так как существует менее 256 различных типов фоновых листов. Слой проводов является чисто двоичным: либо в плитке есть провод, либо его нет. Так что это один бит на плитку. И слой мебели снова может быть байтом без знака, в зависимости от того, сколько существует различных предметов мебели.

Общий объем памяти на плитку: 2 байта + 1 байт + 1 бит + 1 байт: 4 байта + 1 бит. Таким образом, общий размер небольшой карты Terraria составляет 20790000 байт, или ~ 20 МБ. (примечание: эти расчеты основаны на Terraria 1.1. С тех пор игра значительно расширилась, но даже современная Terraria может умещаться в пределах 8 байт на ячейку или ~ 40 МБ. Все еще вполне терпимо).

Вы никогда не должны хранить это представление в виде массивов классов C #. Они должны быть массивами целых чисел или чем-то подобным. AC # struct также будет работать.

Теперь, когда приходит время рисовать часть карты (обратите внимание на акцент), Terraria необходимо преобразовать эти концептуальные тайлы в настоящие тайлы. Каждая плитка должна на самом деле выбрать изображение переднего плана, фоновое изображение, дополнительное изображение мебели и иметь изображение в виде провода. Здесь XNA входит со своими различными листами спрайтов и тому подобным.

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

Теперь видимая область листов должна быть разбита на различные фрагменты, чтобы вы не создавали спрайт-листы постоянно, когда камера движется. Таким образом, у вас могут быть куски мира размером 64х64 в виде спрайтов. Независимо от того, какие фрагменты мира размером 64x64 видны с текущей позиции игрока на экране, это фрагменты, которые вы рисуете. У любых других кусков даже нет листов спрайтов; если кусок падает с экрана, вы выбрасываете этот лист (примечание: вы на самом деле не удаляете его; вы сохраняете его и назначаете заново для нового фрагмента, который может стать видимым позже).

Я хотел бы, чтобы вся карта была обработана, по крайней мере, на серверном приложении (и на клиенте, если это возможно).

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

Николь Болас
источник
4
+1 Отличный ответ. Нужно голосовать больше, чем за меня.
MichaelHouse
22

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

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

Вы можете найти больше идей, как это здесь:

Как они это сделали: миллионы плиток в Террарии

«Зонирование» областей на большой карте тайлов, а также подземелий

MichaelHouse
источник
Проблема в том, что такой подход хорош для клиентского приложения, но не так хорош для сервера. Когда некоторые тайлы в мире исчезают, и начинается цепная реакция (и это часто случается), вся карта должна быть в памяти для этого, и это проблема, когда она выбрасывается из памяти.
user1306322
6
Что содержит каждая плитка? Сколько памяти используется на плитку? Даже по 10 байт каждый - всего 190 мегабайт. Сервер не должен загружать текстуру или любую другую информацию, которая не нужна. Если вам требуется симуляция для 20 миллионов плиток, вам нужно как можно больше отделить эти плитки, чтобы сформировать набор симуляции, который содержит только информацию, необходимую для ваших цепных реакций.
MichaelHouse
5

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

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

Даже если вы поместите в память такую ​​гигантскую карту (а вы, скорее всего, не сможете), вы не захотите . Загрузка данных, которые вам не нужно сразу использовать, абсолютно нулевая, это неэффективно и медленно. Даже если бы вы поместили это в память, вы, скорее всего, не смогли бы обработать все это за разумный промежуток времени.

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

MrCranky
источник
3

Эффективный способ, который я использовал в игре XNA, был:

  • используйте таблицы спрайтов, а не изображения для каждой плитки / объекта, и просто загрузите то, что вам нужно на этой карте;
  • разбить карту на более мелкие части, скажем, на куски карты размером 500 x 200, если ваша карта в 4 раза больше этого размера, или что-то, что вы можете сложить;
  • загрузить этот блок в память и закрасить только видимые части карты (скажем, видимые плитки плюс 1 плитку для каждого направления движения игрока) в закадровом буфере;
  • очистить область просмотра и скопировать пиксели из буфера (это называется двойной буфер и сглаживает движение);
  • когда вы перемещаете чар, отслеживайте облака карты и загружайте следующий фрагмент, когда он приближается к нему, и добавляйте текущий;
  • как только игрок полностью на этой новой карте, вы можете выгрузить последнюю.

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

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

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

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

Совет здесь заключается в том, что я ни в коем случае не являюсь экспертом в разработке игр, и моя игра была сделана для финального проекта моей выпускной (который у меня был лучший результат), так что я могу быть не очень прав в каждой точке, но я исследовал сеть и такие сайты, как http://www.gamasutra.com и сайт создателей XNA creators.xna.com (в настоящее время http://create.msdn.com ), чтобы собрать знания и навыки, и это отлично сработало для меня ,

Рикардо Соуза
источник
2

Или

  • купить больше памяти
  • представлять ваши структуры данных более компактно
  • не храните все данные в памяти сразу.
Томас
источник
У меня на Win7 64bit 8 ГБ оперативной памяти, и приложение падает, когда он достигает 1,5 ГБ. Так что на самом деле нет смысла покупать больше барана, если я что-то упустил.
user1306322
1
@ user1306322: Если у вас 20 миллионов листов, и это занимает около 1,5 ГБ памяти, это значит, что размер ваших плиток составляет около 80 байт на плитку . Что именно вы храните в этих вещах?
Никол Болас
@NicolBolas, как я уже сказал, несколько целых, байтов и texture2d. Кроме того, они не все занимают 1,5 ГБ, приложение вылетает при достижении этой стоимости памяти.
user1306322
2
@ user1306322: Это все еще намного больше, чем нужно. Насколько большой texture2d? У каждой плитки есть уникальный, или они разделяют текстуры? Кроме того, где ты это сказал? Вы, конечно, не поставили это в своем вопросе, где информация должна быть. Объясните ваши конкретные обстоятельства лучше, пожалуйста.
Никол Болас
@ user1306322: Честно говоря, я не совсем понимаю, почему мой ответ был опущен. Я перечислил три средства защиты от нехватки памяти - конечно, это не значит, что все они обязательно применимы. Но я все еще думаю, что они правы. Я, вероятно, должен был написать «расширить вашу память» вместо «купить», чтобы включить случай виртуальных машин. Меня опускают, потому что мой ответ был кратким, а не многословным? Я думаю, что они достаточно просты, чтобы понять их без объяснения причин ?!
Томас
1

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

В ответ на ваше обновление вы не можете распределять массивы по Int32.MaxValueразмеру. Вы должны разделить его на куски. Вы даже можете инкапсулировать его в класс-обертку, который предоставляет массивоподобный фасад:

Джимми
источник