C# для Unity: программирование игр с нуля

Курс знакомит с основами C# и их применением в Unity для создания игровых механик. Вы научитесь писать скрипты, работать с объектами сцены, физикой, UI и организовывать код проекта. Итогом станет небольшой законченный проект, который можно расширять дальше.

1. Введение в Unity и основы C# для скриптов

Введение в Unity и основы C# для скриптов

Unity — это игровой движок и редактор, в котором вы собираете сцену из объектов, настраиваете поведение и запускаете игру. В Unity поведение чаще всего задаётся скриптами на C#. Эта статья поможет вам уверенно начать: понять интерфейс Unity, основные термины и написать первые скрипты.

Что такое Unity и как в ней «устроена» игра

В Unity игра собирается из трёх ключевых сущностей:

  • Сцена — «уровень» или экран игры. Сцена хранит расположение объектов и их компоненты.
  • GameObject — объект в сцене (например, игрок, камера, кнопка UI). Сам по себе GameObject почти ничего не умеет.
  • Компонент — модуль поведения или данных, который добавляется на GameObject (например, Transform, Camera, Rigidbody, Collider, скрипт на C#).
  • Главная идея Unity:

  • GameObject — это «контейнер».
  • Компоненты — это «возможности».
  • Скрипт на C# чаще всего тоже является компонентом.
  • !Карта основных окон Unity и их назначение

    Основные окна Unity Editor

    В типичном проекте вы чаще всего используете такие окна:

  • Hierarchy — список GameObject текущей сцены.
  • Scene — редактирование сцены (расположение объектов).
  • Game — то, что «видит игрок» во время запуска.
  • Inspector — свойства выбранного объекта и его компонентов.
  • Project — файлы проекта (папка Assets и содержимое).
  • Console — сообщения, предупреждения и ошибки, включая Debug.Log.
  • Структура проекта и папка Assets

    Unity проект — это набор файлов, но вам важнее понимать логику папки Assets:

  • Assets — место, где лежат ресурсы игры: сцены, скрипты, модели, материалы, звуки.
  • Внутри Assets удобно сразу создавать папки, например:
  • - Scripts - Scenes - Materials - Prefabs

    Важно:

  • Unity «видит» и импортирует всё, что находится в Assets.
  • Скрипты на C# — это файлы с расширением .cs.
  • Официальная справка:

  • Unity Manual
  • Создание первого скрипта и подключение к объекту

    Типичный рабочий цикл:

  • В окне Project нажмите правой кнопкой мыши.
  • Выберите CreateC# Script.
  • Назовите файл (например, PlayerMover).
  • Перетащите скрипт на GameObject в Hierarchy или добавьте через Inspector кнопкой Add Component.
  • Правило именования, которое сэкономит время:

  • Имя файла скрипта и имя класса внутри файла должны совпадать.
  • Базовый шаблон скрипта Unity

    Когда вы создаёте C# Script, Unity генерирует класс. Чаще всего он наследуется от MonoBehaviour.

  • Класс — «чертёж» поведения.
  • MonoBehaviour — базовый класс Unity для скриптов-компонентов, которые можно повесить на GameObject.
  • Пример скрипта:

    Что здесь происходит:

  • using UnityEngine; подключает пространство имён Unity (чтобы были доступны MonoBehaviour, Debug.Log и другое).
  • public class HelloUnity : MonoBehaviour объявляет класс HelloUnity, который является компонентом Unity.
  • Start() — метод, который Unity вызывает автоматически при запуске сцены для активного объекта.
  • Debug.Log(...) печатает сообщение в Console.
  • > “Start is called before the first frame update.” — документация Unity > > MonoBehaviour.Start

    Жизненный цикл: когда выполняются методы скрипта

    Unity вызывает некоторые методы по имени автоматически. Самые важные на старте:

  • Start() — выполняется один раз перед первым кадром.
  • Update() — выполняется каждый кадр.
  • Пример:

    Что важно понимать:

  • Кадр — один цикл отрисовки игры. Если игра работает на 60 FPS, то Update() вызывается примерно 60 раз в секунду.
  • Логи в Update() быстро «засоряют» Console — это нормально для эксперимента, но в реальной игре так делают редко.
  • !Порядок основных методов, которые Unity вызывает автоматически

    Документация по Update():

  • MonoBehaviour.Update
  • Минимальные основы C# для Unity-скриптов

    Переменные и типы

    Переменная хранит значение. В C# у переменной есть тип.

    Частые типы в Unity:

  • int — целое число.
  • float — число с дробной частью.
  • booltrue или false.
  • string — текст.
  • Vector3 — позиция/направление в 3D (x, y, z).
  • Пример:

    Обратите внимание:

  • У float в C# обычно пишут суффикс f: 5.5f.
  • Vector3 относится к Unity и доступен через using UnityEngine;.
  • Методы

    Метод — это «действие».

  • void означает, что метод ничего не возвращает.
  • Метод может принимать параметры.
  • Пример метода:

    Условия и циклы

    Условия позволяют выполнять код по ситуации:

    Доступность полей: public и private

  • public — поле видно извне и, как правило, отображается в Inspector.
  • private — поле доступно только внутри класса.
  • Пример:

    Inspector: как «связывать» данные со сценой

    Одна из сильных сторон Unity — вы можете настраивать параметры скрипта без изменения кода.

    Публичные поля

    Если поле public, Unity обычно показывает его в Inspector:

    Вы можете менять speed в Inspector и сразу видеть эффект при запуске.

    Ссылки на другие объекты

    Частая задача — дать скрипту ссылку на другой объект (например, цель камеры).

    Что здесь важно:

  • Transform — компонент, который есть у каждого GameObject; он хранит позицию, поворот и масштаб.
  • transform — удобное свойство MonoBehaviour, которое даёт Transform текущего объекта.
  • Проверка if (target == null) защищает от ошибки, если вы забыли назначить ссылку в Inspector.
  • Первая практическая механика: движение объекта

    Сделаем самое базовое движение: объект будет ехать вправо с заданной скоростью.

  • Создайте 3D ObjectCube.
  • Создайте скрипт SimpleMover.
  • Повесьте скрипт на Cube.
  • Код:

    Разбор:

  • Vector3.right — направление вправо (1, 0, 0).
  • Time.deltaTime — время между текущим и предыдущим кадром.
  • Умножение на Time.deltaTime делает движение плавным и одинаковым на разных FPS.
  • Документация по Time.deltaTime:

  • Time.deltaTime
  • Как читать ошибки и использовать Console

    Когда что-то не работает, сначала смотрите в Console:

  • Красные сообщения — ошибки (код не запустится или будет ломаться).
  • Жёлтые — предупреждения (часто игра запускается, но есть риск проблемы).
  • Белые — логи Debug.Log.
  • Полезная привычка:

  • Если вы меняете код, а в Unity ничего не меняется — проверьте Console. Иногда проект не компилируется из-за одной ошибки.
  • Что дальше по курсу

    В этой статье вы:

  • Разобрались, что такое сцена, GameObject и компоненты.
  • Создали скрипт на C# и подключили его к объекту.
  • Узнали про Start() и Update().
  • Попрактиковались с переменными, ссылками и Inspector.
  • Дальше логичный шаг — научиться управлять объектом через ввод (клавиатура/мышь), работать с физикой (Rigidbody/Collider) и строить структуру кода так, чтобы её было легко расширять.

    Полезные официальные ресурсы для обучения:

  • Unity Learn
  • Документация C# (Microsoft)
  • 2. Компоненты, GameObject и жизненный цикл MonoBehaviour

    Компоненты, GameObject и жизненный цикл MonoBehaviour

    Unity устроена вокруг простой идеи: в сцене есть GameObject, а всё поведение и данные добавляются к ним компонентами. В прошлой статье вы уже создали скрипт, повесили его на объект и использовали Start() и Update(). Теперь разберём, что на самом деле происходит под капотом: как работает модель GameObject–Component и в каком порядке Unity вызывает методы MonoBehaviour.

    GameObject и Transform

    GameObject — это объект в сцене. Он:

  • имеет имя, тег, слой
  • может быть активным или неактивным
  • содержит список компонентов
  • У каждого GameObject всегда есть компонент Transform. Он хранит:

  • позицию
  • поворот
  • масштаб
  • связи в иерархии (родитель, дети)
  • transform в скрипте — это быстрый доступ к Transform текущего GameObject.

    Документация:

  • GameObject (Unity Scripting API)
  • Transform (Unity Scripting API)
  • Компоненты: данные и поведение

    Компонент — это модуль, который добавляет GameObject возможности. Примеры компонентов:

  • MeshRenderer — отображение меша
  • Collider — столкновения
  • Rigidbody — физика
  • AudioSource — звук
  • MonoBehaviour-скрипт — ваша логика
  • Идея композиции в Unity такая: вместо одного большого класса игрока вы собираете объект из компонентов. Например, игрок может состоять из:

  • CharacterController или Rigidbody
  • Collider
  • PlayerInput (скрипт)
  • Health (скрипт)
  • Weapon (скрипт)
  • Так код легче расширять: вы добавляете или убираете компоненты, не переписывая всю структуру.

    !Схема показывает, что GameObject является контейнером, а поведение реализуется компонентами

    Скрипт как компонент: MonoBehaviour

    Скрипты, которые вы добавляете на объекты через Inspector, чаще всего наследуются от MonoBehaviour. Это даёт два важных свойства:

  • Unity может вызывать методы этого класса автоматически (по имени)
  • объект может получать события жизненного цикла (включение, выключение, уничтожение, кадры и физика)
  • Документация:

  • MonoBehaviour (Unity Scripting API)
  • Жизненный цикл MonoBehaviour: какие методы и когда вызываются

    У Unity есть набор “магических” методов, которые вызываются автоматически, если они существуют в вашем скрипте.

    Ниже — практичный порядок, который нужно знать новичку. Он описывает типичное поведение (есть детали и исключения, но на старте это даст правильную картину).

    Awake

    Awake() вызывается, когда объект загружается (создан на сцене или создан через Instantiate).

    Используйте Awake() для:

  • первичной инициализации
  • получения ссылок на компоненты на этом же объекте (GetComponent)
  • Документация:

  • MonoBehaviour.Awake
  • OnEnable

    OnEnable() вызывается каждый раз, когда компонент становится активным:

  • объект включили SetActive(true)
  • компонент включили галочкой в Inspector (или через enabled = true)
  • Используйте OnEnable() для:

  • подписок на события
  • запуска логики, которая должна повторяться при повторном включении
  • Документация:

  • MonoBehaviour.OnEnable
  • Start

    Start() вызывается один раз перед первым кадром, если компонент активен.

    Часто Start() используют для:

  • инициализации, которой нужны уже существующие и активные объекты сцены
  • кода, который должен выполниться один раз при начале игры
  • Документация:

  • MonoBehaviour.Start
  • Update и LateUpdate

    Update() вызывается каждый кадр. Это место для:

  • чтения ввода
  • не-физического движения
  • таймеров
  • LateUpdate() тоже вызывается каждый кадр, но после Update(). Часто применяется для камеры: объект (игрок) уже обновился в Update(), и камера догоняет его в LateUpdate().

    Документация:

  • MonoBehaviour.Update
  • MonoBehaviour.LateUpdate
  • FixedUpdate (для физики)

    FixedUpdate() вызывается с фиксированным шагом по времени и связан с физическим движком.

    Используйте FixedUpdate() для:

  • работы с Rigidbody (например, AddForce, изменение скорости)
  • физики и движения, которое должно быть стабильным
  • Важно различать:

  • Update() зависит от FPS
  • FixedUpdate() стремится работать равномерно
  • Документация:

  • MonoBehaviour.FixedUpdate
  • OnDisable и OnDestroy

    OnDisable() вызывается, когда компонент или объект выключают.

    OnDestroy() вызывается, когда объект уничтожается (Destroy(gameObject)), либо когда сцена выгружается.

    Используйте их для:

  • отписок от событий
  • остановки корутин
  • освобождения временных ресурсов (если вы их создавали)
  • Документация:

  • MonoBehaviour.OnDisable
  • MonoBehaviour.OnDestroy
  • !Шпаргалка по основным методам и их порядку

    Активность GameObject и enabled у компонентов

    Есть два разных переключателя:

  • gameObject.SetActive(false) выключает весь объект и все его компоненты
  • myBehaviour.enabled = false выключает только конкретный компонент
  • Практическое следствие:

  • если объект неактивен, Update() и FixedUpdate() не вызываются
  • если компонент выключен, Update() и FixedUpdate() этого компонента тоже не вызываются
  • при включении/выключении срабатывают OnEnable() и OnDisable()
  • Документация:

  • GameObject.SetActive
  • Behaviour.enabled
  • Как компоненты находят друг друга: ссылки и GetComponent

    В Unity самый частый способ получить другой компонент на том же объекте — GetComponent<T>().

    Замечания:

  • GetComponent ищет компонент на этом же GameObject
  • вызов GetComponent относительно дорогой, поэтому часто ссылку кешируют в Awake()
  • Документация:

  • Component.GetComponent
  • Поиск в детях и родителях

    Иногда объект хранит нужный компонент в иерархии:

  • GetComponentInChildren<T>() — поиск в детях
  • GetComponentInParent<T>() — поиск в родителях
  • Эти методы удобны, но их тоже лучше не вызывать каждый кадр без необходимости.

    Документация:

  • Component.GetComponentInChildren
  • Component.GetComponentInParent
  • Как требовать обязательные компоненты: RequireComponent

    Если вашему скрипту обязательно нужен, например, Rigidbody, можно попросить Unity автоматически добавлять его при добавлении скрипта.

    Это снижает количество ошибок “забыл добавить Rigidbody”.

    Документация:

  • RequireComponent (Unity Scripting API)
  • Prefab как шаблон GameObject с компонентами

    Когда вы собрали GameObject из компонентов и настроили его в Inspector, обычно хочется переиспользовать его много раз. Для этого используют Prefab — сохранённый шаблон объекта.

    Что важно понимать новичку:

  • Prefab хранит состав компонентов и значения полей
  • экземпляры Prefab можно создавать на сцене вручную или через Instantiate
  • изменение Prefab может примениться ко всем экземплярам
  • Документация:

  • Prefabs (Unity Manual)
  • Практический пример: логирование жизненного цикла

    Сделайте простой скрипт, чтобы увидеть порядок вызовов в Console.

  • Создайте пустой объект GameObjectCreate Empty
  • Назовите его LifecycleTester
  • Добавьте скрипт LifecycleLogger
  • Запустите сцену, затем в Play Mode выключите/включите компонент (галочка слева от названия компонента)
  • Наблюдения, которые стоит закрепить:

  • Awake() и OnEnable() могут происходить до Start()
  • Start() срабатывает один раз
  • OnDisable() срабатывает при выключении компонента или объекта
  • Типичные ошибки новичков и как их избегать

  • Пытаться двигать Rigidbody в Update. Для физического объекта лучше применять силы или скорость в FixedUpdate().
  • Забывать назначить ссылки в Inspector. Если поле public Transform target; не назначено, добавляйте проверку if (target == null) return;.
  • Вызывать GetComponent в Update без причины. Обычно достаточно получить ссылку один раз в Awake().
  • Не понимать разницу между выключенным объектом и выключенным компонентом. Помните про SetActive и enabled.
  • Что дальше

    Теперь у вас есть правильная модель Unity: GameObject — контейнер, компоненты — функциональность, а MonoBehaviour получает события жизненного цикла.

    Следующий логичный шаг в курсе — научиться управлять объектами через ввод (клавиатура/мышь), а также осознанно выбирать, где писать код: Update() или FixedUpdate(), и как связывать объекты через Inspector и Prefab.

    3. Ввод игрока, перемещение и работа с камерой

    Ввод игрока, перемещение и работа с камерой

    В предыдущих статьях вы разобрались, что игра в Unity состоит из GameObject и компонентов, а скрипты на MonoBehaviour получают события жизненного цикла (Awake, Start, Update, FixedUpdate, LateUpdate). Теперь соберём из этого первую полноценную связку: считать ввод игрока, двигать персонажа и настроить камеру, чтобы она следовала за игроком.

    Цель статьи: вы должны уметь сделать управляемый объект и камеру, которая ведёт себя предсказуемо и плавно.

    Как Unity получает ввод

    В Unity есть два подхода к вводу:

  • Input Manager (класс Input) — простой и понятный способ для начала.
  • New Input System (пакет) — более современный и гибкий, но требует отдельной настройки.
  • В этой статье мы начнём с Input, потому что он позволяет быстро получить результат и сфокусироваться на логике движения.

    Документация:

  • Input (Unity Scripting API)
  • Виды ввода, которые вы будете использовать чаще всего

    Непрерывный ввод

    Непрерывный ввод подходит для движения и поворота: пока игрок держит клавишу — значение “активно”.

    Частые варианты:

  • Input.GetKey(KeyCode.W)кнопка удерживается.
  • Input.GetAxis("Horizontal") и Input.GetAxis("Vertical") — оси движения, которые дают значение в диапазоне примерно от -1 до 1.
  • Документация:

  • Input.GetKey
  • Input.GetAxis
  • “Нажатие в этот кадр”

    Это важно для прыжка, выстрела, открытия двери: действие должно сработать один раз.

  • Input.GetKeyDown(KeyCode.Space) — сработает только в кадре нажатия.
  • Документация:

  • Input.GetKeyDown
  • Перемещение без физики: движение через Transform

    Если ваш объект не должен взаимодействовать с физикой (не сталкивается реалистично, не падает, не отталкивается), можно двигать его напрямую через transform.position.

    Простейшее движение по WASD

  • Создайте объект игрока:
  • - GameObject3D ObjectCapsule - Назовите Player
  • Создайте скрипт PlayerMoverTransform и повесьте его на Player.
  • Что здесь важно:

  • Input.GetAxis("Horizontal") обычно привязан к A/D и стрелкам влево/вправо.
  • Input.GetAxis("Vertical") обычно привязан к W/S и стрелкам вверх/вниз.
  • Time.deltaTime делает скорость движения стабильной при разном FPS.
  • direction.Normalize() нужен, чтобы по диагонали персонаж не двигался быстрее. Без нормализации длина вектора при одновременных W и D будет больше.
  • Документация:

  • Time.deltaTime
  • Vector3.Normalize
  • Когда не стоит двигать через Transform

    Не двигайте через transform.position, если:

  • у объекта есть Rigidbody и вы хотите корректные столкновения
  • объект должен падать, скользить, отталкиваться
  • В таких случаях используйте физическое движение через Rigidbody и FixedUpdate().

    Перемещение с физикой: Rigidbody и FixedUpdate

    Если вы хотите, чтобы объект корректно сталкивался с миром, обычно добавляют:

  • Rigidbody
  • Collider (у капсулы он уже есть)
  • Настройка объекта игрока

  • Выберите Player.
  • Add Component → добавьте Rigidbody.
  • Часто для простого персонажа удобно заморозить вращение, чтобы капсула не падала набок:
  • - в Rigidbody включите ConstraintsFreeze Rotation X и Freeze Rotation Z.

    Документация:

  • Rigidbody (Unity Scripting API)
  • Движение через скорость Rigidbody

    Создайте скрипт PlayerMoverRigidbody:

    Пояснения:

  • Ввод читаем в Update(), потому что ввод обновляется “по кадрам”.
  • Физику меняем в FixedUpdate(), потому что физика работает фиксированными шагами.
  • Мы сохраняем текущую вертикальную составляющую скорости (velocity.y), чтобы гравитация продолжала работать.
  • Важное замечание про свойства скорости:

  • В некоторых версиях Unity используется Rigidbody.velocity, в новых версиях Unity 6 добавлено/используется Rigidbody.linearVelocity.
  • Если у вас linearVelocity не компилируется, замените на rb.velocity.
  • Документация:

  • MonoBehaviour.FixedUpdate
  • RequireComponent
  • !Диаграмма, куда помещать ввод, движение и камеру

    Поворот и направление движения относительно камеры

    Самая частая цель: W — идти вперёд относительно того, куда смотрит камера.

    Для этого нужно взять направления камеры на плоскости XZ:

  • “вперёд” камеры без вертикали
  • “вправо” камеры без вертикали
  • Пример для движения через Transform:

    Что важно:

  • Поле cameraTransform назначается в Inspector (перетащите туда Main Camera).
  • Quaternion.LookRotation(direction) разворачивает игрока по направлению движения.
  • Документация:

  • Transform.forward
  • Quaternion.LookRotation
  • Камера: следование за игроком

    Есть два популярных подхода:

  • простая камера-следователь (вы пишете небольшой скрипт)
  • Cinemachine (мощный инструмент, часто используемый в проектах)
  • Для старта сделаем простой вариант, чтобы вы поняли принципы.

    Почему камеру часто обновляют в LateUpdate

    Если игрок двигается в Update(), а камера тоже двигается в Update(), иногда появляются визуальные “подёргивания”: камера может обновиться до движения игрока или в неудобный момент.

    LateUpdate() вызывается после всех Update() в кадре, поэтому камера “догоняет” уже обновлённого игрока.

    Документация:

  • MonoBehaviour.LateUpdate
  • Простая камера с фиксированным смещением

  • Выберите Main Camera.
  • Создайте скрипт FollowCamera и повесьте на камеру.
  • Пояснения:

  • offset задаёт положение камеры относительно игрока.
  • LookAt поворачивает камеру на игрока.
  • Документация:

  • Transform.LookAt
  • Плавное следование (сглаживание)

    Чтобы камера двигалась мягче, используйте Vector3.Lerp.

    Что важно:

  • Чем больше followSpeed, тем быстрее камера догоняет цель.
  • Lerp здесь используется как “мягкое приближение” к нужной позиции.
  • Документация:

  • Vector3.Lerp
  • Частые проблемы новичков и как их исправлять

  • Игрок двигается быстрее по диагонали
  • - Причина: сумма осей даёт более длинный вектор. - Решение: Normalize() при длине больше 1.

  • Движение зависит от FPS
  • - Причина: забыли умножить на Time.deltaTime при движении через Transform. - Решение: всегда учитывайте Time.deltaTime для “кадрового” движения.

  • Дёргается камера
  • - Причина: камера обновляется не в тот момент относительно движения игрока. - Решение: следование камеры писать в LateUpdate().

  • Столкновения странные, объект “пролетает” сквозь стены
  • - Причина: двигаете физический объект через transform.position. - Решение: используйте Rigidbody и перемещение через физику (FixedUpdate, скорость/силы).

    Что дальше

    Теперь у вас есть базовый игровой цикл управления:

  • считываем ввод в Update()
  • двигаем объект либо через Transform (без физики), либо через Rigidbody (с физикой)
  • обновляем камеру в LateUpdate()
  • Дальше по курсу логично перейти к столкновениям и триггерам (Collider, OnCollisionEnter, OnTriggerEnter), а также к тому, как строить взаимодействия между объектами, не превращая код в набор случайных ссылок и GetComponent в каждом кадре.

    4. Физика, коллизии, триггеры и события взаимодействия

    Физика, коллизии, триггеры и события взаимодействия

    В прошлой статье вы научились считывать ввод в Update(), двигать персонажа через Transform или Rigidbody и обновлять камеру в LateUpdate(). Теперь добавим в игру физический мир: столкновения со стенами и полом, зоны подбора предметов, области урона и другие взаимодействия через события.

    Цель статьи: понять, как в Unity устроены Rigidbody, Collider, чем отличаются коллизии и триггеры, и как правильно обрабатывать события OnCollision... и OnTrigger....

    Как работает физика в Unity

    Unity использует физический движок для 3D-сцен (PhysX). Физика считается не каждый кадр, а с фиксированным шагом.

  • Update() вызывается каждый кадр и зависит от FPS.
  • FixedUpdate() вызывается с фиксированным интервалом и используется для физики.
  • Практическое правило:

  • Ввод и решения читать в Update().
  • Скорость, силы и другие физические действия применять в FixedUpdate().
  • Справка:

  • FixedUpdate (Unity Scripting API)
  • Time.fixedDeltaTime (Unity Scripting API)
  • Два ключевых компонента физики

    Collider

    Collider описывает форму для столкновений: коробка, сфера, капсула, сетка.

    Частые 3D-коллайдеры:

  • BoxCollider
  • SphereCollider
  • CapsuleCollider
  • MeshCollider
  • Справка:

  • Collider (Unity Scripting API)
  • Rigidbody

    Rigidbody делает объект физическим: на него действует гравитация, у него есть масса и скорость, он участвует в расчёте столкновений как динамическое тело.

    Справка:

  • Rigidbody (Unity Scripting API)
  • Что нужно, чтобы были столкновения и события

    Минимальные условия для 3D:

  • Чтобы объекты сталкивались, у них должны быть Collider.
  • Чтобы вы получали события OnCollision... или OnTrigger..., обычно нужно, чтобы хотя бы у одного из объектов был Rigidbody.
  • Важно: коллайдер без Rigidbody чаще всего считается статическим (как стена или пол). Два чисто статических коллайдера не будут генерировать события столкновения друг с другом.

    !Диаграмма различий коллизии и триггера и где обрабатывать логику

    Коллизии и триггеры: в чём разница

    Один и тот же Collider может работать в двух режимах:

  • Коллизия: объект физически блокирует другие объекты.
  • Триггер: объект не блокирует, но сообщает о пересечении.
  • Переключается это флажком Is Trigger в Inspector у коллайдера.

    | Поведение | Коллизия (isTrigger = false) | Триггер (isTrigger = true) | |---|---|---| | Блокирует движение | Да | Нет | | Реакция физики (отталкивание) | Да | Нет | | События | OnCollisionEnter/Stay/Exit | OnTriggerEnter/Stay/Exit | | Типичное применение | стены, пол, препятствия, физические предметы | зоны подбора, чекпоинты, области урона, детекторы |

    Справка:

  • Collider.isTrigger (Unity Scripting API)
  • События столкновений: OnCollisionEnter, OnCollisionStay, OnCollisionExit

    Эти методы вызываются, когда ваш объект участвует в реальном физическом контакте.

  • OnCollisionEnter(Collision collision) вызывается в момент начала столкновения.
  • OnCollisionStay(Collision collision) вызывается, пока контакт продолжается.
  • OnCollisionExit(Collision collision) вызывается, когда объекты перестали соприкасаться.
  • Справка:

  • OnCollisionEnter (Unity Scripting API)
  • Пример: печатаем, во что врезались

  • Создайте Plane как пол.
  • Создайте Cube, добавьте ему Rigidbody.
  • Повесьте скрипт CollisionLogger на Cube.
  • Полезные детали:

  • collision.gameObject это объект, с которым вы столкнулись.
  • Если вы хотите проверять тип объекта, чаще используют CompareTag.
  • Справка:

  • GameObject.CompareTag (Unity Scripting API)
  • События триггеров: OnTriggerEnter, OnTriggerStay, OnTriggerExit

    Триггеры не блокируют движение. Они нужны, чтобы сообщить: кто-то вошёл в область, вышел, находится внутри.

  • OnTriggerEnter(Collider other)
  • OnTriggerStay(Collider other)
  • OnTriggerExit(Collider other)
  • Справка:

  • OnTriggerEnter (Unity Scripting API)
  • Пример: предмет-подбор (Coin) через триггер

    Сделаем монетку, которая исчезает при входе игрока.

  • Создайте Sphere, назовите Coin.
  • Добавьте SphereCollider (скорее всего он уже есть) и включите Is Trigger.
  • Добавьте скрипт CoinPickup на Coin.
  • У игрока должен быть Collider, а для надёжности событий добавьте игроку Rigidbody.
  • Практичный вариант для игрока:

  • Rigidbody включён
  • Use Gravity включён
  • Constraints заморозить Rotation X и Rotation Z
  • Код монетки:

    Что важно:

  • Назначьте игроку тег Player.
  • Триггер лучше использовать для подбора и зон, потому что он не мешает движению.
  • Справка:

  • Object.Destroy (Unity Scripting API)
  • Как сделать взаимодействие удобнее: передача действия в игрока

    Частая архитектура: триггер не решает, что делать с игроком, а просто сообщает: вошёл игрок и вызывает метод на компоненте игрока.

    Пример: у игрока есть простой счётчик.

    Скрипт на игроке PlayerInventory:

    Скрипт на монете CoinPickupToInventory:

    Почему это удобно:

  • Монета не хранит глобальные данные.
  • Игрок управляет своей логикой сам.
  • Компоненты остаются маленькими и переиспользуемыми.
  • Справка:

  • Component.GetComponent (Unity Scripting API)
  • Физические настройки Rigidbody, которые влияют на поведение

    Use Gravity

    Если включено, объект падает.

    Is Kinematic

    Если включено:

  • объект не подчиняется силам и гравитации
  • объектом чаще управляют кодом напрямую
  • он может толкать другие тела, если вы двигаете его осмысленно
  • Справка:

  • Rigidbody.isKinematic (Unity Scripting API)
  • Constraints

    Обычно для персонажа замораживают вращение по X и Z, чтобы капсула не заваливалась.

    Справка:

  • RigidbodyConstraints (Unity Scripting API)
  • Слои (Layers) и матрица столкновений

    В игре быстро появляется много коллайдеров: игрок, враги, пули, триггер-зоны, предметы. Не все должны сталкиваться друг с другом.

    Для этого используют Layers и матрицу столкновений.

  • Назначьте объектам слои в Inspector.
  • Откройте настройки:
  • - Project SettingsPhysics - настройте, какие слои сталкиваются

    Справка:

  • Physics settings (Unity Manual)
  • Практические примеры:

  • Player сталкивается со World, но не сталкивается с PlayerTrigger.
  • Bullet сталкивается с Enemy, но не сталкивается с Player.
  • Physics Material: трение и упругость

    Physic Material задаёт физические свойства контакта:

  • трение (friction)
  • упругость (bounciness)
  • Это удобно для льда, резиновых платформ и поверхностей с разным сцеплением.

    Справка:

  • PhysicMaterial (Unity Scripting API)
  • Быстрые объекты и "пролёт" сквозь стены

    Если объект движется очень быстро, он может "проскочить" сквозь тонкий коллайдер между шагами физики. Это называют туннелированием.

    Один из способов уменьшить проблему:

  • в Rigidbody изменить Collision Detection на Continuous или Continuous Dynamic (подходит для быстрых тел, например пуль)
  • Справка:

  • Rigidbody.collisionDetectionMode (Unity Scripting API)
  • Важное про 2D и 3D

    Физика 2D и 3D в Unity разделены.

  • Для 3D используйте Rigidbody, Collider, Physics, OnCollisionEnter, OnTriggerEnter.
  • Для 2D используйте Rigidbody2D, Collider2D, Physics2D, OnCollisionEnter2D, OnTriggerEnter2D.
  • Справка:

  • Rigidbody2D (Unity Scripting API)
  • Частые ошибки новичков и как их избежать

  • Двигаете объект с Rigidbody через transform.position
  • - Решение: двигайте через Rigidbody в FixedUpdate().

  • Триггер не срабатывает
  • - Решение: проверьте, что у обоих объектов есть Collider, и хотя бы у одного есть Rigidbody.

  • События срабатывают, но не на том объекте
  • - Решение: убедитесь, что скрипт висит на объекте с коллайдером, который участвует в контакте.

  • Проверяете тип объекта по имени
  • - Решение: используйте Tag и CompareTag.

    Что дальше

    Теперь у вас есть полноценная основа игрового взаимодействия:

  • физические тела через Rigidbody
  • столкновения через Collider
  • зоны и сенсоры через Is Trigger
  • события OnCollision... и OnTrigger...
  • Следующий шаг обычно такой: научиться делать конкретные игровые механики на этой базе, например прыжок и проверку земли, урон при столкновениях, простые враги и проектилы, а также освоить Raycast для более точных проверок.

    5. Данные и архитектура: классы, интерфейсы, ScriptableObjects, события

    Данные и архитектура: классы, интерфейсы, ScriptableObjects, события

    На прошлых шагах курса вы научились управлять объектом, двигать его через Transform или Rigidbody, а также реагировать на столкновения и триггеры через OnCollision... и OnTrigger.... Теперь появляется типичная проблема: механики начинают «расползаться» по скриптам, а связи между объектами превращаются в хаос из ссылок и GetComponent.

    В этой статье вы соберёте базовый набор архитектурных инструментов Unity:

  • классы как способ организовать логику и данные
  • интерфейсы как способ отвязать системы друг от друга
  • ScriptableObject как способ хранить и переиспользовать данные и «каналы событий»
  • события как способ строить взаимодействия без жёстких ссылок
  • !Карта того, как данные и события помогают уменьшить связность между объектами

    Главная цель архитектуры: меньше связности, больше переиспользования

    В Unity очень легко сделать «работает прямо сейчас», и очень легко получить код, который:

  • трудно расширять (каждое изменение ломает другое)
  • трудно тестировать и искать ошибки
  • нельзя переиспользовать (скрипт жёстко привязан к конкретным объектам)
  • Практичный ориентир для новичка:

  • Данные храните отдельно от поведения, если они должны переиспользоваться и настраиваться.
  • Поведение делайте маленькими компонентами.
  • Связи между системами делайте через интерфейсы и события, а не через «прямые» ссылки на конкретные классы.
  • Классы в Unity: не всё должно быть MonoBehaviour

    В Unity есть два частых типа ваших классов:

  • MonoBehaviour — компонент на GameObject, участвует в жизненном цикле (Awake, Start, Update, OnTriggerEnter и т.д.).
  • обычный C# класс — не компонент, не висит на объекте, создаётся через new, удобен для чистой логики и данных.
  • Когда использовать обычный класс

    Используйте обычный класс, когда:

  • логика не зависит от сцены и объектов
  • нужно хранить состояние, которое удобно контролировать
  • хочется избежать лишних компонентов
  • Пример: класс здоровья без Unity-магии.

    Такой класс можно «встроить» в компонент на игроке.

    Что оставлять в MonoBehaviour

    В MonoBehaviour обычно остаётся то, что связано с Unity:

  • ссылки на Transform, Rigidbody, Animator
  • обработка Update и FixedUpdate
  • реакции на триггеры и коллизии
  • доступ к объектам сцены
  • Интерфейсы: общий контракт вместо зависимости от конкретного класса

    Интерфейс — это «контракт»: он описывает, что объект умеет, но не говорит, как именно он это делает.

    Документация:

  • Интерфейсы в C# (Microsoft)
  • Зачем интерфейсы в Unity

    Типичная ситуация из прошлой статьи про триггеры:

  • пуля (или ловушка) должна наносить урон
  • но урон могут получать игрок, враг, бочка, дверь
  • Если вы напишете GetComponent<PlayerHealth>(), то пуля начнёт «знать» про игрока. Потом добавится враг — придётся переписывать.

    Правильнее: пуля ищет не конкретный класс, а интерфейс.

    Пример: IDamageable

    Игрок реализует интерфейс:

    Ловушка наносит урон любому, кто поддерживает интерфейс:

    Плюсы:

  • ловушка не знает, кто именно вошёл
  • вы добавляете новых получателей урона без изменения ловушки
  • Минус:

  • нужно следить, чтобы нужный компонент реально был на объекте
  • ScriptableObject: данные как ассеты, а не как поля на сцене

    ScriptableObject — это Unity-объект, который хранится как ассет в проекте (в папке Assets) и не привязан к конкретной сцене.

    Документация:

  • ScriptableObject (Unity Scripting API)
  • CreateAssetMenuAttribute (Unity Scripting API)
  • Зачем ScriptableObject новичку

    ScriptableObject полезен, когда вам нужно:

  • хранить конфиги баланса (скорости, урон, стоимость)
  • описывать предметы (имя, иконка, цена)
  • переиспользовать одни и те же данные в разных сценах
  • централизованно менять значения без поиска по сценам
  • Пример: описание предмета (ItemDefinition)

    Как использовать:

  • создайте ассет: ProjectCreateGameItem Definition
  • заполните поля в Inspector
  • на монетке или предмете храните ссылку на этот ассет
  • Важное отличие от MonoBehaviour

    | Критерий | MonoBehaviour | ScriptableObject | |---|---|---| | Где живёт | на GameObject в сцене или Prefab | как ассет в Project | | Есть Update() | да (если написать) | нет | | Хранит настройки | да, но обычно привязаны к сцене/префабу | да, удобно переиспользовать | | Для чего лучше | поведение и связь с Unity-сценой | данные, конфиги, общие ресурсы |

    Практическое правило: скрипт движения игрока — это MonoBehaviour, а настройки скорости могут быть в ScriptableObject, если вы хотите легко переиспользовать и балансить.

    События: связываем системы без прямых ссылок

    В Unity часто хочется, чтобы одно действие запускало реакцию в разных местах:

  • подобрали монету → обновить UI, проиграть звук, сохранить прогресс
  • игрок умер → показать экран смерти, остановить управление, записать статистику
  • Если делать это прямыми ссылками, получается «ком» зависимостей.

    События решают задачу так:

  • отправитель сообщает, что что-то произошло
  • подписчики сами решают, как реагировать
  • Два популярных варианта событий

    | Вариант | Где используется | Плюсы | Минусы | |---|---|---|---| | C# событие event / Action | в коде | быстро, типобезопасно, не требует Inspector | не настраивается в Inspector | | UnityEvent | в компонентах | можно настраивать вызовы через Inspector | меньше контроля на уровне кода, сложнее типизация |

    Документация:

  • UnityEvent (Unity Scripting API)
  • Пример: UnityEvent для «подобрано»

    Это удобно, когда вы хотите связать реакцию без кода: например, перетащить AudioSource и выбрать метод Play().

    Пример: C# событие для чистой логики

    Если вы делаете систему счёта:

    UI подписывается:

    Ключевая привычка: подписки делайте в OnEnable(), отписки в OnDisable(). Так объект безопасно переживает выключение/включение.

    ScriptableObject Event Channel: события между объектами и сценами

    Иногда нужно, чтобы событие не было привязано к конкретному объекту на сцене. Например:

  • на сценах разные UI, но все хотят реагировать на «монета подобрана»
  • отправитель и подписчик могут появляться/исчезать
  • Один из практичных паттернов в Unity — Event Channel на ScriptableObject:

  • создаётся ассет-канал
  • отправитель вызывает Raise(...)
  • подписчики слушают событие у ассета
  • !Как один ассет-событие связывает много систем

    Канал события с параметром (int)

    Отправитель (монета) не ищет UI:

    Подписчик (например, система счёта) слушает канал:

    Преимущество: монета и система счёта не знают друг о друге.

    Важная дисциплина:

  • ассеты ScriptableObject живут между запусками Play Mode и сценами
  • избегайте хранить в них «временное состояние» (например, текущие очки) без явной логики сброса
  • Практические правила для начинающего проекта

  • Компоненты делайте маленькими: один компонент — одна задача.
  • Не тащите ссылки «на всё»: если компоненту нужна только реакция, лучше событие.
  • Интерфейс — для взаимодействия на месте: когда вы столкнулись/вошли в триггер и хотите «вызвать способность» объекта.
  • Событие — для оповещения: когда нужно сообщить сразу нескольким системам.
  • ScriptableObject — для общих данных и связующих ассетов: конфиги, описания, event channels.
  • Типичные ошибки и как их избегать

  • ScriptableObject хранит текущее HP/score и оно не сбрасывается
  • - Решение: текущее состояние храните в компонентах или в отдельном классе, а ScriptableObject используйте как конфиг.

  • Подписались на событие и забыли отписаться
  • - Решение: подписка в OnEnable(), отписка в OnDisable().

  • Интерфейс не находится через GetComponent<IDamageable>()
  • - Причина: нужный компонент не на том же GameObject. - Решение: проверяйте структуру объекта, при необходимости используйте GetComponentInParent или переносите компонент.

  • Система зависит от конкретного класса вместо интерфейса
  • - Решение: формулируйте «что нужно сделать» (контракт) и выносите в интерфейс.

    Что дальше

    Теперь у вас есть базовый набор инструментов, чтобы строить проект аккуратно:

  • обычные классы для чистой логики
  • интерфейсы для взаимодействий без жёсткой привязки
  • ScriptableObject для данных и каналов
  • события для слабосвязанных систем
  • Следующий логичный шаг — применить это к более игровым задачам: оружие и стрельба, урон и здоровье, простые враги, спавн объектов и UI. Именно там хорошая архитектура начинает экономить больше всего времени.

    6. UI и игровой цикл: Canvas, HUD, меню, сохранения

    UI и игровой цикл: Canvas, HUD, меню, сохранения

    Когда у вас уже есть управление игроком, физика, взаимодействия через триггеры и базовая архитектура (классы, интерфейсы, ScriptableObject и события), следующий шаг, который превращает технодемо в игру — это UI и управление игровым циклом.

    В этой статье вы соберёте практический набор:

  • UI в Unity: Canvas, элементы UI, обработка кликов
  • HUD: здоровье, счёт, подсказки
  • Меню: главное меню, пауза, экран поражения
  • Игровой цикл: состояния игры и кто ими управляет
  • Сохранения: PlayerPrefs и сохранение в файл JSON
  • !Структура типичного UI: Canvas и EventSystem, панели HUD и меню

    UI в Unity: из чего он состоит

    В классическом UI Unity (uGUI) обычно участвуют такие объекты:

  • Canvas: корневой контейнер, который рисует UI
  • RectTransform: специальный Transform для UI (позиция и размеры в 2D)
  • EventSystem: система событий, которая обрабатывает клики, наведение, выбор
  • GraphicRaycaster: компонент на Canvas, который позволяет кликам попадать в UI-элементы
  • Полезные ссылки:

  • Canvas (Unity Manual)
  • Event System (Unity Manual)
  • UI system (Unity Manual)
  • Режимы Canvas

    У Canvas есть Render Mode, который влияет на то, как UI размещается в мире:

  • Screen Space - Overlay: UI рисуется поверх всего, проще всего для HUD и меню
  • Screen Space - Camera: UI привязан к камере (нужна ссылка на камеру)
  • World Space: UI как объект в 3D-мире (например, полоска HP над врагом)
  • Для первых проектов почти всегда достаточно Screen Space - Overlay.

    Масштабирование под разные экраны

    Чтобы UI не разваливался на разных разрешениях, на Canvas обычно добавляют Canvas Scaler:

  • UI Scale Mode: Scale With Screen Size
  • Reference Resolution: например 1920x1080
  • Ссылка:

  • Canvas Scaler (Unity Manual)
  • Якоря (Anchors) в RectTransform

    Anchors определяют, к какой части экрана “приклеен” элемент. Это основа адаптивного UI:

  • HP слева сверху: якорь в левом верхнем углу
  • счёт справа сверху: якорь в правом верхнем углу
  • кнопка паузы сверху по центру: якорь сверху по центру
  • Если якоря выставлены неправильно, UI будет “плавать” при смене разрешения.

    HUD: отображаем здоровье, счёт и подсказки

    HUD обычно должен:

  • быстро обновляться
  • не зависеть от конкретных сценовых ссылок “на всё подряд”
  • обновляться по событиям, а не каждую рамку в Update()
  • Почему не стоит обновлять UI в Update

    Если вы пишете в Update() что-то вроде scoreText.text = score.ToString(), вы:

  • делаете лишнюю работу каждый кадр
  • усложняете отладку
  • Лучше: обновлять UI только когда данные изменились (через события). Это напрямую связано с прошлой статьёй про event и слабую связность.

    Пример данных: счёт с событием

    Пример UI-контроллера HUD (TextMeshPro)

    Для текста в UI рекомендуется TextMeshPro.

    Ссылка:

  • TextMeshPro (Unity Manual)
  • Практические правила:

  • Ссылки назначайте через Inspector ([SerializeField]), а не через Find.
  • Подписка в OnEnable(), отписка в OnDisable().
  • Меню: панели, кнопки, переходы между экранами

    Типичный набор экранов:

  • Main Menu: Start, Settings, Quit
  • Pause Menu: Resume, Restart, Exit to Main Menu
  • Game Over: Retry, Exit
  • Часто это реализуют как набор панелей (UI Panels) внутри одного Canvas:

  • PanelMainMenu
  • PanelPause
  • PanelGameOver
  • PanelHud
  • Вместо уничтожения и создания UI проще включать и выключать панели.

    Обработка кнопок

    Кнопка UI обычно вызывает публичный метод компонента через Inspector:

  • Select Button
  • в Inspector у Button найдите On Click()
  • добавьте объект со скриптом
  • выберите нужный метод
  • Ссылка:

  • Button (Unity Manual)
  • Пример MenuController: переключаем панели

    Игровой цикл: состояния игры и GameManager

    Игровой цикл в контексте Unity-проекта — это ответ на вопросы:

  • когда игрок может управлять персонажем
  • когда игра на паузе
  • что происходит при смерти игрока
  • как начинается новый забег
  • Самый понятный инструмент для старта — машина состояний через enum.

    !Состояния игры и переходы между ними

    Пример GameManager со временем и меню

    Для паузы часто используют Time.timeScale:

  • Time.timeScale = 1f нормальная игра
  • Time.timeScale = 0f пауза (останавливает физику и Update()-время, но UI продолжает работать)
  • Ссылка:

  • Time.timeScale (Unity Scripting API)
  • Важно:

  • Если вы используете корутины, часть из них зависит от “игрового времени” и может остановиться при Time.timeScale = 0.
  • Для UI-анимаций на паузе часто используют время “реального мира” (Time.unscaledDeltaTime) или анимации UI, которые не зависят от физики.
  • Ссылка:

  • Time.unscaledDeltaTime (Unity Scripting API)
  • Связь со вводом

    Пауза обычно включается по Escape:

    Этот подход согласуется с прошлой статьёй:

  • ввод читаем в Update()
  • физику двигаем в FixedUpdate()
  • UI переключаем событиями и включением панелей
  • Переходы между сценами: главное меню и игра

    Если вы делаете отдельную сцену MainMenu и отдельную сцену Game, вам нужен SceneManager.

    Ссылка:

  • SceneManager (Unity Scripting API)
  • Пример простого загрузчика:

    Ссылки:

  • Application.Quit (Unity Scripting API)
  • Практичный подход для новичка:

  • держите MainMenu отдельной сценой
  • в Game держите UI HUD и Pause
  • убедитесь, что сцены добавлены в Build Settings
  • Ссылка:

  • Build Settings (Unity Manual)
  • Сохранения: что и как сохранять

    Сохранение — это не про “сохранить сцену”. Обычно сохраняют данные игрока:

  • очки, монеты, прогресс
  • выбранный персонаж
  • настройки (громкость, чувствительность)
  • Два базовых инструмента

    | Инструмент | Для чего подходит | Плюсы | Минусы | |---|---|---|---| | PlayerPrefs | мелкие настройки и простые числа | очень просто | не для сложных структур, не для больших данных | | JSON-файл в persistentDataPath | прогресс, инвентарь, статистика | хранит структуры, переносимо | нужно писать код сериализации |

    Ссылки:

  • PlayerPrefs (Unity Scripting API)
  • Application.persistentDataPath (Unity Scripting API)
  • JsonUtility (Unity Scripting API)
  • PlayerPrefs: быстрый пример

    Сохранение прогресса в JSON

    Главная идея:

  • описываем структуру данных SaveData
  • преобразуем её в JSON строку через JsonUtility.ToJson
  • пишем в файл в Application.persistentDataPath
  • Пример данных сохранения:

    Пример системы сохранения:

    Ссылка по File:

  • File (Microsoft .NET)
  • Пример интеграции: обновляем bestScore при GameOver

    Практический совет:

  • Сохраняйте в моменты “событий”: конец уровня, смерть игрока, выход в меню.
  • Не сохраняйте каждую секунду без причины.
  • Как связать UI, игру и сохранения без жёстких зависимостей

    Из прошлой статьи вам уже знакомы варианты:

  • интерфейсы для “взаимодействия на месте” (например, IDamageable)
  • события для “оповещения” нескольких систем
  • ScriptableObject для конфигов и event channel
  • Пример хорошей связки для UI:

  • система очков вызывает score.Changed
  • HUD подписывается и обновляет текст
  • ProgressManager на GameOver сохраняет bestScore
  • экран GameOver показывает Score и BestScore
  • Это позволяет:

  • менять UI без переписывания логики
  • менять механику очков без переписывания UI
  • Типичные проблемы UI и игрового цикла

  • UI не реагирует на клики
  • - Проверьте, что в сцене есть EventSystem и на Canvas есть GraphicRaycaster.

  • UI уехал на другом разрешении
  • - Проверьте якоря в RectTransform и настройку Canvas Scaler.

  • Пауза остановила всё, включая нужные анимации
  • - Используйте Time.unscaledDeltaTime там, где нужно обновляться “в реальном времени”.

  • Сохранение “ломается” на устройстве
  • - Не пишите в произвольные пути, используйте Application.persistentDataPath.

    Что дальше

    Теперь у вас есть полный контур “игры как продукта”:

  • HUD для отображения состояния
  • меню для управления
  • GameManager для игрового цикла
  • сохранения для прогресса
  • Следующий логичный шаг после UI и цикла — собирать законченные механики (оружие, враги, спавн, UI-индикаторы, прогресс) и постепенно выносить повторяющиеся вещи в переиспользуемые компоненты и данные (ScriptableObject-конфиги, event channels).

    7. Сборка мини-проекта, отладка, оптимизация и публикация

    Сборка мини-проекта, отладка, оптимизация и публикация

    На предыдущих этапах курса вы собрали базовые механики: ввод, перемещение, камера, физика и взаимодействия, структуру кода через классы, интерфейсы и события, а также UI, игровые состояния и сохранения. Теперь важно сделать последний шаг, который превращает набор скриптов в мини-проект, готовый к показу другим: собрать сцену, отладить ошибки, найти узкие места производительности и сделать сборку под нужную платформу.

    Цель статьи: довести небольшой прототип до состояния стабильно запускается, предсказуемо работает, не тормозит на типичных сценах, собирается в билд и готов к публикации.

    !Общая карта пути от прототипа до публикации

    Мини-проект как цель

    Чтобы закрыть цикл разработки, выберите простую, но законченную идею. Хорошие форматы для новичка:

  • Сбор монет с таймером: набрать максимум очков за 60 секунд.
  • Арена на выживание: игрок живёт, пока не кончится здоровье.
  • Мини-раннер: движение вперёд, препятствия, счёт.
  • Минимальный состав, который уже ощущается как игра:

  • Игровой цикл: старт, игра, пауза, поражение.
  • HUD: счёт, здоровье, подсказка управления.
  • Победа или поражение: хотя бы одно чёткое условие.
  • Сохранение прогресса: например, best score.
  • Сборка сцены: быстрый чеклист

    Перед тем как отлаживать и оптимизировать, убедитесь, что проект структурирован. Это сильно ускоряет работу.

    Структура в Project

  • Scenes (одна или две сцены: MainMenu, Game)
  • Scripts
  • Prefabs
  • ScriptableObjects (конфиги и event channels)
  • UI
  • Структура в Hierarchy (на сцене Game)

    Типичный рабочий вариант:

  • GameManager (управляет состояниями, паузой, переходами)
  • Player (движение, коллайдеры, здоровье)
  • Spawners (если вы спавните монеты или врагов)
  • UI (Canvas, HUD, Pause, GameOver)
  • Environment (пол, стены, декорации)
  • Практический совет: старайтесь, чтобы зависимости были явными.

  • Ссылки назначайте через Inspector ([SerializeField]).
  • Событийные связи делайте через event или ScriptableObject event channel (из прошлой архитектурной статьи).
  • Отладка: как искать проблемы системно

    Console и логи

    Базовый порядок действий при любой проблеме:

  • Проверьте окно Console.
  • Откройте самый верхний красный лог.
  • Дважды кликните по строке со скриптом в stack trace.
  • Исправьте причину, а не симптом.
  • Полезные инструменты документации:

  • Unity Console
  • Debug.Log
  • Практика логирования:

  • Не логируйте каждую рамку в Update() без нужды.
  • Для важного состояния логируйте события: старт игры, смерть, подбор предмета, смена состояния.
  • Breakpoints и пошаговая отладка

    Для логических ошибок удобнее не Debug.Log, а дебаггер IDE.

  • Откройте скрипт в Visual Studio или Rider.
  • Поставьте breakpoint (точку останова).
  • Запустите Play Mode.
  • Выполните действие, которое приводит к ошибке.
  • Посмотрите значения переменных и порядок вызовов.
  • Официально про интеграцию IDE:

  • Unity - Visual Studio integration
  • Частые причины багов в Unity-мини-проектах

  • NullReferenceException: забыли назначить ссылку в Inspector или компонент отсутствует.
  • Путаете Update() и FixedUpdate(): ввод читается в Update(), физика применяется в FixedUpdate().
  • Триггеры не срабатывают: нет Rigidbody хотя бы на одном из объектов, или перепутан 2D/3D.
  • Состояния игры конфликтуют: например, управление не отключается на паузе.
  • Профилирование: как понять, что именно тормозит

    Оптимизация без измерений почти всегда превращается в угадывание. В Unity для измерений есть Profiler.

  • Unity Profiler overview
  • Development Build для профилирования билда

    В редакторе игра часто работает иначе, чем в сборке. Для проверки используйте Development Build.

  • Включается в окне Build Settings.
  • Позволяет подключать Profiler к запущенному билду.
  • Build Settings
  • CPU Profiler: где тратится время кадра

    Смотрите, какие системы занимают больше всего времени:

  • Scripts (ваш код)
  • Physics (столкновения, FixedUpdate)
  • Rendering (отрисовка)
  • GC.Alloc (выделения памяти, приводящие к сборке мусора)
  • !Как читать Profiler: модули, таймлайн и таблица вызовов

    Memory Profiler: почему появляются фризы

    Типичная причина микрофризов в Unity-играх новичка: сборка мусора.

    Частые источники лишних выделений:

  • создание строк в цикле (конкатенация текста каждую рамку)
  • Instantiate/Destroy в большом количестве
  • LINQ в горячем коде (Update, FixedUpdate)
  • Документация по памяти и профилированию:

  • Understanding automatic memory management
  • Практичная оптимизация: что чаще всего даёт результат

    Уберите лишнее из Update

    Почти любой код, который можно выполнять реже, чем каждый кадр, нужно выполнять реже.

    Подходы:

  • обновляйте UI по событиям (score.Changed, health.Changed), а не в Update()
  • таймеры делайте через накопление времени и редкие проверки
  • если объект далеко и неактивен в геймплее, отключайте его компонент (enabled = false) или весь объект (SetActive(false))
  • Кешируйте компоненты

    Если вы вызываете GetComponent часто, кешируйте ссылку в Awake().

  • Component.GetComponent
  • Пример:

    Не создавайте и не уничтожайте объекты постоянно

    Instantiate и Destroy создают нагрузку на CPU и память.

    Решение для частых объектов (пули, враги, монеты): object pooling.

    Базовый смысл:

  • заранее создаёте N объектов
  • включаете/выключаете их вместо создания/уничтожения
  • Полезная отправная точка:

  • Object.Instantiate
  • Object.Destroy
  • Оптимизация физики

    Если у вас много коллайдеров и триггеров:

  • используйте Layers и матрицу столкновений, чтобы лишние объекты не сталкивались
  • упрощайте коллайдеры (Box/Capsule дешевле MeshCollider)
  • проверяйте, не слишком ли много объектов с Rigidbody
  • Physics settings (collision matrix)
  • Оптимизация UI

    UI может быть неожиданно дорогим, если вы постоянно меняете разметку.

    Практические советы:

  • обновляйте текст только при изменении данных
  • избегайте частых включений/выключений больших UI-деревьев без необходимости
  • следите, чтобы анимации UI на паузе использовали Time.unscaledDeltaTime, если нужно
  • Canvas
  • Time.unscaledDeltaTime
  • Сборка проекта: Build Settings и Player Settings

    Добавьте сцены в Build

    Unity собирает только те сцены, которые добавлены в список сцен билда.

  • Откройте окно: FileBuild Settings.
  • Откройте нужную сцену.
  • Нажмите Add Open Scenes.
  • Build Settings
  • Выбор платформы

    Для мини-проекта чаще всего выбирают:

  • Windows/Mac/Linux Standalone
  • Android
  • WebGL (удобно для публикации в браузере, но есть ограничения)
  • Документация по платформам:

  • Build your application
  • Development Build и Script Debugging

    Для теста проблем на устройстве включайте:

  • Development Build
  • Script Debugging
  • Затем проверьте:

  • работают ли ввод и UI
  • не пропали ли ссылки на ассеты
  • нет ли ошибок, которых не было в Editor
  • Player Settings: важные поля для новичка

    Откройте: EditProject SettingsPlayer.

    Самые полезные настройки на старте:

  • Company Name и Product Name
  • Version
  • иконка приложения
  • ориентация экрана (для мобильных)
  • Player settings
  • Проверка билда: что тестировать обязательно

    Сделайте короткий тест-план на 5 минут и гоняйте его после каждого изменения, влияющего на сборку.

    Пример тест-плана:

  • Игра запускается без ошибок.
  • Главное меню открывается и кнопки работают.
  • Старт игры переводит состояние в Playing.
  • Пауза по Escape работает и снимается.
  • Подбор предметов засчитывается в счёт.
  • При поражении открывается Game Over.
  • Best score сохраняется и сохраняется между перезапусками.
  • Публикация: как упаковать мини-проект для других

    Windows (Standalone)

    Практичный минимум:

  • собирайте в отдельную папку Builds/Windows
  • распространяйте как архив: папка с .exe и папкой _Data
  • добавьте README.txt (управление, цель, системные требования)
  • Android

    Нужно дополнительно:

  • настроить Keystore для подписания (если планируете публикацию)
  • проверить разрешения, ориентацию, качество
  • Документация:

  • Android - Getting started
  • WebGL

    Подходит для портфолио и быстрого шеринга. Учитывайте:

  • ограничения по памяти
  • загрузку в браузере
  • особенности работы файловой системы (сохранения могут отличаться)
  • Документация:

  • WebGL
  • Итог

    Теперь у вас полный цикл разработки мини-проекта на Unity:

  • вы собираете сцену и связываете системы аккуратно (Inspector, события, ScriptableObject)
  • отлаживаете ошибки через Console и дебаггер
  • измеряете производительность через Profiler и устраняете узкие места
  • собираете билд через Build Settings и проверяете его как отдельный продукт
  • упаковываете проект для публикации на нужной платформе
  • Если вы хотите закрепить результат, лучший следующий шаг после этой статьи: выбрать один мини-проект, довести его до состояния можно дать другу и он поймёт, что делать, и только потом расширять механики.