Игра же позиционирует себя как бесконечный раннер, в котором идет постоянное движение вперед, без возможности свернуть с вертикального маршрута. А сам червь, при всем при этом, может как угодно извиваться (да-да, и даже проползать через себя). Вследствие чего, хотелось бы заострить внимание на трудностях, которые я испытал пытаясь реализовать движение столь незамысловатого персонажа.
Управление достаточно простое и описывается лишь одной фразой:
“Куда кликнешь, туда и ползет”.
Первая реализация алгоритма движения наглядно продемонстрирована на схеме ниже.
Суть движения такова: со скоростью n растет длина первого сегмента (головного) и с той же скоростью уменьшается длина последнего (хвостового), а в момент нажатия на экран создается новый сегмент. Соединение идет по центральным линиям прямоугольников. А на стыках, чтобы сгладить разрезы, размещены окружности радиусом равным ширине сегмента. Однако, этот способ, к сожалению, не подошел, т. к. визуальный стиль игры не подразумевает использование закругленных углов. Пришлось изобретать велосипед дальше, поэтому прошу ознакомиться со следующей реализацией.
Стыковка происходит не по центральным линиям, а по углам сегментов: по левому или по правому, где сторона выбирается в зависимости от того, по какую сторону от прямой AB лежит новая точка P.
(Bx — Ax) * (Py — Ay) — (By — Ay) * (Px — Ax), если выражение >0 то точка P лежит по левую сторону.
Как найти координату точки стыковки я расписывать не буду, т.к. ничего особенного в этом нет, простая геометрия. Лучше подробнее расскажу про сам подход.
Хочу сразу оговорить, что в данном случае максимальный угол между сегментами должен быть не более 90 градусов, иначе, как можно догадаться, углы вылезут наружу. Поэтому во время крутых (>90 градусов) разворотов происходит добавление переходного сегмента.
Когда игрок нажимает на экран, то запоминается угол, на который червь должен повернуть и как только головной сегмент достигает допустимой длины, то создается новый сегмент под заданным углом, при условии, что он не более 90 градусов, в противном случае создается переходный сегмент, а когда он достигает заданной длины, то происходит окончательный поворот. Данная стратегия позволяет избежать хаотичных искривлений при очень быстром нажатии по экрану, а также делает движение более естественным. Хотелось бы заметить, визуальных задержек при управлении совсем не ощущается.
На этом эксперименты закончились и продолжилась разработка игры: добавлены различные виды препятствий, генерация уровня, магазин со скинами, бонусный монетный режим при подборе черепка, смена цвета окружения в зависимости от пройденного пути и т.д. За это время взгляд прилично “замылился” и я просто не замечал осечки в своем алгоритме движения червя. А именно, небольших рывков при изменении направления движения. Как оказалось, все дело в стыковке сегментов по крайним точкам.
При создании нового сегмента данным способом нет возможности соединить центры сторон сегментов(красную и синюю точки на рисунке). А поскольку голова червя всегда привязана к центральной точке первого сегмента (а хвост привязан к центру края последнего), то происходит рывок при повороте, и чем больше угол разворота, тем сильнее заметен рывок. Конечно, на завершительных этапах разработки сталкиваться с подобным очень обидно, ведь нужно переделывать весь алгоритм. Но стремление закончить проект дошлифованным и без косяков взяло верх. Было решено вернуться к первой реализации алгоритма, т.к. вести расчет относительно ломаной сплошной линии, вокруг которой строится тело, гораздо проще и логичнее. Осталось решить проблему со стыками прямоугольников, а именно, каким образом их заполнить.
Немного поразмыслив, пришла идея продления сегментов навстречу друг другу для устранения зазора.
Решение оказалось вполне рабочим, и, на удивление, с первого раза все заработало. Математические шаги следующие:
- определяем в какую сторону смотрит новый сегмент относительно предыдущего (в левую или в правую)
- находим угловые точки B, C и точки на другом конце сегментов B’, C’
- находим точку пересечения 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))
- удлиняем сегменты на длины отрезков BA и CA соответственно
В целом, на этом разработка алгоритма для движения червя была успешно завершена. Отдельно хотелось бы заметить, что этот алгоритм можно использовать для создания червя или змеи со скругленными (реалистичными) краями при изгибах. Для этого достаточно задать максимальный угол разворота около 10-15 градусов, а также уменьшить допустимую длину сегмента для разворота. Результат изображен ниже:
Вдобавок, пару слов хотелось бы рассказать о генерации окружающего мира. Все объекты сохранены в sprite sheet’ы в черно-белом виде. Белым цветом закрашены рамки, которые меняют расцветки во время игры, потому что изменяя цвет у Sprite Renderer все белые участки становятся именно выбранного цвета.
(боковые стенки с добавленным Polygon Collider 2D, и фоновое изображение)
Та же техника используется для игровых препятствий и главного персонажа.
На скриншоте с препятствиями можно заметить синие и желтые ромбики, это места возможного появления монетки (желтые) или черепка (синие). Они являются пустыми gameobject с выбранной иконкой.
Я выбрал такой подход, чтобы, во-первых, упростить задачу по добавлению монет/черепов на сцену (не заморачиваться с рандомной генерацией) и, во-вторых, чтобы они появлялись в интересных для геймплея местах. А, по-скольку, в игре около 70 разных туннелей, скал и проходов, то для игрока не так заметны предопределенные места.
Расскажу еще пару слов о черве, а именно его голове. У игрока есть возможность ее менять (как и прочие части тел), и, в том числе, цвет кожи и глаз.
Для меня было не очевидно как реализовать подобное. Поэтому появившееся решение, возможно, не самое грамотное, но рабочее.
Имеется prefab головы worm_head, в котором находятся все головы head+n, состоящие из объектов head (спрайт головы белого цвета) и eyes (спрайт с глазами, расположенный поверх головы, который также белого цвета). По умолчанию, все объекты head+n невидимы. Во время старта игры происходит проверка номера установленной головы, чтобы сделать объект head+n видимым. Вместе с этим применяется выбранная игроком цветовая схема: основной цвет задается для спрайта головы, тела, хвоста, а дополнительные цвета задаются для глаз и шипов.
head.GetComponent<SpriteRenderer>().color = headColor;
eyes.GetComponent<SpriteRenderer>().color = eyesColor;
На мой взгляд, про самые интересные и не очевидные моменты разработки я рассказал, но если у кого-то есть вопросы, буду рад на них ответить!
Спасибо за внимание. Поиграть в игру можно в Google Play.
P.S. Паблишера у игры нету, пытаемся раскрутиться своими силами.
Комментарии (20)
nightrain912
03.01.2017 15:12И в отдельной ветке:
Про скины к червю и их реализации. Есть более красивое решение, которое, к тому-же, аккуратнее кастомизируется и не подразумевает создание кучи объектов:
- Создаем новый класс WormSkins, наследуемый от ScriptableObject'а
- Прописываем атрибут CreateAssetMenuAttribute, чтобы его можно было в редакторе создать
- В WormSkins делаем SerializeField массивы для голов, хвостов и тд:
[SerializeField] Sprite[] heads;
- Делаем геттеры (точнее, функции-геттеры, т.к по индексу берем):
Sprite GetHead(int index)
- Создаем в ассетах экземпляр этого класса (пункт 2)
- Прописываем в нём все головы, хвосты и т.д.
- Оставляем ссылку на этот экземпляр в префабе червя, в том классе, где у вас сейчас кастомизация происходит (или как-то более красиво, тут много вариантов)
- Оставляем в черве только одну голову, один хвост и тд, без указанного спрайта
- При запуске берем индекс из пользовательских настроек, получаем спрайт из WormSkins и засовываем его в нужный SpriteRenderer
По идее, лучше использовать не индексы, а уникальные id, которые самостоятельно прописываются для спрайта (не знаю, насколько хорошо делать его текстовым или привязываться к имени самого спрайта). В этом случае скин игрока не поедет при добавлении нового элемента в начало.
ian_phobos
03.01.2017 15:24Я понял ваш ход мыслей, в принципе у меня так реализовано для хвостов и шипов.
А для голов проблема в том, что у них еще есть глаза как отдельный объект, которые размещены в разных местах относительно спрайта головы. Поэтому просто менять sprite у SpriteRenderer в данном случае не вариант. Приходится где-то хранить группу с головой и размещенными для нее глазами.nightrain912
03.01.2017 15:33Ну, тогда можно сделать каждую голову (уже с глазами) отдельным префабом, а в WormSkins хранить массив этих префабов. Идея в том, чтобы не хранить все головы в префабе, который будет создан на сцене.
ian_phobos
03.01.2017 15:16Рад, что понравилось :)
О сплайнах, к сожалению, не слышал. Но буду иметь в виду, спасибо!
На момент начала разработки хотелось скорее написать рабочий прототип, поэтому был выбран первый пришедший в голову подход.
Maklaud
03.01.2017 15:43Понравился стиль игры — простенький, но интересный, а что еще нужно?..
Какую монетизацию используете? Покупка игровых денег за реальные? Реклама?
Что делаете для продвижения? Похоже, обычных статей на хабре, постов на форуме уже становится недостаточно.
Успехов с игрой!ian_phobos
03.01.2017 15:54Приятно слышать, спасибо! :)
Сам играю на телефоне только в простые игры с короткой сессией, пока еду в метро.
Встроили рекламу в игру + видео за монетки (rewarded video). Стандартно можно отключить рекламу или купить монет за реальные деньги. Еще можно приобрести уникальный скин для червя.
Для продвижения был скомпонован пресс-кит на английском и русском языках. Два раза разослали в игровую прессу. Только 1 сайт написал полноценный обзор, который привлек около 50 игроков. Остальные просят денюжки или игнорируют из-за огромного кол-ва писем. Сегодня про игру написали на белорусском онлайнере, будем надеятся это поможет дальнейшей раскрутке.Kavabunga
08.01.2017 11:32Попробуйте выложить игру на форуме 4pda. Мне в прошлом году это принесло чуть меньше трех сотен установок за вечер. Но там специфическая аудитория — будьте готовы к читерам и паре-тройке единиц на сотню установок. Мне тогда читеры подпортили таблицу рекордов.
ian_phobos
08.01.2017 11:32Выложил в первый же день публикации игры. Увы, ни одной установки это не принесло.
Kavabunga
08.01.2017 23:02А в какой день недели/время публиковали? Возможно тема просто потерялась в недрах форума. У меня игрушка была гораздо слабее, но я видимо попал в струю. Еще через пару недель после апдейта приложения написал в теме и это принесло еще несколько десятков установок. Сейчас нашел тему, это был март 2015 года, может с тех пор форум перестал быть таким полезным.
afrokick
03.01.2017 16:51+2Еще нужно везение) Не в обиду автору, но таких игр куча, с короткой сессией, милой графикой и простым геймплеем. Сейчас качество продукта — не опциональный параметр, а по умолчанию. А вот чтобы игру заметили, нужен хороший пиар и везение. Удачи автору!
Tutanhomon
03.01.2017 17:52Что верно то верно. Не обязательно это крупные денежные вложения в рекламу. Но нужно попотеть, потыкаться везде, где только можно заявить о себе и о игре. Удачи, коллега!
ian_phobos
04.01.2017 11:05Абсолютно верно, именно этим мы и занимаемся. :) Пока деньги на рекламу и не пытаемся вкладывать, только после какого-либо успеха можно будет попробовать вложиться. Спасибо!
afrokick
05.01.2017 23:10У меня был опыт получения 100к установок (выход в топы20 по категории) без единого цента, лишь на ключевых запросах. Причем игра — готовый проект купленный за гроши(20$ вроде) + рекламная сетка от appodeal.
И вот вижу кучу прилаг в магазе — ну херь херью, клоны, а набирают по 1млн загрузок.
Вопрос напрашивается: куда катитьсямирстор?)
PS не в коем разе не призываю делать подделки на коленках
nightrain912
Спасибо за хорошую статью, их по unity3d не так уж много теперь.
Не думали использовать сплайны для создания тела червя? Ключевые точки все известны, можно построить Mesh используя их, а в unity3d есть удобный [AnimationCurve](https://docs.unity3d.com/ScriptReference/AnimationCurve.html, с помощью которого можно рассчитать сплайн.
ian_phobos
Рад, что понравилось :)
О сплайнах, к сожалению, не слышал. Но буду иметь в виду, спасибо!
На момент начала разработки хотелось скорее написать рабочий прототип, поэтому был выбран первый пришедший в голову подход.
vtulin
Сразили прямо в сердце!(
ian_phobos
Попробую вас утешить. Я вспомнил, что изучал сплайны, но, увы, забыл и даже не задумался о них при разработке. Хороший повод восстановить утраченные знания :)