Как сделать простую трехмерную игрушку на Unity за два дня
Самым графически впечатляющим проектом нашего хакатона был Skyeng Action. Когда автор накануне мероприятия презентовал свою идею, мало кто верил, что она будет доведена до рабочего состояния за двое суток. Тем не менее, команда из трех не знакомых до того ни друг с другом, ни с Unity разработчиков с задачей успешно справилась. Рассказываем, как это было.
Идея
Когда мы делаем наши приложения, мы почти всегда стараемся их геймифицировать, добавить какие-то элементы игры: достижения, призы, соревновательность. В данном случае была идея пойти от обратного: взять за основу именно игру, и уже в нее привносить элементы обучения.
Постепенно эта идея кристаллизовалась в концепцию 3D-игрушки, построенной на коротком игровом цикле длиной в минуту или несколько минут. Это не Far Cry, чтобы залипать часами, а возможность быстро где-то пробежаться, потренировать несколько слов и снова заниматься своими делами. Игрушка должна быть сетевой, один на один с человеком, а не с ботом. Трехмерный лабиринт, который можно тупо весь пробежать от начала до конца, а можно находить специальные калитки, позволяющие срезать, если правильно вспомнить слово. Соответственно, побеждает тот, кто быстрее и точнее вспоминает слова — ну, а при равном счете тот, кто быстрее «бегает» в виртуальном пространстве.
С учетом ограниченного времени хакатона было решено сделать логику и один уровень (с возможностью потом быстро добавлять новые); слова для демонстрационной версии зашить непосредственно в игру (чтобы не отвлекаться на посторонние задачи типа подключения API Skyeng); пока отказаться от какого-либо бэкенда и сделать непосредственное подключение игроков друг к другу по локальной сети, при котором один выступает в роли сервера, а второй — клиента.
Команда
В команду Skyeng Action вошли три человека: двое кодили, третий дизайнил уровни. Плюс четвертый «внештатный» 2D-дизайнер, который помог с оформлением менюшек.
Примечательно, что эти трое разработчиков до хакатона не только не были лично знакомы, но и практически не имели никакого опыта работы с инструментами, на которых все делалось. Автор идеи писал игрушки когда-то очень давно в школе, c Unity ковырялся пару дней на досуге и имел опыт программирования на Java, но не на C#; у второго разработчика был опыт работы с C#, но не игровой; ну, а дизайнер уровня вообще никогда ничем подобным не занимался (разработчик без опыта с Unity и C#) — он просто приехал на хакатон без команды и присоединился уже на месте.
Как следствие, процесс создания Skyeng Action оказался не менее увлекательным, чем результат: полноценная командная работа, выстроенная с нуля. Учитывая, что каждый занимался своей частью проекта, а сборка его произошла глубокой ночью накануне презентации, это было как минимум забавно. Коллеги, в два часа ночи наблюдавшие парящий в пустоте прямоугольник на одном мониторе, куски кода на втором и набор статичных картинок на третьем, не верили, что этот проект будет доведен до ума. Однако уже в три часа все заработало, и на презентации эти коллеги были немало удивлены.
Устройство
Написать 3D-игрушку за два дня на пустом месте — задача непосильная. К счастью, Unity содержит простой и понятный редактор уровней и огромное количество готовых ассетов, как в библиотеке стандартных компонентов, так и в «ассет сторе». Unity позволяет быстро сконструировать MVP, минимально работоспособную версию игры, используя готовые компоненты, не всегда идеальные, но по крайней мере показывающие логику процесса. Например, ту же дверь не так уж сложно сделать самостоятельно, но эта задача съела бы драгоценное время; был использован готовый объект, у которого варьируется геометрия, скорость и направление открывания, текстура. В итоге игровая логика и некоторые уникальные элементы были написаны самостоятельно, а окружение и взаимодействие объектов взяты из библиотеки (Nature Starter Kit 2, Character Pack: Free Sample, FirstPersonCharacter). Для сетевой игры также использовалось то, что было в Unity: клиент и сервер в одном приложении; на одном компьютере запускается сервер, который рассылает сообщение по локальной сети, на другом — клиент, который это сообщение ждет (встроенные компоненты NetworkDiscovery, NetworkManager, NetworkIdentity, NetworkTransform).
Тем не менее, работа над проектом не сводилась к перетаскиванию готовых ассетов в корзинку; было написано около тысячи строк кода. Одним из собственных объектов был планшет, прикрепленный к двери и показывающий задачку со словом.
Ниже — несколько фрагментов кода из компонента, который задавал его поведение (реализует передачу управления камере, не привязанной к игроку, ее подлет к планшету):
// playerCamera - камера, привязанная к игроку
// flyingCamera - камера, не привязанная к игроку
// source - исходный вектор направления камеры
// target - конечный вектор направления камеры
// positionSource - исходный вектор положения камеры
// positionTarget - конечный вектор положения камеры
// tablet - объект, к которому перемещаем камеру. В нашем случае - планшет на двери.
// spaceBeforeTablet - расстояние между конечным положением камеры и планшетом
float spaceBeforeTablet = 0.5f;
playerCamera.gameObject.SetActive(false);
flyingCamera.gameObject.SetActive(true);
Vector3 source = flyingCamera.transform.forward;
// Следующая строка задает направление от игрока перпендикулярно к передней поверхности объекта.
// У нас передняя поверхность объекта по осям не совпала с фактической передней поверхностью,
// на которой расположен "экран" со словом. Поэтому повернули вектор вокруг вертикальной оси.
// Если бы совпала - повернули бы на 180 градусов. Была бы направлена в другой бок - на -90.
Vector3 target = Quaternion.AngleAxis(90, Vector3.up) * tablet.transform.forward;
Vector3 positionSource = flyingCamera.transform.position;
Vector3 positionTarget = tablet.transform.position - target * spaceBeforeTablet;
StartCoroutine(MoveCamera(flyingCamera, source, target, positionSource, positionTarget, null));
Coroutine в Unity — это процесс, который может длиться больше одного кадра. Подлет камеры к планшету — один из таких процессов.
public IEnumerator MoveCamera(
Camera camera,
Vector3 source,
Vector3 target,
Vector3 positionSource,
Vector3 positionTarget,
Camera cameraToActivateInTheEnd)
{
float animationStart = Time.time;
float animationEnd = animationStart + cameraAnimationDuration;
while (Time.time < animationEnd)
{
float phase = (Time.time - animationStart) / cameraAnimationDuration;
camera.transform.forward = Vector3.Lerp(source, target, phase);
camera.transform.position = Vector3.Lerp(positionSource, positionTarget, phase);
yield return new WaitForEndOfFrame();
}
camera.transform.forward = target;
camera.transform.position = positionTarget;
if (cameraToActivateInTheEnd != null)
{
flyingCamera.gameObject.SetActive(false);
cameraToActivateInTheEnd.gameObject.SetActive(true);
}
}
Отлет обратно с переходом на камеру, привязанную к игроку:
Vector3 source = flyingCamera.transform.forward;
Vector3 target = playerCamera.transform.forward;
Vector3 positionSource = flyingCamera.transform.position;
Vector3 positionTarget = playerCamera.transform.position;
StartCoroutine(MoveCamera(flyingCamera, source, target, positionSource, positionTarget, playerCamera));
Результат
Подводя итоги, можно отметить следующее:
— участники команды познакомились с совершенно новыми инструментами, с которыми раньше не имели дела. Удалось вообще избежать ситуации «хакатон — это та же работа, только в лесу»;
— была продемонстрирована возможность быстрого создания прототипов трехмерных приложений и игр с помощью Unity и определенного энтузиазма;
— была доведена MVP-версия простой игрушки с полезным образовательным наполнением, которую можно достаточно легко развивать и поддерживать;
— был достигнут вау-эффект на презентации, поскольку никто не ждал, что за два дня можно добиться такой красочной картинки.
Ну, а главное — все это сделали люди, фактически познакомившиеся на хакатоне, и сумевшие быстро наладить командную работу.
И напоминаем, что мы постоянно в поиске крутых людей в нашу команду!