Исходный код tower defense игры, построенной на Entity Component System (Entitas) с использованием MVP паттерна для UI и Dependency Injection
Исходный код tower defense игры с 8 типами башен, системой слияния, волнами врагов и динамическими лабиринтами. Реализована модульная архитектура с чётким разделением слоёв и ответственности.
GEMTD/
├── Scripts/
│ ├── ECS/ # Entity Component System
│ │ ├── Game/
│ │ │ ├── Features/ # Модульные игровые системы
│ │ │ │ ├── Towers/ # Система башен
│ │ │ │ ├── Enemies/ # ИИ и спавн врагов
│ │ │ │ ├── Players/ # Игровые системы
│ │ │ │ ├── Battle/ # Боевая механика
│ │ │ │ └── Selection/ # Система выбора объектов
│ │ │ └── Generated/ # Автогенерируемый код Entitas
│ │ └── GameEntityFactories.cs # Фабрики сущностей
│ ├── Infrastructure/ # Слой приложения
│ │ ├── States/ # Управление состояниями
│ │ ├── Installers/ # Настройка DI контейнера
│ │ └── Scenes/ # Менеджмент сцен
│ ├── Services/ # Сервисы
│ │ ├── AssetProviders/ # Управление ресурсами
│ │ ├── AudioServices/ # Звуковая система
│ │ └── Cameras/ # Управление камерами
│ ├── UserInterface/ # MVP архитектура UI
│ │ ├── GameplayHeadsUpDisplay/ # Игровой HUD
│ │ ├── MainMenu/ # Главное меню
│ │ └── MazeSelectorMenu/ # Выбор уровня
│ └── Tools/ # Инструменты разработки
│ └── MazeDesigner/ # Кастомный редактор лабиринтов
Игровая логика построена на ECS архитектуре, где данные хранятся в компонентах, а логика выполняется в системах. Сущности башен, врагов и игровых объектов управляются через единый GameContext.
Весь UI разделён на View (Unity MonoBehaviour), Presenter (бизнес-логика) и Model (ECS сущности). Каждая панель интерфейса имеет свой презентер.
Все зависимости между сервисами и системами управляются через DI контейнер. Настройка происходит в MonoInstaller классах для каждой сцены.
Переходы между игровыми экранами (меню, загрузка, геймплей) управляются через конечный автомат состояний.
Компонент башни (TowerComponent.cs):
using Entitas;
namespace Game.Towers
{
public class TowerComponent : IComponent
{
}
}Компонент типа башни (TowerEnumComponent.cs):
using Entitas;
namespace Game.Towers
{
[Game]
public class TowerEnumComponent : IComponent
{
public TowerEnum Value;
}
}Система поворота башен (RotateTowerSystem.cs):
using Entitas;
using UnityEngine;
namespace Game.Towers
{
public class RotateTowerSystem : IExecuteSystem
{
private readonly GameContext _game;
private readonly IGroup<GameEntity> _towers;
public RotateTowerSystem(GameContext game)
{
_game = game;
_towers = game.GetGroup(GameMatcher
.AllOf(
GameMatcher.TowerEnum,
GameMatcher.WorldPosition,
GameMatcher.Rotation
)
);
}
public void Execute()
{
foreach (GameEntity tower in _towers)
{
if (!tower.hasTargetId)
continue;
GameEntity target = _game.GetEntityWithId(tower.TargetId);
Vector3 direction = target.WorldPosition - tower.WorldPosition;
direction.y = 0;
Quaternion rotation = Quaternion.LookRotation(direction);
tower.ReplaceRotation(rotation);
}
}
}
}Базовый класс Presenter (Presenter.cs):
namespace UserInterface
{
public abstract class Presenter<T> where T : View
{
protected T View { get; }
protected Presenter(T view)
{
View = view;
}
public void Show() => View.Show();
public virtual void Hide() => View.Hide();
}
}Базовый класс View (View.cs):
using UnityEngine;
namespace UserInterface
{
public abstract class View : MonoBehaviour
{
public void Show()
{
gameObject.SetActive(true);
}
public void Hide()
{
gameObject.SetActive(false);
}
}
}Installer для геймплея (GameplayInstaller.cs):
using Services.Cameras;
using Services.ViewContainerProviders;
using UnityEngine;
using Zenject;
namespace Infrastructure.Scenes.Hubs
{
public class GameplayInstaller : MonoInstaller, IInitializable
{
[Inject]
private ViewContainerProvider _viewContainerProvider;
[Inject]
private ICameraProvider _cameraProvider;
public Transform Common;
public Transform Blocks;
public Transform Enemies;
public Transform Map;
public Camera Camera;
public override void InstallBindings()
{
Container.BindInterfacesAndSelfTo<GameplayInstaller>().FromInstance(this).AsSingle().NonLazy();
}
public void Initialize()
{
_cameraProvider.Camera = Camera;
_viewContainerProvider.CommonContainer = Common;
_viewContainerProvider.BlockContainer = Blocks;
_viewContainerProvider.EnemyContainer = Enemies;
_viewContainerProvider.MapContainer = Map;
}
}
}Состояние игрового цикла (BattleLoopState.cs):
using Game.GameMainFeature;
using Infrastructure.States.StateInfrastructure;
using Services.SystemsFactoryServices;
using UserInterface.GameplayHeadsUpDisplay;
namespace Infrastructure.States.GameStates
{
public class BattleLoopState : IState, IUpdateable
{
private readonly ISystemFactory _systems;
private readonly GameContext _gameContext;
private readonly GameplayHeadsUpDisplayPresenter _gameplayHeadsUpDisplayPresenter;
private GameplayFeature _gameplayFeature;
public BattleLoopState(
ISystemFactory systems,
GameContext gameContext,
GameplayHeadsUpDisplayPresenter gameplayHeadsUpDisplayPresenter
)
{
_systems = systems;
_gameContext = gameContext;
_gameplayHeadsUpDisplayPresenter = gameplayHeadsUpDisplayPresenter;
}
public void Enter()
{
_gameplayFeature = _systems.Create<GameplayFeature>();
_gameplayFeature.Initialize();
_gameplayHeadsUpDisplayPresenter.Enable();
}
public void Update()
{
if (_gameplayFeature == null)
return;
_gameplayFeature.Execute();
_gameplayFeature?.Cleanup();
}
public void Exit()
{
_gameplayFeature.DeactivateReactiveSystems();
_gameplayFeature.ClearReactiveSystems();
DestructEntities();
_gameplayFeature.Cleanup();
_gameplayFeature.TearDown();
_gameplayFeature = null;
_gameplayHeadsUpDisplayPresenter.Disable();
}
private void DestructEntities()
{
foreach (GameEntity entity in _gameContext.GetEntities())
entity.isDestructed = true;
}
}
}Организация систем башен (TowerFeature.cs):
using Game.Towers.MergeSpirits.Systems;
using Game.Towers.SelectSpirits.Systems;
using Services.SystemsFactoryServices;
namespace Game.Towers
{
public sealed class TowerFeature : Feature
{
public TowerFeature(ISystemFactory systems)
{
Add(systems.Create<RotateTowerSystem>());
Add(systems.Create<SelectSpiritSystem>());
Add(systems.Create<SpiritPlacedReactiveSystem>());
MergeSystems(systems);
}
private void MergeSystems(ISystemFactory systems)
{
Add(systems.Create<MarkMergeableSpiritsSystem>());
Add(systems.Create<FilterMergeableSpiritsSystem>());
Add(systems.Create<FinilizeMergeSpiritsSystem>());
}
}
}Реальный пример MVP Presenter (GameplayHeadsUpDisplayPresenter.cs):
using System;
using Entitas;
using Game.Battle;
using Infrastructure.States.StateMachine;
using Services.StaticData;
using UnityEngine;
using UserInterface.GameplayHeadsUpDisplay.CheatsPanel;
using UserInterface.GameplayHeadsUpDisplay.FinishPanel;
using UserInterface.GameplayHeadsUpDisplay.GoldHealthPanels;
using UserInterface.GameplayHeadsUpDisplay.InfoPanel;
using UserInterface.GameplayHeadsUpDisplay.ObjectInfoPanel;
using UserInterface.GameplayHeadsUpDisplay.PlayerAbilityPanel;
using UserInterface.GameplayHeadsUpDisplay.PlayerAbilityPanel.SwapAbility;
using UserInterface.GameplayHeadsUpDisplay.PlayerPanel;
using UserInterface.GameplayHeadsUpDisplay.TimerPanel;
using Zenject;
namespace UserInterface.GameplayHeadsUpDisplay
{
public class GameplayHeadsUpDisplayPresenter :
Presenter<GameplayHeadsUpDisplayView>,
IInitializable
{
private readonly ChooseTowerPanelPresenter _chooseTowerPanelPresenter;
private readonly TowerMergePanelPresenter _towerMergePanelPresenter;
private readonly TimerPanelPresenter _timerPanelPresenter;
private readonly PlayerPanelPresenter _playerPanelPresenter;
private readonly PlayerAbilityPanelPresenter _playerAbilityPanelPresenter;
private readonly CheatsPanelPresenter _cheatsPanelPresenter;
private readonly InfoPanelPresenter _infoPanelPresenter;
private readonly TowerSwapAbilityPanelPresenter _towerSwapAbilityPanelPresenter;
private readonly PausePanelPresenter _pausePanelPresenter;
private readonly FinishPanelPresenter _finishPanelPresenter;
private readonly ObjectInfoPanelPresenter _objectInfoPanelPresenter;
private readonly GoldHealthPanelPresenter _goldHealthPanelPresenter;
private readonly GameContext _gameContext;
private readonly IGameStateMachine _stateMachine;
private readonly IStaticDataService _staticDataService;
private IGroup<GameEntity> _spirits;
private IGroup<GameEntity> _humans;
private IGroup<GameEntity> _aliveEnemies;
private float _tickTime;
public event Action SettingPanelActivated;
// ... остальная реализация
}
}- 42 уникальные башни: 7 типов × 6 уровней каждый
- Механика слияния: Объединение башен для создания более сильных вариантов
- Стратегическое размещение: Система позиционирования на сетке
- Пути улучшения: Множественные маршруты прогрессии для каждого типа башни
| Тип | Уровни | Описание | Особая способность |
|---|---|---|---|
| B (Синие) | 1-6 | ❄️ Ледяные башни с замедлением | Заморозка врагов |
| D (Алмазные) | 1-6 | 💎 Высокий урон кристальных башен | Цепная молния |
| Y (Жёлтые) | 1-6 | ⚡ Быстрая стрельба молниями | Область поражения |
| G (Зелёные) | 1-6 | 🌿 Природные башни с ядом | Ядовитое облако |
| E (Изумрудные) | 1-6 | 💚 Сбалансированный урон | Множественный выстрел |
| Q (Кварцевые) | 1-6 | 🔮 Магические башни | Телепорт врагов |
| R (Рубиновые) | 1-6 | 🔥 Огненные башни с поджигом | Взрывной урон |
- Поиск пути: Алгоритм A* для навигации по лабиринту
- Динамический спавн: Генерация врагов по волнам
- Поведенческий ИИ: Различные типы врагов с уникальными способностями
- Оптимизация производительности: ECS обеспечивает плавную работу с сотнями сущностей
- Бои в реальном времени: Плавные сражения на 60fps
- Типы урона: Физический, магический, элементальный урон
- Статусные эффекты: Заморозка, поджиг, отравление, замедление
- Система комбо: Синергии и комбинации башен
| Категория | Технология | Назначение |
|---|---|---|
| 🎮 Движок | Unity 6000.0.38f1 | Платформа разработки игр |
| 💻 Язык | C# 10.0 | Основной язык программирования |
| 🏗️ Архитектура | Entity Component System | Производительная игровая архитектура |
| 📦 ECS Framework | Entitas | Генерация кода и реализация ECS |
| 🔌 Dependency Injection | Zenject (Extenject) | Модульная организация кода |
| 🎨 UI Pattern | MVP (Model-View-Presenter) | Чистая архитектура UI |
| 🔊 Аудио | Unity Audio System | Управление звуком |
| 📱 Платформы | PC, Mac, WebGL | Мультиплатформенная поддержка |
- Components - Данные игровых объектов (здоровье, позиция, тип башни)
- Systems - Игровая логика (движение, стрельба, AI врагов)
- Features - Группировка систем по функциональности
- Contexts - Управление жизненным циклом сущностей
- States - Управление переходами между экранами
- Installers - Настройка DI контейнера
- Scene Management - Загрузка и переключение сцен
- Cross-cutting concerns - Аудио, камеры, ассеты, сохранения
- Провайдеры - Абстракция над Unity API
- Фабрики - Создание игровых объектов
- MVP паттерн - Разделение UI логики от данных
- Презентеры - Бизнес-логика интерфейса
- Представления - Unity UI компоненты
Каждая игровая механика (башни, враги, боевка) выделена в отдельную Feature с собственными компонентами и системами.
- MazeDesigner - Редактор лабиринтов в Unity Editor
- Entitas CodeGeneration - Автогенерация ECS кода
- Visual Debugging - Отображение состояний систем
- Игровая логика отделена от представления
- Сервисы изолированы через интерфейсы
- Конфигурация вынесена в ScriptableObjects
Использование Entity Component System для управления игровыми объектами, где каждая башня, враг или снаряд представлены как сущность с набором компонентов.
Каждая панель интерфейса разделена на View (Unity UI), Presenter (логика) и Model (данные из ECS). Это обеспечивает тестируемость и переиспользование.
Все сервисы и зависимости управляются через Zenject контейнер, что позволяет легко подменять реализации и тестировать компоненты изолированно.
Игровые состояния (меню, загрузка, геймплей) управляются через паттерн State Machine с чёткими переходами между состояниями.
🎮 Играть • 📖 Архитектура • 🔧 Реализация


