Какие абстракции вы бы использовали при реализации игры жанра шутер?

«Какие абстракции вы бы использовали при реализации игры жанра шутер?» — вопрос из категории Архитектура, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Я бы выстроил архитектуру на основе компонентного подхода (как в Entity Component System) или классического ООП с четким разделением ответственности. Вот ключевые абстракции:

  1. Сущность (Entity) / Актор (Actor): Базовый класс для всех игровых объектов (игрок, враг, снаряд, бонус). Содержит только идентификатор и список компонентов.
  2. Компоненты (Components): Данные и поведение, прикрепляемые к сущностям.
    • TransformComponent: Позиция, вращение, масштаб.
    • HealthComponent: Текущее здоровье, максимальное здоровье, события получения урона/смерти.
    • WeaponComponent: Ссылка на текущее оружие, логика переключения.
    • MovementComponent: Логика перемещения (физика, ввод игрока, ИИ).
  3. Системы (Systems): Обрабатывают логику для определенных компонентов.
    • MovementSystem: Обновляет позиции сущностей с MovementComponent.
    • CombatSystem: Обрабатывает выстрелы, попадания, нанесение урона.
    • RenderingSystem: Отвечает за отрисовку.
  4. Оружие (Weapon): Абстракция для стрелкового оружия. Реализуется через стратегию или состояние.

    public interface IWeapon
    {
        int CurrentAmmo { get; }
        int MaxAmmo { get; }
        float FireRate { get; }
        float Damage { get; }
        void Fire(TransformComponent origin);
        void Reload();
    }
    
    public class AssaultRifle : IWeapon
    {
        public int CurrentAmmo { get; private set; } = 30;
        public int MaxAmmo => 30;
        public float FireRate => 10f; // Выстрелов в секунду
        public float Damage => 25f;
        private float _lastFireTime;
    
        public void Fire(TransformComponent origin)
        {
            if (CurrentAmmo <= 0 || Time.time < _lastFireTime + 1f / FireRate)
                return;
    
            CurrentAmmo--;
            _lastFireTime = Time.time;
            // Создать снаряд (ProjectileEntity) в направлении origin.forward
            var projectile = EntityManager.CreateProjectile(origin.Position, origin.Forward, Damage);
        }
        public void Reload() { /* Логика перезарядки с анимацией и задержкой */ }
    }
  5. Снаряд (Projectile): Сущность с компонентами TransformComponent, MovementComponent (летит вперед) и CollisionComponent. При коллизии вызывает CombatSystem для нанесения урона цели.
  6. Уровень/Мир (Level/World): Контейнер для всех сущностей и систем. Управляет их жизненным циклом (создание, обновление, удаление).
  7. Сервисы (Services): Глобальные менеджеры (абстрагированные через интерфейсы для тестирования).
    • IInputService: Чтение ввода игрока.
    • IAssetService: Загрузка ресурсов.
    • IScoreService: Учет очков и статистики.

Почему такой подход? Он обеспечивает высокую гибкость (новый тип врага — это новая комбинация компонентов), упрощает тестирование систем по отдельности и облегчает поддержку кода.