Birthday Sale! Up to 40% off unlimited courses & creative assets Birthday Sale! Save up to 40%!
Advertisement
  1. Game Development
  2. Programming
Gamedevelopment

Программируем разрушаемый пиксельный ландшафт:  как взорвать всё

by
Difficulty:IntermediateLength:LongLanguages:

Russian (Pусский) translation by Alex Grigorovich (you can also view the original English article)

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

Примечание. Хотя этот урок написан в на Processing и скомпилирован с помощью Java, вы получите возможность использовать те же методы и концепции в любой среде разработки игр.


Предварительный результат

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


Шаг 1: Ландшафт

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

Для рендеринга ландшафта песочница сначала рисует статические пиксели, а затем динамические пиксели и все остальное поверх.

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

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

Черные линии представляют норму по ландшафту в разных точках.

Вот как это выглядит в виде кода:


Шаг 2: Динамический пиксель и физика

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

  • Положение и скорость (требуется для работы физики).
  • Не только текущее местоположение, но и предыдущее местоположение пикселя. (Мы можем определить разницу между двумя точками для обнаружения коллизий.)
  • Другие свойства включают цвет пикселя, тяжесть и подвижность.

Чтобы пиксель мог двигаться, его положение должно быть направлено со скоростью. Интеграция Эйлера, хотя и неточная для сложных симуляций, достаточно проста для того, чтобы мы могли эффективно перемещать наши частицы:

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

Мы будем использовать временные метки фиксированного размера, возьмём время и разделим его на куски определённого размера. Каждый фрагмент представляет собой полное «обновление» для физики, причем все оставшиеся данные передаются в следующий кадр.


Шаг 3: Обнаружение столкновений

Обнаружение столкновений для наших летающих пикселей так же просто, как нарисовать несколько линий.

Линейный алгоритм Бресенхама был разработан в 1962 году джентльменом по имени Джек Э. Бресенхам. По сей день он используется для эффективного рисования псевдонимов линий. Алгоритм строго придерживается правил целых чисел и использует в основном сложение и вычитание для того, чтобы эффективно использовать сюжетные линии. Сегодня мы будем использовать его для другой цели: обнаружение столкновения.

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

Наклон является неотъемлемой частью линейного алгоритма Брешенема. Алгоритм работает путем разделения наклона на его «подъем» и «запуск» компонентов. Если, например, наклон линии равен 1/2, мы можем построить линию, поместив две точки горизонтально, поднявшись вверх (и направо), а затем еще два раза.

Алгоритм, который я показываю здесь, учитывает все сценарии, имеют ли линии положительный и отрицательный наклон или он вертикальный. Автор подробно объясняет, как всё это работает на gamedev.net.


Шаг 4: Обработка столкновений

Динамический пиксель может сделать одну из двух вещей во время столкновения.

  • Если он движется достаточно медленно, динамический пиксель удаляется, а статический добавляется к рельефу, с которым он  сталкивается. Это будет нашим самым простым решением.  В линейном алгоритме Брешенема лучше всего отслеживать предыдущую точку и текущую точку. Когда обнаружено столкновение, «текущая точка» будет первым цельным пикселем, на который попадает луч вектора, а «предыдущая точка» - это пустое пространство непосредственно перед ним. «Предыдущая точка» - это именно то место, где нам нужно закрепить пиксель.
  • Если он движется слишком быстро, он отскакивает от поверхности. Вот где нужна наша норма поверхности! Отразите начальную скорость мяча по норме, чтобы пиксель отскочил.
  • Угол с обеих сторон нормали один и тот же.


Шаг 5: Пули и взрывы!

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

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


Шаг 6: Игрок

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

  1. Для каждого края, пройдите цикл от одного угла в следующий, проверяя каждый пиксель.
  2. Если пиксель цельный, начните от центра игрока и проверьте все пиксели до тех пор, пока вы не наткнетесь на цельный пиксель.
  3. Переместите игрока  от первого   цельного  пикселя, с  которым вы столкнетесь.

Шаг 7: Оптимизация

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

Вы можете делать все, чтобы оптимизировать уровень алгоритма. Например, количество частиц от взрывов может быть уменьшено путем уменьшения разрешения уничтожения. Обычно мы находим каждый пиксель и превращаем его в динамический 1x1. Вместо этого сканируйте каждые 2x2 пикселя или 3x3 и запустите динамический пиксель такого размера. В демо мы используем 2x2 пикселя.

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

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

При необходимости используйте примитивы.  Например, использование  объектов для обозначения позиций и скоростей создает больше трудностей в плане мусора. Будет лучше, если вы сможете сохранить всё, как примитивы, в одномерных массивах.


Шаг 8: Сделай это самостоятельно

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

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

Существует также множество различных вещей, с помощью которых вы можете сделать оружие.  Для снаряда могут быть заданы настройки «глубины проникновения», чтобы позволить ей перемещаться   определенное количества пикселей до момента столкновения. Традиционная механика снаряда также может включать в себя различные настройки, такие как различная скорость стрельбы, или, как дробовика, возможность выстрелить сразу несколькими пулями. Вы можете даже создать пули, отскакивающие от металлических поверхностей/пикселей.


Вывод

Разрушение 2D-ландшафта не является уникальным. Например, классики жанра Worms and Tanks удаляют части ландшафта при взрывах. Команда Cortex использует похожие прыгающие частицы, как и мы использовали здесь. Возможно есть еще и другие игры, но я о них не слышал. Я с нетерпением жду того, что другие разработчики будут использовать эту механику.

Пожалуйста, посмотрите на его исходный код, если что-то кажется двусмысленным или непонятным. Пожалуйста, посмотрите на его исходный код, если что-то кажется двусмысленным или непонятным. Я добавил комментарии к исходному коду, чтобы сделать его максимально ясным. Спасибо за участие!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.