Услуги по программированию геймплея

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

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

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

Посетить персонализированный сайт
Показано 5 из 5 услугВсе 242 услуг
Средняя
от 2 рабочих дней до 2 недель
Средняя
от 2 рабочих дней до 1 недели
Часто задаваемые вопросы
Наши компетенции
Какие этапы разработки игры?
Последние работы
  • image_games_mortal_motors_495_0.webp
    Разработка игры для компании Mortal Motors
    683
  • image_games_a_turnbased_strategy_game_set_in_a_fantasy_setting_with_fire_and_sword_603_0.webp
    Пошаговая стратегия в фэнтези сеттинге With Fire And Sword
    860
  • image_games_second_team_604_0.webp
    Разработка игры для компании Second term
    490
  • image_games_phoenix_ii_606_0.webp
    3D-анимация — тизер для игры phoenix 2.
    533

Геймплейное программирование

Разработчик передаёт проект со словами «там архитектура немного странная, но работает». Открываешь — и видишь: контроллер персонажа на 2000 строк, где физика, анимация, UI и звук перемешаны в одном MonoBehaviour. В Update() — проверки состояния через десяток boolean-флагов. Сохранение через PlayerPrefs с ключами типа "player_hp_current_value_int". Это не гипотетика — это типичное состояние кода в проектах, которые росли органически без архитектурного решения в начале.

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

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

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

Базовый выбор: Character Controller или 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

Глубже: архитектура ИИ противников

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

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

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

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 (BT) — следующий уровень. Дерево поведения описывает логику агента через иерархию задач: Sequence, Selector, Decorator, Leaf.

Преимущество перед 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 (GOAP) — подход для действительно сложного AI, где агент должен планировать последовательность действий для достижения цели с учётом текущего состояния мира.

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

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

NavMeshAgent и навигация

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

  • Agent Radius и Agent Height должны точно соответствовать коллайдеру персонажа
  • Stopping Distance нужно настраивать под дальность атаки каждого типа врага
  • NavMesh Obstacle с Carve: true для динамических препятствий (падающие ящики, закрывающиеся двери) — иначе агенты будут пытаться пройти сквозь них
  • Для больших открытых миров — NavMesh Links для соединения отдельных сегментов и Off-Mesh Links для прыжков и спусков

Глубже: система сохранений

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

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-событиях. Проект становится значительно легче тестировать и расширять.

Что мы делаем в рамках этой услуги

  • Проектирование и реализация контроллера персонажа (CharacterController или Rigidbody в зависимости от жанра)
  • Настройка физики и системы коллизий
  • Реализация AI: State Machine, Behaviour Tree, GOAP — в зависимости от сложности врагов
  • Настройка навигации: NavMesh, NavMeshAgent, динамические препятствия
  • Проектирование и реализация системы сохранений с версионированием
  • Аудит существующего кода: выявление архитектурных проблем, рефакторинг узких мест
  • Code review и написание документации по игровым системам