Как сделать пиксельную игру на unity

Обновлено: 06.07.2024

С помощью этого пошагового руководства вы сможете самостоятельно создать игру в жанре RPG в Unity.

  1. Создание игрового мира с помощью тайловых карт.
  2. Базовая анимация для игрового персонажа.
  3. Простое взаимодействие объектов.
  4. Создание меню.

Посмотреть демо готовой игры можно здесь.


Начало работы

Создайте проект 2D Unity.

Добавьте образцы пользовательского интерфейса Unity из Asset Store. Этот актив будет использоваться для создания сцены меню.

Создайте в окне проекта следующее дерево папок (на русском или английском языке, в зависимости от вашей версии программы):

►Animals
►OtherCharacters
►Plants
►Players

Вы можете скачать любые бесплатные шрифты, которые вам нравятся и использовать их при работе с проектом (добавьте их в папку Шрифты/Fonts).
Что касается спрайтов, то вы можете воспользоваться этим сервисом для их создания, если у вас нет готового набора.
Объекты не должны превышать размер 64 на 64 пикселя
Персонаж и его экипировка могут быть созданы с помощью еще одного простого онлайн создателя спрайтов.
Большинство спрайтов размещено на листах спрайтов, которые вам необходимо предварительно нарезать в Unity 3D.

Выберите лист спрайтов в окне проекта, как показано на изображении ниже:

В инспекторе измените параметр Sprite Mode на Multiple и нажмите кнопку Sprite Editor:


Теперь в меню Slice выберите значение Type.

После закрытия окна Slice, справа от листа спрайтов в окне проекта появляется небольшая кнопка; это позволяет видеть и получать доступ ко всем нарезанным вами спрайтам:

Создание карты

Для быстрой прорисовки базовых карт пиксельных RPG используется специальная палитра.

Откройте меню Window ⇒ 2D ⇒ Tile Palette.

Теперь создайте новую палитру и добавьте спрайты.

При необходимости можно добавить несколько палитр.


Теперь вам нужно выбрать нужные плитки:


И установите тип коллайдера в инспекторе.



Теги и слои
Создайте недостающие теги:

Создайте несколько слоев для хранения объектов.

Определите также порядок сортировки слоев в разделе Sorting Layers:


Создание анимации

Добавьте все типы имеющихся анимаций в один и тот же GameObject:

1.выбирайте все спрайты движения в одну сторону (на север)
2.перетаскивайте выбранные спраты в меню их в Hierarchy, чтобы добавить GameObject и сохранить анимационный клип
3. выбирайте оставшиеся спрайты движения и перетащите их на созданный GameObject
4. сделайте то же самое со всеми другими анимациями
5. Проверьте анимационные клипы в окне Animation (меню Window ⇒ Animation ⇒ Animation).

Добавьте камеру как дочерний элемент персонажа.

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


В инспекторе в разделе Sprite Renderer установите для параметра Sorting Layer значение Player.


Добавьте 2D-компонент Rigidbody.

Установите Gravity Scale = 0 и в разделе Constraints заморозьте вращение по Z.


Добавьте 2D-компонент Collider для взаимодействия с другими GameObject, такими как предметы или животные.


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

Откройте меню Window ⇒ Animation ⇒ Animator.

Добавьте два параметра: DirectionX и DirectionY.


Для каждого перехода определите в Инспекторе следующее:

►снимите галочку с Has Exit Time
►в разделе Settings снимите флажок Fixed Duration
►в разделе Settings установите Transition Duration на 0.
►определить условия (например, EastIdle ⇒ WalkNorth ≡ DirectionY ⇒ 0, WalkNorth ⇒ IdleNorth ≡ DirectionX = 0 & DirectionY = 0)


Создайте новый скрипт PlayerMovement и прикрепите его к GameObject.

Добавьте этот код:

Вернувшись в Инспектор, установите значения:


После чего создайте префаб персонажа.

В окне Project удалите Animation Controller, созданный при добавлении клипов анимации.

Создайте новый, используя контекстное меню Create ⇒ Animation Controller, и назовите его.

В окне Animator создайте два новых состояния из Blend Trees.


Переименуйте объекты в окне Inspector, IdleTree и MoveTree, как показано на изображени ниже.

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


Создайте два перехода и установите настройки и условия, как показано на изображениях ниже.

Создайте 4 новых параметра float:

►DirectionX
►НаправлениеY
►LastDirX
►LastDirY

Параметр LastDir будут использоваться, чтобы узнать, какой неактивный клип вы должны использовать.

Теперь дважды щелкните IdleTree:

установите Blend Type на 2D Simple Directional
установите Parameters в LastDirX и LastDirY
в разделе Motion добавьте 4 элемента.
для каждого движения добавьте параметр idle clip (вверх, вниз, вправо, влево) и установите значения PosX и PosY


Вернитесь к Base Layer и сделайте то же самое с MoveTree.


Вернувшись в окно Scene, проверьте, есть ли у GameObject игрока компонент Animator.

Если он существует, удалите его.

Перетащите вновь созданный контроллер из окна проекта в инспектор.

Создайте новый скрипт PlayerBlendMovement и добавьте его в плеер.

Добавьте этот код:

Создание карты

Для начала рекомендуется создать несколько тайловых карт:

►карта мира
►тайловая карта помещений
►карта растений
►Карта дорог и скал

Таким образом вы сможете быстрее работать с фоновыми изображениями на ваших локациях.

Карта тайлов мира содержит информацию о мире, такую как вода, песок, трава, и т.п.

Прежде всего, создайте новую карту в меню Hierarchy с помощью контекстного меню 2D Object ⇒ Tilemap.

Это добавляет сетку и карту тайтлов, которую нужно переименовать в Tilemap_Map.

Установите значение Grid’s Layer на MapBackground.

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


В Tilemap Map в разделе Tilemap Renderer установите Sorting Layer на Map Background.


Откройте окно Tile Palette, выберите Active Tilemap и палитру.

Попробуйте нарисовать свой игровой мир, на рисунке ниже наш вариант, в качестве примера.


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

Чтобы это исправито вам нужно выполнить несколько действий:

убедитесь, что у плитки с водой есть коллайдер
добавить новый Tilemap Collider 2D в тайловую карту
добавить Composite Collider 2D на карту
указать Tilemap Collider 2D использовать Composite Collider 2D
добавьте Edge Radius в Composite Collider 2D, чтобы сгладить границы


Теперь персонаж может перемещаться только по мелководью.


В Иерархии выберите существующую сетку и создайте новую карту листов с именем Tilemap_Indoor.


На этот раз вы будете рисовать Tilemap_Indoor, используя несколько палитр (например, стены, полы).


Добавим несколько деревьев на нашу локацию.

В меню Hierarchy выберите существующую сетку и создайте новую карту листов с именем Tilemap_Plants.

Создайте новый слой, как дочерний элемент сетки.


Таким же образом вы можете добавить дороги или другие объекты на карте.


Создание интерактивных объектов

Для начала поместите префаб персонажа в исходное местоположение игрока.


Теперь вы сможете бегать по миру с помощью клавиш со стрелками.

Согласитесь, пока выглдит пустовато.

Давайте поставим несколько зданий.

Поместите спрайт дома в нужное место, затем установите Sorting Layer и добавьте Polygon Collider 2D.


Все внутренние объекты хранятся как дочерние по отношению к дому GameObject.

Таким же образом, добавьте дополнительные объекты на локации, а также деревья и животных.


так как это RPG, персонажу нужен инвентарь и древо умений.

Создайте новый UI Text и назовите его MessageBox, пока не заполняйте эту область.


Создайте еще один UI Text и назовите его CounterBox, также оставьте это поле пустым.


Создайте новую сцену, содержащую стартовое меню вашей игры, как показано на изображении ниже.


Используйте для этого следующий скрипт:

Далее нужно настроить кнопку Player Setup, используя следующий код:

Далее приступим к созданию меню для основных действий:

►Инвентарь: когда игрок собирает/обменивает вещи.
►Загрузка: когда игра запускается (запуск или изменение уровня).
►Сохранение игрового процеса.

Для этого вам понадобится следующий скрипт:

В Unity назначьте префабы трех игроков и текстовое поле Counter, как показано ниже.


Создание дорожных знаков

Создайте новый SignScript и добавьте его к знаку.

Вернувшись в Unity3D, свяжите MessageBox и добавьте текст для отображения.


Добавление звуковых эффектов

Фоновая музыка добавляется к объекту Grid.


Чтобы иметь возможность включать и выключать музыку, добавьте этот код в функцию Grid’s Update:

Меню выбора языка

Создайте обычный шаблон с фоновым изображением и флажком и кнопкой для каждого языка, как показано ниже.


Добавьте новый GameObject и назовите его LocalizationManager.

Добавьте сценарий LocalizationManager.cs.

Добавьте еще один GameObject и назовите его StartupManager.

Добавьте сценарий StartupManager.cs.

Перетащите LocalizationManager GO в область OnClick каждой языковой кнопки и установите локализованный файл JSON.

Сцена меню

Содержание пользовательского интерфейса (включая названия кнопок и области прокрутки) переводятся путем добавления LocalizedText.cs к объекту Text и ввода ключа перевода.


Для DropDown нужен сценарий DropDownOverwrite, который воссоздает параметры перехода с нуля.

Текст вызывается с помощью этой команды:

Далее ипользуйте следующий код:


Реализация WebGL

WebGL не принимает папку StreamingAssets, и ее файлы не входят в состав приложения.

Чтобы иметь возможность скомпилировать сборку WebGL, вам нужно загрузить папку StreamingAssets и ее файлы.

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

Узнайте, как создавать двумерные игры в Unity, используя карты плиток, спрайты, 2D-физику, анимацию и многое другое.

Made with Unity – Night in the Woods от Finji

Made with Unity – Night in the Woods by Finji

Unity — самая популярная в мире платформа разработки двумерных и трехмерных игр: 50% всех мобильных игр созданы в Unity! Теперь пользователям доступна бесплатная версия Unity. Вы получите доступ к платформе разработки 2D-игр и к богатой базе бесплатных ресурсов, созданных нами и энтузиастами из нашего прекрасного сообщества.


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

Unity Creator Kit: 2D-RPG

Unity Creator Kit: 2D RPG

Наборы Creator Kit — это простые игровые проекты, доступные на Unity Learn, которые можно освоить примерно за час. Набор 2D RPG Kit не затрагивает программирование, чтобы вы могли сосредоточиться на изучении Unity Editor и настройке игры в соответствии с вашими идеями и интересами.

The Explorer: набор для 2D-игры от Unity

The Explorer: 2D Kit by Unity

The Explorer: 2D — это коллекция игровой механики, инструментов, систем, графики и других ассетов для разработки двумерных игр. В набор включен пример игры, созданной с использованием этих систем. Этот проект можно освоить за пару часов, и вам не потребуется писать ни единой строчки кода.

Проект 2D-игры от Unity: Ruby’s Adventure

Unity 2D game project: Ruby’s Adventure

Ruby's Adventure — это проект, который познакомит начинающих разработчиков с процессом создания и программирования 2D-игры. В путешествии с Руби вы узнаете, как и зачем использовать спрайты, как создать первый скрипт, научитесь работать с инструментом Tilemap, украшать сцену, создавать эффекты частиц, пользовательский интерфейс, звуковое оформление и многое другое.

Советы по 2D-графике от сообщества Unity


Thomas Brush: советы по разработке 2D-игр

Канал Thomas Brush богат практическими советами и вдохновляющими идеями. Начните с видео 5 steps to making a gorgeous 2D game и Anyone can make 2D games!


Brackeys: Как создать 2D-игру

Brackeys — один из самых популярных и авторитетных каналов для Unity-разработчиков. Рекомендуем посмотреть серию, посвященную разработке 2D-игр в Unity.


Blackthornprod: руководства по текстурам и 2D-анимации

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

Как создать игру на Unity

Программирование и разработка

Как создать игру на Unity

Из этой статьи вы узнаtте, как начать создавать игру с помощью Unity, начиная только с идеи. Кроме того, вы изучите хороший метод запуска для создания игр Unity 2d с нуля.

Важно отметить, что это руководство не сделает из вас полноценного разработчика Unity и не научит программировать. Однако это поможет вам быстро заставить что-то работать на экране.

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

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

Ниже приведено пошаговое руководство по созданию 2D-игры в Unity для начинающих.

Шаг 1. Идея

идея

Шаг первый довольно прост. Вы легко можете сделать это в уме.

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

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

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

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

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

Шаг 2. Получите инструменты

Чтобы создать игру в Unity, вам понадобится Unity. Вы можете скачать персональную версию здесь . Имейте в виду, что личная версия необходима только в том случае, если вы не получили более 100 тысяч долларов для финансирования или дохода от своей игры.

Начните с загрузки Unity Hub. Затем откройте концентратор, выберите «Установить» и нажмите «Добавить». В этом руководстве будет использоваться версия Unity 2019.2.14f1. Вы можете установить любые пакеты, которые хотите или для которых есть место. В этом руководстве игра будет запущена в Windows, поэтому убедитесь, что установлен флажок «Поддержка сборки Windows».

Получите инструменты

Мы используем Unity версии 2019.2.14f1

Вы также можете создавать графику для своей игры. Поскольку Unity использует пиксельную графику , я настоятельно рекомендую использовать GraphicsGale . GraphicsGale бесплатна, проста в использовании и обладает множеством функций. Однако вы можете свободно использовать любой удобный вам редактор, будь то Photoshop, Blender или Windows Paint.

Шаг 3. Настройте свой проект

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

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

Вот пример

Сделайте что-нибудь быстрое и лёгкое, чтобы представить элементы нашей игры.

После завершения загрузки Unity вы можете открыть Unity Hub и начать новый проект. Назовите проект как хотите, но не забудьте выбрать 2D для настройки проекта.

Обязательно используйте 2D-шаблон

Обязательно используйте 2D-шаблон.

Настройте свой проект2

В Unity есть невероятная функция под названием Unity Asset Store. Asset Store полон готовых фрагментов игры, таких как модели, текстуры, утилиты или программы AI. Они доступны для продажи, а некоторые даже предлагаются бесплатно. Из-за этого легко создать игру, не создавая слишком много ресурсов самостоятельно. Вы можете просто взять заранее запрограммированную игровую механику и прикрепить её к собственному арту.

Чтобы получить доступ к этим бесплатным базовым программным ресурсам, просто перейдите на вкладку «Asset Store» в верхней части окна просмотра. Отсюда перейдите в раздел «Лучшие бесплатные ресурсы» и загрузите пакет «Стандартные активы (для Unity 2017.3)». Возможно, вам придётся войти в систему или создать учётную запись.

Настройте свой проект3

Загрузите стандартные ресурсы из Unity Store.

После загрузки пакета нажмите «Импорт» (она заменяет кнопку загрузки после загрузки ресурсов), чтобы импортировать пакет в текущий проект Unity. Вам будет представлен длинный список флажков. Что вы добавляете из пакета, зависит от вас. Для этого руководства вам понадобятся только папка «2D» и папка «CrossPlatformInput» в разделе «стандартные ресурсы».

Вам также понадобятся некоторые инструменты для создания 2D-игр в Unity, которые вам немного помогут. Перейдите по этой ссылке и загрузите Git в виде zip-архива. Затем разархивируйте и перетащите папку внутри в обозревателя ресурсов. Возможно, вам придётся перезапустить Unity после этого, чтобы он работал правильно.

Теперь у вас должно быть всё необходимое для начала работы.

Настройте свой проект4

Шаг 4. Создайте карту

Чтобы начать создание карты, сначала вернитесь на вкладку «Сцена». В обозревателе ресурсов в нижней части экрана щёлкните правой кнопкой мыши и выберите создать папку. Назовите новую папку с ресурсами «Плитки».

Создайте карту

Вы также захотите добавить свою иллюстрацию-заполнитель в браузер ресурсов. Просто перетащите всю папку из окон в обозревателя ресурсов.

Затем создайте палитру, которая будет служить блоками для вашего персонажа. Перейдите в Window> 2D> Tile Palette, чтобы открыть окно Tile Palette. Щёлкните «Создать новую палитру», чтобы создать новую палитру плиток. Назовите его «solid», но оставьте все значения по умолчанию и сохраните его в папке «Tiles».

Создайте карту2

Ваша новая палитра плитки, там ещё ничего нет.

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

Затем создайте что-нибудь для рисования с помощью палитры. В окне Hierarchy щёлкните правой кнопкой мыши и выберите 2D Object> Tilemap.

Создайте карту3

Щёлкните здесь правой кнопкой мыши, чтобы создать карту листов.

Теперь у вас должна быть возможность рисовать на своём уровне. Постарайтесь пока держать его в пределах границ камеры и добавить несколько платформ, на которые можно прыгать.

Создайте карту4

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

Создайте карту5

Компонент, который вам нужно добавить, называется Tilemap Collider 2D. Просто воспользуйтесь окном поиска, чтобы найти его и добавить на карту листов.

Шаг 5. Сделайте персонажа

Теперь, когда у вас есть карта, следующий шаг — создать кого-нибудь, кто будет перемещаться по ней. К счастью, Unity проделала большую часть тяжёлой работы на этом этапе. Если вы зайдёте в папку стандартных ресурсов> 2D> Prefabs в браузере ресурсов, вы увидите, что там есть маленький робот под названием «CharacterRobotBoy.prefab».

Сделайте персонажа1

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

Теперь перетащите мальчика-робота в свою сцену и щёлкните по нему в иерархии. Глядя на все компоненты, прикреплённые к нему, вы заметите, что у него есть система анимации, средство рендеринга спрайтов и несколько различных функций столкновения и управления.

Сделайте персонажа2

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

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

Сделайте персонажа3

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

Шаг 6. Дорожная карта и сборка

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

Например, ваша дорожная карта может выглядеть примерно так:

  1. Добавить систему монет.
  2. Добавить здоровье и урон игрока.
  3. Основные враги.
  4. Проверка ошибок.
  5. Улучшить базовое искусство.
  6. Создавайте игровые сценарии и выигрывайте.
  7. Добавить элементы пользовательского интерфейса.
  8. Добавить систему меню.

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

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

Заключение

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


Думаю, я не единственный в детстве играл на Amiga в Lemmings. Прошли десятилетия, и я стал, среди всего прочего, разработчиком игр, ведущим канал на Youtube с туториалами по Unity.

Одним вечером я наткнулся на эти два видео (часть1 , часть 2) Майка Дейлли о воссоздании Lemmings с помощью Game Maker 2. Во мне разгорелась ностальгия и я решил что-нибудь с ней сделать. Поэтому я начал создавать собственную версию в Unity с использованием собственных ресурсов (по очевидным причинам).

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

Кроме того, здесь можно поиграть в проект на WebGL. Возможны баги.

Сложность проекта заключалась в воссоздании ощущений и механик Lemmings. В том числе обеспечение pixel-perfect-коллизий при перемещении по уровню множества персонажей, которые могут изменяться в зависимости от своих умений.

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

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

Для этого нам нужно знать, что при изменении текстуры с диска Unity меняет саму текстуру, а не её экземпляр. Поэтому нам необходимо вручную создать этот экземпляр. Это очень просто делается с помощью следующего кода:


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


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


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

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

Теперь у нас есть текстура и мы должны её отрендерить. Мы добавляем GameObject со SpriteRenderer (в коде ниже хранится как levelRenderer ) и преобразуем нашу Texture2D в спрайт, чтобы назначить её. Это можно сделать с помощью следующих строк:


Можно сделать то же самое для миникарты, но вместо Sprite Renderer я использовал компонент Image UI.

Vector2.zero — это осевая точка, позиция 0,0 в нижнем левом углу. Значение 100 рядом с ним — это соотношение пикселя к точке, по умолчанию в Unity 1 точка на 100 пикселей. Также при выполнении любых вычислений с мировыми координатами важно знать, что, например, для нахождения позиции в мире узла node(5,6) мы умножаем x и y на соотношение, т.е. (x *(1 / 100)). Или же можно задать для всех спрайтов в настройках импорта соотношение 1:1/

Наконец, важно, чтобы сетка спрайта (sprite mesh) имела тип FullRect. Иначе Unity будет оптимизировать спрайты, создавая «острова» из непрозрачных пикселей. Это не станет проблемой при удалении пикселей с карты, но нам нужно и добавлять пиксели в пустые области. Задав тип FullRect, мы заставим Unity хранить спрайт как целый прямоугольник размером с исходное изображение.


На рисунке выше показана проблема, возникающая, когда спрайты имеют тип не FullRect.

Благодаря всему вышесказанному мы узнали, как воссоздать текстуру как карту.

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

Так как же нам реализовать pixel-perfect-коллизии?

Мы уже знаем, где на нашей сетке находятся пустые узлы и узлы земли. Давайте определимся с тем, по каким узлам мы можем ходить. «Правило» нашей игры будет следующим: если узел является землёй, а узел над ним — воздухом, то верхний узел является узлом, по которому можно идти. В результате мы сможем ходить из одного пустого узла в другой, но когда мы достигаем узла, под которым нет земли, мы падаем. Таким образом создание поиска путей для каждого юнита будет относительно простым процессом.

Нам просто нужно выполнить следующие действия в нужном порядке:

  1. Проверить, является ли текущий узел null. Если это верно, то мы, вероятно, упали с карты.
  2. Проверить, является ли curNode узлом выхода, и если это так, то юнит покинул уровень (подробнее об этом ниже).
  3. Проверить, является ли нижний узел пустым. Это означает, что мы находимся в воздухе, а значит падаем. Если мы падаем больше четырёх кадров (если последние четыре узла, по которым мы двигались, являлись пустыми), то переключаемся на анимацию падения. Благодаря этому анимация не меняется, когда мы просто спускаемся по склону.
  4. Если под нами земля, то нам нужно смотреть вперёд. Мы смотрим вперёд, если там пустой узел, то движемся туда.
  5. Если узел впереди не является пустым, то мы начинаем смотреть на четыре узла вверх, пока не найдём пустой узел.
  6. Если мы не нашли пустой узел, то просто поворачиваемся и идём в противоположную сторону.

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

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

Все юниты управляются простым диспетчером, который вручную выполняет их Update(). Поскольку в Lemmings была кнопка «ускорения», для её воссоздания нам нужна имитация времени timeScale (при ускорении она имеет значение 2 или 3), которая передаётся юнитам вместе с отмасштабированной версией дельты времени. Юниты используют отмасштабированное deltaTime (вместо Time.deltaTime) для интерполяции между позициями и используют timeScale для изменения скорости в своём Animator. Таким образом создаётся впечатление, что игра находится в «быстром режиме», хотя масштаб времени Unity сохраняет свою обычную скорость.

И здесь начинается самое интересное. У нас есть уровень, но основной частью геймплея Lemmings было динамическое взаимодействие с уровнями.

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


Где c = color (цвет). Единственная разница между добавлением и удалением пикселя заключается в значении альфа-канала. Не забывайте. что узел считается пустым, если значение альфы равно 0.

Однако, как мы помним из вышесказанного, когда мы используем .SetPixel(), нам также нужно вызвать .Apply() на текстуре, чтобы она действительно обновилась. Нам не нужно делать это каждый раз, когда мы меняем пиксель, потому что мы можем изменять за кадр несколько пикселей. Поэтому мы избегаем использовать .Apply() до конца кадра. Поэтому в конце нашего цикла Update() у нас есть простое булево значение. Когда оно истинно, и для textureInstance, и для миникарты выполняется .Apply():

После создания этой системы нам просто достаточно определять, на какие пиксели мы воздействуем и как именно. В статье я рассмотрю только высокоуровневую логику событий, но вы можете посмотреть код в видео. Здесь и далее, когда я говорю «есть ли пиксель или нет», то имеется в виду пустота узла, ведь если вы не упали с карты, то в любом её месте всегда будет пиксель.

  • Хождение
    • Это базовая способность. Двигаемся дальше, здесь нет ничего интересного.
    • Стоп! Перенаправляем юнитов в другую сторону.
    • Когда юнит падает с большой высоты, он умирает, но с этой способностью он плавно опускается вниз, как Мэри Поппинс. Эта способность работает очень просто, она всего лишь проверяет свою активность. Кроме того, она меняет скорость падения для интерполяции между пикселями, чтобы создать эффект расслабленного падения.
    • В оригинальной игре она называлась «Basher» — юнит просто копал тоннель вперёд. С точки зрения логики она переопределяет поиск пути: если впереди есть хотя бы один пиксель, то юнит продолжает копать на определённое количество пикселей, затем для каждого пикселя вперёд он берёт 8 пикселей над ним и отправляет их, чтобы они были удалены. Размер в восемь пикселей выбран из-за роста наших персонажей.
    • Похоже на копание вперёд,. Вместо того, чтобы брать пиксели спереди и сверху, способность берёт 2-3 пикселя под юнитом, 2-3 вперёд, и, разумеется, из одного ряда ниже.
    • Это простой юнит-самоубийца, он просто взрывается, оставляя вместо себя дыру. Удаляемые пиксели находятся на заданном радиусе вокруг его позиции.
    • Ещё одна классическая способность — юнит строит ряд пикселей диагонально вверх с точки, в которую он смотрит. Это достигается получением 4-5 пикселей впереди и вверху с дальнейшей передачей их для «добавления». Также это влияет на свойства узлов (превращает узел из пустого в заполненный). После этого способность интерполирует до следующей диагональной позиции и повторяет операции пока, не столкнётся со стеной или пока не закончится количество пикселей, выделенных на постройку.
    • Ещё одна забавная способность, которая плавно подводит нас к следующему разделу. Она впервые появилась в Lemmings 2: это юнит, бросающий пиксели «песка», которые имеют свойства «жидкости» — всегда спускаются вниз, пока не достигнут точки покоя. Подробнее мы рассмотрим их позже.

    Спускаемся плавно, как Мэри Поппинс

    Примеры использования некоторых способностей

    Или как я называю их, узлы заполнения. Это ещё одно забавное дополнение. Высокоуровневая логика их такова: это динамические узлы, имеющие собственный «поиск путей». Сложите их все вместе и получите эффект жидкости. Как показано на видео, можно также создать эффект падающего снега.

    Вот как это работает — у нас есть следующей класс:


    x и y — это, как можно догадаться, адрес узла. t здесь — это количество раз, когда узел заполнения находился в «позиции тупика». Можно поэкспериментировать с числами, но для себя я выбрал, что если он находился в ней 15 раз, то узел считается остановившимся, таким образом превращаясь из узла заполнения в обычный узел.

    Узлы заполнения не обновляются каждый кадр, потому что тогда им пришлось бы менять позицию 60 раз в секунду, из-за чего они могли бы двигаться слишком быстро. Мы используем действующий только на них универсальный таймер. Лично я использовал обновление раз в 0,05 секунды, но вы можете поэкспериментировать с ним, чтобы получить различные эффекты жидкости.

    Итак, когда они обновляются, то их «поиск путей» выглядит следующим образом:

    1. Проверяем, существует ли узел под нашим узлом, если нет, то этот узел заполнения упал за пределы карты.
    2. Если он существует, то проверяем, пуст ли он. Если он пуст, то очищаем пиксель, в котором находимся и добавляем к пикселю (узлу) ниже. Таким образом мы продолжаем двигаться вниз.
    3. Однако если он не пуст, проверяем пиксель вперёд-вниз. Если он пуст, перемещаемся туда.
    4. Если вперёд-вниз заполнен, то перемещаемся назад-вниз.
    5. Если мы находимся в позиции, из которой некуда двигаться, то прибавляем к t узла заполнения.
    6. Если t больше заданного нами значения, то удаляем узел заполнения и оставляем пиксель полным.

    Учитывая всё вышесказанное, можно подумать, что эффект «жидкости» сильно влияет на скорость, но в нашем случае это не так. Как показано в видео, я тестировал его с большими числами (больше 10 000 узлов заполнения). Хотя не было шансов, что все 10 000 будут «живыми» одновременно, это создало очень красивый эффект, показанный ниже.

    Игра с этой дополнительной системой. Способности «заполнитель» нужна только позиция спауна, после чего она создаст несколько узлов заполнения и позволит им двигаться самостоятельно.

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

    Но я хочу рассказать подробнее о сериализации, поэтому…

    Как я показывал в видео, есть несколько способов её реализации. Мне даже необязательно использовать для создания уровней редактор. Мы можем просто загрузить с диска изображение в .jpg и использовать его в качестве уровня. Уже созданная нами система открывает широкие возможности. Однако нам по-прежнему необходимо создавать игру из текстуры, так что нам требуется сохранять позицию спауна и позицию выхода (давайте называть их двумя событиями). В сиквелах оригинальных Lemmings было несколько таких событий, но давайте сосредоточимся на каждом из них, основа логики будет одинаковой.

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

    В результате я решил сериализировать всё необходимео на уровне в один файл. Единственное, что стоит здесь упомянуть — разумеется, невозможно сериализировать непосредственно Texture2D, но вы можете преобразовать её в массив байтов, закодировав текстуру. Unity упростила нам жизнь, потому что мы просто сделать следующее:

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

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

    Как же это сделать? В Unity есть класс WWW, поэтому мы создадим корутину для загрузки текстуры:


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

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

    … в WebGL это не разрешено. Обойти эту проблему можно, вставив в файл index WebGL-сборки небольшой фрагмент javascript. Что-нибудь подобное:


    Здесь linkField — это элемент UI InputField, ничего особенного.

    Стоит заметить следующее:

    • Даже несмотря на то, что у нас есть скрипт под названием GameManager, JavaScript ищет игровой объект (а не класс!) с таким именем. Вызываемая нами функция расположена в классе UIManager (который находится в том же gameobject).
    • Текстура не загружается, пока игрок не нажимает на кнопку загрузки текстуры в игровом UI


    Кроме того, в WebGL есть ещё одно ограничение. Да, вы уже догадались — мы не можем сохранить уровень пользователя, не обеспечив себе головную боль. Как же решить эту проблему? Очень просто — зададим MVP для нашего проекта.

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


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


    Это был очень интересный проект. Я работал над ним в свободное время и он занял 1-2 недели, но непосредственная работа заняла примерно столько же времени, сколько в видео, если не считать время на исследования и создание «графики» и «анимации» юнитов. И разумеется, в игре есть баги.

    Также стоит заметить, что кроме нескольких вызовов API код в основном очень прост.

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

    Читайте также: