Быть Бертрамом
Как прыгнуть из состояния лёжа
Если вы читали мои предыдущие статьи, то знаете, что я очень часто бросаю проекты неоконченными. Иногда доводя до какого-то «приемлемого» состояния, но чаще всего просто теряя к ним интерес, или переключаясь на что-то другое. Это качество не делает мне чести, и я решил попробовать немного побороться с ним. Для этого я выбрал то, чем я, вероятно, смогу заниматься относительно продолжительное время, и пообещал себе, что доведу проект до завершения. Такого, чтобы можно было сказать, что я им доволен.
Конечно, что-то подобное я уже делал на Ludum Dare, но там были люди, которые сделали то, что я не мог, не хотел, или не умел. Ну и сроки там, конечно, совсем другие. В этот раз мне точно придётся пройти несколько «волн» потери интереса и справиться с ними. В качестве дополнительного условия (сразу не скажешь, усложняющего или наоборот) я решил не реже раза в месяц писать большую статью. В этот раз, правда, на волне энтузиазма, материал на статью набрался всего лишь за три дня, так что, будем считать этот текст нулевым. Также я постараюсь выйти на новый уровень статей, подготавливая для каждой как можно больше материала. Это процесс итеративный, и так как именно вы потребляете этот вид контента, выражайте свои мысли на этот счёт.
У меня уже есть множество идей как по визуальному стилю, так и по механике игры. И, сразу оговорюсь, что я делаю игру для себя, а не для того чтобы продать или что-то подобное. Так что я не только буду использовать минимум готового материала (да, я планирую даже рисовать сам), но и буду ориентироваться на свои хотелки. Если у меня появятся тестеры (а с какого-то момента, я хочу начать писать на reddit) — будет чудесно, но я все-таки постараюсь делать не «чтобы было», а на совесть.
Почему я решил делать именно игру? Да, банально, но у этого решения есть два неоспоримых плюса. Во-первых, создание игры охватывает множество областей как программирования, так и применения творческой энергии, а значит есть шанс не утонуть в рутине. Во-вторых, что тоже немаловажно, у игр есть более или менее понятные критерии готовности. Расплывчатые, но хоть что-то. И, в-третьих, игра — это относительно универсально, почти каждый сможет хотя бы представить объём и сложность проделанной работы.
Шестерёнки
Пока я не взялся за графику и прочие визуальные штуки, а как показала практика, не стоит с них начинать, я использую готовые спрайты из набора Kenney Assets 2. Поэтому пока моим главным героем будет Берти.
Я решил снова обратиться к движку Unity, с болью в сердце покинув на некоторое время страну C++ и переметнуться на сторону богомерзкого C#. Не то чтобы это вызвало какие-то проблемы, но немного жаль. Пока у меня всё в голове не устоялось, не буду раскрывать все карты, но игра будет двухмерная, по сути «платформер». И с чего начинается наш буфет? Правильно, с управления.
Открой любую обучающую статью по Unity и тебе расскажут как сделать управление. Да чего там, каждый школьник сообразит, что достаточно проверять в цикле нажата ли кнопка, и двигать человечка левее или правее. Три строчки. Или нет?
Ещё до начала написания кода, я понимал, что, то, чего я хочу, быстро превратится в очень сложный и запутанный код, в котором я же и начну увязать. А я помимо прочего хочу сделать сложные системы боя и движения. Недавно я рассказывал, что писал систему клавиатурных сочетаний, основанную на последовательных нажатиях. Так вот, в игре всё будет куда сложнее, ведь там гораздо строже дело обстоит с задержками и куда сложнее и разнообразнее контекст. Так что я вспомнил когда-то давно прочитанную статью, про то, что файтинги основываются на конечных автоматах. У меня от этого термина канцелярит случается, так что я буду сбиваться на англицизмы.
Так вот. Основная идея в том, что персонаж в каждый момент времени находится в неком состоянии, которое отвечает за его поведение, а также за возможные изменения этого состояния. Иными словами, если Берти прыгнул и ещё летит, он не может побежать или прыгнуть ещё раз (или может? Читайте дальше). В моей схеме это означает, что состояния занимаются тем, что слушают нажатия кнопок и другие параметры героя, чтобы в нужный момент переключить состояние на какое-то другое. Сам же персонаж, а точнее контроллер движения, только «перемещает» изображение в нужные координаты. Здесь не верны как слово «перемещает», так и «изображение». Но об этом позже.
Следует также отметить, что состояния очень помогут мне, когда я займусь анимацией и звуками, так как именно «вход» в состояние — это то место, где начинается звук или очередная стадия анимации.
Признаюсь, я не стал изобретать велосипед, и взял готовую реализацию стейт-машины. Правда она всего порядка ста строчек и большая часть из них мне не нужна. Я уже немного изменил её и, вероятно, скоро от неё и вовсе ничего не останется. Так что я себя простил.
Попячемся
А пока начнём. И начнём с шагов вперёд-назад. Почему-то я захотел чтобы направление взгляда персонажа было важно, но при этом сам регулярно спотыкаюсь об это. Но тут или я буду делать автоматические развороты, либо сделаю механику сложнее и интереснее за счёт такой простой, но не всегда заметной мелочи. Так что у меня, как в файтингах, будут шаги «назад» и «вперед». Опять-таки, думая об анимации, это разумно.
Всем известно, что любая дорога начинается с одного шага. Поэтому зададим Бертраму состояние IDLE и займёмся шагами. На самом деле, сразу придется думать о будущем, чтобы не переписывать лишний раз. Что вы ожидаете, нажимая на кнопку направления? Логично, что то, чем мы управляем, сразу начнет двигаться. А мы держим кнопку, чтобы движение продолжалось. И все было бы просто, не реши я придумать себе сложностей. Например, я хочу перекаты. Это частый кейс: дважды быстро нажимаешь стрелку и персонаж делает рывок в указанную сторону. Обычно это что-то вроде кувырка, часто еще и защищающего от получаемого урона. Очень важный тактический приём, позволяющий рвать дистанцию и уклоняться от ударов. Поэтому мне и понадобились эти несколько технические состояния STEP и BACKSTEP. Они не только начинают движение (за этим следит контроллер, а не состояние), они также запускают таймер, по истечению которого, если кнопка не будет отжата, состояние сменится на WALK, то есть, непрерывное движение.
Если же произойдёт ещё одно нажатие, то мы переключаемся на соответственно DASH или BACKDASH. По сути, это тот же самый «шаг», но чуть дольше и безусловно переходящий в IDLE. Я заранее предусмотрел раздельные настройки скорости и длительности для разного направления рывков, чтобы можно было сделать их по-разному анимированными.
Направление в виде LEFT/RIGHT хранится в контроллере. Шаги запоминают направление, на которое произойдёт переключение при входе в состояние постоянного движения (WALK). Направление влияет на знак изменения координат, а также отрисовку спрайта и пригодится в будущем для определения того, спиной или лицом Берти стоит к стене.
Физика против
Механику движения мне пришлось переписывать раз, наверное, пять. Хотя первый вариант, всего лишь перемещал объект в указанные координаты, уже там было немножко физики. Всё дело в том, что я сразу рассматривал Бертрама как объект, лежащий на плоскости, а не висящий в воздухе. У нас был твёрдый пол, детектор столкновений и гравитация. Казалось, добавь движение вверх и вот у тебя прыжки.
Неа. Если пропустить всю боль, которую я испытал в процессе, то движение сейчас работает следующим образом: у меня есть «сила» и «импульс». Две эти переменные управляются контроллером, который меняет их в зависимости от текущего состояния (и активированных «способностей»). Сила задаёт вектор перемещения по земле и она действует всё время, пока активировано состояние движения. Импульс отвечает за прыжки (коих у меня аж три), и отрабатывает мгновенно (ну почти). Таким образом получается, что всё сложное, вроде препятствий, наклонных плоскостей, гравитации и так далее, я переложил на физический движок Unity. Он очень особенный, но в целом, значительно лучше всего, что сделал бы я.
Пришлось, правда, вбить пару костылей, но куда без этого. Например, инерция от движения, которую приходится гасить при окончании движения. Да ещё и гасить плавно, чтобы выглядело реалистично. А знали бы вы, сколько времени я не мог понять, почему Берти отскакивает от пола при приземлении! У материалов же стоит ноль в показателе «отскакиваемости»! Ан нет, этого не достаточно, ведь если сильно столкнуть два твёрдых предмета, они всё равно отскочат. Так что когда Берти касается земли, на него действует где-то две с половиной гравитации, чтобы не повадно было скакать. Скорее всего, большая часть этих костылей вызвана моим незнанием тонкостей движка, но так как всё равно ещё несколько раз всё это придётся переделать, пусть пока живёт так.
На самом деле прыжок состоит из трёх состояний: собственно JUMP, MIDAIR (когда взлёт ослабевает и начинается падение), и FALL ( падение ускоряется). Такое разделение не только позволяет добавлять новые движения в прыжке, но и более тонко управлять гравитацией, например, ускоряя падение. Если кратко, то схема выглядит так:
- Берти стоит на земле, нажата кнопка прыжка
- Переходим в состояние JUMP, задаем импульс, направленный вверх
- Ускорение замедляется до указанного порога, переходим в MIDAIR, ускоряем падение, так как стандартное очень медленное
- Начинаем быстро падать, переходим в состояние FALL
- Всё, мы на земле, переходим в IDLE (или WALK, если нажата кнопка движения)
Звучит просто и логично, не так ли? Ага, когда читаешь уже готовый вариант.
Его Воздушество
Следующие два «движения» я сделал отключаемыми, вероятнее всего, они не будут доступны игроку с самого начала. Это двойной прыжок и управление направлением падения. Первое я реализовал в виде «мгновенного» состояния, которое срабатывает на один кадр и задаёт дополнительный импульс наподобие прыжка. А вот с управляемым падением так не получилось, так как пришлось бы вводить некий флаг состояния, отвечающий за то, нажата ли кнопка движения и проверять его в контроллере. А я стараюсь избегать подобного связывания и ограничиваюсь только проверкой типа состояния, которая определяет только, является ли текущее состояние экземпляром того или иного класса. Поэтому я сделал связь в обратную сторону, то есть состояние дёргает метод контроллера, который добавляет силу движения, ровно как это делает перемещение по земле. Так как это именно придание силы, а не перемещение в координаты, Берти продолжает падать, хотя уже и отклоняясь от перпендикуляра.
Лорд Бертрам, между прочим, в молодости был ниндзя. На самом деле нет. Он выработал умение лазать по стенам, когда забирался по плющу в окно любимой. Так или иначе, пришлось делать ему ещё одну «умелку» — прилипание к стенам. Для определения стоит ли Берти на твёрдой земле я применяю отдельный маленький обработчик столкновений, расположенный у него под ногами. Таким же образом я нахожу и касания стен. Контроллер по сигналу детекторов определяет, куда сейчас смотрит персонаж и поднимает соответствующий флаг, например «лицом к стене». Если в падении поднимается этот флаг, то состояние меняется на WALLSTICK, в котором не только замедленно падение, но и снова возможно сделать прыжок или контролировать падение, даже если способности не активированы. Так как при этом ещё происходит разворот в сторону от стены, можно взобраться на высоту, если две стены расположены достаточно близко.
Первые впечатления
Вообще, разрабатывать игру очень весело, особенно те части, которые сразу видно. Прыгает — хорошо. Не прыгает — плохо. Но при этом ещё и баги сразу видно. Зачастую, очень забавные. Жаль идея снять «неудачные дубли» пришла уже в конце этой итерации, так что я записал буквально пару сценок. Ведь именно этим разработчик занят большую часть времени, «играет» в своё детище, так как в играх даже малейшие косяки управления сразу бросаются в глаза. Поэтому приходится тестировать, тестировать и ещё раз тестировать. И это при том, что скоро опять всё переделывать.
Дальше я подумываю наконец заняться графикой. То есть сделать спрайты для Берти, попробовать анимацию и уже начать делать всё остальное в рамках мира игры, а не абстрактно. Так что не переключайтесь!
P.S. При разработке прототипа не пострадал ни один глазастик, а также не было употреблено ни одного мануала или туториала.
P.P.S. Возможно, стиль этой статьи несколько отличается от обычных, так как я писал ее урывками. Более того, я пытался избегать слишком неправильных оборотов и слов, на которые бы ругался спеллчекер. Не факт, что это пошло на пользу. В «ё» и кавычках-лапках я несколько более уверен. Но это уже вам решать.