Привет, читатель! В этой статье не будет практики, а только спецификация формата и примеры для его объяснения. Код парсера будет в следующей части статьи!

Что такое ECS

ECS - Entity Component System (Сущности Компоненты Системы), это распространённый паттер программирования игр, используемый для упрощения разработки. В этом паттерне есть 3 определения:

  • Entity (Сущность) - контейнеры, хранящие в себе Компоненты (Данные). Сами не хранят свойства.

  • Component (Компонент) - классы/структуры, хранящие в себе всевозможные данные, нужные для игровой логики (Систем).

  • System (Системы) - Логические ядра игры. Они манипулируют сущностями и данными внутри них. Чаще всего реализуются классами

Если же вы хотите разобраться в теме подробнее, предлагаю вам вот эту статью.

Хочу отметить, что в Формате будет возможно описывать только ПЕРВЫЕ два определения, так как для описания игровой логики нужен язык программирования, а это задача сложная.

Зачем?

"Зачем?" - задаст вопрос читатель. И я отвечу - упрощение. Вот пример объявления компонента во ECS-фреймворке BrokenBricksECS:

namespace ECSExample {
    [Serializable]
    public struct FloatComponent : IComponent {
        public float value;
 
        public FloatComponent(float value) {
            this.value = value;
        }
    }
 
    public class FloatDataComponent : ComponentDataWrapper<FloatComponent> { }
}

Сначала мы здесь должны создать структуру, внутри которой будет значения и конструктор. Потом мы создаем FloatDataComponent. Это обёрнутый в ComponentDataWrapper FloatComponent. Разве это нужно?

Решение

Для этого я предлагаю создать формат описания компонентов и прототипов сущностей (префабов). И все что мне пришло на ум, так только это.

@component Component1 {
  value: int,
}
@component Comp2(Component1) {
  value2: str = "default_value" # Будет иметь value: int и value2: str
} # Родитель - Component1

Используя ключевое слово @component, мы создаем новый компонент. Внутри фигурных скобок типы объявляются так: <name>: <type>, также можно указать дефолтное значение <name>: <type> = <value>

Комментарии начинаются с символа решётки # Комментарий

Наследование компонентов?

Компоненты могут наследоваться друг от друга. Это делается так:

@component <name>(<parent>) {
  data_from_parent,
  ...,
  new_and_overriden_data
}
# Пример
@component Component1 {
  value: int,
}
@component Comp2(Component1) {
  value2: str = "default_value" # Будет иметь value: int и value2: str
} # Родитель - Component1

Множественного наследования формат не поддерживает, так как есть проблема ромба.

Хочу заметить, ради оптимизации компоненты при наследовании просто сливаются.

Прототипы сущностей (Префабы)

А тут, во время написания, мне приходит мысль? А почему бы и не добавить в формат описание сущностей! И я добавил их:

@entity Name {
  Component1(value=10)
}
@entity Name2(Name) {
  Comp2(10, 'default_value')
}
@entity Name3 { # Равен Name
  Component1(10),
  Comp2(value=10)
}

Используя ключевое слово @entity <name>, мы создаем новый прототип сущности. Внутри фигурных скобок компоненты добавляются так: <name>(<value>, <field_name>=<value>)

Наследование же работает также, как у компонентов.

Итог

Итого, у нас есть вот такой example-файл:

@component Component1 {
  value: int,
}
@component Comp2(Component1) {
  value2: str = "default_value" # Будет иметь value: int и value2: str
} # Родитель - Component1
@entity Name {
  Component1(value=10)
}
@entity Name2(Name) {
  Comp2(10, 'default_value')
}
@entity Name3 { # Равен Name
  Component1(10),
  Comp2(value=10)
}

В следующей части я напишу парсинговый механизм для этого формата. Удачи!

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


  1. kipar
    07.11.2023 10:00

    Сущности есть смысл грузить (и сохранять) в рантайме, этакая сериализации в текстовый формат (для конфигов, дерева исследований и подобных вещей которые хочется редактировать в виде текста).

    А компоненты - наоборот, если уж грузить то во время компиляции/кодогенерации, потому что если грузить их в рантайме, то половина преимуществ ецс (скорость обработки за счёт фиксированной раскладки в памяти) уйдёт, да и работать с ними из компилируемого языка не выйдет.

    Так что возникает вопрос - а есть ли смысл объединять одно с другим в один формат?


    1. Lainhard
      07.11.2023 10:00
      +1

      если грузить их в рантайме, то половина преимуществ ецс (скорость обработки за счёт фиксированной раскладки в памяти) уйдёт

      Зависит от библиотеки. Есть flecs, в котором можно в рантайие определять компоненты.

      работать с ними из компилируемого языка не выйдет

      А как насчёт кодогенерации? ????

      Но в целом согласен. Сущностям в рантайме быть. Компонентам - нихть!


      1. gagarinten Автор
        07.11.2023 10:00
        -1

        Компоненты должны быть как структуры. Это желательно.


    1. gagarinten Автор
      07.11.2023 10:00
      -1

      Почему бы и нет? Можно сделать разные реализации, например - метапрограммирование компонентов.


  1. Nognomar
    07.11.2023 10:00

    Складывается впечатление что автор перепутал ecs и компонентную архитектуру (на подобие той что используется в юнити). В ecs не используется никакого наследования и уж тем более компоненты не должны содержать внутри другие компоненты. Вы взяли ecs, но притащили за собой ровно те же проблемы из ооп от которых этот самый ecs должен был вас избавить.


    1. gagarinten Автор
      07.11.2023 10:00
      -1

      Ну как вам сказать, каждый компонент НЕ содержит родителей, их содержимое складывается.


      1. Nognomar
        07.11.2023 10:00

        В таком случае это практически тоже самое наследование, разве нет? В любом случае, мой вам совет - уберите эти фичи. Наследование в компонентах в любом его проявлении - путь в ад. Вы упростите себе как написание парсера, так и жизнь тем, кто решит пользоваться вашим фреймворком. Вся прелесть ecs в том что у вас плоская архииектура без лишних иерархий и абстракций. Соответственно её сравнительно легко расширять и рефакторить. Зачем сознательно тащить проблемы ооп подхода - непонятно.


        1. gagarinten Автор
          07.11.2023 10:00
          -1

          Это удобно в некоторых случаях.


        1. gagarinten Автор
          07.11.2023 10:00

          На самом деле, формат вам не запрещает использовать наследования. Ваше дело не использовать эту фичу, но это не отменять, что она должна быть.


  1. DizzyJump
    07.11.2023 10:00
    +1

    Плоховато описана проблематика, которую пытается решить этот подход. Бойлерплейтный однотипный код при обьявлении компонентов? Ну допустим, а чем предложенный подход лучше тех же сниппетов? Пока выглядит так что вы пытаетесь заменить один бойлерплейт другим велосипедным.


    1. gagarinten Автор
      07.11.2023 10:00
      -1

      Он проще.