Итак, ранее мы написали вполне себе полноценный игровой движок (можете посмотреть тут) и создали на его основе уровень, в который можно даже играть. Однако, хотя играть в него можно - не слишком-то и интересно, ведь квадратики и, иногда, кружочки - не самое привлекательное визуальное оформление. Поэтому сегодня мы пойдём дальше и добавим немного графики.
Что будем делать
Хотя может показаться, что добавить графику - это довольно просто, есть несколько важных нюансов, на которые надо обратить. С технической точки зрения, HTML5 предоставляет всё, что нам необходимо для решения задачи, но реализация остаётся за мной.
Графика в играх
Обычно, вся графика в подобных играх хранится вместе в виде спрайтов: один или несколько файлов, на которых, с некоторыми отступами по краям, изображены все картинки, которые только могут потребоваться в игре: фрагменты карты, персонажи, игровые объекты и прочее.
Спрайт, если кто не знает, это небольшая картинка, изображающая некоторое состояние игрового объекта или кусочка карты. Например, персонаж двигается вправо.
Однако, когда персонаж движется он, вроде как, передвигает ногами и выглядит не статично. Это - анимация движения. Она достигается относительно легко: несколько спрайтов объединяются в цепочку и по кругу воспроизводятся, обеспечивая нужный эффект. Нетрудно догадаться, что чем больше кадров, тем плавнее анимация и "тяжелее" файл с графикой.
Во времена консолей типа NES (Dendy) видеопамять (память для хранения графики) была сильно ограничена, имела некоторый минимальный объём хранения и стоила дорого, поэтому изображения подгонялись под эти условия: графика была составной из уникальных кусочков. То есть движение персонажа влево и вправо занимало не из 4x4 ячейки памяти для движения вправо и 4x4 ячейки для движения влево (при условии, что размер 1 спрайта 4 ячейки), а 2 ячейки на верхюю половину тела и 2х4 ячейки для анимации движения шагов в одну сторону; для движения в другую сторону спрайты просто отображались зеркально. То есть 10 ячеек против 32 возможных.
Мы не столь сильно ограничены в памяти, хотя лично мне идея зеркального отображения некоторых спрайтов кажется интересной.
При реализации задумки, нам потребуется:
- Для статичных объектов (вроде стен и пола) - анимация, скорее всего, не понадобится, но предусмотреть её возможность следует: всё может поменяться.
- Для объектов карты: игровые персонажи должны иметь 3 или 4 набора спрайтов под анимацию движения (движения вверх, вниз, влево и вправо), 3 - если выбираем использовать зеркальное отражение; игровые объекты, вроде ключа, должны иметь спрайты для анимации, но минимум; герою потребуется ещё анимация гибели и возрождения.
- Предусмотреть возможность "случайных" анимаций (это эффект применяется к большому количеству однотипных объектов, например, стен; в произвольный момент времени случайные ячейки воспроизводят какой-либо эффект, скажем, отражают блик, что несколько оживляет весь процесс).
Технические аспекты анимации
Первое, что надо помнить, что анимация должна совпадать со скоростью перемещения по экрану. Если персонаж делает маленький шажок, а перемещается сильно (либо большой шаг, а перемещается совсем чуть-чуть) возникает ощущение несуразности и игра выглядит неубедительной.
Анимация, обычно, также привязывается к частоте обновления экрана. При минимальной частоте обновления 24 кадра в секунду нетрудно подсчитать, что изображение должно обновляться, хотя бы 1 раз в 41 милисекунду. Для 60-герцовых мониторов это примерно раз в 2-3 отрисовки, 6 - для 120 герцового. Как видите, знание частоты обновления экрана необходима для создания хорошего продукта. Я же не стремлюсь угодить всем, буду ориентироваться на 60-герцовые мониторы.
С точки зрения реализации, анимация - это заранее предопределённый массив кадров и счётчик кадров; с определённым интервалом счётчик увеличивается на 1 и, при достижении значения, выходящего за размер массива - сбрасывается в ноль (также он сбрасывается при смене анимации).
Любая анимация имеет продолжительность, а это значит, что обновление счётчика привязано ко времени: обновляется каждые несколько миллисекунд.
В современных средствах разработки, обычно, присутствует редактор анимаций, но мы-то всё делаем с нуля, поэтому, для настройки, придётся обойтись подручными методами.
Добавляем анимации
Для начала, давайте отладим процесс создания анимаций. Я создал небольшой файл со спрайтами (размером всего 670 байт) - на нём и буду тренироваться. Задача - управляя параметрами получить приемлемое воспроизведение.
Вторая задача (и она выглядит сложней) - это включить анимации в игру. И вот в чём тут причина: анимации имеют время выполнения, а события в игре обрабатываются мгновенно. Например, герой погиб - сразу оказался на точке возрождения. А теперь надо запустить анимацию гибели и только после этого перемещать персонажа. С технической точки зрения, это значит, что:
- Все параметры получают два состояния: текущее и следующее;
- Состояние должно изменяться либо по истечении некоторого времени, либо по достижению некоторого условия (скажем, завершение анимации);
- Требуется некоторый механизм, который позволит реализовать эту "переходную модель".
Первая задача выглядит проще, чем вторая, поэтому давайте с неё и начнём.
Механика анимации
Для создания и отладки анимаций, я создал небольшой редактор, который позволяет:
- Устанавливать время воспроизведения и сообщает, сколько милисекунд занимает каждый кадр;
- Просматривать все имеющиеся спрайты;
- Выбирать порядок воспроизведения спрайтов;
- Менять масштаб окна предпросмотра;
- Делать выгрузку настройки анимации.
Для проверки я создал небольшой файл - всего 670 байт со спрайтами и, параллельно, сделал такой же точно файл, но побольше. Код редактора получился небольшой и, будем честны, написан плохо, т.к. писался на скорую руку, поэтому я его выкладывать не буду. По крайней мере сейчас.
Получилось примерно так:
В конечном итоге можно констатировать, что редактор выглядит вполне себе работоспособным. Осталось только выгрузить данные - просто запишем всё в JSON и опубликуем.
Из настроек анимации видно, из какого файла надо вытащить спрайты, каков размер 1 спрайта и координаты верхнего левого угла каждого нового кадра. С этим можно работать и можно много чего добавить, но потом! А раз настройки анимации у нас есть, можно переходить к следующему этапу - внедрение анимации в игру.
Анимация в игре
Тут, как я и писал ранее, основная сложность состоит лишь в том, что игра создавалась как "моментальная", а теперь надо все состояния сделать "плавно изменяющимися". И вопрос возникает не только в том, как это реализовать, но и как это использовать, не теряя в качестве кода.
Для решения задачи, придётся учесть следующее:
- Требуется режим остановки игры - пока он выполняется, игра перестаёт полноценно работать - он необходим для отработки блокирующих анимаций, вроде начала игры или гибели героя;
- Требуются реализовать механизм "переходных" параметров - имеют текущее значение, новое значение, условие перехода и правило перехода (чаще всего это будет продолжительность какой-то анимации, поэтому для начала привязка будет ко времени).
Начнём с механизма плавного перехода. Он должен работать с числами и логическими величинами. Для чисел он должен иметь возможность плавного изменения величины и моментального.
Функция работает так: инициализируется начальным и конечным значением, временем окончания и функцией перехода (в ней можно написать алгоритм, высчитывающий переход). Функция будет выполняться до тех пор, пока не наступит время завершения. Иными словами, возвращаемое значение привязывается не столько к конечной величине, сколько к конечной временной метке. Надо её проверить: при запуске игры, наверное, будет некоторая заставка и управление будет блокироваться. Давайте её добавим.
Тестирование прошло успешно: затенение блокирует всех персонажей и позволяет не спеша провести перенос героя на точку возрождения. Хотя за это время функция перехода несколько изменилась.
Затенение с последующим "растенением" потребовало использовать большее количество интервалов... ну, я и переписал её.
Отлично, переходы работают. Теперь давайте добавим анимации персонажам.
Анимация главного героя
Сама по себе анимация - дело не сложное (как мы убедились ранее). Анимация для героя должна соответствовать тому, что он сейчас делает. Иными словами, требуется состояние героя и привязанная к ней анимация. Состояние героя должно включать следующее:
- Что сейчас делает герой (выбирает и, возможно, подстраивает нужную анимацию);
- На каком кадре анимации находится персонаж;
- Когда следует сменить кадр.
Подобно тому, как мы делали функцию плавного перехода - напишем функцию, которая будет обрабатывать, в соответствии с параметрами настройки, текущее отображение. Однако, тут есть особенности, которые до сих пор нигде не встречались. Возможность использования анимации предполагается в три этапа:
- Загружаются именованные файлы с графикой - это требуется для того, чтобы точно знать, что мы готовы рисовать всё, что надо;
- Для каждого объекта и ячейки на карте уже прописаны свои анимации или же параметры статичного изображения (например, для пола);
- В момент отрисовки - используется механизм извлечения нужного кадра.
А поскольку решение должно быть сделано в общем виде, то должно оно выглядеть примерно так: загрузка_ресурсов -> настройка_ресурсов -> использование_ресурсов. Это предполагает наличие двух наборов настроек. Но это уже дело техники.
В конечном итоге, получился такой код: если есть ресурсы для отрисовки состояния персонажа - он рисуется. Если нет - рисуется квадрат-заглушка.
Можно констатировать, что анимация работает вполне себе успешно. Теперь осталось только определить все состояния, в которых могут находиться все персонажи, определить уникальные состояния, заполнить таблицу выбора анимаций и нарисовать наконец все спрайты для игры.
Кроме того, по мере написания игры стало ясно, что вид с верху - весьма посредственный способ передавать игровую составляющую. Короче говоря, есть ещё над чем подумать.
Увеличение размера
Покуда, вся наша карта вполне себе успешно влезала на экран, но ведь очевидно, что это так себе решение: экраны у всех разные, да и не всегда нужно, чтобы 100% картинки было видно - где же интрига? Но главная причина, всё же, слишком мелко всё выглядит.
Итак, я изменил настройки, выбрав размер клетки 40 на 40 вместо 30 на 30.
И часть карты, сразу же, потерялась. Произошло это потому, что у меня отсутствуют какие-либо алгоритмы управления окном отрисовки. Но мы это сейчас исправим.
Окно просмотра
Это некоторый прямоугольник, внутри которого рисуется изображение. Мы будем реализовывать классическое решение, в котором персонаж всё время посередине экрана и лишь у краёв карты начинает перемещаться относительно угла экрана.
Данный код всегда делает так, чтобы персонаж был бы в центре экрана, а если размер карты меньше чем зона просмотра - то будет в центре экрана.
Как видите, центровать карту по экрану - совсем не так сложно. Я совершенно не менял код отрисовки, потому что всё равно существует внутренняя оптимизация браузера: что не попадает в зону отрисовки будет проигнорировано. И хотя прокрутку можно сделать более интересной, делать я этого пока не планирую; может потом.
Промежуточные итоги
В игру были добавлены некоторые дополнительные механики, которые сделали её, хочется надеяться, более привлекательной. Однажды я отрисую все необходимые спрайты и всё будет выглядеть более симпатично, но пока что я пытаюсь ответить на вопрос, как лучше сделать вид. Вид сверху даёт слишком мало простора, чтобы персонажи и окружение были бы привлекательными. А это немаловажно. Короче, буду думать, а после отрисую спрайты.
Ну а пока можете попробовать обновлённую версию. Поделитесь впечатлениями: что нравится, что раздражает.
Продолжение - тут.