Что такое спрайт и тайл
Разработка игр под NES на C. Главы 4-6. Рисуем персонажа
В этой части рассмотрим работу с графикой: фон и спрайты персонажей.
>>
Что такое V-blank?
PPU — графический процессор — может или отправлять сигнал в телевизор, или получать информацию от процессора, но не одновременно. Так что единственное время для пересылки это V-blank, период кадрового гасящего импульса.
90% времени PPU отправляет пиксели в видеовыход, строка за строкой слева направо и сверху вниз. Внизу экрана делается пауза, и все повторяется снова. Это происходит 60 раз в секунду. Пауза после отрисовки кадра и есть V-blank. Это весьма короткий промежуток времени. В него реально вложить обновление 2-4 столбцов фоновых тайлов и обновление спрайтов. Обновление фона особенно критично для игр с прокруткой.
Сейчас мы напишем программу, которая будет выводить “HELLO WORLD” по 1 букве каждые 30 кадров. После вывода всей фразы очистим экран, и повторим все по кругу.
Обратите внимание на фрагмент
в файле reset.s. Это код обработчика немаскируемого прерывания, который фактически реализует счетчик кадров в глобальной переменной. Каждый кадр выставляет флаг NMI_flag, и в случае если прошло 30 кадров (0.5 секунды), выводится спрайт очередной буквы. Вся логика реализована в main().
В идеале, надо ждать V-blank перед вызовом All_On(), иначе один кадр после него будет искажен, и экран будет мерцать. В этом примере этот эффект незаметен, потому что All_On() вызывается единственный раз при старте приставки, экран в это время черный, и искажение незаметно.
Немного цвета
Теперь можно раскрасить то что получилось.
Таким образом можно одновременно отрисовать 25 цветов из 50 возможных.
Для фона палитра определяется для блока из 2х2 тайлов, размером 16х16 точек. Для этой разбивки удобно использовать Nes Screen Tool. Большинство игр строит фон именно из таких счетверенных метатайлов.
Здесь одна буква соответствует одному тайлу, малый квадрат — метатайл 2х2.
То есть чтобы поменять палитру правого нижнего квадранта на первую из 4 возможных, надо поменять два старших бита атрибута на 01: 01хххххх.
Я добавил к прошлому примеру палитры, так что буквы теперь разноцветные. Палитры вынесены в отдельный файл и импортируются через #include, а переменные определены в нулевой странице памяти через #pragma — просто для демонстрации этой фичи. Нулевая страница работает быстрее, но 10-20 переменных там используется для внутренних нужд компилятора. Так что надо рассчитывать где-то на 235 доступных байт.
Вот полезная шпаргалка по адресам таблицы атрибутов и ее привязке к экранным координатам:
Экран шириной 256 точек, каждая клетка таблицы соответствует квадрату 32х32
Спрайты
Спрайт — это картинка 8х8, которая может перемещаться по экрану. Есть хитрый способ использовать спрайты 8х16, но мы так делать не будем. Почти все персонажи игр состоят из спрайтов. Хотя в некоторых случаях нужно отрисовывать их фоновыми тайлами из-за ограничений на количество спрайтов, такое используется для финальных боссов в некоторых играх.
8х8 точек это очень мало, поэтому придется собирать персонажа из нескольких спрайтов: маленького Марио из 2х2, а большого из 2х4.
Надо помнить об ограничениях. Поддерживается не больше 64 спрайтов, и не более 8 на одной строке экрана. При превышении лимита будут отрисованы только спрайты с большим приоритетом. Если менять приоритет спрайта между кадрами, он будет мигать. Этот способ часто используется в играх.
PPU хранит информацию о спрайтах в таблице OAM. Ее размер 256 байт: по 4 байта на каждый из 64 возможных спрайтов. Если накладываются два спрайта с одинаковым приоритетом, то спрайт с меньшим номером отрисовывается поверх. Если на одной строке экрана будет больше 8 спрайтов, то отрисуются только 8 с меньшими номерами.
Таблица OAM хранит такие атрибуты:
В нашем примере мы сделаем метаспрайт из 4 спрайтов и добавим анимацию.
Надо помнить, что спрайты отрисовываются на 1 точку ниже от ожидаемой координаты. На картинке с Марио вверху поста видно, что он на 1 пиксель ушел в пол.
Есть немного улучшенная версия, где персонаж поворачивается при движении по квадрату.
Программный графический сопроцессор на STM32
Прошел год и многие вечера коротались написанием очередного, куда более крупного и на этот раз полезного проекта. В прошлый раз везде приходилось ужиматься, как только возможно. Ресурсов того многострадального камня мне стало не хватать и в какой-то момент пришло интересное решение. Отдать часть задач другому контроллеру. (Как и в прошлый раз, под катом много воды и изображений.)
Несмотря на наличие таких проектов как Nextion HMI и Gameduino, я решил сделать свое решение по ряду причин. Gameduino хоть и казалась интересным решением (VGA выход и FPGA на борту), но невозможность достать вторую версию этой платы, вынудила приобрести ее первую ревизию. Это были боль и страдания:
Для чего это вообще
Может показаться, что sGPU очень похож на Nextion дисплей, но это не так. Он значительно проще и появился из интереса, но со временем раздулся настолько, что захотелось поделиться с сообществом, особенно после немногочисленных просьб.
Часть идей была позаимствована от всевозможных библиотек, личное по мелочам и старых консолей (после прочтения кучи мануалов по устройству, но далеко не все, что хотелось бы).
Описывать все возможности в одной статье явно не выйдет, более того все доступные команды можно посмотреть в исходниках для хоста (Arduino библиотеки) или в файле «commandDiscriptions.txt».
Делать было нечего
Что делать если есть неплохой дисплей и простой контроллер stm32? Очевидно же! Скинуть все взаимодействие с экраном на него!
Первым делом была взята библиотека для работы с дисплеем от Adafruit и почти полностью переписана, потом прикручено DMA. Графические примитивы есть, вывод текста есть,
Контроллер для sGPU
От серии STM32F4xx отказался сразу, ввиду несуразности идеи (использовать такой контроллер для такой ерунды! Если только для pro2…).
Выбраны были STM32F103C8T6 с 20 кб RAM (далее mini версия) и STM32F103VET6 с 64 кб RAM (далее pro версия).
Так как mini имеет куда большее распространение благодаря китайцам и их дешевому и любимую клону maple mini, было решено как основное ядро выбрать его, несмотря на некоторые ограничения, описанные далее (плату с pro версией китайцы тоже выпускают как клон ministm32).
Не просто так написал ядро, достаточно сменить тип контроллера в проекте и изменить пару инклюдов и получаем sGPU с большим объемом памяти и контроллером FSMC (дешевизна и распространённость mini версии, не дают покоя не использовать ее).
Экран
Использовался экран, на базе драйвера ILI9341 (разрешение экрана 320×240). Взаимодействие с экраном идет по SPI (так же имеется возможность по FSMC, но протестировать так и не удалось по различным причинам) на максимально возможной скорости для данного контроллера — 36 Мбит/с (из реально замеренных, примерно 2,7 Мб/с или 22 Мбит/с), но только при DMA передаче, как и было указано в Reference manual (RM0008 for F101-F107).
После тщетных попыток, так и не удалось завести SPI на большей скорости. Блок SPI в контроллере банально начинает сходить с ума (стабильный максимум 80 МГц для F_CPU, вместо 72 МГц, дает чуть больше не разогнанных 36 Мбит/с).
Интерфейс
Самое интересное началось, когда предстал выбор перед интерфейсом общения хоста и sGPU.
Требования к интерфейсу были следующие:
Из них только 4 использовано, остальное в резерве (можно сделать хоть 1200 Бод).
Скорость выбирается по маске, посредством
Тайлы
Это первое, что должен уметь sGPU.
Каждый тайл, это всего-навсего массив байт с индексами цветов в текущей палитре цветов.
Подробнее описывать, что такое тайлы я точно не буду, понадеюсь на вашу способность найти информацию в сети, уж больно велик объем информации.
Так как тайлов могут быть сотни и тысячи, то они будут занимать безумные объемы ПЗУ. Совершенно нелогично передавать их со стороны хоста (так сделано в Gameduino первой ревизии).
Выход был цеплять карту памяти по SPI, SDIO пока нет. Самым тяжелым, оказалось, поднять FatFS. Так как все пишется при помощи SPL библиотеки, а не HAL и CubeMX и возможно потому, что плохо проверил подключение питания для SD карты (к питанию щепетильны жутко они), в какой-то момент все просто заработало.
В конечном счете, со стороны хоста достаточно отправить название файла и сколько тайлов загрузить в RAM sGPU.
Согласитесь, что команда размером в 5 байт (это минимальный размер для загрузки одного тайла на момент написания статьи) куда меньше чем громоздкая библиотека FatFS съедающая значительную часть как оперативной, так и постоянной памяти, что совершенно не заметно на контролере уровня stm32.
Поддерживаются следующие размеры тайлов:
Так же упомяну, что воспользовался хитростью при выводе их на экран. Помимо использования DMA(есть буфер для одного преобразованного тайла в RGB565), я проверяю индекс каждого тайла, и если он совпадает с предыдущим, то преобразование пропускается, и старый тайл выводится по новым координатам. Не смотря на такую трату памяти, это имеет неоспоримое преимущество, когда подряд на экран выводятся одинаковые тайлы. Время на преобразование отсутствует, поэтому тайл выводится почти сразу.
Ложка дегтя – на видео сверху код для хоста использует С версию библиотеки (идентичного можно достигнуть только если использовать STM32 под Arduino).
По ней можно заметить множество пустот во время выставления адресного окна. Эти пустоты по большей части переключение линии DC (выбор данных или команды, второй канал красная линия) и ожидание освобождения буфера SPI (ожидание передачи всех данных). С этим из-за особенностей дисплея почти ничего не сделаешь (только 9 бит режим или FSMC).
Создание тайлов
Прежде чем любой тайл попадет RAM sGPU, его необходимо предварительно загрузить с SD карты, но до этого его нужно туда поместить.
Создать тайл не так сложно, достаточно сделать следующее:
Сделав там изображение его достаточно экспортировать как *.png и скормить GIMP (как описано выше).
главное, не забывать передавать корректную, конечную ширину изображения в тайлах, иначе sGPU загрузит мусор! Также нужно соблюдать кратность размера тайлов к 8 (8×8, 16×16, 32×32). Это справедливо и для набора тайлов.
Спрайты
Второе, что должен уметь sGPU: создавать спрайты из тайлов. Спрайт это объединение тайлов в группу (если упрощенно).
Количество спрайтов для mini — 56, тогда как для pro версии немногим больше — 63. Их количество для версий mini и pro вычислялось по-разному. Так для mini это сумма половины максимального количества тайлов каждого типа, тогда как для pro это сумма четвертей 63 спрайта хватит всем.
Спрайты могут состоять из любого, но одинакового типа тайлов, т.е. нельзя сделать спрайт из тайлов 8×8 и 16×16 одновременно, но, можно два спрайта из тайлов 8×8 и 16×16. Каждый спрайт состоит из четырех тайлов (максимум) спрайта 64×64 хватит всем, для всего остального есть вывод *.bmp.
Возможны следующие комбинации размеров:
Помимо этого каждый спрайт содержит координаты в пикселях, где он будет рисоваться (может помочь для расчета столкновений двух спрайтов, да это тут тоже есть).
Тайловый фон
Третье, что должен уметь sGPU.
В памяти содержится массив из 1200 байт (40×30 тайлов) с индексами тайлов только размером 8×8 (в будущем может быть будет возможность выбора).
Тайловая карта хранится в RAM и грузится с SD карты (расширение *.map). На текущий момент инструменты, которые позволили бы создавать файл карты, отсутствуют.
Пример тайлового фона можно увидеть во время подачи питания.
Теперь самое неприятное. Спрайты и тайловый фон используют одни и те же тайлы 8×8. Это означает, что для фона и спрайтов необходимо использовать разные тайлы (да, которых не так много на mini версии).
Распределение памяти
Очевидно, что RAM память stm32 ограничена и много в нее не впихнешь. По неведомым мне уже причинам решил отдать память под тайлы для mini – 7680 байт, для pro — 40960 байт.
Но мне не хотелось всю эту память отдавать под один тип тайлов. Проблема возникла с распределением этой памяти, точнее какое количество каких тайлов использовать.
Ранее, когда читал статьи про старые консоли, натыкался на то, что в зависимости от консоли всегда не хватало какого либо типа тайлов.
Поэтому учтя кучу возможных сценариев, было решено распределить так:
Цветовая палитра
Для уменьшения используемой памяти, тайлы используют не цвет целиком для каждого пикселя (жаль, нет столько памяти), что значительно бы увеличило производительность, а всего-навсего индексы цветов из цветовой палитры. Это решение снизило требование к памяти вдвое и сделало возможным использование трюка по смене палитры и выводу тех же тайлов (не нужно загружать новые).
Подобный трюк использовался во всех старых консолях. Самый заметный и известный
Как самая простая палитра была использована палитра NES, но была обнаружена ее недостаточность.
Вооружившись GIMP и встроенным колориметром в системе, расширил цветовой набор до 76 (4 черных цвета для резерва).
Не являясь человеком с идеальным цветовосприятием, я, конечно же, не смог сделать адекватную палитру из 76 цветов:
Буду премного благодарен, если кто сможет найти или сделать более адекватную палитру на 256 цветов (с одним цветом для будущего альфа канала).
Если нужно будет использовать другую палитру, ее можно загрузить с SD карты. В RAM sGPU есть целых 512 байт под это (256 цветов по два байта на цвет, прямо как в GameBoy Advance, но только в RGB565).
Экспортировать палитру из GIMP очень просто, достаточно экспортировать любое индексированное изображение (как raw data), и GIMP рядом с файлом изображения создаст еще один с расширением *.pal. Его и нужно поместить на SD карту (не забыв про размер имени до 8 символов).
Протокол
Протокол писался так, чтобы размер любой команды был как можно меньше. Строго заданные размеры и параметры команд, и полное отсутствие контроля корректности команды. Все для максимальной скорости выполнения команд (ведь все выполняется программно, если помните).
Любая команда начинается с байта ее кода. Конечный размер всей команды зависит от ее кода. Так для заполнения всего экрана одиночным цветом нужно только 3 байта, но для того чтобы нарисовать треугольник уже 15 байт.
Коды команд аккуратно разбиты по диапазонам (секциям), есть большое количество не использованных кодов. Всего в текущей реализации протокола доступно 255 команд из них использована даже не половина (есть свободное поле для творчества).
Сторона хоста
На стороне хоста sGPU выглядит как обычный экран, но только по UART интерфейсу.
Не смотря на большой входной буфер для команд sGPU, все равно есть риск его переполнения. Поэтому есть два варианта защиты (по факту 1):
съедает 1634 байт ROM и 217 байт RAM, и это без метода синхронизации с sGPU! Более того, если использовать STM32, то эти цифры будут уже в 10 (как минимум) раз выше – 16732 байта ROM и 3960 байт RAM!
Поэтому есть версия на С. К сожалению, пока заточенная под atmega328p (и ей подобным) и лишенная почти всех достоинств Arduino. Эти недостатки компенсируются как увеличенной скоростью работы, так и значительно меньшим размером: 922 байт ROM и 46 байт RAM (можно сделать и меньше конечно) только уже с синхронизацией.
Самое главное если вы использовали библиотеку для экранов ili9341 от Adafruit, то не придется переписывать почти ничего! Имеется почти полная совместимость со всеми методами.
Недостатки
Нет большого количества памяти, из-за этого невозможно сделать фрэймбуфер, так как требуемое количество памяти для одного кадра всего экрана будет занимать 180 килобайт. Из-за этого на текущий момент нет альфа канала для тайлов и спрайтов.
Медленно. Против FPGA (FT800, RA8875 и им подобным) простой контроллер на 72МГц не имеет ни малейшего шанса противостоять. Однозначно, что запуск FSMC на pro версии поможет ситуации, но не столь сильно как хотелось бы.
Исходники
Все исходные коды и даже прошивка для stm32 есть на Github (если вдруг у вас нет программатора, то можно прошить через STM32 Flash Loader Demonstrator по UART1).
Вот ссылка на репозиторий проекта (надеюсь не получу бан/mute аккаунта из-за трафика хабраэффекта, если такое все еще есть). В исходниках вы найдете проект для sGPU (под IAR ARM версии 7.40), код для AVR и Arduino (тавтология та еще выходит), так же там есть описания по подключению и многое немногое другое.
В целом проект очень сырой, несмотря на уже имеющиеся возможности. Не так много шагов осталось до консоли с аудио сопроцессором на YM2149F.
Если есть вопросы, задавайте, буду рад ответить. Также могу написать еще, осветив какие либо моменты куда подробнее (только дайте знать надо это или нет). В одну статью, как уже писал выше, все не впихнуть.
Введение в новую систему тайловых карт Unity
Знакомство с систему двухмерных тайловых карт Unity даёт отличную возможность экономии времени инди-разработчиков и игровых студий на прототипирование и создание качественных 2D-игр.
Без этой системы можно потратить дни, если не недели на написание собственной системы тайловых карт или переработку чужой. И это только программирование, а как насчёт редактора тайловых карт?
Новая система бесплатна и встроена непосредственно в редактор Unity. Она предоставляет множество возможностей, которые мы рассмотрим в этом туториале.
В этой статье мы воспользуемся простой тайловой 2D-игрой, чтобы узнать следующее:
Примечание: в этом туториале подразумевается, что вы хорошо умеете работать в редакторе Unity. Если вы считаете, что недостаточно в нём освоились, то в туториале Introduction to Unity есть всё необходимое для изучения этого туториала. Кроме того, вам необходима версия Unity 2017.3 или выше.
Что такое тайловая игра?
Тайловая 2D-игра — это любая игра, в которой уровни или игровые области состоят из множества небольших плиток (тайлов), вместе образующих сетку тайлов. Иногда различия между тайлами могут быть очевидными, а иногда они кажутся игрокам сплошными и неразличимыми.
Коллекция имеющихся в игре тайлов называется «тайлсетом», и каждый тайл обычно является спрайтом, частью листа спрайтов (spritesheet). Если вы хотите лучше разобраться с листами спрайтов, то у нас есть туториал, в котором описываются листы спрайтов Unity.
Как можно увидеть в этом туториале, обычно тайлы являются квадратами. Но они могут принимать и другую форму — прямоугольники, параллелограммы или шестигранники. В играх обычно используется вид сверху или сбоку, но иногда в тайловых играх применяется и 2.5D.
Возможно, вам уже известны две самые популярные игры, в которых используется система тайловых карт: Starbound и Terraria.
Приступаем к работе
Запустите редактор Unity и загрузите проект Rayzor-starter из распакованных материалов проекта.
Вот, с чем вы будете работать в этом проекте:
Создание игры
Откройте сцену Game из папки Scenes.
Нажмите на кнопку Play в редакторе, чтобы запустить игру. В окне Game перемещайте героя клавишами WASD или «стрелками».
Пока герой бродит по кажущемуся бесконечным фону камеры с цветом #00000, потерявшись в пустоте.
Чтобы исправить это, нам потребуются инструменты 2D-тайлов для построения интересных уровней и механик игры.
Знакомимся с палитрой тайлов
Это окно станет вашим лучшим другом при работе над тайловыми играми в Unity.
Нажмите на Create и выберите сохранение новой палитры в папке Assets\Palettes проекта. В ней создайте новую папку RoguelikeCave.
Теперь структура папок вашего проекта должна выглядеть так:
В окне редактора Tile palette должна быть выбрана RoguelikeCave; на этом этапе у нас всё ещё нет никаких тайлов:
Как художник может творить свои шедевры, если у него нет материалов?
Не закрывая окно Tile Palette, выберите папку проекта Sprites/roguelike-cave-pack и разверните ассет roguelikeDungeon transparent.png. Затем выделите все спрайты в этом листе спрайтов: выберите первый спрайт, зажмите shift и выберите последний спрайт.
Перетащите все выбранные спрайты в окно Tile Palette RoguelikeCave:
Перетащив спрайты в окно Tile Palette, выберите в Unity место для хранения ассетов.
Создайте в Assets/Palettes/RoguelikeCave новую папку Tiles и выберите эту папку в качестве места хранения:
Unity сгенерирует тайловый ассет для каждого спрайта, добавленного из листа спрайтов. Дождитесь завершения процесса, затем увеличьте размер окна Tile Palette и полюбуйтесь на ровные ряды красивых новых тайлов, расположившихся в палитре RoguelikeCave:
Повторите описанный выше процесс для создания палитры тайлов с помощью окна Tile Palette, но на этот раз назовите новую палитру RoguelikeCustom.
Поместите новую палитру в новую папку. Назовите папку RoguelikeCustom и переместите её в папку Assets/Palettes проекта.
На этот раз, воспользовавшись описанным выше процессом, используйте спрайты из листа Assets/Sprites/roguelike-custom/roguelike-normal-cutdown-sheet.png для заполнения тайлами новой палитры. Создайте внутри папки палитры RoguelikeCustom папку Tiles и переместите ассеты тайлов туда:
Порадуйтесь за себя, теперь вам известна магия создания тайловой палитры!
Создание сетки карты тайлов
Откройте меню GameObject в верхней части редактора Unity (или панель меню Unity, если вы работаете под MacOS), нажмите на 2D Object, а затем Tilemap, чтобы создать новую сетку Tilemap:
Вы должны увидеть, что в иерархию сцены добавился новый GameObject Grid. Разверните его и выберите встроенный GameObject Tilemap.
Воспринимайте этот объект Tilemap как слой (возможно, один из многих) вашей игры. Можно добавить новые объекты для создания дополнительных слоёв Tilemap.
В инспекторе вы увидите два компонента, которые Unity автоматически добавила к этому GameObject:
Использование различных инструментов рисования палитры тайлов
Переключитесь в редакторе на режим Scene.
Не закрывая окно Tile Palette, выберите палитру RoguelikeCave, а затем выберите инструмент brush (или нажмите B). Выберите тайл песка, как показано ниже:
В окне Scene переместите курсор на сетку рядом с игроком. Кисть с тайлом песка будет привязываться к сетке.
Зажав и удерживая левую клавишу мыши, нарисуйте вокруг игрока прямоугольную область. Она будет отрисована на слое Tilemap BaseLayer:
Рисование больших областей может оказаться монотонным занятием, поэтому существует кисть Filled Box, которую можно использовать для закрашивания крупных площадей. В окне Tile Palette нажмите на квадратный значок кисти (или нажмите U).
Вернитесь в редактор и нарисуйте вокруг игрока прямоугольник ещё большего размера, нажав и удерживая левую клавишу мыши, перетаскивая курсор из верхнего левого в нижний праый угол:
Хотя мы добавили в игру немного цвета, это песчаное подземелье выглядит уныло. Настало время добавить немного деталей!
В окне Tile Palette переключите Active Tilemap на слой DungeonFloorDecoration:
Выберите инструмент brush (B), затем нарисуйте в окне Scene разбросанные на карте объекты:
Отключите, а затем снова включите в иерархии GameObject DungeonFloorDecoration, чтобы увидеть, как отрисовка на активном Tilemap изменяет слой DungeonFloorDecoration, а все отрисованные тайлы попадают на этот новый слой:
Переключите Active Tilemap в окне Tile Palette на Collideable. Выберите инструмент brush (B), а затем нарисуйте следующие тайлы, чтобы построить вокруг игровой области стену. Выделенные красным зоны на изображении ниже — это новые части, которые нужно добавить:
Посмотрите на показанный ниже скриншот окна Tile Palette, чтобы разобраться, где найти тайлы, необходимые для постройки стены. Не забывайте, что можно использовать сочетания CTRL-Z или CMD-Z для отмены действия или стирать ошибки с помощью текущей кисти (удерживая Shift):
Запустите игру в редакторе и попытайтесь пройти сквозь стену:
Вы ведь не этого ожидали?
Проблема в том, что мы просто нарисовали стандартные тайлы и пока не применяли к слою Tilemap волшебную физику Unity.
Выберите GameObject Collideable и добавьте новый компонент, нажав кнопку Add Component в окне Inspector; в поле поиска введите Tilemap Collider 2D:
Этот компонент был создан специально для тайловых 2D-игр на Unity. Он просто применяет форму физического коллайдера ко всем тайлам слоя, к которому он был добавлен, не выполняя никакой другой работы.
Снова запустите игру и попробуйте пройти сквозь стену. Доступ запрещён!
Примечание: иногда при движении камеры можно заметить между некоторыми тайлами небольшие чёрные линии. Похоже, что это проблема движения камеры в проектах с системой 2D Tilemap Unity. От неё можно почти полностью избавиться, отключив Anti-Aliasing в параметрах графики. Однако даже если это сделать в проекте-заготовке, эффект всё равно слегка заметен. Решением этой проблемы может стать добавление собственного скрипта движения камеры с пиксельным смещением. Хорошее обсуждение этой проблемы можно найти здесь.
Коллизии работают хорошо, и вы можете подумать, что этого достаточно. Но пока коллайдеры не оптимизированы эффективно. В режиме Scene приблизьте часть стены и посмотрите на контуры коллайдеров:
Вокруг каждого тайла есть коллайдер. Средним секциям стен не нужны эти дополнительные коллайдеры.
Выбрав GameObject Collideable, добавьте к нему компонент Composite Collider 2D. Это также автоматически добавит RigidBody2D.
Задайте параметру BodyType RigidBody2D значение Static, а затем поставьте флажок Used by Composite в компоненте Tilemap Collider 2D:
После этого вы заметите, что эти ненужные квадратные коллайдеры посередине стен исчезнут.
Завершите создание стен, достроив их вверх и замкнув наверху, высотой примерно в 16 тайлов. Не забывайте. что в качестве Active Tilemap окна Tile Palette должен быть выбран Collideable:
Участок подземелья не будет представлять никакой сложности для нашего героя без препятствий. Теперь мы начнём работу над созданием комнаты смерти, дополненной красивыми древними мраморными коридорами. После преодоления всех этих препятствий игрока ждёт награда — гора золота.
Для отрисовки этих коридоров мы воспользуемся специальной тайловой кистью Rule Tile. Как вы видели в начале туториала, в проект уже добавлены дополнительные тайловые скрипты из Github-репозитория Unity 2D Extras. Одним из них является Rule Tile.
Rule Tile позволяет нам задавать правила о том, какие тайлы нужно отрисовывать в зависимости от соседних располагаемых нами тайлов.
Воспользовавшись инструментом box fill brush (B) в окне Tile Palette и выбрав слой Tilemap BaseLayer, нарисуйте прямую секцию мраморной стены. Нужно, чтобы она закрывала всё пока свободное пространство пола.
Можно заметить, что когда мы будем это делать, слой будет закрывать тайлы стен с колладерами, потому что пока не задан порядок слоёв. Это легко исправить, выбрав GameObject Collideable и изменив Order in Layer компонента Tilemap Renderer на более высокое значение (достаточно будет 5):
Вернитесь в папку Prefabs проекта, откройте окно Tile и выберите палитру RoguelikeCave, а затем перетащите MarbleFloorRuleTile в пустое место на палитре:
Воспользуйтесь box fill brush и нарисуйте в комнате несколько секций мраморного пола:
Заметьте, что настроенный тайл правил, полностью окружённый со всех углов и граней, становится украшенным тайлом (спрайтом, выбранным в редакторе Tiling Rules).
Это ловушка!
Нет, мы не будем добавлять в игру в качестве персонажа адмирала Акбара. Мы создадим тайловую кисть префаба ловушки, которой воспользуемся для отрисовки ловушек, стреляющих вращающимися лезвиями!
Создайте в иерархии новый пустой GameObject и назовите его ShootingTrap. Создайте в ShootingTrap пустой дочерний GameObject. Назовите его Sprite:
Выберите Sprite и добавьте к нему компонент Sprite Renderer. Задайте Sorting Layer значение Player, а Order in Layer значение 1, чтобы он рендерился поверх остальных слоёв. Выберите поле Sprite, а в качестве спрайта поставьте roguelikeDungeon_transparent_180.
Теперь поверните Transform объекта Sprite на -90 по оси Z:
Далее вернитесь к GameObject ShootingTrap и добавьте с помощью инспектора новый компонент. В поле поиска найдите Shooting Trap и прикрепите этот скрипт.
Этот скрипт добавлен в скачанные вами файлы проекта; по сути, он каждые две секунды запускает корутину, создающую экземпляр префаба вращающегося лезвия пилы (или любого другого префаба) в текущей позиции ловушки.
Задайте параметру Item to Shoot Prefab компонента Shooting Trap значение Projectile (префаб находится в /Assets/Prefabs):
Снова запустите игру в редакторе и воспользуйтесь режимом Scene, чтобы найти ловушку. Она работает!
Перетащите копию ShootingTrap из иерархии в папку /Assets/Prefabs проекта, чтобы создать префаб. Удалите ShootingTrap из иерархии.
Мы используем ещё один скрипт тайловой кисти под названием PrefabBrush для создания кисти, способной рисовать префабы на слоях Tilemap.
Воспользуйтесь инспектором, чтобы задать параметру Prefabs Size PrefabBrush значение 1, а параметру Element 0 — значение ShootingTrap.
Создайте в Grid новый слой Tilemap под названием Traps и откройте окно Tile Palette.
Выберите обычную тайловую кисть (B) и воспользуйтесь раскрывающимся меню в нижней части окна Tile Palette для выбора PrefabBrush. Выберите в качестве слоя Active Tilemap Traps и используйте окно Scene для отрисовки нескольких префабов ловушек вдоль левой границы комнаты.
Разверните в иерархии GameObject Traps и поэкспериментируйте со значением Shoot Start Delay для каждого value on each Gameobject ShootingTrap с помощью скрипта Shooting Trap в инспекторе. Добавляйте к значению каждой ловушки по 0.25, т.е.:
Конечная цель
Цель этого мини-подземелья заключается в получении груды золота. Слава и богатство ждут тех, кто доберётся до неё, избежав смертельно опасных летающих лезвий.
Создайте в GameObject Grid новый слой Tilemap под названием Goal. Выберите Goal и измените значение Tilemap Renderer Order in Layer на 2:
Не закрывая окно Tile Palette, убедитесь, что всё ещё выбрана PrefabBrush. Сделайте так, чтобы Element 0 ссылался на заготовку Goal из папки /Assets/Prefabs проекта.
Воспользуйтесь стандартной тайловой кистью для отрисовки одного тайла-префаба цели в верхней части комнаты за ловушками:
Последние штрихи
Сейчас подземелье слишком светлое и свободное. Мы можем добавить ему стиля, переключившись на материал 2D-спрайта, способный реагировать на свет.
Выберите Sprite объектов Player, Goal и ShootingTrap, и сделайте так, чтобы Material компонента Sprite Renderer использовал SpriteLightingMaterial:
Это материал с прикреплённым к нему шейдером Sprite/Diffuse. Он позволяет освещению сцены воздействовать на спрайты.
В GameObject Grid выберите объекты BaseLayer, DungeonFloorDecoration, Collideable и Goal, а потом воспользуйтесь инспектором, чтобы тоже использовать в материале Tilemap Renderer Material SpriteLightingMaterial.
Затем выберите в GameObject Lights Directional light и снизьте значение Intensity Light до 0.3.
Также вы сейчас заметите, что Player носит с собой Point light, то есть светящий вокруг него Lantern.
Теперь, когда спрайты в сцене используют подходящий материал, освещение Unity влияет на все окружающие его спрайты.
Перетащите две копии префаба FlickerLight из папки /Assets/Prefabs проекта в Scene и разместите их в GameObject Lights.
Задайте первому префабу позицию (X:-11.25, Y:4, Z:-1.35), а второму — (X:2.75, Y:4, Z:-1.35).
Создайте новый слой Tilemap под названием WallsAndObjects и задайте в инспекторе Tilemap Renderer Order in Layer значение 15. Не забудьте, чтобы Material тоже использовал материал SpriteLightingMaterial.
Переключите кисть палитры тайлов обратно на Default Brush, а Active Tilemap на WallsAndObjects.
Воспользуйтесь инструментом brush (B) для отрисовки двух тайлов «света фонарей» под каждым из новых FlickerLight, которые мы разместили по углам начальной области:
Время трудностей
Посмотрим, сможем ли мы ещё больше улучшить подземелье. Используйте слой Tilemap WallsAndObjects для создания книжных шкафов в верхней части комнаты подземелья с помощью другой палитры тайлов под названием RoguelikeCustom. Также нарисуйте одну-две части стены с трещинами.
Вернитесь обратно в слой Tilemap DungeonFloorDecoration и добавьте ещё немного деталей на мраморный пол, например, трещины на нескольких тайлах:
Поздравляю, вы завершили свой первый мини-уровень подземелья! В результате у вас должно получиться нечто подобное:
Куда двигаться дальше?
Если вы пропустили какой-то шаг, то можете посмотреть на готовый результат этого туториала, открыв проект Unity Rayzor-final из скачиваемых материалов.
В этом туториале мы научились многому, но как и в любом другом деле, всегда есть что-то ещё!
Существуют интересные скрипты тайловых кистей, не рассмотренные в этом туториале. Прочитайте о них здесь и подумайте, сможете ли вы воспользоваться ими.
Также можете изучить создание анимированных тайлов здесь.