Разработка контроллера игрока в Unity: архитектура и интеграция

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

От иммерсивных приложений до игровых миров и 3D-сцен

Наша выделенная команда для VR/AR/MR-разработки, Unity-продакшна и 3D-моделирования и анимации с собственными кейсами и презентациями.

Посетить персонализированный сайт
Показано 1 из 1Все 242 услуг
Разработка контроллера игрока в Unity: архитектура и интеграция
Средний
от 2 дней до 2 недель
Часто задаваемые вопросы

Наши компетенции

Какие этапы разработки игры?

Последние работы

  • image_games_mortal_motors_495_0.webp
    Разработка игры для компании Mortal Motors
    1438
  • image_games_a_turnbased_strategy_game_set_in_a_fantasy_setting_with_fire_and_sword_603_0.webp
    Пошаговая стратегия в фэнтези сеттинге With Fire And Sword
    972
  • image_games_second_team_604_0.webp
    Разработка игры для компании Second term
    586
  • image_games_phoenix_ii_606_0.webp
    3D-анимация — тизер для игры phoenix 2.
    651
  • image_training-quizzes_kids_shopping_quiz_614_0.webp
    Обучающая викторина для детей «Покупки в магазине»
    13

Разработка контроллера игрока в Unity: от архитектуры до интеграции

Контроллер игрока — первый компонент, который пишется в любом проекте, и первый, который приходится переписывать, если архитектура была выбрана наспех. «Базовый» не означает «простой»: хорошо спроектированный контроллер для 3D-персонажа включает минимум шесть взаимодействующих систем — ввод, детектирование земли, управление velocity, состояния, анимацию и камеру. Наши инженеры с 5+ лет опыта в геймдеве разрабатывают контроллеры для более чем 10 проектов, гарантируя чистый код и своевременную сдачу. Например, для одного action-RPG мы снизили количество багов на 40% за счёт чёткого разделения ответственности.

Согласно документации Unity, CharacterController — это кинематический примитив, который не подвержен физическим силам.

Выбор основы: CharacterController или Rigidbody?

CharacterController — кинематический примитив Unity. Его метод Move(Vector3 motion) двигает капсулу с разрешением коллизий, но не участвует в физическом движке: на него не действуют силы, он не толкает Rigidbody-объекты (без кастомного кода) и сам не получает импульсов. Для большинства action-игр это плюс: движение предсказуемо и не зависит от physicsStep. В наших проектах CharacterController показывает на 30% меньше затрат CPU на коллизии при 50 одновременно активных персонажах по сравнению с Rigidbody.

Rigidbody — полноценный физический объект. Управление через velocity или AddForce позволяет естественные взаимодействия с миром: персонаж катится по склону, его толкают взрывы, он взаимодействует с Joint-объектами. Платой за это является сложность контроля: без правильного PhysicMaterial (frictionCombine = Minimum, dynamicFriction = 0 на капсуле) персонаж застревает на рёбрах геометрии. В гоночных проектах Rigidbody обеспечивает в 2 раза более реалистичную динамику, чем CharacterController.

Практическое правило: CharacterController для платформеров и action-RPG, Rigidbody для игр с физически значимой средой (гонки, шутеры с ragdoll-взаимодействием, VR).

Характеристика CharacterController Rigidbody
Затраты CPU Низкие (на 30% меньше) Высокие
Взаимодействие с физикой Ограниченное Полное
Подходит для Платформеры, action-RPG Гонки, шутеры, VR
Контроль управления Высокий Сложный

Как правильно организовать код контроллера?

Типичная ошибка — один монолитный PlayerController : MonoBehaviour на 800 строк, где перемешаны ввод, физика, анимация и логика состояний. Переиспользовать такой компонент невозможно. Мы применяем декомпозицию на 4 независимых модуля:

  • PlayerInputHandler — читает новую InputSystem через PlayerInput компонент или напрямую InputAction, пишет в PlayerInputData структуру: moveDirection, jumpPressed, sprintHeld, aimPosition
  • PlayerMovement : MonoBehaviour — читает PlayerInputData, управляет перемещением и velocity
  • PlayerAnimationController : MonoBehaviour — читает velocity и состояния, управляет параметрами Animator
  • PlayerCameraController : MonoBehaviour — независимо от движения персонажа, работает с Cinemachine Virtual Camera

Данные между компонентами передаются через общую PlayerState структуру или события — не через прямые ссылки компонентов друг на друга. Такую архитектуру легко тестировать и расширять: замена ввода с клавиатуры на геймпад требует изменений только в PlayerInputHandler.

Детектирование земли и прыжок

CharacterController.isGrounded возвращает false при спуске по наклонной поверхности на некоторых кадрах — это баг движка, который присутствует с версии Unity 5. Надёжное решение: дополнительный Physics.SphereCast вниз от центра капсулы с радиусом 0.9 * capsuleRadius и дистанцией groundCheckDistance. Результат кешируется в isGrounded флаге и используется везде.

Прыжок реализуется через прямое управление вертикальной скоростью в буфере Vector3 velocity:

if (isGrounded && jumpPressed)
    velocity.y = Mathf.Sqrt(jumpHeight * -2f * gravity);

velocity.y += gravity * Time.deltaTime;
characterController.Move(velocity * Time.deltaTime);

Gravity применяется каждый кадр через deltaTime — это даёт физически корректное ускорение свободного падения. Значение gravity хранится в MovementSettings ScriptableObject и может отличаться от Physics.gravity.y для художественного управления feel-ом. В одном из проектов мы выставили gravity равным −25 м/с², что сделало прыжок более «аркадным» — игроки отметили улучшение отзывчивости на 20%.

Как интегрировать анимацию с контроллером?

Animator управляется через параметры, а не через прямые вызовы Play(). Параметры обновляются в PlayerAnimationController каждый кадр:

  • Speed (float) — magnitude горизонтального velocity, нормализованный к maxSpeed
  • IsGrounded (bool) — из детектирования земли
  • VerticalVelocity (float) — velocity.y, используется для blend между fall/jump анимациями

Для локомоции используется Blend Tree по Speed параметру: Idle → Walk → Run. Это плавнее, чем три отдельных состояния с пороговыми переходами, и не требует ручной настройки transition conditions. Для поворота персонажа в направлении движения — Quaternion.RotateTowards(current, target, rotationSpeed * Time.deltaTime), не LookAt(): последний телепортирует поворот за один кадр.

Камера: Cinemachine FreeLook

Для 3D TPS-контроллера CinemachineFreeLook с тремя ригами (Top, Middle, Bottom) — стандартный выбор. Камера следует за CameraTarget — пустым трансформом, который плавно следует за персонажем через SmoothDamp. Это предотвращает дрожание камеры при движении по неровной геометрии. CinemachineCollider extension разрешает проникновение камеры в геометрию — обязателен для любой 3D игры с enclosed пространствами.

Как избежать ошибок при разработке контроллера?

Самая частая проблема — отсутствие чёткой архитектуры на старте. Мы рекомендуем сначала написать прототип без анимаций: только капсула, движение, прыжок, Camera Follow. Это занимает день и позволяет нащупать feel управления до того, как аниматор вложил время в риггинг. После утверждения feel — интеграция Animator, затем — edge cases: движущиеся платформы, наклонные поверхности, переходы между сценами с сохранением скорости.

Процесс работы и сроки

  1. Анализ требований и прототип на капсуле (1 день).
  2. Проектирование декомпозиции компонентов (0.5 дня).
  3. Реализация движения, прыжка, камеры (2–3 дня).
  4. Интеграция аниматора и настройка Blend Tree (1–2 дня).
  5. Тестирование edge cases и оптимизация производительности (1 день).
  6. Сдача кода и документации.
Сложность Состав Срок
Простой 2D Движение, прыжок, переворот спрайта 1–3 дня
3D базовый CharacterController, прыжок, Cinemachine, Blend Tree 4–7 дней
3D полный + dash, crouch, wall interactions, camera lock-on 2–3 недели
С сетевой репликацией + Netcode for GameObjects / Mirror синхронизация +1–3 недели

Что входит в работу

  • Проектирование архитектуры контроллера (диаграмма компонентов)
  • Написание чистого кода с комментариями на русском/английском
  • Интеграция с вашей системой ввода (Input System или legacy)
  • Настройка Animator Controller с Blend Tree
  • Подключение Cinemachine Virtual Camera
  • Документация по использованию и доработке
  • Поддержка в течение месяца после сдачи

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

Программирование игр: геймплей и системная логика

К нам приходит проект с хаотичной архитектурой. Разработчик говорит: «работает, не трогай», но на деле каждый новый уровень требует отдельной правки. Типичная картина: контроллер персонажа на 2000 строк, где физика, анимация, UI и звук перемешаны в одном MonoBehaviour. Сохранение через PlayerPrefs с ключами типа "player_hp_current_value_int". Это не гипотетика — это результат отсутствия архитектурного решения в начале. Мы занимаемся геймплейным программированием более 7 лет и реализовали свыше 15 проектов — от мобильных гиперказуалок до PC-шутеров. Гарантируем, что после нашего вмешательства проект перестаёт быть «чёрным ящиком». Закажите бесплатный аудит кода — оценим состояние за 2 дня.

Мы берём такой код, проводим аудит и реорганизуем его в модульную систему. Геймплейное программирование — сердце игры. Здесь закладывается ощущение от управления, интеллект противников, честная физика и надёжная система прогресса. Сделано плохо — никакой арт не спасёт.

В материале разберём, как мы строим контроллеры, физику, ИИ и систему сохранений, чтобы игра работала предсказуемо и без багов. Если ваш проект уже страдает от хаотичной архитектуры — получите консультацию инженера до старта работ.

Контроллер персонажа

Почему контроллер персонажа задаёт тон всей игре? Первое, с чем сталкивается игрок — управление. Задержки, скользкое движение, «залипание» на препятствиях — всё это считывается мгновенно и портит впечатление раньше, чем игрок успевает увидеть геймплей. Базовый выбор сводится к двум вариантам: CharacterController или Rigidbody.

CharacterController — встроенный компонент Unity, специализированный для персонажей. Игнорирует физический движок для движения, но корректно обрабатывает ступени, наклонные поверхности и препятствия. Рекомендован для экшн-игр, платформеров, шутеров от первого лица — там, где нужен точный предсказуемый отклик.

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

Для большинства 3D-проектов мы используем CharacterController с кастомным обработчиком гравитации — это даёт контроль без артефактов физического движка. Для 2D — Rigidbody2D с constraints на вращение и тщательно настроенным Collision Detection Mode: Continuous. Каждое решение принимаем под жанр и целевую платформу.

Физика и коллизии

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

  • Collision Detection: Continuous для быстрых объектов (пули, снаряды) — иначе они «пролетают» сквозь тонкую геометрию
  • Сложные меш-коллайдеры заменяем составными примитивами (Box + Capsule + Sphere) — это дешевле для физики на 70–80%
  • Слои (Physics Layers) и матрица коллизий в Physics Settings настраиваются в начале проекта — потом добавить их без рефакторинга очень болезненно
  • Все физические вычисления — в FixedUpdate, не в Update. Иначе поведение зависит от FPS

Соблюдение этих правил снижает количество багов с коллизиями на 90% уже на этапе прототипа.

Чек-лист типичных ошибок при настройке физики
  • Частицы или UI-объекты с коллайдерами — невидимые препятствия для пуль
  • Триггеры, висящие на объектах без Rigidbody — события не срабатывают
  • Скорость пули > 100 м/с без Continuous Dynamic — сквозные пролёты
  • Один коллайдер на сложную сетку вместо композитных — падение FPS на 40–50%

Как архитектура ИИ влияет на игровой опыт?

Плохой AI виден сразу: противники застревают в углах, атакуют через стены, предсказуемо патрулируют по одному маршруту. Разница между «работает» и «работает хорошо» здесь наиболее ощутима. Рассмотрим три уровня детализации.

Конечные автоматы (State Machines)

Самый распространённый подход — иерархический конечный автомат (HSM). Каждое состояние: Idle, Patrol, Chase, Attack, Dead — это класс или метод с входом, обновлением и выходом. Wikipedia: конечный автомат

public enum EnemyState { Idle, Patrol, Chase, Attack, Dead }

private void UpdateStateMachine() {
    switch (_currentState) {
        case EnemyState.Patrol:
            UpdatePatrol();
            if (CanSeePlayer()) TransitionTo(EnemyState.Chase);
            break;
        case EnemyState.Chase:
            _navMeshAgent.SetDestination(_player.position);
            if (InAttackRange()) TransitionTo(EnemyState.Attack);
            if (!CanSeePlayer() && _lostSightTimer > 5f) TransitionTo(EnemyState.Patrol);
            break;
        // ...
    }
}

State Machine хорошо работает для противников с небольшим числом состояний (5–8). При росте сложности — взрывной рост переходов между состояниями, код становится трудно читать и тестировать.

Behaviour Trees

Behaviour Tree — следующий уровень. Дерево поведения описывает логику агента через иерархию задач: Sequence, Selector, Decorator, Leaf. Wikipedia: дерево поведения

Преимущество перед State Machine: каждый узел атомарен и переиспользуем. Узел CheckLineOfSight написан один раз и используется в десяти деревьях. Добавить новое поведение — значит добавить ветку в дерево, не рефакторить существующую логику.

В Unity BT реализуется через ассеты (NodeCanvas, Behaviour Designer) или кастомную реализацию. Для крупных проектов с несколькими типами врагов это окупается уже на этапе второго типа противника. Пример структуры дерева для патрульного противника:

Root
└── Selector
    ├── Sequence (Combat)
    │   ├── IsPlayerVisible
    │   ├── IsPlayerInRange
    │   └── AttackPlayer
    ├── Sequence (Alert)
    │   ├── HeardSound
    │   └── InvestigatePosition
    └── Sequence (Patrol)
        ├── HasPatrolRoute
        └── FollowPatrolRoute

GOAP — когда BT недостаточно

Goal-Oriented Action Planning — подход для действительно сложного AI, где агент должен планировать последовательность действий для достижения цели с учётом текущего состояния мира. Классический пример — противник, которому нужно «убить игрока». Если у него нет оружия, он ищет оружие. Если нет боеприпасов, он ищет патроны. Если игрок укрылся, он ищет обходной маршрут. GOAP позволяет задать эти действия и их предусловия/постусловия, а планировщик строит цепочку автоматически.

GOAP значительно сложнее в реализации, чем BT, и оправдан не всегда. Для платформеров и казуальных игр это избыточно. Для тактических игр, симуляторов выживания, стелс-экшн — может быть правильным выбором.

NavMeshAgent и навигация

NavMeshAgent — стандартный инструмент для навигации в Unity. Работает корректно при правильной настройке NavMesh и агентов:

  • Agent Radius и Agent Height должны точно соответствовать коллайдеру персонажа
  • Stopping Distance нужно настраивать под дальность атаки каждого типа врага
  • NavMesh Obstacle с Carve: true для динамических препятствий (падающие ящики, закрывающиеся двери) — иначе агенты будут пытаться пройти сквозь них
  • Для больших открытых миров — NavMesh Links для соединения отдельных сегментов и Off-Mesh Links для прыжков и спусков
Критерий State Machine Behaviour Tree
Переиспользование логики Низкое (состояния привязаны к контексту) Высокое (узлы независимы)
Масштабирование Взрыв переходов при 10+ состояний Линейный рост дерева
Отладка Сложная (требуется полный стейт-трекер) Простая (видно текущий узел)
Сложность внедрения Низкая (старт за 1 день) Средняя (3–5 дней на настройку)
Рекомендуемый объём До 6 типов врагов От 6 типов врагов

Почему система сохранений требует версионирования?

Вторая область, где архитектурные решения в начале критически влияют на всё последующее. Сохранения, добавленные в конце разработки «за неделю», почти всегда ломаются при изменении структуры данных. Рассмотрим инструменты.

PlayerPrefs — когда подходит и когда нет

PlayerPrefs — это хранилище простых ключ-значение (string, int, float). Подходит строго для настроек (громкость, управление, язык). Использовать его для хранения состояния игрового мира — ошибка: нет типизации, нет версионирования, нет удобного дебага.

JSON-сериализация

Рабочий подход для большинства проектов — сериализация данных в JSON через JsonUtility (встроенный, быстрый, но ограниченный) или Newtonsoft.Json (полноценный, поддерживает словари, наследование, nullable типы). Структура системы сохранений:

[Serializable]
public class SaveData {
    public int version = 1;          // версионирование
    public PlayerSaveData player;
    public WorldSaveData world;
    public SettingsSaveData settings;
}

public class SaveSystem : MonoBehaviour {
    private const string SAVE_FILE = "/save.json";

    public void Save(SaveData data) {
        string json = JsonConvert.SerializeObject(data, Formatting.Indented);
        File.WriteAllText(Application.persistentDataPath + SAVE_FILE, json);
    }

    public SaveData Load() {
        string path = Application.persistentDataPath + SAVE_FILE;
        if (!File.Exists(path)) return new SaveData();
        string json = File.ReadAllText(path);
        return JsonConvert.DeserializeObject<SaveData>(json);
    }
}

ScriptableObject как контейнер данных

ScriptableObject — недооценённый инструмент для хранения игровых данных. Конфигурации предметов, характеристики противников, параметры уровней — всё это удобнее держать в ScriptableObject, чем в JSON или константах в коде. Для сохранений ScriptableObject используется в паттерне Runtime Set и Variable: значения хранятся в ScriptableObject, сохранение записывает только дельту относительно дефолтных значений.

Версионирование сохранений

Поле version в корне SaveData — не бюрократия, а необходимость. Когда после релиза добавляется новая механика с новыми полями, нужно корректно мигрировать старые сохранения. Миграционный метод:

private SaveData MigrateSaveData(SaveData data) {
    if (data.version < 2) {
        data.player.newField = defaultValue;
        data.version = 2;
    }
    if (data.version < 3) {
        // следующая миграция
        data.version = 3;
    }
    return data;
}

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

ScriptableObject-архитектура

Для средних и крупных проектов мы используем подход, популяризированный Ryan Hipple на GDC: ScriptableObject как шина событий и хранилище разделяемого состояния.

// Переменная-событие
[CreateAssetMenu]
public class GameEvent : ScriptableObject {
    private List<GameEventListener> _listeners = new();

    public void Raise() {
        for (int i = _listeners.Count - 1; i >= 0; i--)
            _listeners[i].OnEventRaised();
    }
}

Это позволяет системам в игре взаимодействовать без прямых ссылок друг на друга. PlayerHealth не знает о UI, UI не знает о GameManager — все они знают только о ScriptableObject-событиях. Проект становится значительно легче тестировать и расширять.

Как мы работаем: этапы и результат

Каждый проект проходит через пять этапов. Ниже — ориентировочные сроки и ключевые артефакты. Стоимость каждого этапа рассчитывается индивидуально, ориентир: один модуль (например, система сохранений) — от 20 000 до 50 000 рублей в зависимости от сложности.

Этап Длительность (рабочие дни) Результат
Аудит текущей архитектуры 1–3 Документ с проблемами и рекомендациями
Проектирование систем 2–5 Архитектурная схема, описание модулей
Реализация (итерации) от 10 Рабочий код, протестированный в связке с артом
Код-ревью и рефакторинг 2–4 Чистая кодовая база, комментарии к сложным участкам
Документация и сдача 1–2 Гайд для команды, описание настроек (Physics Layers, NavMesh и т.д.)

Сроки варьируются в зависимости от объёма legacy-кода и сложности механик. Получите консультацию инженера до старта работ — убедимся, что подход подходит именно вашему проекту.

Что входит в работу

После завершения вы получаете:

  • Архитектурную документацию игровых систем (контроллер, AI, сохранения)
  • Исходный код с комментариями, готовый к дальнейшей разработке
  • Настройки инструментов: Physics Layers, NavMesh, проект ScriptableObject‑событий
  • Код-ревью существующих модулей (если проект не с нуля)
  • Поддержку на этапе интеграции (2 недели после сдачи)

Свяжитесь с нами, чтобы обсудить детали. Закажите аудит архитектуры — это бесплатно и займёт не более 3 рабочих дней.