Почему плохая идея хранить методы в сущностях и компонентах? (Вместе с некоторыми другими вопросами Entity System.)

16

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

Этот ответ помог мне понять Entity Systems даже лучше, чем статья.

Я прочитал (да) статью о Entity Systems, и она сказала мне следующее:

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

Это кажется действительно практичным во многих ситуациях, но часть о компонентах, являющихся просто классами данных, беспокоит меня. Например, как я могу реализовать свой класс Vector2D (Position) в Entity System?

Класс Vector2D содержит данные: координаты x и y, но он также имеет методы , которые имеют решающее значение для его полезности и отличают класс от всего двухэлементного массива. Пример метода: add(), rotate(point, r, angle), substract(), normalize(), и все другие стандартный, полезные, и абсолютно необходимы методы , что позиции (которые являются экземплярами класса Vector2D) должны иметь.

Если бы компонент был просто держателем данных, у него не было бы этих методов!

Вероятно, может появиться одно из решений, заключающееся в том, чтобы внедрить их в системы, но это кажется очень нелогичным. Эти методы - вещи, которые я хочу выполнить сейчас , чтобы они были завершены и готовы к использованию. Я не хочу ждать, пока MovementSystemкто-то прочитает дорогой набор сообщений, которые инструктируют его выполнить вычисление позиции сущности!

И в статье очень четко говорится, что только системы должны иметь какую-либо функциональность, и единственное объяснение этому, которое я мог найти, было «избегать ООП». Прежде всего, я не понимаю, почему я должен воздерживаться от использования методов в сущностях и компонентах. Затраты памяти практически одинаковы, и в сочетании с системами их очень легко реализовать и объединить интересными способами. Системы, например, могут предоставлять базовую логику только сущностям / компонентам, которые сами знают реализацию. Если вы спросите меня - это, по сути, получение положительных отзывов как от ES, так и от ООП, что, по мнению автора статьи, сделать невозможно, но мне кажется хорошей практикой.

Подумайте об этом таким образом; В игре есть много разных типов прорисовываемых объектов. Простые старые изображения, анимации ( update()и getCurrentFrame()т. Д.), Комбинации этих примитивных типов, и все они могут просто предоставить draw()метод для системы рендеринга, который затем не должен заботиться о том, как реализован спрайт объекта, только об интерфейсе (ничья) и позиции. И тогда мне понадобится только система анимации, которая будет вызывать специфичные для анимации методы, которые не имеют ничего общего с рендерингом.

И еще одна вещь ... Есть ли действительно альтернатива массивам, когда речь идет о хранении компонентов? Я не вижу другого места для хранения компонентов, кроме массивов внутри класса Entity ...

Возможно, это лучший подход: хранить компоненты как простые свойства сущностей. Например, компонент позиции будет приклеен к entity.position.

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

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

Ответы:

25

Я думаю, что совершенно нормально иметь простые методы для доступа, обновления или манипулирования данными в компонентах. Я думаю, что функциональность, которая должна оставаться вне компонентов, является логической функциональностью. Служебные функции просто отлично. Помните, что система сущностей и компонентов - это просто руководство, а не строгие правила, которым вы должны следовать. Не старайся следовать за ними. Если вы думаете, что имеет смысл сделать это одним способом, то сделайте это так :)

РЕДАКТИРОВАТЬ

Чтобы уточнить, ваша цель не избежать ООП . Это было бы довольно сложно в большинстве распространенных языков, используемых в наши дни. Вы пытаетесь минимизировать наследование , которое является важным аспектом ООП, но не обязательным. Вы хотите избавиться от Object-> MobileObject-> Creature-> Bipedal-> Human type Наследование.

Тем не менее, нормально иметь какое-то наследство! Вы имеете дело с языком, который сильно зависит от наследования, очень трудно не использовать его. Например, у вас может быть Componentкласс или интерфейс, который расширяют или реализуют все остальные ваши компоненты. То же самое касается вашего Systemкласса. Это делает вещи намного проще. Я настоятельно рекомендую вам взглянуть на рамки Artemis . Это открытый исходный код, и у него есть несколько примеров проектов. Откройте эти вещи и посмотрите, как это работает.

Для Артемиды сущности хранятся в массиве, просто. Однако их компоненты хранятся в массиве или массивах (отдельно от сущностей). Массив верхнего уровня группирует массив нижнего уровня по типу компонента. Таким образом, каждый тип компонента имеет свой собственный массив. Массив нижнего уровня индексируется по идентификатору объекта. (Сейчас я не уверен, если бы я сделал это таким образом, но это так, как здесь). Артемида повторно использует идентификаторы сущностей, поэтому максимальный идентификатор сущности не превышает вашего текущего числа сущностей, но вы все равно можете иметь разреженные массивы, если компонент не является часто используемым компонентом. Во всяком случае, я не буду разбирать это слишком много. Этот метод для хранения сущностей и их компонентов, кажется, работает. Я думаю, что это будет отличным первым шагом на пути внедрения вашей собственной системы.

Объекты и компоненты хранятся в отдельном менеджере.

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

MichaelHouse
источник
1
Хм, это значительно упрощает ситуацию, спасибо! Я думал, что происходит какая-то волшебная вещь «ты пожалеешь об этом позже», и я просто не мог этого увидеть!
Jcora
1
Нет, я полностью использую их в своей системе компонентов сущностей. У меня даже есть некоторые компоненты, которые наследуются от общего родителя, задыхаются . Я думаю, что единственное сожаление, которое вы могли бы сделать, это если бы вы попытались обойтись без использования таких методов. Все дело в том, что для вас наиболее разумно. Если имеет смысл использовать наследование или поместить некоторые методы в компонент, сделайте это.
MichaelHouse
2
Я узнал из моего последнего ответа на эту тему. Отказ от ответственности: Я не говорю , что это способ сделать это. :)
MichaelHouse
1
Да, я знаю, как сложно научиться новой парадигме. К счастью, вы можете использовать аспекты старой парадигмы, чтобы сделать вещи проще! Я обновил свой ответ с информацией о хранилище. Если вы посмотрите на Артемиду, посмотрите, EntityManagerгде хранятся вещи.
MichaelHouse
1
Ницца! Когда это будет сделано, это будет довольно приятный двигатель. Удачи с этим! Спасибо, что задали интересные вопросы.
MichaelHouse
10

«Эта» статья - не та, с которой я особенно согласен, поэтому я думаю, что мой ответ будет несколько критичным.

Это кажется действительно практичным во многих ситуациях, но часть о компонентах, являющихся просто классами данных, беспокоит меня. Например, как я могу реализовать свой класс Vector2D (Position) в Entity System?

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

Не обращайте внимания на то, что в статье говорится, что это не ООП - это такой же ООП, как и любой другой подход. Когда большинство компиляторов или языковых сред выполнения реализуют методы объекта, это в основном аналогично любой другой функции, за исключением того, что есть скрытый аргумент, называемый thisили self, который является указателем на место в памяти, где хранятся данные этого объекта. В системе, основанной на компонентах, идентификатор объекта можно использовать для определения того, где находятся соответствующие компоненты (и, следовательно, данные) для данного объекта. Таким образом, идентификатор объекта эквивалентен указателю this / self, и концепции в основном одно и то же, только немного переставленные.

И в статье очень четко говорится, что только системы должны иметь какую-либо функциональность, и единственное объяснение этому, которое я мог найти, было «избегать ООП». Прежде всего, я не понимаю, почему я должен воздерживаться от использования методов в сущностях и компонентах.

Хорошо. Методы являются эффективным способом организации вашего кода. Важно отойти от идеи «избегать ООП» - избегать повсеместного использования наследования для расширения функциональности. Вместо этого разбейте функциональность на компоненты, которые можно объединить, чтобы сделать то же самое.

Подумайте об этом таким образом; В игре есть много разных типов прорисовываемых объектов. Простые старые изображения, анимации (update (), getCurrentFrame () и т. Д.), Комбинации этих примитивных типов, и все они могут просто предоставить метод draw () для системы рендеринга [...]

Идея системы, основанной на компонентах, заключается в том, что у вас не будет отдельных классов для них, но у вас будет один класс Object / Entity, а изображение будет Object / Entity с ImageRenderer, а Animations будет Object / Сущность, у которой есть AnimationRenderer и т. Д. Соответствующие системы будут знать, как визуализировать эти компоненты, и поэтому не потребуется никакого базового класса с методом Draw ().

[...] который тогда не должен заботиться о том, как реализован спрайт сущности, только об интерфейсе (рисование) и позиции. И тогда мне понадобится только система анимации, которая будет вызывать специфичные для анимации методы, которые не имеют ничего общего с рендерингом.

Конечно, но это не очень хорошо работает с компонентами. У вас есть 3 варианта:

  • Каждый компонент реализует этот интерфейс и имеет метод Draw (), даже если ничего не рисуется. Если бы вы делали это для каждой функциональности, компоненты выглядели бы ужасно.
  • Только компоненты, которым есть что рисовать, реализуют интерфейс - но кто решает, какие компоненты вызывать Draw ()? Должна ли система каким-либо образом запрашивать каждый компонент, чтобы увидеть, какой интерфейс поддерживается? Это было бы подвержено ошибкам и потенциально сложно реализовать на некоторых языках.
  • Компоненты обрабатываются только когда-либо их собственной системой (это идея в связанной статье). В этом случае интерфейс не имеет значения, потому что система точно знает, с каким классом или типом объекта она работает.

И еще одна вещь ... Есть ли действительно альтернатива массивам, когда речь идет о хранении компонентов? Я не вижу другого места для хранения компонентов, кроме массивов внутри класса Entity ...

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

Kylotan
источник
+1 Спасибо за другую точку зрения. Хорошо иметь несколько, имея дело с таким неоднозначным предметом! Если вы храните компоненты в системе, означает ли это, что компоненты могут быть изменены только одной системой? Например, система рисования и система перемещения будут иметь доступ к компоненту положения. Где вы храните это?
MichaelHouse
Ну, они будут хранить только указатель на эти компоненты, которые, насколько я могу судить, могут быть где-то ... Кроме того, зачем вам хранить компоненты в системах? Есть ли в этом преимущество?
Jcora
Я прав, @Kylotan? Вот как бы я это сделал, это кажется логичным ...
Jcora
В примере Adam / T-Machine предполагается, что на компонент приходится 1 система, но система, безусловно, может получить доступ и изменить другие компоненты. (Это мешает многопоточности преимуществ компонентов, но это другой вопрос.)
Kylotan
1
Хранение компонентов в системе обеспечивает лучшую локальность ссылок для этой системы - эта система только (как правило) работает только с этими данными, так зачем же перебирать всю память вашего компьютера от объекта к объекту, чтобы получить его? Это также помогает в параллелизме, потому что вы можете разместить всю систему и ее данные на одном ядре или процессоре (или даже на отдельном компьютере в MMO). Опять же, эти преимущества уменьшаются, когда одна система обращается к более чем одному типу компонента, поэтому это следует учитывать при принятии решения о том, где разделить обязанности компонента / системы.
Kylotan
2

Вектор - это данные. Функции больше похожи на служебные функции - они не являются специфичными для данного экземпляра данных, их можно применять ко всем векторам независимо. Хороший способ думать об этом: можно ли переписать эти функции как статические методы? Если это так, это просто утилита.

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