Доброго времени суток, пикабушники!
Я хотел бы рассказать про свой опыт создания мобильной android-игры на Unity под названием HellWorm. Из названия можно понять, что игра про червяка. Ползаем, кушаем монетки, не врезаемся в препятствия. Казалось бы, клон классической игры, на которой большинство из нас выросло. Но, на самом деле, параллель со змейкой на этом заканчивается.
Игра же позиционирует себя как бесконечный раннер, в котором идет постоянное движение вперед, без возможности свернуть с вертикального маршрута. А сам червь, при всем при этом, может как угодно извиваться (да-да, и даже проползать через себя). Вследствие чего, хотелось бы заострить внимание на трудностях, которые я испытал пытаясь реализовать движение столь незамысловатого персонажа.
Управление достаточно простое и описывается лишь одной фразой:
“Куда кликнешь, туда и ползет”.
Давайте рассмотрим первую реализацию алгоритма.
Суть движения такова: со скоростью n растет длина первого сегмента (головного) и с той же скоростью уменьшается длина последнего (хвостового), а в момент нажатия на экран создается новый сегмент. Соединение идет по центральным линиям прямоугольников. А на стыках, чтобы сгладить разрезы, размещены окружности радиусом равным ширине сегмента. Однако, этот способ, к сожалению, не подошел, т. к. визуальный стиль игры не подразумевает использование закругленных углов. Пришлось изобретать велосипед дальше, поэтому прошу ознакомиться со следующей реализацией.
Стыковка происходит не по центральным линиям, а по углам сегментов: по левому или по правому, где сторона выбирается в зависимости от того, по какую сторону от прямой AB лежит новая точка P.
(Bx — Ax) * (Py — Ay) — (By — Ay) * (Px — Ax), если выражение >0 то точка P лежит по левую сторону.
Как найти координату точки стыковки я расписывать не буду, т.к. ничего особенного в этом нет, простая геометрия. Лучше подробнее расскажу про сам подход.
Хочу сразу оговорить, что в данном случае максимальный угол между сегментами должен быть не более 90 градусов, иначе, как можно догадаться, углы вылезут наружу. Поэтому во время крутых (>90 градусов) разворотов происходит добавление переходного сегмента.
Когда игрок нажимает на экран, то запоминается угол, на который червь должен повернуть и как только головной сегмент достигает допустимой длины, то создается новый сегмент под заданным углом, при условии, что он не более 90 градусов, в противном случае создается переходный сегмент, а когда он достигает заданной длины, то происходит окончательный поворот. Данная стратегия позволяет избежать хаотичных искривлений при очень быстром нажатии по экрану, а также делает движение более естественным. Хотелось бы заметить, визуальных задержек при управлении совсем не ощущается.
На этом эксперименты закончились и продолжилась разработка игры: добавлены различные виды препятствий, генерация уровня, магазин со скинами, бонусный монетный режим при подборе черепка, смена цвета окружения в зависимости от пройденного пути и т.д. За это время взгляд прилично “замылился” и я просто не замечал осечки в своем алгоритме движения червя. А именно, небольших рывков при изменении направления движения. Как оказалось, все дело в стыковке сегментов по крайним точкам.
При создании нового сегмента данным способом нет возможности соединить центры сторон сегментов(красную и синюю точки на рисунке). А поскольку голова червя всегда привязана к центральной точке первого сегмента (а хвост привязан к центру края последнего), то происходит рывок при повороте, и чем больше угол разворота, тем сильнее заметен рывок. Конечно, на завершительных этапах разработки сталкиваться с подобным очень обидно, ведь нужно переделывать весь алгоритм. Но стремление закончить проект дошлифованным и без косяков взяло верх. Было решено вернуться к первой реализации алгоритма, т.к. вести расчет относительно ломаной сплошной линии, вокруг которой строится тело, гораздо проще и логичнее. Осталось решить проблему со стыками прямоугольников, а именно, каким образом их заполнить.
Немного поразмыслив, пришла идея продления сегментов навстречу друг другу для устранения зазора.
Решение оказалось вполне рабочим, и, на удивление, с первого раза все заработало. Математические шаги следующие:
1. определяем в какую сторону смотрит новый сегмент относительно предыдущего (в левую или в правую)
2. находим угловые точки B, C и точки на другом конце сегментов B’, C’
3. находим точку пересечения A прямых BB’ и CC’:
Ax = ((Bx * B'y — By * B'x) * (Cx — C'x) — (Bx — B'x) * (Cx * C'y — Cy * C'x)) /
((Bx — B'x) *(Cy — C'y) — (By — B'y) * (Cx — C'x))
Ay = ((Bx * B'y — By * B'x) * (Cy — C'y) — (By — B'y) * (Cx * C'y — Cy * C'x)) /
((Bx — B'x) * (Cy — C'y) — (By — B'y) * (Cx — C'x))
4. удлиняем сегменты на длины отрезков BA и CA соответственно
В целом, на этом разработка алгоритма для движения червя была успешно завершена. Отдельно хотелось бы заметить, что этот алгоритм можно использовать для создания червя или змеи со скругленными (реалистичными) краями при изгибах. Для этого достаточно задать максимальный угол разворота около 10-15 градусов, а также уменьшить допустимую длину сегмента для разворота. Результат изображен ниже:
Спасибо за внимание! Если аудиторию статья заинтересует, то я напишу другую часть, где подробнее расскажу про интересные нюансы разработки на Unity. А именно про игровой магазин, внутреигровые покупки, изменение цвета червя и всего окружения, механизм генерации препятствий и расположения монет и черепов вокруг них.
Буду рад ответить на любые вопросы!
Поиграть можно тут: HellWorm (Google Play)
Видео геймплея:
Ах, чуть не забыл, с Новым годом!