Разработка игр

Анатомия скроллера

Как платформенные 2D-игры достигают бесконечной прокрутки

Многие из самых популярных игровых хитов в App Store и Play Store попадают в категорию «Endless Runner». Простота их двухмерности и управление одним касанием делают их доступными для широкого круга пользователей. Они также являются одними из моих личных фаворитов. В этой статье рассматривается, как их можно создать на iOS для достижения эффекта «бесконечной прокрутки».

Цели:

  • С нуля: игровые движки и сторонние библиотеки только затеняют основы и добавляют раздувания. Создание этой демонстрации с использованием чистого Swift проливает свет на основные необходимые механики.
  • Бесконечная прокрутка: без карт уровней. Действие никогда не должно заканчиваться без потери производительности или утечки памяти.
  • Темы. Продемонстрируйте несколько визуальных тем, которые помогут проиллюстрировать общие элементы и прояснить структуру демонстрации.
  • Динамическое рисование: в качестве бонусного задания вся игровая графика должна быть прорисована процедурно (без предварительно обработанных иллюстраций). Подробнее об этом и о том, как это влияет на производительность, позже.

Код

Полный код этой демонстрации можно найти здесь.

Почему SpriteKit?

Для разработчиков iOS SpriteKit предлагает низкий барьер для входа, поскольку нет сложных фреймворков или библиотек движков, которые нужно изучить. SpriteKit имеет плоскую кривую обучения; если вы знаете Swift (или Objective-C), вы можете освоить его, приложив разумные усилия. Использование SpriteKit также исключает необходимость интеграции сторонних зависимостей и их своевременного обновления. А поскольку в этой статье рассматриваются 2D-игры, имеет смысл использовать нативную 2D-структуру, которая, как рекламируется, обеспечивает быструю и недорогую производительность.

Визуальная иерархия

Чтобы создать убедительную игровую среду, необходимо составить несколько графических слоев. Технически эффекта параллакса можно добиться, используя всего 2 графических слоя. В этой демонстрации используются еще 3, в общей сложности 5. Это сделано для улучшения визуального восприятия. Перемещая одни слои с разной скоростью, оставляя другие неподвижными, создается убедительный эффект параллакса. На следующем рисунке показан стационарный атмосферный слой и 4 прокручиваемых слоя, представляющих передний план, средний план, задний план и дальний фон. Кроме того, транспортное средство, которое взаимодействует с физическим ландшафтом, еще больше повышает реалистичность сцены. Там, где это возможно, эффект частиц, таких как снег, пыль или выхлопные газы двигателя, может создать дополнительную атмосферу.

Черепица

В основе этой демонстрации лежит цель бесконечной прокрутки. Чтобы эффективно визуализировать бесконечную графическую среду, ее нужно разбить на страницы, то есть выложить плиткой. Мозаика - широко используемый метод, позволяющий прокручивать больше контента, чем может отобразить устройство. Самый распространенный пример этого на iOS - UITableView. UITableView выстраивает свои ячейки одну за другой, чтобы создать цельный список. Распространяя эту концепцию на этот проект, каждый из 4 движущихся слоев параллакса должен быть выложен плиткой так, чтобы зритель не мог различить, где начинается одна страница или заканчивается другая. В этой демонстрации за это отвечает класс ScrollingNode. Он упорядочивает узлы страницы один за другим, чтобы создать цельный «список» сбоку:

Концептуально это заимствует идеи повторного использования и удаления из очереди из UITableView. Практически он следует методам, изложенным в сеансе WWDC с 2011 года (Расширенные методы просмотра прокрутки) превосходными Джошем Шаффером и Элиза Блок. Короче говоря, ScrollingNode создает кеш для переработанных страниц. Всякий раз, когда страница прокручивается за пределы экрана, она перерабатывается и становится доступной для повторного использования. Поскольку максимальное количество видимых страниц для любого данного слоя равно 2, всегда выделяются только 2. Некоторые математические вычисления определяют индексы страниц, которые необходимо отобразить, и на основе этих индексов ScrollingNode добавляет или удаляет страницы в иерархию и соответственно позиционирует их. При оптимизации мозаика незаметна для зрителя, и в результате получается плавная бесшовная прокрутка, которая может надежно продолжаться бесконечно:

В этой сокращенной версии ScrollingNode показано, как выполняется мозаика с использованием базовой математики для вычисления того, где в среде должна находиться страница и должна ли она отображаться в данный момент. Удобные Set методы перемещают страницы между видимым и переработанным кешем. Сами страницы ничего не знают о своем содержании (они должны быть тупыми контейнерами), но их нужно идентифицировать и сравнивать, поэтому они сохраняют свойство pageIndex. ScrollerNode несет ответственность за укладку плитки. Чтобы заполнить страницы фактическим графическим содержимым, он делегирует полномочия выделенному поставщику содержимого, передавая страницу, для которой требуется графика. Это не указано в приведенной выше сути, но есть в демонстрационном коде.

Графика

Для решения дополнительной задачи процедурного рисования всей графики в этой демонстрации используются встроенные в SpriteKit возможности рисования. В частности, SKShapeNode обеспечивает удобный способ рисования векторной графики во время выполнения. SKShapeNode использует CGPath объекты для рисования своего содержимого. В этой демонстрации вся графика тем нарисована с использованием SKShapeNode. Это было сделано путем абстрагирования предполагаемого изображения в более примитивные формы, которые можно выразить с помощью точек, линий и дуг. Например, кактусы в теме Desert на самом деле представляют собой просто сегменты линий и кривые, интегрированные в общий путь страницы. Каждый из них призван вызвать вид кактусового растения без мелких деталей, которые ухудшили бы производительность во время выполнения. Точно так же деревья в теме «Альпы» рисуются по тропинке, напоминающей дерево, с заливкой ее сплошным цветом.

В целях оптимизации производительности целые страницы содержимого отображаются в виде единой формы (где это возможно). На рисунке выше один CGPath содержит как форму местности, так и переменные кактусы, которые сидят наверху. Это уменьшает общее количество узлов, а также уменьшает количество вызовов отрисовки. Поскольку механизм мозаичного изображения предоставляет страницы непосредственно перед их отображением, не может быть задержки в отображении содержимого каждой страницы. Чтобы поддерживать 60 FPS, на подготовку страницы все время доступно 16 миллисекунд. Поэтому упрощение кода рисования является ключом к обеспечению плавного рендеринга игры.

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

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

Транспорт и физика

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

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

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

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

Представление

Работая с мощной нативной 2D-структурой, можно легко увлечься требованиями игровой обработки. Однако есть предел, и лучший способ убедиться, что игра не попадает в него, - это тщательно проверять технические запросы игры. Экологическая мантра Recycle / Reuse / Reduce на 100% применима к разработке игр (и разработке программного обеспечения в целом), особенно на мобильных устройствах с ограниченными ресурсами. Эта демонстрация применяет эти принципы, уделяя особое внимание традиционным подходам к оптимизации:

  • Уменьшите количество узлов. Как упоминалось в разделе Мозаика выше, демонстрация визуализирует каждый бесконечный слой, используя только 2 узла страницы, которые используются повторно и мозаично. Это сокращает количество необходимых узлов до минимума.
  • Уменьшите количество вызовов отрисовки. Как упоминалось выше в разделе Графика, вызовы отрисовки сводятся к минимуму за счет комбинирования форм ландшафта с другими формами, например деревьями. Это снимает тяжелую работу с CGPath математикой, позволяя отображать графику каждой страницы с использованием одного SKShapeNode.
  • Сокращение вычислений в update(_:): метод update(_:) вызывается каждый кадр во время работы сцены. Таким образом, он особенно чувствителен к снижению производительности. Эта демонстрация ограничивает update(_:) код только управлением камерой слежения за автомобилем, когда он движется по окружающей среде.
  • Предварительная загрузка текстур. SKTexture обеспечивает функцию предварительной загрузки, но не помогает при процедурном рисовании узлов. Эта демонстрация обеспечивает собственную предварительную загрузку путем предварительного рисования SKShapeNode объектов, их растеризации и кэширования в банке, что снижает накладные расходы во время выполнения.
  • Упростите физические тела: SKPhysicsBody объекты имеют обратно пропорциональную зависимость между их точностью и характеристиками. Эта демонстрация выбирает прагматичную золотую середину в этом спектре. Например, физические тела транспортных средств не соответствуют 1: 1 внешнему виду транспортного средства на пиксельном уровне. Но они также не представляют собой круг (наиболее эффективная форма тела). Вместо этого они представляют собой прямоугольную форму, которая приближается к общему виду транспортного средства, обеспечивая физическую динамику, которая достаточно реалистична, чтобы убедить зрителя.

Структура кода

Эта демонстрация состоит из 2 групп объектов:

Основная многоразовая группа, которая создает среду бесконечной прокрутки. Эти объекты одинаковы для каждой темы:

  • Контроллер представления: объединяет корневые SKView, SKSceneобъекты и руководство. Также добавляет пользовательский интерфейс для изменения тем и создает новые SKScene объекты при изменении темы.
  • GameScene: создается с помощью объекта темы. Создает слои параллакса, узел транспортного средства, дополнительные эффекты частиц темы и пользовательский интерфейс для ускорителя. Также обрабатывает прикосновения пользователя
  • ScrollingNode: обрабатывает мозаику страниц, чтобы создать иллюзию одной длинной непрерывной прокрутки.

Вторая группа предоставляет тематический контент:

  • Тема: это перечисление инкапсулирует все данные, относящиеся к теме, включая ее поставщика контента, транспортное средство, эффекты частиц и т. д.
  • Content Provider: эта статическая библиотека работает вместе с ScrollingNode, чтобы сопоставить графику темы для каждой страницы, которая
  • Транспортное средство: этот подкласс SKNode создает корпус транспортного средства и предоставляет физические характеристики, которые определяют его поведение при движении по местности.

Что дальше?

Чтобы продвинуть этот проект дальше, можно подумать о добавлении некоторых реальных игровых элементов (например, уклонение от препятствий, убийство врагов, получение очков и т. Д.). Есть еще вопросы с прокруткой, на которые нужно ответить (например, как можно перемещать среду по вертикали, чтобы создать более интересный игровой процесс? Как можно уменьшить масштаб камеры, чтобы показать больше среды вокруг автомобиля?). Из кода можно выжать дополнительную производительность, такую ​​как использование динамических каталогов ресурсов времени выполнения, заполненных процедурно нарисованными графическими ресурсами. И последнее, но не менее важное: есть некоторые ошибки, которые нужно исправить, например, проблема с физическими соединениями, теряющими герметичность через некоторое время:

В целом, я надеюсь, что этот проект демонстрирует, как простой код Swift в сочетании со SpriteKit может создать убедительную среду с бесконечной прокруткой на основе параллакса. В сочетании с объектом игрока и некоторыми базовыми элементами управления в одно касание можно получить игровой опыт, закладывающий основу для 2D-игры, которую можно скромно отнести к категории «Бесконечный скроллер».