Реализация шаблона ECS (Entity Component System) для создания игр.
Делайте игру вместо архитектуры для игры.
Python version | 3.3+ |
License | Apache-2.0 |
PyPI | https://pypi.python.org/pypi/ecs_pattern/ |
Dependencies | dataclasses before 3.7, typing before 3.5 |
Contents
ECS - Entity-Component-System - это архитектурный шаблон, созданный для разработки игр.
Он отлично подходит для описания динамического виртуального мира.
Основные принципы ECS:
- Композиция важнее наследования (Composition over inheritance)
- Данные отделены от логики (Data Oriented Design)
Component - Свойство с данными объекта
Entity - Контейнер для свойств
System - Логика обработки данных
EntityManager - База данных сущностей
SystemManager - Контейнер для систем
$ pip install ecs-pattern
from ecs_pattern import component, entity, EntityManager, System, SystemManager
- Опишите компоненты - component
- Опишите сущности на основе компонентов - entity
- Распределите ответственность обработки сущностей по системам - System
- Храните сущности в менеджере сущностей - EntityManager
- Управляйте системами менеджером систем - SystemManager
Свойство с данными объекта. Содержат только данные, без логики.Используйте декоратор ecs_pattern.component для создания компонентов.Технически это python dataclass.Используйте компоненты как миксины для сущностей.@component class ComPosition: x: int = 0 y: int = 0 @component class ComPerson: name: str health: int
Контейнер для свойств. Состоит только из компонентов.Запрещено добавлять атрибуты к сущности динамически.Используйте декоратор ecs_pattern.entity для создания сущностей.Технически это python dataclass со slots=True.Используйте EntityManager для хранения сущностей.@entity class Player(ComPosition, ComPerson): pass @entity class Ball(ComPosition): pass
Логика обработки сущностей.Не содержит данных о сущностях и компонентах.Используйте абстрактный класс ecs_pattern.System для создания конкретных систем:system.start - Инициализировать систему. Вызывается один раз перед основным циклом обновления системы.system.update - Обновить состояние системы. Вызывается в основном цикле.system.stop - Остановка системы. Вызывается один раз после завершения основного цикла.Используйте SystemManager для управления системами.class SysInit(System): def __init__(self, entities: EntityManager): self.entities = entities def start(self): self.entities.init( TeamScoredGoalEvent(Team.LEFT), Spark(spark_sprite(pygame.display.Info()), 0, 0, 0, 0) ) self.entities.add( GameStateInfo(play=True, pause=False), WaitForBallMoveEvent(1000), ) class SysGravitation(System): def __init__(self, entities: EntityManager): self.entities = entities def update(self): for entity_with_pos in self.entities.get_with_component(ComPosition): if entity_with_pos.y > 0: entity_with_pos.y -= 1
Контейнер для сущностей.Используйте класс ecs_pattern.EntityManager для создания менеджера сущностей.Временная сложность get_by_class и get_with_component - как у словаряentities.add - Добавить сущности.entities.delete - Удалить сущности.entities.delete_buffer_add - Сохранить сущности в буфер удаления, чтобы удалить позже.entities.delete_buffer_purge - Удалить все сущности в буфере удаления и очистить буффер.entities.init - Дать менеджеру знать о сущностях. При доступе к неизвестным объектам бросается KeyError.entities.get_by_class - Получить все сущности указанных классов. Учитывает порядок сущностей.entities.get_with_component - Получить все сущности с указанными компонентами.entities = EntityManager() entities.add( Player('Ivan', 20, 1, 2), Player('Vladimir', 30, 3, 4), Ball(0, 7) ) for entity_with_pos in entities.get_with_component(ComPosition): print(entity_with_pos.x, entity_with_pos.y) for player_entity in entities.get_by_class(Player): print(player_entity.name) entities.delete_buffer_add(player_entity) entities.delete_buffer_purge() entities.delete(*tuple(entities.get_by_class(Ball))) # one line del
Контейнер для систем.Работает с системами в заданном порядке.Используйте класс ecs_pattern.SystemManager для управления системами.system_manager.start_systems - Инициализировать системы. Вызовите один раз перед главным циклом обновления систем.system_manager.update_systems - Обновить состояние систем. Вызывайте в главном цикле.system_manager.stop_systems - Завершить работу систем. Вызовите один раз после завершения главного цикла.entities = EntityManager() entities.add( Player('Ivan', 20, 1, 2), Player('Vladimir', 30, 3, 4), Ball(0, 7) ) system_manager = SystemManager([ SysPersonHealthRegeneration(entities), SysGravitation(entities) ]) system_manager.start_systems() while play: system_manager.update_systems() clock.tick(24) # *pygame clock system_manager.stop_systems()
- Pong: игра - pygame + ecs_pattern
- Snow day: сцена - pygame + ecs_pattern
- Trig fall: коммерческая игра - pygame + ecs_pattern + numpy
- Эффективное использования памяти - Component и Entity используют dataclass
- Удобный поиск объектов - по классу сущности и по компонентам сущности
- Гибкость - слабая связность в коде позволяет быстро расширять проект
- Модульность - код легко тестировать, анализировать производительность, переиспользовать
- Контроль за выполнением - системы работают строго друг за другом
- Следование принципам шаблона помогает писать качественный код
- Удобно распараллеливать обработку
- Компактная реализация
- Чтобы научиться правильно готовить ECS, может потребоваться много практики
- Данные доступны откуда угодно - сложно искать ошибки
- Наследование компонентов, сущностей, систем
- Игнорирование принципов ECS, например хранение данных в системе
- Возведение ECS в абсолют, ООП никто не отменяет
- Адаптация существующего кода проекта под ECS "как есть"
- Использование рекурсивной или реактивной логики в системах
- Использование EntityManager.delete в циклах get_by_class, get_with_component
- Используйте компоненты "одиночки (Singleton)" с данными и флагами
- Минимизируйте места изменения компонента
- Не создавайте методы в компонентах и сущностях
- Делите проект на сцены, сценой можно считать цикл для SystemManager с его EntityManager
- Используйте пакеты для разделения сцен
Пример дерева проекта:
/common_tools __init__.py resources.py i18n.py gui.py consts.py components.py math.py /menu_scene __init__.py entities.py main_loop.py surfaces.py systems.py /game_scene __init__.py entities.py main_loop.py surfaces.py systems.py main.py
История важных изменений: release_notes.rst
- Нашли ошибку или есть предложение - issue / merge request 🎯
- Нечем помочь этому проекту - помогите другому открытому проекту, который используете ✋
- Некуда деть деньги - потратьте на семью, друзей, близких или окружающих вас людей 💰
- Поставьте проекту ⭐