Приветствую.
Я творческая личность под псевдонимом Anvoy Black. Одна из моих творческих деятельностей — разработка игр (game development). Этим делом я занимаюсь полтора года. Это очень интересная сфера и я достаточно много материала по ней изучил, благо английский мне известен хорошо.
В этом посте я хочу поделится с вами одним из своих проектов (игр) под названием One Lighter (разработка которого ещё ведётся). Конкретно – показать игру и поведать об особенностях разработки. Создаётся данная игра в известном игровом движке Unity.
One Lighter представляет собой игру, в которой вы должны играя за сферу добраться до финишной линии через различные препятствия. Чем быстрее доберётесь – тем лучше. Врезались в препятствие – всё по новой. Основная идея заключается в том, что игрок должен с помощью своей реакции добраться до финишной линии (Жёлтая Линия) как можно быстрее. В мечтах суть заключается в том, что игроки должны соревноваться между собой за наименьшее время прохождения игры. Но для этого должен быть leader board, который на данный момент я не могу реализовать (не умею).
Всё что вам дано - это двигать мышью вверх-вниз, влево-вправо. Влево-вправо - понятно, вы поворачиваетесь чтобы избежать столкновение с припятствием. Движение мышью вверх-вриз позволит вам снизить скорость в половину (или вернуть её).
Данный проект, кстати, является второй реализаций данной идеи. Первая реализация выглядит почти так же, но побогаче (примерно на 20%) в контенте. А вот код там настолько ужасен, что я решил переписать всё с 0.
Выглядит игра вот так:
Игровой процесс:
https://youtu.be/2___Ukt3Xig
(Вроде бы я не нашёл в правилах запрета на указание ссылок)
В качестве персонажа я модифицировал стандартный FirstPersonShooter. Ограничил углы обзора камеры. Прикрутил mouse sensitivity к своим переменным.
Персонажа я двигаю простой командой:
transform.Translate(vec * TotalSpeed * debugSpeed * Time.deltaTime);
где vec – это просто Vector3(0,0,1); (персонаж двигается в плюс координаты z)
Реализация. Во время второй реализации я решил подойти очень модульно к вопросу о генерации игрового пространства, что представляет собой платформы (я их называю roads) разной длины; на которых есть блоки (floorBlock); на которых спавнится пол (floor); на которых спавнятся препятствия (obstacles).
С названиями не очень удобно получилось. Но пока что так.
Выглядит это вот так:
По сути у меня есть класс Road, которому задана своя уникальная длина floorBlock`ов.
Класс FloorBlock же знает сколько у него floor в ширину и в длину (по умолчанию сейчас везде 5х5, но это можно регулировать). И он знает какой тип floor он должен спавнить, что позволит создавать различные floor, для различных целей, на различных дорогах. Ещё FloorBlock имеет BoxCollider, который нужен для того, чтобы рандомно заспавнить на нём obstacles, которые он так же знает, как и их количество на BoxCollider. Так же он знает свой road.
Класс Floor знает свою ширину и длину в координатах. А также свой GameObject. Благодаря всем этим данным можно без проблем вычислить где заканчивается road, чтобы сразу за ним заспавнить другой road.
Генерация мира. Получается, что для того чтобы заспавнить дорогу мне просто нужно инстанцировать пребаф Road. Когда он появится в мире (первый появляется на координатах [0,0,0]), он смотрит данные о том, сколько его длина в flooBlock’ов и спавнит их в соответствующих местах относительно себя. Инстанцирование происходит через IEnumerator, который инстанцирует каждый floorBlock раз в кадр. После этого, следующим IEnumerator спавнятся floor на floorBlock’е. И затем так же на floorBlock'е спавнятся obstacles.
К сожалению, obstacles спавнятся обычным Unity.Random, что подразумевает себе не самый лучший рандом (не знаю, как грамотно объяснить, я в случайностях практически не разбираюсь), который спавнит меньше obstacles к краю дорог. Собственно, по причине того, что я не разбираюсь в рандомах, пока что это работает так.
Чтобы препятствия не спавнились внутри друг друга я использую специальную проверку, которая отличается от первой реализации (потому что тогда я не знал об одном инструменте). Хочу поведовать сперва вам о том, как я в первой реализации делал проверку на занятость места спавна.
Во время первой реализации, зная не так много о возможностях и инструментах, я решил проверить есть ли рядом с точкой спавна obstacle другой obstacle с помощью RayCast. Конкретно – я создал префаб со скриптом, который при спавне создавал лучи в определённом направлении определённой длиной и проверял, врезался ли во что-либо луч. После этого он поворачивался на 15 градусов, и создавал луч вновь. Если он во что-то врезался, то возвращал неуспех в функцию спавна obstacles. Так же я создавал дебаг линии, чтобы была возможность лицезреть это извращение. Выглядело это так:
Работало это хреново. Не всегда.
Теперь же, я узнал о таком встроенном классе Physics. Который позволяет узнать есть ли в данной области (указанной вами) с определённым объёмом соприкосновение с каким-либо collider. Его я и использую для проверки занятости точки спавна obstacle.
Когда Road появляется, она говорит классу менеджеру дорог координату своего конца, пользуясь которой менеджер спавнит следующую дорогу в нужной точке. Если Road следующего id’а не существует, то заспавнить YellowLine. В моих планах YellowLine является не финишной чертой, но неким чекпоинтом важным. Финишной линией я хочу сделать RedLine. Почему? Просто отсылка.
Рекорд я записываю в классе GoalData. В нём есть 3 переменные: Time (текущее время езды), Length (сколько road пройдено) и List<float>, в который добавляется время на момент прохождение дороги. Индекс – является id’ом дороги. Что не очень безопасно.
Достаточно иметь 2 экземпляра этого класса на игроке, один из которых отвечает за текущие данные, а второй за лучшие (лучшее время); и можно легко сравнить побил ли ты свой рекорд.
Оптимизация. Когда игрок проезжает немногим меньше середины road, начинает спавнится следующий road.
За игроком есть невидимая стена, которая удаляет все obstacles, которые попали в неё через секунду. Так же road через полторы секунды самоуничтожается, если игрок заехал на следующую дорогу.
Визуальная часть. Сейчас она сильно недоработана, но конкретно визуально (текстуры и т.п.) особо вряд ли изменятся. Как и модели, вряд ли будут они особо сложнее. Но я обращаю внимание на следующие вещи:
Контраст. Я считаю, что в игре, основанной на скилле, реакции и рефлексах очень важна визуальная часть. Особенно контраст, который позволяет максимально быстро выявить силуэты препятствий. Для этого можно воспользоваться разными инструментами. Мне доступны лишь цвет, яркость, текстура, контраст с фоном. Текстуру я сделал самую простую в мире – квадрат с рамками. Это была моя первая текстура в жизни. Ну, если не первая, то для material точно первая. Поэтому всё не очень. Но во всяком случае эта текстура позволяет лучше выделить силуэт.
Ну и вообще всё должно быть чётко и плавно, без лишних эффектов влияющих на чёткость объектов и их дальность прорисовки.
Так же я сделал приятное и красивое меню с ярким и красочным лого.
Звуки. Я люблю баловаться в FL Studio, поэтому я обязательно пишу в нём музыку (пусть и простую, короткую, любительскую, недописанную) для своих игр. Так же звуковые эффекты некоторые в нём создаю. Для проекта One Lighter есть 2 недописанных (других и не бывает) мною трека, которые играют во время меню и непосредственно во время игры.
Чтобы приукрасить объезд препятствий, я решил добавить звук, который представляет собой нечто, похожее на то, что вы слышите, когда мимо вас быстро проезжает машина. Чем ближе вы проехали к препятствию – тем сочнее и писклявее звук, дабы прочувствоваться насколько близко было поражение, и насколько остры ваши реакции. В данный момент я ещё не до конца это реализовал. Не получается добиться желаемого результата.
Ну. Зачем данная игра? Я создаю её как прототип одной из своих идей. Так же приобрёл много опыта во время разработки. Можно посоревноваться с другом или просто поиграть ради фана. В мечтах (где есть работающий онлайн leader board, и забалансенный законченный контент) можно посоревноваться с миром.
На этом я поделился основными моментами разработки и своими мыслями. Это своеобразная первая попытка поделиться чем-то. Не уверен полезна ли вам данная информация и стоило ли мне расписывать в таком духе, но если вам понравилось и показалось полезным/интересным, то я рад. Так же буду рад любым комментариям.
Спасибо за прочтение.