Вопрос

Для 2D-игры я делаю (для Android), я использую компонентную систему, где GameObject содержит несколько объектов Game Connent. Game Components могут быть такими как входные компоненты, рендеринг компонентов, компоненты излучения пули и так далее. В настоящее время Gameecomponents имеют ссылку на объект, который принадлежит им, и может его изменить, но сам GameObject сам просто имеет список компонентов, и он не заботится о том, какие компоненты пока они могут быть обновлены, когда объект обновляется.

Иногда компонент имеет некоторую информацию, которую нужно знать GameObject. Например, для обнаружения столкновения выявление GameObject регистрирует себя подсистемой обнаружения столкновения, которая будет уведомлена, когда она сталкивается с другим объектом. Подсистема обнаружения столкновений должна знать ограничивающую коробку объекта. I Храните x и y в объекте непосредственно (потому что он используется несколькими компонентами), но ширина и высота известны только для компонента рендеринга, который содержит растровое изображение объекта. Я хотел бы иметь метод GetBoundingBox или GetWidth в GameObject, который получает эту информацию. Или в целом я хочу отправить некоторую информацию от компонента на объект. Однако в моем текущем дизайне GameObject не знает, какие конкретные компоненты он имеет в списке.

Я могу придумать несколько способов решить эту проблему:

  1. Вместо того, чтобы иметь совершенно универсальный список компонентов, я могу позволить GameObject иметь конкретное поле для некоторых важных компонентов. Например, он может иметь переменную участника, называемую рендериренком; Всякий раз, когда мне нужно получить ширину объекта, которую я просто использую renderingComponent.getWidth(). Отказ Это решение все еще обеспечивает универсальный список компонентов, но он относится к некоторым из них по-другому, и я боюсь, что в конечном итоге имею несколько исключительных полей, так как больше компонентов необходимо запрашивать. Некоторые объекты даже не имеют рендеринга компонентов.

  2. Имейте необходимую информацию в качестве членов GameObject, но позволяют компонентам обновлять его. Таким образом, объект имеет ширину и высоту, которая по умолчанию составляет 0 или -1, но компонент рендеринга может установить их в правильные значения в своем контуре обновления. Это похоже на взлом, и я мог бы в конечном итоге толкать много вещей в класс GameObject для удобства, даже если они не все необходимы объекты.

  3. У компонентов реализуют интерфейс, который указывает, какой тип информации их можно запрашивать. Например, компонент рендеринга будет реализовывать интерфейс Hassize, который включает в себя такие методы, как GetWidth и GetHeight. Когда GameObject нуждается в ширине, она циклирует ее компоненты, проверка, если они реализуют интерфейс Hassize (используя instanceof ключевое слово в Java или is в C #). Это похоже на более общее решение, один недостаток состоит в том, что поиск компонента может занять некоторое время (но затем большинство объектов имеют только 3 или 4 компонента).

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

Короткая версия

В компонентной системе для игры для игры, какой лучший способ пройти информацию от компонентов к объекту, сохраняя дизайн универсального?

Это было полезно?

Решение

Мы получаем вариации на этот вопрос три или четыре раза в неделю на Gamedev.net (где GameObject обычно называется «сущностью»), и до сих пор нет консенсуса по лучшему подходу. Было показано, что несколько разных подходов будут работоспособными, поэтому я не буду беспокоиться об этом слишком много.

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

Полностью общие компоненты в основном бесполезны: им нужно предоставить какой-то известный интерфейс, в противном случае, в противном случае существуют мало точек. В противном случае вы также можете просто иметь большой ассоциативный массив неразъедных ценностей и сделать с ним. В Java, Python, C # и других незначительных языках более высокого уровня, чем C ++, вы можете использовать отражение, чтобы дать вам более общий способ использования определенных подклассов без необходимости кодировать тип и информацию о интерфейсах в самих компонентов.

Что касается связи:

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

Некоторые люди используют публикуемые / подписки, сигналы / слоты и т. Д., Для создания произвольных соединений между компонентами. Это кажется немного более гибким, но в конечном итоге вам все еще нужно что-то со знанием этих неявных зависимостей. (И если это известно во время компиляции, почему бы не просто использовать предыдущий подход?)

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

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

Другие советы

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

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

  1. Храните его в сам компонент столкновения.
  2. Сделайте код обнаружения столкновения работать с компонентами напрямую.

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

Это дает вам пару преимуществ:

  1. Листья ущерба для столкновения состояния из GameObject.
  2. Засматривает вас от Iterating of GameObjects, у которых нет компонентов столкновений. (Если у вас много неинтерактивных объектов, таких как визуальные эффекты и украшения, это может сохранить приличное количество циклов.)
  3. Засматривает вас от горения циклов, прогулки между объектом и его компонентом. Если вы повторяете через объекты, то сделайте getCollisionComponent() На каждом из них этот указатель - следующее может вызвать пропустить кэш. Делать это для каждого кадра для каждого объекта может сожжать много процессора.

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

ИспользоватьАвтобус событий«. (Обратите внимание, что вы, вероятно, не можете использовать код как есть, но оно должно дать вам основную идею).

По сути, создайте центральный ресурс, когда каждый объект может зарегистрироваться в качестве слушателя и сказать «Если х случается, я хочу знать». Когда что-то происходит в игре, ответственный объект может просто отправить событие X на автобус событий, и все интересные стороны заметит.

Править] Для более подробного обсуждения см. сообщение прохождения (благодаря snk_kid за то, что указывает на это).

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

В самой простой форме у вас есть однозначные соединения между компонентами, но вам также понадобится однозначное соединение. Например CollectionDetector будет иметь список компонентов, реализующих IBoundingBox.

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

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

То Управляемая структура расширяемости для .NET это хорошее решение этой проблемы. Я понимаю, что вы намереваетесь развиваться на Android, но вы все равно можете получить вдохновение из этой рамки.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top