Czytaj książkę: «ГОНКА КАК КОНСТРУКТОР. Собери и опубликуй свою игру на Unity»
Table of Contents
Введение
Эта книга — о том, как собрать свою первую гоночную игру и довести её до публикации в браузере. Не «когда-нибудь, после трёх лет обучения», а за несколько вечеров — с работающим результатом, которым можно поделиться ссылкой.
Есть распространённый миф: чтобы сделать игру, нужно сначала выучить программирование «от и до», написать всё с нуля и только потом, может быть, увидеть результат. Индустрия так не работает. Веб-разработчики опираются на фреймворки, мобильные — на готовые SDK, а в геймдеве мы используем движки, плагины и шаблоны. Игра по своей сути — это конструктор: вы собираете проект из деталей — сцен, префабов, материалов, скриптов и интерфейса.
Поэтому мы пойдём не «с нуля», а от готового проекта. Перед вами Pursuit (на заставке она представляется полным именем Petrol Pursuit, но мы для краткости будем звать её просто Pursuit) — казуальная гонка: машина мчится по бесконечной дороге, топливо тает, дорогу приходится делить с грузовиками и автобусами, а на обочинах ждут спасительные канистры. В гараже — три машины на выбор, для смелых — сложный режим со встречным движением. Игра уже работает: откройте https://sciencepub.ru/carcasual/ и проедьте пару заездов прямо в браузере. Это цель, к которой мы пойдём, — только теперь вы будете не игроком, а автором.

Рис. В.1. Pursuit в браузере: машина, дорога, счётчики топлива и дистанции.
Чем эта книга отличается от видеокурсов и справочников? Тем, что мы разбираем настоящий, живой проект — целиком, до последнего скрипта. В Pursuit всего пятнадцать коротких скриптов и шесть сцен: этого достаточно, чтобы игра ощущалась настоящей, и достаточно мало, чтобы вы поняли каждую строчку. К концу книги в проекте не останется ни одного «чёрного ящика».
Эта книга продолжает линейку практикумов «…как конструктор»: в «Шутере как конструкторе» мы собирали космический шутер Deep Space, в «Ловце Драконов» — аркаду с нуля. Читать их перед этой книгой не обязательно: каждый практикум самодостаточен. Но если вы уже прошли один из них — здесь вы увидите знакомый путь на новом жанре, и это лучший способ закрепить навык.
Вот наш маршрут:
В первой главе — быстрый старт: установим Unity, откроем проект и запустим игру в редакторе. Уже через час у вас на экране будет собственная работающая гонка.
Во второй разберём «скелет» игры: шесть сцен и переходы между ними — меню, заезд, гараж, экран поражения. Познакомимся с менеджером игры и паузой.
В третьей сядем за руль: разберём контроллер машины — физику, управление, расход топлива и звук двигателя. Это сердце игры.
В четвёртой построим бесконечную дорогу: узнаем, как игра создаёт полотно трассы впереди машины и убирает его за спиной, не съедая память.
В пятой выпустим на трассу препятствия: случайные машины, взрывы при столкновении и «замирание» мира в момент аварии.
В шестой займёмся ресурсами и интерфейсом: канистры с топливом, счётчик дистанции и передача рекорда между сценами.
В седьмой откроем гараж: выбор из трёх машин — и первая серьёзная самостоятельная доработка проекта.
В восьмой посмотрим на игру глазами гейм-дизайнера: цель, ядро геймплея, уникальность, мини-диздок и баланс — то, что превращает «работает» в «интересно».
В девятой займёмся развитием: сложный режим со встречным движением, рекорды, новые механики — от нитро до полиции на хвосте — и разговор о монетизации.
В десятой впряжём в работу нейросети: как ставить задачи ИИ-ассистенту, проверять его код и отлаживать игру вдвоём с машиной — так, чтобы ускоряться, а не терять контроль.
В одиннадцатой — публикация: соберём браузерную WebGL-версию, разберём плагин публикации на Яндекс.Играх и посмотрим, как игра живёт на собственном сайте с рейтингом игроков.
Важно: все материалы книги — стартовые файлы проекта Pursuit — скачиваются бесплатно на сайте автора. Где именно и как — подробно написано в Приложении «Материалы к книге» в конце; короткая версия: откройте https://sciencepub.ru/carcasual/ и нажмите кнопку «Стартовые файлы проекта · бесплатно».
Совет: читайте эту книгу за компьютером с открытым Unity. Практикум работает только тогда, когда вы повторяете шаги руками: глазами код не выучивается.
Игра, которую мы разбираем, намеренно написана просто — так, как пишут первые проекты. Кое-где вы встретите решения, которые опытный разработчик сделал бы иначе, — и мы будем честно на них останавливаться: почему так написано, чем это грозит и как улучшить. Умение читать и дорабатывать чужой несовершенный код — это, пожалуй, самый востребованный навык программиста вообще.
Поехали!
Глава 1. Быстрый старт: Unity и первый запуск
Дорога начинается не с первого километра, а с поворота ключа.
Введение
Задача этой главы — максимально короткая: к её концу игра должна работать у вас на компьютере. Мы установим Unity, откроем проект Pursuit, дождёмся импорта и нажмём Play. Никакой теории сверх необходимого — всё интересное начнётся, когда перед глазами будет живая игра, которую можно «пощупать».
1.1 Устанавливаем Unity Hub и редактор
Unity ставится в два шага: сначала Unity Hub — небольшая программа-менеджер, которая управляет версиями редактора и списком проектов, затем через неё — сам редактор.
Скачайте Unity Hub с официального сайта https://unity.com/download и установите его.
Запустите Hub и войдите в аккаунт Unity (или создайте новый — это бесплатно). Для некоммерческого обучения и небольших проектов действует бесплатная лицензия Unity Personal.
Во вкладке Installs нажмите Install Editor и выберите версию ветки Unity 6 LTS — проект из этой книги собран на версии 6000.3.18f1. Подойдёт и более свежая версия 6000.3.x.
В списке модулей отметьте WebGL Build Support — он понадобится в одиннадцатой главе для сборки браузерной версии. Остальные модули можно не ставить.
Совет: установка редактора занимает несколько гигабайт и заметное время. Запустите её и налейте чаю — торопить этот процесс бесполезно.
1.2 Открываем проект
Проект Pursuit распространяется архивом (как вы его получили — по ссылке из книги или купив готовый проект — не важно, содержимое одно и то же).
Распакуйте архив в удобную папку. Надёжнее всего — путь без кириллицы и пробелов, например C:\Games\Pursuit или ~/Games/Pursuit.
В Unity Hub во вкладке Projects нажмите Add → Add project from disk и укажите распакованную папку. Правильная папка — та, внутри которой лежат Assets, Packages и ProjectSettings.
Кликните по проекту в списке. Первый запуск идёт несколько минут: Unity строит служебную папку Library и импортирует все ассеты. Это разовая операция — дальше проект будет открываться быстро.
Важно: папки Library, Temp и Logs — это кэш. Их не бывает в архиве, их не нужно копировать при переносе проекта и не нужно жалеть при удалении: Unity пересоздаст их автоматически.
1.3 Экскурсия по редактору

Рис. 1.1. Редактор Unity с проектом Pursuit: слева Hierarchy, в центре Scene с выделенной машиной, справа Inspector с её компонентами, внизу Project.
Первое впечатление от Unity у всех одинаковое: слишком много окон, и все чем-то заняты. Хорошая новость — рабочих окон всего пять, и каждое отвечает ровно на один вопрос. Пять минут ориентирования сейчас сэкономят вам полчаса растерянности потом.
Hierarchy (слева) — что есть в сцене. Обычный список всех объектов открытой сцены: камера, свет, дорога, машина, интерфейс. Треугольники слева от имён разворачивают вложенность: объекты в Unity живут семьями — родитель и дети.
Scene (в центре) — взгляд режиссёра. Свободная камера, которой можно облететь мир с любой стороны, выделить объект и подвинуть его мышкой. Здесь вы собираете сцену; игрок этого вида никогда не увидит.
Game (вкладка рядом со Scene) — взгляд игрока: ровно то, что видит игровая камера. Именно эта картинка появится в браузере или на телефоне.
Inspector (справа) — настройки выделенного. Выделили объект в Hierarchy — справа выпал список его компонентов и всех их полей. Здесь живут те самые значения, которые побеждают код: если в скрипте написано одно, а в инспекторе выставлено другое, работать будет инспектор. Мы будем возвращаться к этому правилу всю книгу.
Project (внизу) — все файлы игры: сцены, скрипты, модели, звуки, префабы. Это проводник по папке Assets, только внутри редактора.
Есть и шестое окно, которое стоит открыть сразу же: Console (Window → General → Console). Это место, куда игра жалуется — ошибки компиляции, предупреждения и ваши собственные сообщения. Пока в консоли красная строка, кнопка Play работать не будет, поэтому привычка заглядывать туда первым делом окупается с первой же ошибки.
Как летать в окне Scene. Колесо мыши — зум. Зажатая правая кнопка — поворот взгляда, и, не отпуская её, можно лететь клавишами WASD, как в шутере. Выделили объект и потеряли его из виду — нажмите F, камера подлетит вплотную. Alt с зажатой левой кнопкой — облёт выделенного по орбите. Всё, этого набора хватит на всю книгу.
Теперь — живой пример, чтобы почувствовать, как окна связаны между собой. Откройте в окне Project сцену Assets/Scenes/Game.unity.
В Hierarchy разверните объект GameWorld и кликните по newRoads. В Inspector появится компонент Road Controller — генератор дороги, а в нём числа: Road Length 14, Despawn Offset 30, Spawn Offset 42, Despawn Distance 56. Дорога в Pursuit бесконечная, и вот эти четыре значения задают, какими кусками она склеивается. В четвёртой главе мы разберём их по косточкам.
Разверните объект carPrefab и кликните по player_purple — это машина игрока. В инспекторе выстроился её «паспорт»: Rigidbody (физическое тело — масса, торможение, ограничения), Box Collider (невидимая коробка, которой машина касается мира), Car Controller (управление и топливо) и Obstacle (Script) (обработка аварии; да, имя странное — это отдельная история из пятой главы).
Нажмите F — окно Scene подлетит к машине. Переключитесь на вкладку Game и обратно на Scene: одна и та же сцена, два разных взгляда.
Вот и весь приём осмотра: выделил в Hierarchy — прочитал в Inspector. Им мы будем пользоваться в каждой главе, разбирая чужой проект по частям.
Совет: окна редактора можно таскать за заголовки, как вкладки браузера, — собирайте раскладку под себя, это нормально. Если в процессе всё разъехалось и половина окон потерялась, есть кнопка «как было»: Window → Layouts → Default. Ничего в проекте при этом не ломается — раскладка окон и содержимое игры никак не связаны.
Задание: откройте сцену Menu и найдите в Hierarchy объект ButtonPlay (внутри него — дочерний play, это просто надпись-картинка). Выделите ButtonPlay и найдите в инспекторе компонент Button, а в нём — блок On Click (). Пока просто посмотрите, что там лежит: во второй главе мы выясним, почему кнопка Play дёргает вовсе не тот скрипт, который можно было бы ожидать.
1.4 Что лежит в проекте
Теперь заглянем в окно Project поближе — это файловая система игры. Пройдёмся по главным папкам внутри Assets:
Scenes — шесть сцен игры, от меню до экрана поражения; их карту мы разберём во второй главе.
Scripts — вся логика игры: пятнадцать коротких скриптов на C#. К концу книги вы будете знать каждый из них в лицо.
Player, Road, Obstacle — префабы и модели машин, дороги и препятствий.
Meshtint Free City Lite Pack Mega Toon, 48 Particle Effect Pack, 2D Casual UI, Vehicle_Essentials — готовые наборы: модели города, эффекты взрывов, элементы интерфейса и большая библиотека автомобильных звуков (лежит про запас — в сценах пока звучит один carSound.wav из папки Audio). Типичный пример «конструкторного» подхода: готовые паки снимают вопрос арта, чтобы сосредоточиться на механике.
Audio — звуки: двигатель, меню, столкновение.
YandexGame и WebGLTemplates — плагин PluginYG для публикации на Яндекс.Играх; о нём поговорим в одиннадцатой главе.
Editor — служебный скрипт сборки WebGL (тоже дождётся одиннадцатой главы).
1.5 Первый заезд
Пора за руль.
В окне Project откройте Assets/Scenes/Menu.unity двойным кликом.
Нажмите кнопку Play над сценой. Появится главное меню игры (рис. 1.2).
Кнопка Play в меню запускает заезд. Управление — стрелки или WASD: вверх — газ, влево и вправо — перестроение между полосами. Следите за счётчиком топлива: он тает на ходу, а пополняется канистрами, которые появляются на дороге.
Врезались в машину или сожгли всё топливо — увидите экран Game Over с пройденной дистанцией. Это и есть ваш счёт.

Рис. 1.2. Главное меню Pursuit: Play, Garage, Options.
Задание: сыграйте три заезда и запишите лучшую дистанцию. А теперь загляните в гараж (кнопка Garage в меню) и попробуйте другую машину. Заодно найдите в игре сложный режим — и почувствуйте разницу.
Важно: остановить игру в редакторе — повторное нажатие Play. Пока игра запущена, кнопка подсвечена, а любые правки в сцене живут только до остановки — Unity откатит их. Это классическая ловушка новичка: полчаса настраивать сцену в режиме Play и потерять всё при остановке.
Выводы
Unity ставится через Unity Hub; для этой книги нужна ветка Unity 6 LTS и модуль WebGL Build Support.
Проект открывается через Add project from disk; правильная папка содержит Assets, Packages, ProjectSettings.
Library — пересоздаваемый кэш, его не копируют и не хранят в архивах.
Рабочих окон редактора всего пять: Hierarchy (что есть в сцене), Scene (взгляд режиссёра), Game (взгляд игрока), Inspector (настройки выделенного), Project (файлы игры); шестое — Console — открывается через Window → General → Console.
Вся игра — шесть сцен и пятнадцать скриптов в Assets/Scenes и Assets/Scripts; арт и эффекты — из готовых наборов.
Запуск и остановка игры в редакторе — кнопка Play; правки в режиме Play не сохраняются.
Итог: игра запускается у вас на компьютере — значит, инструмент настроен и можно разбираться, как всё устроено. В следующей главе мы посмотрим на «скелет» Pursuit: как шесть сцен связаны в единое целое и кто управляет переходами между ними.
Глава 2. Скелет игры: сцены и переходы
Игра — это не одна комната, а квартира. Главное — знать, где двери.
Введение
Прежде чем лезть в физику машины и генерацию дороги, разберёмся, из каких «комнат» состоит Pursuit и как игрок между ними перемещается. В Unity такие комнаты называются сценами: каждая — отдельный мир со своими объектами, камерой и интерфейсом. Понимание карты сцен — это половина понимания архитектуры любой небольшой игры.
2.1 Шесть сцен Pursuit
Откройте папку Assets/Scenes:
Menu — главное меню: кнопки Play, Garage, Options.
Game — обычный заезд: весь трафик едет попутно.
HardGame — сложный режим: к попутному трафику добавляется встречный.
Garage — выбор одной из трёх машин.
GameOver — экран поражения с пройденной дистанцией.
SampleScene — пустая сцена-заготовка из шаблона Unity; в игре не используется, её можно смело игнорировать (или удалить — это ни на что не повлияет).
Схема переходов проста: из меню — в заезд или гараж; из гаража — в заезд; из заезда — на экран поражения (или через паузу назад в меню); с экрана поражения — снова в меню. Круг замкнулся.
2.2 Кто переключает сцены: MenuController
За навигацию из меню отвечает крошечный скрипт MenuController.cs — он висит на объекте SceneManager сцены Menu:
using UnityEngine;
using UnityEngine.SceneManagement;
public class MenuController : MonoBehaviour
{
public void PlayGame()
{
SceneManager.LoadScene("Game"); // загружаем сцену "Game"
}
public void OpenGarage()
{
SceneManager.LoadScene("Garage"); // загружаем сцену "Garage"
}
public void OpenMenu()
{
SceneManager.LoadScene("Menu"); // загружаем сцену "Menu"
}
}
Вся магия — в одной строке: SceneManager.LoadScene("имя") выгружает текущую сцену и загружает названную. Обратите внимание: сцена вызывается по имени. Отсюда два практических следствия. Первое — имена сцен нельзя менять бездумно: строка "Game" в коде не узнает, что файл переименован. Второе — каждая сцена должна быть добавлена в список сборки (File → Build Profiles → Scene List), иначе при загрузке игра «не найдёт дверь».
А как кнопки узнают об этих методах? Выделите кнопку в сцене и посмотрите в инспекторе на блок On Click () компонента Button: туда перетаскивается объект с нужным скриптом, а в выпадающем списке выбирается метод. Это стандартный приём UI в Unity: кнопка — отдельно, логика — отдельно, связываются они в инспекторе.
И вот тут проект преподносит первый сюрприз — посмотрите внимательно, что именно подключено к кнопкам меню. Кнопка ButtonGarage честно вызывает MenuController.OpenGarage. А вот кнопка Play идёт в обход MenuController: в её On Click перетащен объект GarageManager (да, он есть и в сцене меню) с методом StartGame — тем самым, который мы разберём в главе про гараж: он проверяет, какая машина выбрана, и только потом загружает заезд. Метод MenuController.PlayGame(), который мы только что прочитали, не вызывается ниоткуда — это мёртвый код, оставшийся от ранней версии игры. А кнопка ButtonOptions и вовсе без обработчика: нажимайте сколько угодно — ничего не произойдёт. Такие «окаменелости» есть почти в любом реальном проекте; уметь их замечать — часть профессии.

Рис. 2.1. Инспектор кнопки ButtonPlay: в блоке On Click () вместо MenuController — GarageManager.StartGame.
Задание: добавьте в меню четвёртую кнопку — «Hard», — которая запускает сложный режим. Вам понадобится новый метод в MenuController с LoadScene("HardGame") и копия кнопки Play с новым обработчиком On Click — на этот раз подключите её к MenuController сами, как положено. А если хотите размяться — оживите заодно кнопку Options: пусть она хотя бы открывает гараж.
2.3 Менеджер игры и пауза
В сцене заезда живёт второй управляющий скрипт — GameManager.cs. Посмотрим на его ключевые части:
public class GameManager : MonoBehaviour
{
public static GameManager instance;
public Button PauseButton;
public Button ResumeButton;
public Button ExitButton;
private bool isPaused = false;
private void Awake()
{
instance = this;
}
public void PauseGame()
{
Time.timeScale = 0;
isPaused = true;
PauseButton.gameObject.SetActive(false);
ResumeButton.gameObject.SetActive(true);
}
public void ResumeGame()
{
Time.timeScale = 1;
isPaused = false;
PauseButton.gameObject.SetActive(true);
ResumeButton.gameObject.SetActive(false);
}
}
Здесь три важных приёма.
Синглтон. Строка public static GameManager instance и присваивание instance = this в Awake() — это упрощённый паттерн «одиночка»: любой скрипт игры может обратиться к менеджеру через GameManager.instance, не разыскивая его по сцене. Для маленькой игры — то, что нужно.
Пауза через время. Time.timeScale = 0 останавливает игровое время целиком: физика замирает, Time.deltaTime становится нулём, всё, что двигалось, — останавливается. timeScale = 1 — обычная скорость. Одна строка — и честная пауза готова.
Важно: Time.timeScale — глобальная настройка, она переживает загрузку сцен. Поэтому в методах перехода (GameOver(), ExitMenu()) менеджер обязательно возвращает timeScale = 1: забудете — и следующая сцена встретит вас «замороженной». Это реальная ошибка, на которую натыкается почти каждый новичок.
2.4 Кто связывает кнопки: GameUI
В проекте есть и второй способ связать кнопки с логикой — кодом. Для этого написан скрипт GameUI.cs:
public class GameUI : MonoBehaviour
{
private void Awake()
{
GameManager gameManager = GameManager.instance;
Transform gameUI = transform;
gameManager.PauseButton = gameUI.Find("Button_Pause").GetComponent<Button>();
gameManager.ResumeButton = gameUI.Find("Button_Resume").GetComponent<Button>();
gameManager.ExitButton = gameUI.Find("Button_Exit").GetComponent<Button>();
// Настроить обработчики событий для кнопок
gameManager.PauseButton.onClick.AddListener(gameManager.PauseGame);
gameManager.ResumeButton.onClick.AddListener(gameManager.ResumeGame);
gameManager.ExitButton.onClick.AddListener(gameManager.ExitMenu);
}
}
transform.Find("имя") ищет дочерний объект по имени, а onClick.AddListener(метод) подписывает метод на нажатие — то же самое, что блок On Click в инспекторе, только из кода. Кодовый способ удобнее, когда кнопок много или интерфейс создаётся на лету.
А теперь — вторая окаменелость этой главы, и она выразительнее первой. Проверьте, на каком объекте висит GameUI.cs. Ответ: ни на каком — во всём проекте нет ни одного объекта с этим компонентом. Кнопки паузы в сцене заезда называются Pause, Resume и ExitMenu и давно подключены к GameManager через инспектор, как и кнопки меню. А скрипт ищет объекты Button_Pause, Button_Resume, Button_Exit — таких имён в сцене больше нет: повесь его сейчас — он упадёт с NullReferenceException прямо в Awake. Судя по всему, автор начал с кодовой привязки, потом переподключил кнопки мышкой и переименовал их, а скрипт остался лежать. Урок тот же, что с PlayGame(): наличие кода в проекте ещё не значит, что он работает — проверяйте, кто и откуда его вызывает.
Совет: поиск по строковому имени (Find("Button_Pause")) хрупок так же, как загрузка сцены по имени: переименуете кнопку — получите ошибку уже во время игры, а не при компиляции. Привыкайте читать такие строки как «места повышенного риска» проекта.
Darmowy fragment się skończył.