Игровые компоненты, игровые менеджеры и свойства объектов

15

Я пытаюсь разобраться с компонентным дизайном сущностей.

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

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

Теперь я думаю, что мне не нужен менеджер для всех моих компонентов, например, у меня есть SizeComponent, который просто имеет Sizeсвойство). В результате SizeComponentметод обновления не имеет, а метод обновления менеджера ничего не делает.

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

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

  1. Это (имея ObjectProperty, ObjectComponentи ComponentManagerклассы) сверхинжиниринг?
  2. Что было бы хорошей альтернативой?
Джордж Дакетт
источник
1
У вас есть правильная идея, пытаясь изучить компонентную модель, но вам нужно лучше понять, что ей нужно делать - и единственный способ сделать это - [в основном] завершить игру, не используя ее. Я думаю, что делать SizeComponentизлишне - вы можете предположить, что большинство объектов имеют размер - это такие вещи, как рендеринг, ИИ и физика, где используется компонентная модель; Размер всегда будет вести себя одинаково - так что вы можете поделиться этим кодом.
Джонатан Дикинсон
Связанный: gamedev.stackexchange.com/questions/14607/…
День
@JonathanDickinson, @Den: Я думаю, тогда моя проблема в том, где мне хранить общие свойства. Например, объект как позиция, которая используется a RenderingComponentи a PhysicsComponent. Я слишком обдумываю решение о том, где разместить недвижимость? Должен ли я просто вставить его в один, а затем запросить другой объект для компонента, который имеет необходимое свойство?
Джордж Дакетт
Мой предыдущий комментарий и мыслительный процесс, стоящие за ним, заставляют меня иметь отдельный класс для свойства (или, возможно, группы связанных свойств), к которым компоненты могут запрашивать.
Джордж Дакетт
1
Мне действительно нравится эта идея - возможно, стоит попробовать; но иметь объект для описания каждого отдельного свойства действительно дорого. Вы можете попробовать PhysicalStateInstance(по одному на объект) вместе с GravityPhysicsShared(по одному на игру); однако я испытываю желание сказать, что это входит в сферу эйфории архитекторов, не создавайте себе дыру (именно то, что я сделал с моей первой компонентной системой). ПОЦЕЛУЙ.
Джонатан Дикинсон

Ответы:

6

Простой ответ на ваш первый вопрос: да, вы перешли к разработке дизайна. «Как далеко я ломаю вещи?» Вопрос очень часто встречается, когда делается следующий шаг и центральный объект (обычно называемый сущностью) удаляется.

Когда вы разбиваете объекты на такой детальный уровень, чтобы иметь размеры сами по себе, тогда дизайн заходит слишком далеко. Значение данных само по себе не является компонентом. Это встроенный тип данных, который часто можно назвать именно тем, что вы начали называть, свойством. Свойство не является компонентом, но компонент содержит свойства.

Итак, вот несколько рекомендаций, которым я стараюсь следовать при разработке в системе компонентов:

  • Здесь нет ложки.
    • Это шаг, который вы уже сделали, чтобы избавиться от центрального объекта. Это устраняет все споры о том, что входит в объект Entity и что входит в компонент, поскольку теперь все, что у вас есть, это компоненты.
  • Компоненты не являются структурами
    • Если вы разбиваете что-то там, где оно просто содержит данные, то это уже не компонент, а просто структура данных.
    • Компонент должен содержать все функциональные возможности, необходимые для выполнения определенной задачи определенным образом.
    • Интерфейс IRenderable предоставляет универсальное решение для визуального отображения чего-либо в игре. CRenderableSprite и CRenderableModel - это компонентная реализация этого интерфейса, обеспечивающая особенности рендеринга в 2D и 3D соответственно.
    • IUseable - это интерфейс для чего-то, с чем игрок может взаимодействовать. CUseableItem - это компонент, который либо запускает активную пушку, либо выпивает выбранное зелье, тогда как CUseableTrigger может быть тем, где игрок идет, чтобы запрыгнуть в турель или бросить рычаг, чтобы опустить подъемный мост.

Таким образом, с учетом того, что компоненты не являются структурами, SizeComponent был разбит слишком далеко. Он содержит только данные, и то, что определяет размер чего-либо, может варьироваться. Например, в компоненте рендеринга это может быть 1d-скаляр или 2 / 3d-вектор. В физическом компоненте это может быть ограничивающий объем объекта. В инвентаре это может быть количество места, которое занимает 2D-сетка.

Попробуйте провести хорошую грань между теорией и практичностью.

Надеюсь это поможет.

Джеймс
источник
Давайте не будем забывать, что на некоторых платформах вызов функции из интерфейса длиннее, чем вызов из родительского класса (поскольку в ваш ответ включены упоминания об интерфейсах и классах)
АБР
Хорошо помнить, но я пытался оставаться независимым от языка и просто использовал их в общих чертах дизайна.
Джеймс
«Если вы сломаете что-то там, где оно просто содержит данные, то это уже не компонент, а просто структура данных». -- Почему? «Компонент» - это такое общее слово, что оно может означать и структуру данных.
Пол Манта
@PaulManta Да, это общий термин, но весь смысл этого вопроса и ответа в том, где провести черту. Мой ответ, как вы процитировали, - это только мое советное правило, чтобы сделать именно это. Как всегда, я рекомендую никогда не допускать, чтобы теоретические или конструкторские соображения были движущей силой развития, а помогали ему.
Джеймс
1
@James Это была интересная дискуссия. :) chat.stackexchange.com/rooms/2175 Моя самая большая неприятность, если ваша реализация заключается в том, что компоненты слишком много знают о том, что интересуют другие компоненты. Я хотел бы продолжить обсуждение в будущем.
Пол Манта
14

Вы уже приняли ответ, но вот мой удар в CBS. Я обнаружил, что у общего Componentкласса есть некоторые ограничения, поэтому я выбрал дизайн, описанный Radical Entertainment на GDC 2009, который предложил разделить компоненты на Attributesи Behaviors. (« Теория и практика архитектуры компонентов игровых объектов », Марчин Чады)

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

http://www.pdf-archive.com/2012/01/08/entity-component-system/preview/page/1

Вот выдержка из документа:

Атрибуты и поведение вкратце

Attributesуправлять одной категорией данных, и любая логика, которую они имеют, ограничена в объеме. Например, он Healthможет убедиться, что его текущее значение никогда не превышает максимальное значение, и он может даже уведомлять другие компоненты, когда текущее значение падает ниже определенного критического уровня, но оно не содержит более сложной логики. Attributesне зависят от других Attributesили Behaviors.

Behaviorsконтролировать реакцию объекта на игровые события, принимать решения и изменять значения по Attributesмере необходимости. Behaviorsзависят от некоторых из них Attributes, но они не могут напрямую взаимодействовать друг с другом - они реагируют только на то, как Attributes’значения изменяются другим, Behaviorsи на события, которые они отправляют.


Изменить: И вот диаграмма отношений, которая показывает, как компоненты взаимодействуют друг с другом:

Диаграмма связи между атрибутами и поведением

Детали реализации: уровень сущности EventManagerсоздается, только если он используется. EntityКласс просто хранит указатель на EventManagerкоторый инициализируется только если некоторые запросы компонента его.


Изменить: На другой вопрос я дал аналогичный ответ на этот вопрос. Вы можете найти его здесь для, возможно, лучшего объяснения системы:
/gamedev//a/23759/6188

Пол Манта
источник
2

Это действительно зависит от свойств, которые вам нужны и где вы нуждаетесь в них. Объем памяти, который у вас будет, и мощность / тип обработки, которые вы будете использовать. Я видел и пытаюсь сделать следующее:

  • Свойства, которые используются несколькими компонентами, но изменяются только одним, хранятся в этом компоненте. Форма является хорошим примером в игре, где система ИИ, физическая система, а также система рендеринга нуждаются в доступе к базовой форме, это тяжелое свойство, и оно должно оставаться только в одном месте, если это возможно.
  • Свойства, такие как положение, иногда необходимо дублировать. Например, если вы запускаете несколько систем параллельно, вы хотите избежать просмотра различных систем и скорее синхронизировать положение (копировать из основного компонента или синхронизировать через дельты, либо, при необходимости, проходить коллизию).
  • Свойства, возникающие из элементов управления или «намерений» ИИ, могут храниться в выделенной системе, поскольку они могут применяться к другим системам, не будучи видимыми извне.
  • Простые свойства могут стать сложными. Иногда ваша позиция потребует выделенной системы, если вам нужно поделиться большим количеством данных (положение, ориентация, дельта кадра, общее текущее движение дельты, движение дельты для текущего кадра и для предыдущего кадра, вращение ...). В этом случае вам придется пойти с системой и получить доступ к самым последним данным из выделенного компонента, и вам, возможно, придется изменить их с помощью аккумуляторов (дельт).
  • Иногда ваши свойства могут храниться в необработанном массиве (double *), и ваши компоненты будут просто иметь указатели на массивы, содержащие различные свойства. Наиболее очевидный пример - когда вам нужны массивные параллельные вычисления (CUDA, OpenCL). Таким образом, наличие одной системы для правильного управления указателями может оказаться весьма полезным.

Эти принципы имеют свои ограничения. Конечно, вам придется передать геометрию в рендерер, но вы, вероятно, не захотите получить ее оттуда. Основная геометрия будет сохранена в физическом движке в случае, если там происходят деформации и синхронизируются с рендерером (время от времени в зависимости от расстояния объектов). Таким образом, вы все равно продублируете его.

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

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

Нет ничего постыдного в копировании некоторых свойств. Если нескольким компонентам необходимо хранить локальную копию, иногда более эффективно копировать и синхронизировать, а не обращаться к «внешнему» значению »

Также синхронизация не должна происходить каждый кадр. Некоторые компоненты могут быть синхронизированы реже, чем другие. Компонент рендеринга часто является хорошим примером. Те, кто не взаимодействует с игроками, могут синхронизироваться реже, как и те, которые находятся далеко. Те, кто находится далеко от поля камеры, могут синхронизироваться еще реже.


Когда дело доходит до вашего компонента размера, он может быть связан с вашим компонентом позиции:

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

Вместо размера вы могли бы даже использовать модификатор размера, это может пригодиться.

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

койот
источник
2
Кстати, +1 или -1 меня, потому что мой текущий представитель 666 с 9 ноября ... Это жутко.
Койот
1

Если компоненты могут быть произвольно добавлены к объектам, то вам нужен способ запросить, существует ли данный компонент в объекте, и получить ссылку на него. Таким образом, вы можете перебирать список объектов, полученных из ObjectComponent, пока не найдете нужный объект и не вернете его. Но вы бы вернули объект правильного типа.

В C ++ или C # это обычно означает, что у вас будет такой же шаблонный метод для сущности T GetComponent<T>(). И когда у вас есть эта ссылка, вы точно знаете, какие данные он содержит, поэтому просто обращайтесь к ней напрямую.

В чем-то вроде Lua или Python вы не обязательно имеете явный тип этого объекта, и, вероятно, вам тоже все равно. Но опять же, вы можете просто получить доступ к переменной-члену и обработать любое исключение, возникающее при попытке получить доступ к тому, чего там нет.

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

Kylotan
источник
Я понимаю, что для получения строго типизированных компонентов от сущности (с использованием обобщений и т. Д.) Мой вопрос больше касается того, куда должны идти эти свойства, в частности, когда свойство используется несколькими компонентами, и ни один компонент не может владеть им. Смотрите мой 3-й и 4-й комментарий по этому вопросу.
Джордж Дакетт
Просто выберите существующий, если он подходит, или добавьте свойство в новый компонент, если он не подходит. Например, в Unity есть компонент «Transform», который является просто позицией, и если что-то еще нужно изменить позицией объекта, они делают это через этот компонент.
Kylotan
1

«Я думаю, тогда моя проблема в том, где я могу хранить общие свойства. Например, объект в качестве позиции, который используется RenderingComponent и PhysicsComponent. Я слишком обдумываю решение о том, где разместить свойство? Должен ли я просто придерживаться либо в другой, а другой запросить объект для компонента, который имеет необходимое свойство? "

Дело в том, что RenderingComponent использует позицию, но PhysicsComponent предоставляет ее. Вам просто нужен способ сообщить каждому пользовательскому компоненту, какой поставщик использовать. В идеале агностиком, иначе будет зависимость.

«... мой вопрос больше о том, куда должны идти эти свойства, особенно когда свойство используется несколькими компонентами, и нельзя сказать, что оно принадлежит какому-либо одному компоненту. См. мой 3-й и 4-й комментарий по этому вопросу».

Там нет общего правила. Зависит от конкретного свойства (см. Выше).

Создайте игру с уродливой, но основанной на компонентах архитектурой, а затем проведите ее рефакторинг.

логово
источник
Я не думаю, что я вполне понимаю, что PhysicsComponentделать тогда. Я рассматриваю это как управление имитацией объекта в физической среде, что приводит меня к путанице: не все вещи, которые нужно визуализировать, нужно будет имитировать, поэтому мне кажется неправильным добавлять их PhysicsComponentпри добавлении, RenderingComponentпоскольку они содержат позицию который RenderingComponentиспользует. Я мог легко видеть, что я заканчиваю сетью взаимосвязанных компонентов, означая, что все / большинство должно быть добавлено к каждой сущности.
Джордж Дакетт
У меня была похожая ситуация на самом деле :). У меня есть PhysicsBodyComponent и SimpleSpatialComponent. Оба они предоставляют Положение, Угол и Размер. Но первый участвует в физическом моделировании и обладает дополнительными соответствующими свойствами, а второй просто хранит эти пространственные данные. Если у вас есть собственный физический движок, вы можете даже наследовать первый от второго.
День
«Я мог легко увидеть, что я получаю сеть взаимосвязанных компонентов, а это означает, что все / большинство нужно добавить к каждой сущности». Это потому, что у вас нет реального прототипа игры. Мы говорим о некоторых основных компонентах здесь. Неудивительно, что они будут использоваться везде.
День
1

Ваша кишка рассказывает вам , что имея ThingProperty, ThingComponentи ThingManagerдля каждого Thingтипа компонента немного избыточна. Я думаю, что это правильно.

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

TransformPropertyбудет довольно распространенным. Но кто за это отвечает, система рендеринга? Физическая система? Звуковая система? Зачем Transformкомпоненту даже нужно обновлять себя?

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

Читайте об Артемисе: http://piemaster.net/2011/07/entity-component-artemis/

Посмотрите на его код, и вы увидите, что он основан на системах, которые объявляют свои зависимости в виде списков ComponentTypes. Вы пишете каждый из своих Systemклассов и в методе constructor / init объявляете, от каких типов зависит эта система.

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

Затем на этапе обновления вашего цикла у вас Systemтеперь есть список объектов, которые нужно обновить. Теперь вы можете иметь детализацию компонентов , так что вы можете разработать бредовые системы, строить объекты из ModelComponent, TransformComponent, FliesLikeSupermanComponent, и SocketInfoComponent, и сделать что - то странное , как макияж летающая тарелка , которая летит между клиентами подключены к многопользовательской игре. Ладно, может быть и не так, но идея в том, что он делает вещи разъединенными и гибкими.

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

michael.bartnett
источник