# Cadmus — Full Content > Полный контент энциклопедий для LLM-агентов. Unity и Godot — на русском языке; > Phaser двуязычен (здесь — русская версия, английская доступна под /en/phaser/). > Каждая глава — отдельный H2 раздел с URL, метаданными и текстом главы. > Дата генерации: 2026-06-04. # Unity Encyclopedia ## [Unity] Зачем веб-разработчику Unity URL: https://cadmus.page/unity/01-intro/01-zachem/ Section: Введение Description: Что общего и что нового в Unity по сравнению с фронтенд- и бэкенд-разработкой. Если вы пишете на TypeScript/React в браузере и в Node на сервере, переход в Unity ощущается одновременно знакомым и чужим. С одной стороны — управляемая среда выполнения (CLR/Mono/IL2CPP вместо V8), строгая типизация (C# вместо TS), компонентная архитектура (GameObject + Components вместо React), циклы перерисовки и события. С другой — настоящая 3D-сцена, физика, рендеринг через GPU и асинхронный ассет-конвейер. Эта энциклопедия — практическое введение в **разработку 3D-игр в Unity 6** для тех, кому проще объяснять через знакомые веб-абстракции. ## Что вы умеете и что это даст вам в Unity - Декларативная композиция через React-компоненты - Реактивное обновление: state → render - Иерархия DOM, родитель и потомки - CSS transforms (`translate`, `rotate`, `scale`) - `requestAnimationFrame` для анимаций - `addEventListener` для ввода - Vite/Webpack для сборки ассетов - Композиция GameObject через Components (включая ваши `MonoBehaviour`) - Императивный `Update()` или event-driven через подписки на UnityEvents - Иерархия сцены: Transform с родителем и детьми - `transform.position / rotation / localScale` в 3D-пространстве - Game Loop в движке: `Update`, `LateUpdate`, `FixedUpdate` - Input System (новый) или классический `Input` (legacy) - Сборка → Player через IL2CPP/Mono, ассеты — через AssetBundles или Addressables ## Чего нет в браузере и почему это интересно 1. **Главный поток с предсказуемым 60+ FPS.** В вебе вы пишете "к 60 FPS", но это редко жёсткое требование. В Unity всё подчинено фрейм-таймингу: 16.6 мс на кадр для 60 FPS, 11.1 мс для 90 FPS (VR), 8.3 мс для 120 FPS. 2. **GPU-программирование рядом.** Шейдеры (HLSL или Shader Graph), материалы, проходы рендеринга — это ваш повседневный инструментарий, а не экзотика. 3. **Физика как часть движка.** Rigidbody, столкновения, raycast — встроены, а не сторонняя библиотека. 4. **Ассет-конвейер**, который импортирует FBX, текстуры, аудио и автоматически генерирует мета-данные. 5. **Платформ много.** Один проект — это Windows, macOS, Linux, Android, iOS, WebGL, консоли. С разными профилями производительности и ограничениями. Главная ментальная перестройка: вместо "функция отрабатывает за N миллисекунд, и это нормально" думайте — "Update() выполняется 60 раз в секунду, и каждый вызов добавляется в бюджет кадра". Аллокации в `Update()` — главный враг плавности. ## Куда дальше В следующем разделе короткий обзор стека: версии Unity, языки, рендер-пайплайны и сопутствующие инструменты. После этого — главная глава про 3D-разработку. --- ## [Unity] Стек Unity и его экосистема URL: https://cadmus.page/unity/01-intro/02-stack/ Section: Введение Description: Версии Unity, языки, рендер-пайплайны, ключевые инструменты и пакеты. ## Версии Unity Unity выпускается в нескольких ветках одновременно. Хронология ключевых релизов 6.x: - **Unity 6.0** (октябрь 2024) — первый мажорный релиз новой схемы (вместо `2021.x / 2022.x / 2023.x`). - **Unity 6.1** (март 2025) — public WebGPU backend, Deferred+ для URP, GPU Resident Drawer улучшения, DOTS production-ready. - **Unity 6.2** (август 2025) — UI Toolkit World Space experimental, Unity AI интеграции, новые Awaitable примитивы. - **Unity 6.3 LTS** (релиз 5 декабря 2025) — **текущий рекомендованный LTS**, поддержка до декабря 2027 (Enterprise / Industry-лицензии — до декабря 2028). Здесь legacy Input Manager явно помечен deprecated; Box2D v3 для 2D-физики; Hybrid 2D/3D Scenes. - В Tech Stream сейчас Unity 6.4 / 6.5 — постепенное превращение DOTS-пакетов в core engine modules; experimental CoreCLR ожидается в **6.7**, полная замена Mono — в **6.8** (конец 2026). Для нового проекта в 2026 году стандартный выбор — **Unity 6.3 LTS**. Если поддерживаете старый проект: 2022.3 LTS закрыт в июне 2025; 2021.3 LTS снят с поддержки ещё в 2024. Версии до 6.0 не получают функциональных апдейтов. ## Языки - **C#** — единственный first-class язык скриптинга. UnityScript (диалект JS) и Boo были удалены ещё в 2017–2018 годах. Unity 6 поддерживает C# 9 синтаксис при компиляции в .NET Standard 2.1, а с определёнными настройками — и более новые фичи. - **HLSL** — для написания шейдеров под Built-in RP, URP и HDRP. Альтернатива — визуальный редактор **Shader Graph**. - **C++** — только если вы пишете нативный плагин (DLL/dylib/so). ## Runtime: Mono, IL2CPP и грядущий CoreCLR C# код компилируется в IL (CIL), а потом выполняется одним из двух способов: - **Mono** — JIT-компиляция IL на устройстве пользователя. Используется в редакторе и для быстрых итераций. Поддерживается не на всех платформах. - **IL2CPP** — AOT-компиляция IL в C++, потом — в нативный бинарь. Обязательна для iOS, WebGL, большинства консолей. Запуск быстрее, итерационная компиляция дольше. Unity объявила миграцию на **.NET CoreCLR** как третий runtime. По уточнённому roadmap: experimental CoreCLR desktop player — в Unity **6.7**; полная замена scripting runtime (Mono → CoreCLR) — в **6.8** (конец 2026). CoreCLR быстрее Mono на серверах и нативных платформах, поддерживает современный .NET. Серьёзное изменение для долгосрочных проектов — следите за релиз-нотами 6.6+. Под IL2CPP не работает динамическая генерация кода (`Reflection.Emit`, динамические лямбды через `Expression.Compile()`). Если используете рефлексию активно — добавляйте `[Preserve]` атрибуты, чтобы линкер не выкинул нужные типы. ## Рендер-пайплайны Unity поддерживает три рендер-пайплайна, и выбор делается на старте проекта: | Пайплайн | Назначение | Когда выбирать | |---|---|---| | **Built-in RP** | Старый универсальный | Поддержка legacy-проектов, не для нового кода | | **URP** (Universal RP) | Кросс-платформа, мобильные, средняя графика | Дефолт для большинства новых проектов | | **HDRP** (High Definition RP) | ПК и консоли, фотореализм | Высококачественные ПК/консольные проекты | URP и HDRP — это пакеты Scriptable Render Pipeline (SRP), их можно конфигурировать через ассеты качества и Volume-систему. Переключаться между ними в течение проекта дорого: материалы и шейдеры несовместимы. Решайте до того, как начнёте. ## Ключевые пакеты Package Manager Unity Package Manager (UPM) — это `npm` для Unity. Большинство инструментов идёт через него: - **Input System** — современная замена legacy `Input` класса (legacy явно deprecated в Unity 6.3). - **Cinemachine** — мощные камеры (follow, free-look, dolly tracks). UPM-пакет, предустановлен в шаблонах Unity 6. - **TextMeshPro** — текст с SDF-шрифтами (вместо устаревшего `UI.Text`). - **Addressables** — асинхронная загрузка ассетов вместо `Resources.Load`. - **Animation Rigging** — процедурная анимация скелета (IK, aim constraints). - **Burst** + **Job System** + **Collections** — высокопроизводительные системы. - **Entities** (DOTS/ECS) — data-oriented стек для тысяч сущностей. **Production-ready с Unity 6.0** (Entities 1.0); в Unity 6.4 пакет становится core engine package — без отдельной установки. - **Unity Behavior** — официальная graph-based behavior trees система для AI (с Unity 6.1). - **Sentis** — встроенный ML inference (заменил Barracuda). - **Netcode for GameObjects 2.x** — мультиплеер (поддерживает Distributed Authority с Unity Cloud). ## Инструменты вокруг проекта - **Unity Hub** — менеджер версий редактора и проектов (как `nvm` для Node). - **JetBrains Rider** или **Visual Studio** — основные IDE. VS Code тоже работает, но Rider даёт глубокую интеграцию (отладка, навигация по компонентам). - **Git** + **Git LFS** для бинарных ассетов. Не забудьте `.gitignore` для `Library/`, `Temp/`, `Logs/`, `UserSettings/`. - **Plastic SCM (Unity Version Control)** — встроенный VCS, удобный для бинарных файлов и больших проектов; альтернатива Git LFS. В следующей главе — главный материал: всё про 3D-разработку. --- ## [Unity] Unity Editor, сцена и иерархия URL: https://cadmus.page/unity/02-3d/01-editor-i-iyerarkhiya/ Section: 3D-разработка в Unity Description: Главные окна редактора, GameObject и Transform — DOM 3D-мира. ## Editor: с чего вы будете смотреть на мир Unity Editor — это IDE для контента. Несколько окон-доков, которые можно перетаскивать и сохранять как layout. Ключевые из коробки: - **Scene View** — 3D-вьюпорт редактора. Здесь вы летаете и расставляете объекты. - **Game View** — то, что увидит игрок: рендер с активной камеры. - **Hierarchy** — дерево объектов в текущей сцене. - **Project** — файлы проекта (ассеты): модели, текстуры, скрипты, материалы. - **Inspector** — свойства выбранного объекта. Здесь вы редактируете компоненты. - **Console** — лог, ошибки, варнинги (`Debug.Log`, `Debug.LogError`). DevTools браузера: Elements, Console, Sources, Network. Inspector браузера — это про CSS-стили DOM-элемента. Hierarchy — это "Elements" (дерево сцены). Inspector — это "Styles", только редактирует компоненты на GameObject. Project — это файловое дерево "Sources". ## Сцена (Scene) Сцена — это файл `.unity` с описанием конкретного уровня или экрана: какие GameObject в нём, с какими компонентами и значениями. Загружаются сцены через `SceneManager.LoadScene`. Аналог страницы (route) в SPA. В современной разработке часто используют **аддитивную загрузку**: одна базовая сцена с UI и менеджерами, поверх неё подгружаются сцены-уровни. ```csharp using UnityEngine.SceneManagement; SceneManager.LoadScene("Level_01"); // полная замена SceneManager.LoadScene("HUD", LoadSceneMode.Additive); // поверх текущей ``` ## GameObject — главный примитив GameObject — пустой контейнер. Сам по себе он не делает ничего: ни рисуется, ни движется. Всё поведение даётся ему **компонентами**. У GameObject всегда есть один обязательный компонент — `Transform` (или `RectTransform` для UI). DOM-элемент `
`. Сам по себе пустой, но с CSS-классами и атрибутами получает поведение (анимации, позиционирование, обработчики событий). GameObject "Player". Добавили `CharacterController` — он движется как капсула. Добавили `MeshRenderer` — стал виден. Добавили ваш `PlayerInput.cs` — реагирует на ввод. ## Transform — 3D-позиция, поворот и масштаб Transform хранит **локальные** значения относительно родителя: - `localPosition` — `Vector3 (x, y, z)`, метры - `localRotation` — `Quaternion` (внутри), `localEulerAngles` для удобного представления в градусах - `localScale` — `Vector3 (x, y, z)`, множители И производные **мировые**: `position`, `rotation`, `eulerAngles`. Если объект — дочерний, мировые значения вычисляются цепочкой матриц трансформации. ```csharp transform.localPosition = new Vector3(0, 1.5f, 0); // 1.5 м вверх от родителя transform.Rotate(0, 90f, 0); // повернуть на 90° вокруг Y transform.localScale = Vector3.one * 2f; // ×2 по всем осям ``` Если родитель отмасштабирован неравномерно (например, `(2, 1, 1)`), а ребёнок повёрнут, мировой масштаб ребёнка станет "сдвинутым" (skew) — Unity показывает предупреждения в Inspector. Старайтесь не мешать non-uniform scale с поворотами в иерархии. ## Координатная система Unity использует **левостороннюю** систему координат: - **+X** — вправо - **+Y** — вверх (важно для тех, кто привык к Z-up в Blender/3ds Max) - **+Z** — вперёд Один Unit по умолчанию = 1 метр. Это влияет на физику: гравитация — 9.81 м/с² по умолчанию. Если ваши модели импортированы в "сантиметровом" масштабе, физика будет вести себя странно. Поправляйте Scale Factor в импортере модели. ## Иерархия и родительство Сделав GameObject ребёнком другого, вы привязываете его Transform к родительскому. Ребёнок наследует позицию, поворот и масштаб. Это удобный приём для: - **Группировок** (папки в иерархии): пустой GameObject как контейнер для коллекции врагов. - **Логических связей**: рука → ладонь → меч; двигаете руку — меч следует. - **Префабов**: модель персонажа со скелетом — это иерархия Transform'ов с компонентами на нужных узлах. ```csharp // Сделать `child` ребёнком `parent` child.transform.SetParent(parent.transform, worldPositionStays: true); // Отвязать child.transform.SetParent(null); ``` Параметр `worldPositionStays` определяет, сохранится ли мировая позиция при перепривязке. По умолчанию `true` — Unity пересчитает `localPosition`, чтобы объект остался "на месте". В следующем разделе разберёмся, как именно вы пишете поведение — через MonoBehaviour и его жизненный цикл. --- ## [Unity] Скрипты и жизненный цикл MonoBehaviour URL: https://cadmus.page/unity/02-3d/02-monobehaviour/ Section: 3D-разработка в Unity Description: Как Unity вызывает ваш код — Awake, Start, Update и всё, что между ними. ## MonoBehaviour — базовый класс ваших скриптов Любой ваш C#-скрипт, который вешается на GameObject как компонент, наследуется от `MonoBehaviour`. Это даёт Unity точки входа — методы с особыми именами, которые движок вызывает сам в нужный момент. ```csharp using UnityEngine; public class Hello : MonoBehaviour { [SerializeField] private float speed = 5f; private void Start() { Debug.Log("Привет из Unity"); } private void Update() { transform.Translate(Vector3.forward * speed * Time.deltaTime); } } ``` Атрибут `[SerializeField]` делает приватное поле видимым в Inspector — это идиоматичный способ выставлять параметры. Публичные поля тоже сериализуются по умолчанию, но это считается плохим тоном (нарушает инкапсуляцию). React-компонент: `useEffect(() => {}, [])` — это аналог `Start`, а `useEffect(() => {})` без зависимостей — что-то вроде `Update`, но в React render-функция вызывается реактивно, а не каждый кадр. Unity вызывает `Update()` каждый кадр и `Start()` ровно один раз перед первым `Update`. Поля компонента — это состояние, как `useState`. ## Жизненный цикл — порядок вызовов Полный жизненный цикл MonoBehaviour сложнее, но вот основные методы, отсортированные по порядку вызова: 1. **`Awake()`** — когда объект инстанцирован в сцене, до загрузки любых других объектов и до подписок. Здесь обычно `GetComponent<>`. 2. **`OnEnable()`** — каждый раз, когда компонент включается (или объект становится активным). Здесь подписываются на события. 3. **`Start()`** — один раз перед первым `Update`, но только если компонент активен. Хорошее место для инициализации, которая зависит от других объектов. 4. **`FixedUpdate()`** — фиксированный шаг (по умолчанию каждые 0.02 с = 50 Hz). Сюда — физика и `Rigidbody.AddForce`. 5. **`Update()`** — каждый кадр. Сюда — ввод и логика, не привязанная к физике. 6. **`LateUpdate()`** — каждый кадр, **после** всех `Update`. Сюда — следящая камера и финальная корректировка положений. 7. **`OnDisable()`** — когда компонент выключается. Здесь отписываются от событий. 8. **`OnDestroy()`** — когда GameObject уничтожен (`Destroy(obj)`) или сцена выгружена. ```csharp public class LifecycleExample : MonoBehaviour { private Rigidbody _rb; private void Awake() { _rb = GetComponent(); } private void OnEnable() { Health.OnDeath += HandleDeath; } private void Start() { /* инициализация, зависящая от других объектов */ } private void FixedUpdate() { _rb.AddForce(Vector3.up); } private void Update() { /* читаем ввод, логика */ } private void LateUpdate() { /* подвинуть камеру за игроком */ } private void OnDisable() { Health.OnDeath -= HandleDeath; } private void OnDestroy() { /* финальная очистка */ } private void HandleDeath() { /* ... */ } } ``` Подписки на события в `OnEnable` и отписки в `OnDisable` — стандартная практика. Это переживает включение/выключение объекта без утечек и без двойных подписок. ## Update vs FixedUpdate vs LateUpdate Это трёхэтапный цикл, и понимание разницы критично: | Метод | Частота | Что туда | |---|---|---| | `FixedUpdate` | Фиксированно (50 Hz по умолчанию) | Физика: `AddForce`, `MovePosition` для Rigidbody | | `Update` | Каждый кадр | Ввод, AI, нефизическая логика | | `LateUpdate` | Каждый кадр, после Update | Следящая камера, "догон" за изменениями кадра | `Time.deltaTime` в `Update` — время с последнего кадра (переменное). `Time.fixedDeltaTime` в `FixedUpdate` — фиксированный шаг. Если у объекта есть `Rigidbody` (не kinematic), напрямую менять `transform.position` или `Rotate()` каждый кадр — путь к ошибкам столкновений и дрожанию. Используйте `Rigidbody.MovePosition` или физические силы в `FixedUpdate`. ## GetComponent: получение других компонентов ```csharp private Rigidbody _rb; private Animator _animator; private void Awake() { _rb = GetComponent(); _animator = GetComponentInChildren(); // искать в детях } ``` Не вызывайте `GetComponent` в `Update` — это поиск по списку компонентов, дорогая операция. Кэшируйте в `Awake`/`Start`. В Unity 6 есть удобная альтернатива — атрибут `[RequireComponent(typeof(Rigidbody))]` на классе, который гарантирует наличие нужного компонента, и поиск через `TryGetComponent` для ситуаций, где компонента может не быть. ## Связь со сценой: ссылки в Inspector Самый удобный способ соединить два объекта — поле в Inspector: ```csharp public class Enemy : MonoBehaviour { [SerializeField] private Transform target; // перетащите Player в Inspector [SerializeField] private float speed = 3f; private void Update() { var dir = (target.position - transform.position).normalized; transform.position += dir * speed * Time.deltaTime; } } ``` Альтернативы — поиск по сцене (`FindAnyObjectByType()`, `GameObject.FindWithTag("Player")`). Последние медленнее и хрупче, но удобны для прототипа. ## Coroutine: "корутины" вместо async/await (часто) Корутина — `IEnumerator`, который возвращает управление движку через `yield return`. Это удобный способ растянуть логику на несколько кадров, не плодя свои таймеры. ```csharp private IEnumerator FadeOut(float duration) { float t = 0; var renderer = GetComponent(); var color = renderer.material.color; while (t < duration) { t += Time.deltaTime; color.a = Mathf.Lerp(1f, 0f, t / duration); renderer.material.color = color; yield return null; // подождать следующий кадр } Destroy(gameObject); } // Запуск: StartCoroutine(FadeOut(2f)); ``` Полезные `yield return`: - `null` — следующий кадр - `new WaitForSeconds(1f)` — пауза в "игровых секундах" (учитывает `Time.timeScale`) - `new WaitForSecondsRealtime(1f)` — пауза в реальных секундах (игнорирует timescale) - `new WaitUntil(() => isReady)` — ждать условия - `new WaitForEndOfFrame()` — после рендера кадра (для скриншотов) Цепочка `setTimeout`/`requestAnimationFrame` с состоянием. Или RxJS Observable, который каждый "тик" отдаёт следующее значение. Только runtime сам не дёргает шаги — это делает ваш код. `StartCoroutine` дёргает ваш `IEnumerator` каждый кадр (или после нужного `yield`). Никаких тредов — всё на main thread. Движок берёт на себя планирование шагов. ## Async/await: всё ближе к мейнстриму Unity 6 поддерживает `async`/`await` с типом `Awaitable`: ```csharp private async Awaitable LoadAndShow() { await Awaitable.WaitForSecondsAsync(1f); Debug.Log("Прошла секунда"); await SceneManager.LoadSceneAsync("Level_02"); } ``` `Awaitable` — это zero-alloc альтернатива `Task` для Unity. Но почти все API Unity всё равно работают **только на main thread** — отправите вызов `transform.position` из произвольного потока, получите исключение. В следующем разделе — ввод: новый Input System и legacy `Input` класс. --- ## [Unity] Ввод — Input System и legacy Input URL: https://cadmus.page/unity/02-3d/03-input/ Section: 3D-разработка в Unity Description: Клавиатура, мышь, геймпад и тач — два пути и почему стоит идти новым. В Unity сейчас сосуществуют две системы ввода. Legacy `Input` класс — старый и простой. Input System package — новый, мощный и рекомендованный для новых проектов. ## Legacy Input — быстрый старт Класс `Input` доступен из коробки. Опрашивается каждый кадр в `Update`: ```csharp private void Update() { float h = Input.GetAxis("Horizontal"); // -1..1, левый стик/A-D/стрелки float v = Input.GetAxis("Vertical"); if (Input.GetButtonDown("Jump")) { // пробел был нажат в этом кадре } if (Input.GetMouseButton(0)) { // ЛКМ удерживается } Vector2 mouseDelta = new Vector2( Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y") ); } ``` Кнопки и оси определяются в **Edit → Project Settings → Input Manager**. Это словарь со значениями по умолчанию: "Horizontal", "Vertical", "Jump", "Fire1", "Mouse X" и так далее. В Unity 6.3 LTS legacy Input Manager **официально помечен deprecated** с предупреждением в редакторе. API ещё работает, но удалят в одной из будущих major-версий. Используйте legacy только для быстрых прототипов или поддержки старых проектов. В новом коде — Input System. ## Input System — современный путь Input System — это пакет (устанавливается через Package Manager). Главные идеи: 1. **Actions, а не клавиши.** Вы описываете действия ("Move", "Jump", "Shoot") и привязываете к ним физические кнопки/оси/жесты. Один action — много привязок. 2. **Композитные привязки.** "WASD" = композит из 4 клавиш, который возвращает `Vector2`. 3. **Контрольные схемы.** "Keyboard & Mouse" vs "Gamepad" — автоматическое переключение, разные привязки. 4. **Callbacks, не polling.** Подписываетесь на `performed`, `started`, `canceled`. ### Action Asset В Project — Create → Input Actions. Открывается визуальный редактор: создаёте Action Map (например, "Player"), внутри — actions, и для каждого — bindings. После настройки Unity сгенерирует C# класс (через "Generate C# Class" в инспекторе), которым удобно пользоваться: ```csharp using UnityEngine; using UnityEngine.InputSystem; public class PlayerController : MonoBehaviour { private PlayerControls _controls; // сгенерированный класс private Vector2 _moveInput; private void Awake() { _controls = new PlayerControls(); _controls.Player.Move.performed += ctx => _moveInput = ctx.ReadValue(); _controls.Player.Move.canceled += _ => _moveInput = Vector2.zero; _controls.Player.Jump.performed += _ => TryJump(); } private void OnEnable() => _controls.Enable(); private void OnDisable() => _controls.Disable(); private void Update() { var move = new Vector3(_moveInput.x, 0, _moveInput.y); transform.Translate(move * 5f * Time.deltaTime, Space.Self); } private void TryJump() { /* ... */ } } ``` Можно обойтись без ручного кода: вешаете компонент `PlayerInput` на GameObject, указываете Action Asset, и связываете actions с вашими методами через UnityEvents в Inspector. Хорошо для прототипов и кооперативной локалки (легко привязать второго игрока к второму геймпаду). ## Поворот камеры мышью Классическая задача: повернуть голову игрока за курсором. Pattern с Input System: ```csharp public class MouseLook : MonoBehaviour { [SerializeField] private float sensitivity = 0.1f; [SerializeField] private Transform cameraPivot; private float _pitch; private float _yaw; private Vector2 _lookInput; private void Awake() { Cursor.lockState = CursorLockMode.Locked; // спрятать и заблокировать Cursor.visible = false; } public void OnLook(InputAction.CallbackContext ctx) { _lookInput = ctx.ReadValue(); } private void Update() { _yaw += _lookInput.x * sensitivity; _pitch -= _lookInput.y * sensitivity; _pitch = Mathf.Clamp(_pitch, -85f, 85f); transform.localRotation = Quaternion.Euler(0, _yaw, 0); cameraPivot.localRotation = Quaternion.Euler(_pitch, 0, 0); } } ``` Важная деталь: pitch (вертикаль) применяется к **дочернему** объекту-камере, yaw (горизонталь) — к телу игрока. Это разделение упрощает физику: коллайдер игрока не наклоняется при взгляде вверх-вниз. ## Geometry Raycast: "куда смотрит игрок" Стрельба, выбор предмета, click-to-move — всё это построено на raycast. Луч из точки в направлении, ищет первое пересечение с коллайдером: ```csharp private void Shoot() { var ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, maxDistance: 100f)) { Debug.Log($"Попали в {hit.collider.name} на расстоянии {hit.distance:F2}"); if (hit.collider.TryGetComponent(out var enemy)) { enemy.TakeDamage(10); } } } ``` `Camera.main.ScreenPointToRay` — стандартный приём перевести позицию курсора в луч из камеры. Для FPS можно проще: `transform.forward` от камеры. В следующем разделе — физика: как Rigidbody и Collider взаимодействуют между собой и с вашими скриптами. --- ## [Unity] Физика — Rigidbody, Collider и столкновения URL: https://cadmus.page/unity/02-3d/04-physics/ Section: 3D-разработка в Unity Description: Как двигать объекты физикой и реагировать на столкновения. Unity использует **PhysX** от NVIDIA для 3D-физики (для 2D — Box2D). Это означает: симуляция твёрдых тел, столкновения, силы, joints. Всё это работает из коробки, ваша задача — корректно настроить компоненты. ## Три класса участников 1. **Static** — GameObject с Collider, без Rigidbody. Не движется, идеален для стен, пола, статичной геометрии. 2. **Kinematic** — Rigidbody с `isKinematic = true`. Двигается только через ваш скрипт (через `MovePosition` / `MoveRotation`), не реагирует на силы. 3. **Dynamic** — обычный Rigidbody. Реагирует на гравитацию, силы, столкновения. Если у объекта нет Rigidbody, Unity кеширует его в так называемом "static collider tree". Перемещение такого коллайдера каждый кадр заставит PhysX перестраивать дерево — дорого. Нужно двигать что-то без физики? Поставьте Rigidbody с `isKinematic = true`. ## Rigidbody Основные поля: - `mass` — масса в кг. - `linearVelocity` (раньше `velocity`) — текущая линейная скорость в м/с. Старое имя `velocity` помечено `[Obsolete]` в Unity 6. - `angularVelocity` — угловая скорость в рад/с. - `linearDamping` / `angularDamping` — новые имена в скриптинге Unity 6 (раньше `drag` / `angularDrag`). Старые имена `drag`/`angularDrag` пока работают и **не помечены [Obsolete]**; в Inspector свойства до сих пор отображаются как "Linear Drag" / "Angular Drag". - `useGravity` — применять ли гравитацию `Physics.gravity` (по умолчанию `(0, -9.81, 0)`). - `isKinematic` — управляется ли только скриптами. - `interpolation` — `None` / `Interpolate` / `Extrapolate`. Сглаживает движение между шагами физики. Для игрока обычно ставят `Interpolate`. - `collisionDetectionMode` — `Discrete` / `Continuous` / `ContinuousDynamic` / `ContinuousSpeculative`. Для быстрых объектов (пуль, мячей) важно `Continuous`-варианты, чтобы избежать "проскакивания" сквозь тонкие коллайдеры. В Unity 6 поле `velocity` помечено **[Obsolete]** (компилятор выдаст warning) — используйте `linearVelocity` в новом коде. Для `drag`/`angularDrag` Obsolete-маркер пока не выставлен, но идиоматично в новых проектах — `linearDamping`/`angularDamping`. `angularVelocity` остался без переименования. ```csharp public class Ball : MonoBehaviour { private Rigidbody _rb; private void Awake() { _rb = GetComponent(); } private void FixedUpdate() { // Импульс — мгновенное изменение скорости if (Input.GetKey(KeyCode.Space)) { _rb.AddForce(Vector3.up * 10f, ForceMode.Impulse); } } } ``` ### Режимы силы `AddForce` принимает `ForceMode`: | Mode | Смысл | Формула | |---|---|---| | `Force` | Сила, действует постоянно | `Δv = (F/m) * dt` | | `Acceleration` | Ускорение без учёта массы | `Δv = a * dt` | | `Impulse` | Мгновенный импульс | `Δv = F/m` | | `VelocityChange` | Мгновенное изменение скорости | `Δv = F` | Для прыжка обычно `Impulse` или `VelocityChange`. Для постоянной тяги (ракетный двигатель) — `Force`. ## Collider — форма для физики Collider описывает геометрию столкновения. Несколько типов: - **BoxCollider, SphereCollider, CapsuleCollider** — примитивы. Дёшево, точно. - **MeshCollider** — произвольный mesh. Дорого. Должен быть `convex`, если объект динамический. - **TerrainCollider** — для рельефа. - **WheelCollider** — специализированный, для машин (с подвеской и трением шины). Сложная статика? Один MeshCollider достаточно. Динамический объект сложной формы? Соберите его из нескольких BoxCollider/SphereCollider — производительность на порядки выше, чем у одного MeshCollider. ## Триггеры vs столкновения У Collider есть флажок `isTrigger`. Если он включён, объект не блокирует другие — но генерирует события `OnTrigger*`. Без `isTrigger` это физическое столкновение, генерирует `OnCollision*`. ```csharp // Сбор монеток через триггер private void OnTriggerEnter(Collider other) { if (other.CompareTag("Player")) { ScoreManager.Instance.Add(1); Destroy(gameObject); } } // Реакция на столкновение private void OnCollisionEnter(Collision collision) { var contact = collision.GetContact(0); Debug.Log($"Удар в {contact.point}, сила: {collision.impulse.magnitude:F2}"); AudioSource.PlayClipAtPoint(_hitSound, contact.point); } ``` ### Правила вызовов событий Не интуитивно, но критично понимать: | Объект A | Объект B | OnCollision | OnTrigger | |---|---|---|---| | Static collider | Static collider | — | — | | Static collider | Dynamic Rigidbody | Да (на обоих) | Да, если isTrigger | | Dynamic Rigidbody | Dynamic Rigidbody | Да (на обоих) | Да, если isTrigger | | Kinematic Rigidbody | Static collider | — | Да, если isTrigger | | Kinematic Rigidbody | Dynamic Rigidbody | Да | Да, если isTrigger | | Kinematic Rigidbody | Kinematic Rigidbody | — | Да, если isTrigger | Запомните главное: **для `OnCollisionEnter` хотя бы один из объектов должен быть non-kinematic Rigidbody. Для `OnTriggerEnter` достаточно одного Rigidbody любого типа.** ## Layers и Layer Collision Matrix У каждого GameObject есть Layer (0..31). В **Project Settings → Physics → Layer Collision Matrix** включаются/выключаются взаимодействия между слоями. Пример использования: - Слой `Player` не сталкивается с `Trigger`. - Слой `Bullet` сталкивается с `Enemy`, но не с другими `Bullet`. Это эффективнее, чем фильтровать в `OnCollisionEnter` — PhysX даже не вызовет событие, если слои не взаимодействуют. ## Raycast и физика Уже видели в главе про ввод. Полный API: ```csharp // Простой raycast if (Physics.Raycast(origin, direction, out RaycastHit hit, maxDistance, layerMask)) { } // Несколько попаданий RaycastHit[] hits = Physics.RaycastAll(ray, maxDistance, layerMask); // "Толстый" луч — SphereCast / CapsuleCast / BoxCast Physics.SphereCast(origin, radius, direction, out hit, maxDistance); // Не луч, а проверка области Collider[] inSphere = Physics.OverlapSphere(center, radius, layerMask); ``` `layerMask` — это битовая маска слоёв, через которые луч проходит. Удобно с `LayerMask.GetMask("Enemy", "Wall")`. ## CharacterController vs Rigidbody для игрока Для управления персонажем есть два подхода: 1. **CharacterController** — специальный компонент с capsule-формой. Не использует Rigidbody, не реагирует на силы. У вас полный контроль через `Move()`. Идеален для шутеров от первого лица, платформеров — где нужна предсказуемость, а не симуляция. 2. **Rigidbody + CapsuleCollider** — физический персонаж. Реалистичные столкновения, но требует аккуратной настройки damping, max-slope handling, anti-slide. ```csharp // CharacterController public class FpsMove : MonoBehaviour { private CharacterController _cc; private Vector3 _velocity; [SerializeField] private float speed = 5f; [SerializeField] private float gravity = -20f; private void Awake() => _cc = GetComponent(); private void Update() { float h = Input.GetAxis("Horizontal"); float v = Input.GetAxis("Vertical"); Vector3 move = transform.right * h + transform.forward * v; _cc.Move(move * speed * Time.deltaTime); if (_cc.isGrounded && _velocity.y < 0) _velocity.y = -2f; _velocity.y += gravity * Time.deltaTime; _cc.Move(_velocity * Time.deltaTime); } } ``` Подробнее эту схему разберём в практической главе. В следующей главе — камера и Cinemachine. --- ## [Unity] Камера и Cinemachine URL: https://cadmus.page/unity/02-3d/05-camera-cinemachine/ Section: 3D-разработка в Unity Description: Виды от первого/третьего лица, виртуальные камеры и плавный follow без матана. ## Camera-компонент Камера в Unity — обычный GameObject с компонентом `Camera`. Главные поля: - **Projection** — `Perspective` (3D, с перспективой) или `Orthographic` (без перспективы; 2D/изометрия). - **FOV (Field of View)** — угол обзора по вертикали в градусах. Стандарт для FPS — 60–90°. - **Clipping Planes: Near / Far** — расстояние ближней и дальней плоскости отсечения. Всё, что ближе Near или дальше Far, не рисуется. Большой разброс (например, 0.01 ↔ 10000) ухудшает точность Z-buffer и приводит к "Z-fighting" — мерцающим пересечениям. Старайтесь держать `Far/Near < ~10000`. - **Culling Mask** — какие слои объектов рисует эта камера. - **Target Texture** — рендерить картинку не на экран, а в текстуру (мини-карты, портал-эффекты). - **Depth** — порядок отрисовки нескольких камер. ```csharp // Простой follow-камера в LateUpdate public class SimpleFollow : MonoBehaviour { [SerializeField] private Transform target; [SerializeField] private Vector3 offset = new Vector3(0, 2f, -4f); [SerializeField] private float smoothTime = 0.15f; private Vector3 _velocity; private void LateUpdate() { Vector3 desired = target.position + target.rotation * offset; transform.position = Vector3.SmoothDamp(transform.position, desired, ref _velocity, smoothTime); transform.LookAt(target.position + Vector3.up * 1.5f); } } ``` `Vector3.SmoothDamp` — критически удобная функция: сглаживает движение с пружинной механикой, без пересохранения целевой позиции. Хранит "скорость" во внешней переменной. Если двигать камеру в `Update`, она может догонять цель на кадр позже. `LateUpdate` гарантирует, что цель уже перемещена в этом кадре. ## Cinemachine — следить за целью без боли Cinemachine — UPM-пакет (предустановлен в стандартных шаблонах Unity 6, но всё ещё устанавливается через Package Manager) с виртуальными камерами. Идея: на сцене одна реальная Camera, а Cinemachine динамически переключает её на разные виртуальные точки зрения. Без скриптов можно собрать: - **Follow** — следить за объектом с задержкой и demping. - **Look At** — смотреть на объект, плавно поворачиваясь. - **FreeLook** — орбитальная камера с тремя кольцами (третье лицо, как в Dark Souls). - **Cart/Dolly** — движение по сплайну. - **State-Driven Camera** — переключение виртуальных камер по состоянию Animator. ### Базовая follow-camera в Cinemachine 3.x В Unity 6 поставляется Cinemachine 3, у которого API заметно отличается от 2.x: 1. Добавьте на сцену **CinemachineCamera** (раньше — `CmCamera`/`CinemachineVirtualCamera`). 2. В поле "Tracking Target" укажите Transform игрока. 3. На виртуальной камере добавьте stage-компоненты (наследники `CinemachineComponentBase`): `CinemachineFollow` (раньше Body) и `CinemachineHardLookAt` / `CinemachineRotationComposer` (раньше Aim). Это отдельная категория, не то же самое, что Cinemachine Extensions (Impulse Listener, Deoccluder и др.). Один CinemachineBrain на основной Camera автоматически блендит между активными виртуальными камерами по приоритету. Хотите переключить вид? `vcam.Priority = 100;` — и Brain мягко переедет туда за заданное время блендинга. ```csharp public class CameraSwitcher : MonoBehaviour { [SerializeField] private Unity.Cinemachine.CinemachineCamera firstPerson; [SerializeField] private Unity.Cinemachine.CinemachineCamera thirdPerson; public void SwitchToFP() { firstPerson.Priority = 20; thirdPerson.Priority = 10; } } ``` Если читаете старые туториалы — там CinemachineVirtualCamera с `Body` и `Aim` секциями. В Unity 6 это `CinemachineCamera` с отдельными компонентами-расширениями. Концепция та же, имена другие. ## Главный совет про камеру > Камера — это режиссёр, а не оператор-стажёр. Лучшая камера — та, которую игрок не замечает. Сделайте плавность (smooth follow), запас по углу обзора, чтобы цель не упиралась в край экрана, и не давайте камере прыгать на резких поворотах персонажа. Cinemachine за вас уже учитывает большинство этих случаев — пользуйтесь. В следующей главе — рендеринг: как Unity вообще рисует пиксели. --- ## [Unity] Рендеринг, материалы и шейдеры URL: https://cadmus.page/unity/02-3d/06-rendering/ Section: 3D-разработка в Unity Description: URP, Material vs Shader, Shader Graph и почему "разноцветные кубики" дорого. ## Чем вообще рисуется кадр Unity отдаёт сцену в GPU через **рендер-пайплайн** — последовательность проходов (passes): тени, opaque, transparent, postprocess. Конкретные шаги зависят от выбранного пайплайна (см. главу про стек): - **Built-in RP** — старый, монолитный. Forward или Deferred рендеринг по выбору. - **URP** — Forward+ по умолчанию, кросс-платформа. Это рекомендованный выбор сейчас. - **HDRP** — Deferred + Forward, физически-корректный, для High-end ПК и консолей. WebGL/WebGPU draw call — это вызов отрисовки буфера вершин с шейдером. Браузер за вас не делает почти ничего: вы сами собираете батчи, посылаете uniforms. Unity за вас собирает draw call'ы из сцены, сортирует, объединяет (batching), вызывает шейдеры. Вы видите в Frame Debugger список вызовов и можете оптимизировать. ## Mesh, Material, Shader — три кита - **Mesh** — геометрия: массив вершин, нормалей, UV-координат, индексов. Импортируется из FBX/OBJ/glTF. - **Shader** — программа на GPU, описывающая, как из вершин и текстур получаются пиксели. - **Material** — экземпляр Shader с конкретными параметрами (текстуры, цвета, числа). Один Mesh + один Material = один draw call (упрощённо). Если у вас 100 объектов с одинаковым материалом, Unity может слить их в один draw call (Static / Dynamic / GPU Instancing). Если у каждого свой Material — 100 draw call'ов. Стандартный приём оптимизации: объединить текстуры в один атлас, чтобы все объекты использовали один материал. Это даёт автоматический batching и снижает overhead. ## Material Inspector — что вы крутите Откройте материал — увидите параметры активного шейдера. Для URP/Lit (стандарт): - **Surface Type** — Opaque или Transparent. Transparent дороже и требует сортировки. - **Workflow** — Metallic или Specular (физически-корректные PBR). - **Base Map** — основная цветная текстура (albedo). - **Metallic Map** + **Smoothness** — насколько объект металлический и гладкий. - **Normal Map** — карта нормалей для имитации мелкого рельефа без увеличения полигонов. - **Emission** — самосветящиеся участки. - **Tiling / Offset** — повтор и сдвиг UV. ```csharp // Поменять цвет материала из кода (Built-in RP / Standard shader: свойство _Color) var renderer = GetComponent(); renderer.material.color = Color.red; // создаст instance! // Для URP/Lit основное свойство называется _BaseColor — .color может не работать ожидаемо: renderer.material.SetColor("_BaseColor", Color.red); // Через property block — без создания нового материала, без потери batching var block = new MaterialPropertyBlock(); renderer.GetPropertyBlock(block); block.SetColor("_BaseColor", Color.red); renderer.SetPropertyBlock(block); ``` Обращение к `renderer.material` создаёт **уникальную копию** материала для этого объекта. Это ломает batching и течёт памятью, если делается часто. Если меняете часто — используйте `MaterialPropertyBlock` (как в примере выше) — он не создаёт новый материал. ## Shader Graph — визуальный шейдер Если HLSL вам пока неудобно, в URP/HDRP есть **Shader Graph** — визуальный редактор шейдеров. В Project: Create → Shader Graph → URP → Lit Shader Graph. Открывается граф, где вы соединяете ноды: текстуры, операторы, output на Surface. Shader Graph под капотом генерирует HLSL и подходит для большинства gameplay-эффектов: пульсирующее свечение, hologram, dissolve, water surface. Сложные постпроцессы тоже можно собирать. ## Render Graph (URP 17, Unity 6) В Unity 6 / URP 17 рендерер URP **по умолчанию работает через Render Graph** — декларативный API, где каждый pass описывает свои inputs/outputs (textures, buffers), а движок сам строит граф зависимостей и оптимизирует execution (merge passes, reuse render targets, parallel where possible). Это влияет на вас, если: - Пишете **custom Renderer Features** в URP — старое API (`ScriptableRenderPass.Execute`) deprecated; нужно перейти на новый `RecordRenderGraph()`. - Используете built-in passes — ничего не делаете, Render Graph под капотом. - Работаете с HDRP — там Render Graph был с 2021, но в Unity 6 единая модель. Старый non-Render-Graph путь остаётся как "Compatibility Mode" в URP Asset, но deprecated. ## Batching и draw call optimization Чтобы уменьшить количество draw call'ов, Unity предлагает несколько механизмов в порядке исторической давности: 1. **Static Batching** — для не-движущихся объектов. Их меши при загрузке сцены сливаются в один большой меш. Включается флажком "Static" в Inspector. 2. **Dynamic Batching** — Unity на лету сливает маленькие меши (до ~300 вершин) с одинаковым материалом. Дорогой CPU-проход, в URP отключён по умолчанию. 3. **GPU Instancing** — одним draw call'ом рисуется N экземпляров одного меша с разными матрицами. Включается в материале флажком "Enable GPU Instancing". 4. **SRP Batcher** — для URP/HDRP. Объединяет рендер объектов с шейдерами, совместимыми с SRP Batcher (большинство URP-шейдеров такие). По умолчанию включён. 5. **GPU Resident Drawer** *(Unity 6, главная новинка)* — следующий шаг после SRP Batcher. На базе **BatchRendererGroup** автоматически батчит **тысячи повторяющихся мешей** (декорации, скалы, листва) без ручной настройки MultiMesh. Значительно снижает CPU-нагрузку на сценах с большим количеством объектов. ### Как включить GPU Resident Drawer Требования жёсткие — пропуск любого пункта приводит к silent fail: 1. **URP Asset → Rendering → Rendering Path = `Forward+`** (обязательно; Forward / Deferred не поддерживаются). 2. **URP Asset → Rendering → GPU Resident Drawer = `Instanced Drawing`**. 3. **SRP Batcher** включён в URP Renderer (включён по умолчанию). 4. **Project Settings → Graphics → Shader Stripping → BatchRendererGroup Variants = `Keep All`**. Иначе шейдеры урезаются в билде, и GRD молча не работает. 5. Шейдеры объектов должны быть **DOTS Instancing-совместимы**: `#pragma target 4.5` + объявленный `DOTS_INSTANCING_ON` keyword. Стандартные URP/Lit/Unlit удовлетворяют. **BatchRendererGroup** — низкоуровневый Unity API, на котором GRD построен. GRD прозрачно работает и со статичной геометрией (Static-флажок), и с динамическими `MeshRenderer`-инстансами — главное, чтобы шейдер поддерживал DOTS Instancing. Вместе с GPU Occlusion Culling (тоже Unity 6) даёт огромный прирост в open-world сценах: скалы за горизонтом не идут в draw call'ы вообще, видимые батчатся в десятки call'ов вместо тысяч. ### Frame Debugger `Window → Analysis → Frame Debugger`. Останавливает кадр и показывает все draw call'ы пошагово. Понимать, что туда упёрлось — половина успеха в оптимизации рендера. ## Post-processing (Volume Framework) В URP/HDRP вместо отдельного компонента используется **Volume**. Это GameObject с компонентом `Volume` и привязанным **Volume Profile** — ассетом с настройками эффектов. Эффекты: - **Bloom** — свечение от ярких пикселей. - **Tonemapping** — преобразование HDR в LDR (ACES, Neutral, ...). - **Color Adjustments** — экспозиция, контраст. - **Vignette** — затемнение по краям. - **Depth of Field** — размытие вне фокуса. - **Motion Blur** — размытие при быстром движении. - **Chromatic Aberration** — разделение цветовых каналов по краям. Volume бывает **Global** (применяется везде) или **Local** (с триггер-зоной — эффект только в этой области). Удобно: вход в пещеру — Local Volume с другим tonemapping и vignette. Bloom + Motion Blur + Vignette + Depth of Field — типичный набор инди-разработчика, который "хочет как в фильме". На мобильных это легко съест половину бюджета кадра. Включайте только то, что работает на ваш визуальный язык. В следующей главе — освещение, без которого даже идеальные материалы выглядят плоско. --- ## [Unity] Освещение и тени URL: https://cadmus.page/unity/02-3d/07-lighting/ Section: 3D-разработка в Unity Description: Directional, Point, Spot — и почему статичный свет можно "запечь" заранее. Свет в 3D-сцене — то, что превращает геометрию из плоского силуэта в объёмную картинку. Unity поддерживает realtime и baked (запечённое) освещение, и в реальном проекте они комбинируются. ## Типы источников света - **Directional Light** — солнце. Бесконечно далёкий источник параллельных лучей. Один на сцену обычно достаточно. - **Point Light** — лампочка. Свет во все стороны от точки, с радиусом затухания. - **Spot Light** — фонарик. Конус из точки, с углом и радиусом. - **Area Light** — прямоугольный/дисковый источник для запекания (в realtime — только в HDRP). Каждый Light имеет цвет, интенсивность (в люменах или unitless — зависит от пайплайна) и mode: **Realtime**, **Mixed** или **Baked**. ## Realtime, baked и mixed | Mode | Когда вычисляется | Стоимость | Применение | |---|---|---|---| | **Realtime** | Каждый кадр | Высокая | Движущиеся источники, время суток | | **Baked** | Один раз в редакторе | Нулевая в рантайме | Статичная архитектура, интерьеры | | **Mixed** | Прямой — realtime, непрямой — baked | Средняя | Главный солнечный свет, динамические тени | В большинстве проектов главный directional свет ставят в Mixed-режим: прямые тени динамические, непрямое освещение и тени от статичной геометрии запечены. Это лучший баланс качества и производительности. ## Baked GI и Light Probes **Global Illumination (GI)** — это про непрямое освещение: свет, отражённый от стен и пола. В Unity GI запекается через **Progressive Lightmapper** (CPU или GPU). Результат — текстуры **lightmap** для статичной геометрии. Для динамических объектов lightmap не подходит — они двигаются. Вместо этого используется **Light Probes**: сетка "сэмплов" освещения, размещённая в сцене (компонент `LightProbeGroup`). Динамический объект интерполирует освещение из ближайших probe'ов. В Unity 6 (URP/HDRP 17+) появились **Adaptive Probe Volumes (APV)** — автоматическая сетка проб с переменной плотностью (больше проб в детализированных зонах). Не нужно вручную ставить LightProbeGroup. **Активация для URP**: `Edit → Project Settings → Quality → дабл-клик на активный URP Asset → секция Lighting → Light Probe Lighting → Light Probe System: Adaptive Probe Volumes`. **Запекание spacing** — в `Window → Rendering → Lighting → Adaptive Probe Volumes` (отдельная вкладка). Для новых проектов — стандартный выбор. ``` Запекание (грубо): 1. Отметьте статичные объекты как "Static" → Contribute GI. 2. Разместите Light Probes в нужных зонах (где могут быть динамические объекты). 3. Window → Rendering → Lighting → Generate Lighting. 4. Ждёте от минут до часов в зависимости от сцены. ``` **Reflection Probes** — то же самое для отражений. Сферические или box-проектируемые карты окружения, по которым материалы (особенно металлические) узнают, что отражать. ## Тени У каждого Light есть параметры теней: - **Shadow Type** — `No Shadows` / `Hard Shadows` / `Soft Shadows`. - **Shadow Resolution** — разрешение shadow map. - **Shadow Strength / Bias / Normal Bias** — настройка артефактов (acne, peter-panning). Realtime-тени — одна из самых дорогих вещей в кадре. На мобильных часто отключают все тени, кроме одной от directional light, и то с малым shadow distance (URP позволяет настраивать каскадные тени и их дальность). Light Cookie — текстура, проецируемая через источник света (как стенсил кинопроектора). Полезно для красивых паттернов от листвы. Но это дополнительный sampler в шейдере — на слабом железе избегайте. ## Skybox и Ambient **Skybox** — фон в дальнем поле, обычно cubemap. Также служит источником ambient-освещения: даже без прямых источников мир не будет в полной темноте. Настраивается в **Window → Rendering → Environment**: - **Skybox Material** — материал с типом Skybox/Cubemap или Skybox/Procedural. - **Source** для Environment Lighting: Skybox / Gradient / Color. - **Intensity Multiplier** — насколько ярко окружение освещает сцену. Хороший приём для уличной сцены — HDRI cubemap из реальной фотосферы (есть библиотеки бесплатных HDRI, например PolyHaven). HDRI задаёт сразу и фон, и общее освещение. ## Что обычно "ломает" освещение у новичка 1. **Слишком много Point Light'ов в realtime.** В URP forward-рендере каждый light добавляет стоимость на каждый видимый объект. Ограничьте 4–8 активных, остальные — baked или disable по расстоянию. 2. **Все объекты non-static, поэтому GI не работает.** Помечайте статичную геометрию как Static и запекайте. 3. **Auto Generate включён на больших сценах.** Это пересчитывает GI после каждого изменения — медленно. Выключите и запекайте вручную через "Generate Lighting". 4. **Нет Light Probes** в зонах с динамическими объектами — они выглядят неосвещёнными или плоскими. В следующей главе — анимация: как двигается ваш персонаж. --- ## [Unity] Анимация и Animator Controller URL: https://cadmus.page/unity/02-3d/08-animation/ Section: 3D-разработка в Unity Description: Animation clips, state machine, blend trees — как персонаж не выглядит куклой. В Unity анимация — не просто движение Transform. Это система **Mecanim**: Animation Clip + Animator Controller (state machine) + параметры + переходы. ## Animation Clip Clip — это `.anim` файл с записанными значениями свойств по времени. Источники клипов: - **Импортированные из FBX** — анимации, сделанные в Blender/Maya/Motion Capture. - **Записанные в редакторе** через Animation window (`Window → Animation → Animation`). - **Купленные** из Asset Store (Mixamo и т.п.). Каждая дорожка clip'а — путь к свойству относительно корня (например, `Hips/Spine/Head -> rotation`) и набор keyframes. Unity интерполирует между ключами. ## Animator Controller Это state machine: вершины — состояния (clip'ы или sub-state machines), рёбра — переходы с условиями. Условия — параметры контроллера: - `Float` — скорость - `Int` — комбо-номер - `Bool` — isJumping - `Trigger` — одноразовый "выстрелить переход", сбрасывается автоматически ``` Idle ───── Speed > 0.1 ────► Run ▲ │ │ Speed < 0.1 │ └────────────────────────────┘ Любое состояние ── isJumping=true ──► Jump │ ▼ on Land trigger (возврат в Idle) ``` `Any State` — спецвершина, которая переходит куда угодно при выполнении условия. Удобно для "получил урон → реакция", но осторожно: переход срабатывает и на саму себя, если не отключить "Can Transition To Self". ```csharp public class PlayerAnim : MonoBehaviour { private Animator _animator; private void Awake() => _animator = GetComponent(); public void OnMove(float speed) { _animator.SetFloat("Speed", speed); } public void OnJump() { _animator.SetTrigger("Jump"); } } ``` Строковый поиск параметров в `SetFloat("Speed", ...)` дороже, чем через хеш: `_animator.SetFloat(Animator.StringToHash("Speed"), ...)`. Кэшируйте хеши в статических полях. ## Blend Tree Часто нужна не "одна анимация → другая", а плавный переход между похожими. Например, ходьба ↔ бег ↔ стояние. Это **Blend Tree**: внутри состояния клипы смешиваются по параметру. 1D Blend Tree: один float (скорость), смешивает Idle (0), Walk (2), Run (6). 2D Blend Tree: два float (например, `InputX` + `InputY`), смешивает 8 направлений ходьбы. Это и есть стандартный приём для locomotion в шутерах и экшенах. ## Root Motion По умолчанию Animator меняет позы скелета, но не двигает корень GameObject. Если в клипе анимация "шаг вперёд" — игрок останется на месте, ноги "будут шагать". **Root Motion** включает использование смещения корневой кости как реального движения GameObject. Полезно для cinematic-анимаций (драка с захватом, добивание), где движение точно соответствует анимации. Когда не использовать: классический шутер, где скорость определяется кодом и логикой. Тогда Root Motion отключают, а Animator только показывает анимацию. ## Avatar и humanoid retargeting Unity различает Generic и Humanoid скелеты. **Humanoid Avatar** — стандартизированная карта костей (hips, spine, chest, head, плечи/руки/кисти, ноги/стопы — и опциональные кости пальцев), куда мапятся реальные кости вашего скелета. Это даёт **retargeting**: анимация, записанная для одного скелета, идёт и на другом — у NPC и игрока разные модели, одна библиотека анимаций. При импорте FBX выберите Rig → Animation Type → Humanoid, дальше "Configure Avatar" — там Unity автоматически назначит кости, проверьте и сохраните. ## Animation Events В Animation Window есть таймлайн с событиями — можно вызывать методы вашего C#-кода в конкретный кадр клипа. Классический пример — звук шага именно когда стопа касается земли: ```csharp // Метод должен быть на компоненте того же GameObject (или его детей) public void FootStep_AnimEvent() { AudioSource.PlayClipAtPoint(_footstepClip, transform.position); } ``` В импорте клипа можно добавить event на нужный кадр и указать имя метода. Подписей — только один параметр (float/int/string/object). ## Animation Rigging — процедурная анимация Пакет **Animation Rigging** позволяет накладывать процедурные constraint'ы поверх Animator'а: - **TwoBoneIK** — наклонить руку, чтобы кисть достала до точки. - **Aim Constraint** — повернуть голову или оружие к цели. - **Multi-Parent Constraint** — рука держится за дверь, когда персонаж её открывает. Это спасает в ситуациях, когда чистой клип-анимации недостаточно, а полноценный IK-солвер писать не хочется. ## Animator vs Animation: что это вообще Есть два компонента, и они исторически путаются: - **Animator** — современный, с state machine. Используется в 99% новых проектов. - **Animation** — старый (legacy), без state machine. Простой API `animation.Play("clip")`. Не использовать в новом коде. В новом проекте — только Animator. В следующей главе — звук, который не менее важен для ощущения мира. --- ## [Unity] 3D-аудио и AudioMixer URL: https://cadmus.page/unity/02-3d/09-audio/ Section: 3D-разработка в Unity Description: AudioSource, AudioListener, 3D-затухание и маршрутизация через миксер. Звук в 3D-игре — не просто mp3 в фоне. Это пространственное звучание: шаги слева, выстрел за спиной, шёпот за стеной. Unity даёт встроенный 3D-аудиодвижок, для большинства задач достаточный. ## Главные компоненты - **AudioListener** — "уши" в сцене. Один на сцену. По умолчанию висит на главной Camera. - **AudioSource** — источник звука. Прикрепляется к GameObject. Воспроизводит AudioClip. - **AudioClip** — ассет с импортированным wav/mp3/ogg. ```csharp public class Footsteps : MonoBehaviour { [SerializeField] private AudioSource source; [SerializeField] private AudioClip[] clips; // Вызывается из Animation Event public void Footstep_AnimEvent() { var clip = clips[Random.Range(0, clips.Length)]; source.pitch = Random.Range(0.95f, 1.05f); source.PlayOneShot(clip); } } ``` `PlayOneShot` хорош тем, что не прерывает уже играющий звук — несколько шагов могут звучать наложением. Просто `source.Play()` сбросит текущее воспроизведение. ## 3D vs 2D звук У AudioSource есть слайдер **Spatial Blend**: 0 = 2D (играет одинаково, где бы ни был слушатель), 1 = 3D (зависит от расстояния и направления). Музыка и UI — обычно 2D, диегетические звуки (выстрелы, шаги) — 3D. Для 3D-режима настраиваются: - **Min Distance / Max Distance** — на каком расстоянии громкость максимальна и где она падает в ноль. - **Volume Rolloff** — кривая затухания: Linear, Logarithmic, Custom. - **Spread** — насколько широко источник "размазан" в стерео. - **Doppler Level** — насколько применять эффект Доплера при движении. Звук в реальности падает по обратному квадрату расстояния — это близко к Logarithmic. Linear проще управляется, но звучит фейково. Для важных звуков (выстрелы) используйте Logarithmic с тонкой настройкой Min/Max. ## AudioMixer — маршрутизация и эффекты `AudioMixer` — отдельный ассет (Create → Audio Mixer), описывающий каналы (groups). Стандартная структура — `Master → Music + SFX + Voice`. У каждого канала — громкость, экспонируемая в код: ```csharp [SerializeField] private AudioMixer mixer; public void SetMusicVolume(float linear) { // Преобразуем линейную громкость в дБ float db = linear > 0.0001f ? Mathf.Log10(linear) * 20f : -80f; mixer.SetFloat("MusicVolume", db); } ``` Параметры миксера ("MusicVolume") нужно вручную "Expose to Script" — клик правой кнопкой на слайдере группы → Expose. К каналу можно подключить эффекты: low-pass filter, reverb, compressor. Удобно для: - Подводный эффект (low-pass на Master, когда игрок под водой). - Реверберация в комнате (Reverb Zone + reverb-эффект в миксере). - Глушение SFX, когда играет диалог (sidechain через duck volume). ## AudioSource.PlayClipAtPoint — одноразовый источник Удобный helper, когда нужно проиграть звук в точке и забыть: ```csharp AudioSource.PlayClipAtPoint(_explosionClip, transform.position, volume: 1f); ``` Unity создаст временный GameObject с AudioSource, проиграет и уничтожит. Не подходит для контроля звука (нельзя остановить, изменить громкость на лету), но идеален для коротких эффектов. ## AudioMixer Snapshots — состояния микшера Snapshot — это **именованное состояние всех слайдеров и эффектов** микшера. Через AudioMixer можно плавно блендить между snapshot'ами — это main path для "ducking" музыки во время диалогов, смены атмосферы при входе в комнату, ducking SFX в paused state. В Audio Mixer-окне: 1. Создайте AudioMixer (если ещё нет) → правый клик на нём → Add Snapshot. 2. Сохраните несколько snapshot'ов: `Default`, `Underwater`, `Paused`, `Combat`. 3. В коде вызывайте `snapshot.TransitionTo(duration)`: ```csharp [SerializeField] private AudioMixerSnapshot defaultSnap; [SerializeField] private AudioMixerSnapshot underwaterSnap; public void OnEnterWater() { underwaterSnap.TransitionTo(0.5f); // 0.5 секунды плавного перехода } public void OnExitWater() { defaultSnap.TransitionTo(0.5f); } ``` Снапшот не просто меняет громкость — он переключает **все экспонируемые параметры** и **состояния эффектов** (включение/выключение low-pass, EQ-кривые и т.д.). Это быстрее и аккуратнее, чем менять параметры по одному из кода. ## Подводные камни 1. **Один AudioListener.** Если случайно появилось два (например, в обоих сценах при аддитивной загрузке), будет варнинг и звук может звучать странно. Проверяйте в редакторе. 2. **Сжатие на мобильных.** mp3/ogg распакуется на лету. Для коротких звуков ставьте `Load Type: Decompress On Load` (один раз распакуется, в памяти PCM). Для длинных — `Streaming`. 3. **Forced To Mono.** Если 3D-звук импортирован как стерео, Unity может "потерять" пространственный эффект — конкретный канал стерео всегда играет с одной стороны. Включайте Force To Mono для 3D SFX. В следующей главе — UI: HUD, меню, диалоги, и как они вообще существуют в 3D-сцене. --- ## [Unity] UI в 3D-игре — uGUI и UI Toolkit URL: https://cadmus.page/unity/02-3d/10-ui/ Section: 3D-разработка в Unity Description: Canvas, RectTransform, EventSystem — и две конкурирующие UI-системы. В Unity 6 сосуществуют две UI-системы: - **uGUI** (Unity UI) — то, что Unity использовал годами. Через GameObject и Canvas. - **UI Toolkit** — новая система на UXML + USS (HTML + CSS аналогах). Для редактора уже стандарт, для runtime — стабильна и рекомендована для нового UI, но многие проекты всё ещё на uGUI. Начнём с uGUI как с более распространённого, потом — UI Toolkit. ## uGUI: Canvas и RectTransform UI в uGUI живёт внутри Canvas — специального GameObject. Дочерние UI-элементы используют **RectTransform** (расширенный Transform с anchors, pivot и размером). Три режима Canvas: | Render Mode | Где рисуется | Когда выбирать | |---|---|---| | **Screen Space – Overlay** | Поверх всего, отдельный pass | HUD, главное меню | | **Screen Space – Camera** | Перед камерой, на заданной дистанции | Если UI должен попасть в постпроцесс | | **World Space** | В 3D-сцене как обычный объект | Полоска здоровья над врагом, экран компьютера в комнате | ### Anchors — основа респонсивности Anchor — точка на родительском элементе, к которой "приклеена" сторона текущего. По сути — это аналог CSS `position: absolute; top/left/right/bottom`. Если anchor min и max совпадают (например, обе в (0.5, 0.5) — центр), элемент имеет фиксированный размер. Если разнесены (min = (0, 0), max = (1, 1)) — элемент растягивается по всему родителю. Flexbox/Grid делают разметку декларативно — описываете правила, браузер раскладывает. CSS-классы позволяют переиспользовать стили. uGUI ближе к "absolute layouts с якорями". Для flex-подобной раскладки есть компоненты: `HorizontalLayoutGroup`, `VerticalLayoutGroup`, `GridLayoutGroup` — но они императивно пересчитывают layout каждый кадр и могут быть медленными при большом количестве элементов. ### Базовые компоненты uGUI - **Image** — картинка (sprite или solid color). - **RawImage** — texture без sprite-настроек. - **TextMeshPro – Text (UI)** — текст. **Используйте TMP, не Legacy Text** — TMP даёт SDF-шрифты с чётким масштабированием и нормальное форматирование (rich text, outline, gradient). - **Button** — кнопка с подсветками и onClick. - **Toggle** / **Slider** / **InputField (TMP)** — стандартные контролы. - **ScrollRect** — прокручиваемый контент. - **Mask** / **RectMask2D** — отсечение по форме. ```csharp public class HudController : MonoBehaviour { [SerializeField] private TMPro.TextMeshProUGUI scoreText; [SerializeField] private UnityEngine.UI.Image healthBar; public void SetScore(int score) { scoreText.text = $"Очки: {score}"; } public void SetHealth(float normalized) { healthBar.fillAmount = Mathf.Clamp01(normalized); } } ``` ### EventSystem Для работы кнопок и любого ввода в UI в сцене нужен **EventSystem** (один). Он маршрутизирует нажатия, наведения, тач-события на UI-элементы. Создаётся автоматически, когда вы добавляете Canvas через меню "GameObject → UI → ...". Каждое изменение размера или позиции UI-элемента помечает Canvas как "dirty" и пересобирает его меш. Анимация UI с большим количеством элементов на одном Canvas съест FPS. Разбивайте на несколько Canvas (один — статичный HUD, отдельный — анимированные числа урона), или используйте UI Toolkit. ## UI Toolkit: UXML + USS UI Toolkit живёт другими понятиями. Вы описываете дерево элементов в **UXML** (XML), стилизуете в **USS** (близко к CSS), и подключаете к сцене через компонент `UIDocument`. ```xml ``` ```css /* HUD.uss */ .hud { position: absolute; top: 16px; left: 16px; } .score { color: white; font-size: 24px; } .bar { width: 200px; height: 12px; background-color: red; border-radius: 6px; } ``` ```csharp public class HudUI : MonoBehaviour { private Label _scoreLabel; private VisualElement _healthBar; private void OnEnable() { var root = GetComponent().rootVisualElement; _scoreLabel = root.Q