← Назад к вопросам

Какими паттернами руководствуешься при написании кода

1.7 Middle🔥 151 комментариев
#Паттерны проектирования

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Принципы и паттерны написания кода в Unity

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

Основополагающие принципы

Принципы SOLID — фундамент любого поддерживаемого кода:

  • Single Responsibility (единственная ответственность): Каждый класс, особенно MonoBehaviour, отвечает за одну четкую задачу. Например, отдельный класс для движения игрока, стрельбы и здоровья.
  • Open-Closed (открытость/закрытость): Код должен быть открыт для расширения (через наследование, композицию, ScriptableObjects), но закрыт для модификаций. Это снижает риски при внесении изменений.
  • Liskov Substitution (подстановки Барбары Лисков): Наследники класса должны быть взаимозаменяемы с родителем, не ломая логику. Критически важно для создания вариаций врагов, оружия и состояний.
  • Interface Segregation (разделение интерфейсов): Много специализированных интерфейсов (IDamageable, IMovable, IInteractable) лучше одного "толстого". Это повышает гибкость и переиспользуемость.
  • Dependency Inversion (инверсия зависимостей): Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций. Достигается через интерфейсы и внедрение зависимостей, что облегчает тестирование.

Композиция предпочтительнее наследования (Composition over Inheritance) — золотое правило геймдева. Вместо глубоких иерархий (например, Enemy -> FlyingEnemy -> BossFlyingEnemy) я создаю компонуемые сущности:

public class EnemyEntity : MonoBehaviour
{
    [SerializeField] private IMovementBehaviour _movement;
    [SerializeField] private IAttackBehaviour _attack;
    [SerializeField] private Health _health;

    // Логика, объединяющая поведение
}

Такую сущность легко перенастроить, добавив новый ScriptableObject с поведением.

Ключевые архитектурные паттерны в Unity

  1. Состояние (State Pattern): Незаменим для управления сложным поведением (AI врага, анимации, этапов игры).

    public interface IPlayerState
    {
        void Enter(PlayerController player);
        void Update();
        void Exit();
    }
    
    public class RunningState : IPlayerState { ... }
    public class JumpingState : IPlayerState { ... }
    // State Machine переключает состояния, делая код чистым.
    
  2. Наблюдатель (Observer Pattern) и C# Events/UnityEvents: Для создания слабосвязанных систем. Вместо прямых вызовов FindObjectOfType<UI>().UpdateHealth() объекты реагируют на события.

    public static event Action<int> OnHealthChanged;
    // Где-то при получении урона:
    OnHealthChanged?.Invoke(currentHealth);
    // UI и другие системы подписываются на это событие.
    
  3. Стратегия (Strategy Pattern): Часто реализуется через ScriptableObjects. Позволяет менять алгоритмы (поведение движения, расчет урона) на лету.

    [CreateAssetMenu]
    public class MovementStrategy : ScriptableObject
    {
        public virtual Vector3 CalculateMove(Transform actor) { ... }
    }
    
  4. Сервис-локатор (Service Locator) с осторожностью: Для предоставления глобального доступа к менеджерам (AudioService, GameManager), но с явной регистрацией и возможностью подмены на заглушки для тестов.

  5. Модель-Представление- (MV/Entitas)** для сложных проектов: Для UI — Model-View-Presenter (MVP) или UniRX (Reactive), чтобы отделить логику данных от отображения. Для сложной игровой логики рассматриваю Data-Oriented Tech Stack (DOTS) или фреймворки вроде Entitas (ECS).

Паттерны, специфичные для Unity

  • Компонентный подход (Component-Based Architecture): Строго следую парадигме Unity. GameObject — контейнер, логика инкапсулирована в переиспользуемых компонентах.
  • Пулы объектов (Object Pooling): Обязательный паттерн для оптимизации. Все часто создаваемые/уничтожаемые объекты (пули, эффекты) берутся из предсозданного пула.
    public class ProjectilePool : MonoBehaviour
    {
        [SerializeField] private Projectile _prefab;
        private Queue<Projectile> _pool = new Queue<Projection>();
    
        public Projectile Get() {
            if (_pool.Count > 0) return _pool.Dequeue().Activate();
            return Instantiate(_prefab);
        }
        public void Return(Projectile proj) {
            proj.Deactivate();
            _pool.Enqueue(proj);
        }
    }
    
  • Использование ScriptableObjects как конфигов данных: Для хранения параметров оружия, настроек баланса, диалогов. Это позволяет дизайнерам настраивать игру без изменения кода и поддерживать Data Driven Design.

Практики написания кода

  • Принцип "Не спрашивай, а сообщай" (Tell, Don't Ask): Объекты должны получать команды и самостоятельно управлять своим состоянием, а не иметь геттеры для каждого поля.
  • Инверсия управления (IoC) через конструкторы или метод Awake(): Зависимости передаются явно, а не ищутся через GetComponent<>() внутри каждого метода.
  • Антипаттерны, которых я избегаю: "Большой скрипт-бог (God Class)", прямой поиск объектов (Find, GetComponent) в Update(), хардкод ключей и путей, нарушение инкапсуляции через public-поля без нужды.

Итоговый подход — прагматичный. Я выбираю паттерн, адекватный задаче и масштабу проекта. Для прототипа подойдет простая событийная архитектура, для мобильной гипер-казуалки — оптимизированный компонентный подход с пулами, для крупного RPG — комбинация State Machine, Strategy и строгого разделения данных через ScriptableObjects. Главная цель — создание гибкого, тестируемого и поддерживаемого кода, который может эволюционировать вместе с проектом.

Какими паттернами руководствуешься при написании кода | PrepBro