«Игровой объект» - и компонентный дизайн

25

Последние 3-4 года я работал над некоторыми хобби-проектами. Просто простые 2d и 3d игры. Но в последнее время я начал больший проект. Так, в последние пару месяцев я пытался создать класс игровых объектов, который может стать основой всех моих игровых объектов. Поэтому после долгих испытаний и испытаний я обратился к Google, который быстро указал мне на некоторые PDF-файлы GDC и PowerPoints. И теперь я пытаюсь понять, какие игровые объекты основаны на компонентах.

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

У меня сложилось впечатление, что это было что-то вроде этого (в HealthComponent):

if(Health < 0) {
   AnimationComponent.PlayAnimation("played-died-animation")
}

Но опять же, как HealthComponent узнает, что к игровому объекту, к которому он прикреплен, прикреплен AnimationComponent? Единственное решение, которое я вижу здесь, это

  1. Проведите проверку, чтобы увидеть, подключен ли компонент AnimationComponent (внутри кода компонента или на стороне двигателя)

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

  3. Напишите, например, HealthWithAnimationComponent, HealthNoAnimationComponent и так далее, что, опять же, похоже, противоречит всей идее разработки компонентов.

Хайер
источник
1
Люблю вопрос. Я должен был спросить то же самое несколько месяцев назад, но никогда не удосужился. Дополнительная проблема, с которой я столкнулся, - это когда у игрового объекта есть несколько экземпляров одного и того же компонента (например, несколько анимаций). Было бы здорово, если бы ответы могли затронуть это. Я закончил тем, что использовал сообщения для уведомлений, с переменными, общими для всех компонентов объекта Game (поэтому им не нужно отправлять сообщение, чтобы получить значение для переменной).
АБР
1
В зависимости от типа игры у вас, вероятно, не будет игровых объектов, имеющих компонент здоровья и компонент анимации. И все эти игровые объекты, вероятно, представляют что-то вроде Unit. Таким образом, вы можете выбросить компонент здоровья и создать UnitComponent, который будет иметь здоровье поля и знать обо всех компонентах, которыми должен быть блок. Такая гранулярность компонентов на самом деле ничего не помогает - более реалистично иметь по одному компоненту на домен (рендеринг, аудио, физика, логика игры).
Кикаймару

Ответы:

11

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

Когда компонент здоровья обнаруживает, что объект «умер», он отправляет сообщение «Я умер». Компонент анимации отвечает за ответ на это сообщение, воспроизводя соответствующую анимацию.

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

Blecki
источник
Окей, это имеет смысл. Но кто объявляет «состояния» как «мертвые» или «портал сломан» и т. Д. Компонент или двигатель? Потому что добавление состояния «мертвый» к вещи, к которой никогда не будет присоединен компонент здоровья, кажется мне пустой тратой. Думаю, я просто погрузлюсь и начну тестировать некоторый код и посмотреть, что работает.
Hayer
Майкл и Патрик Хьюз имеют правильный ответ выше. Компоненты - это просто данные; так что на самом деле это не компонент здоровья, который определяет, когда сущность умерла, и отправляет сообщение, это некий элемент логики, специфичный для игры. Как абстрагироваться от вас. Фактическое состояние смерти никогда не должно храниться где-либо. Объект считается мертвым, если его здоровье <0, и компонент здоровья может инкапсулировать этот бит логики проверки данных, не нарушая «нет поведения»! ограничение, если вы рассматриваете только вещи, которые изменяют состояние компонента как поведение.
Блецки
Просто любопытно, как бы вы справились с MovementComponent? Когда он обнаруживает ввод, ему нужно увеличить скорость в PositionComponent. Как будет выглядеть сообщение?
Tips48
8

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

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

Майкл
источник
В конечном итоге это хороший способ решения проблемы: компоненты представляют свойства, а системы связывают разрозненные свойства и используют их для работы. Это огромный отход от традиционного ООП-мышления, от которого у некоторых людей болят головы =)
Патрик Хьюз
Ладно, теперь я действительно потерян ... »В ES, напротив, если у вас есть 100 юнитов на поле битвы, каждое из которых представлено сущностью, то у вас есть ноль копий каждого метода, который может быть вызван на юнита - потому что Сущности не содержат методов. Также Компоненты не содержат методов. Вместо этого у вас есть внешняя система для каждого аспекта, и эта внешняя система содержит все методы, которые могут быть вызваны на любом Сущности, обладающей Компонентом, который помечает его как совместимый с этим. система «. Ну, где хранятся данные в GunComponent? Как раунды и т. Д. Если все объекты имеют один и тот же компонент.
Hayer
1
Насколько я понимаю, все объекты не используют один и тот же компонент, к каждому объекту может быть прикреплено N экземпляров компонента. Затем система запрашивает у игры список всех сущностей, к которым прикреплены экземпляры компонентов, к которым они относятся, и затем выполняет любую обработку этих объектов
Джейк Вудс,
Это просто перемещает проблему. Как система знает, какие компоненты использовать? Системе могут потребоваться и другие системы (например, системе StateMachine может потребоваться анимация). Тем не менее, это решает проблему данных, принадлежащих ВОЗ. Фактически, более простой реализацией было бы иметь словарь в игровом объекте, и каждая система создает там свои переменные.
АБР
Это действительно перемещает проблему, но в более устойчивое место. Системы имеют соответствующие компоненты жестко подключены. Системы могут связываться друг с другом через Компоненты (StateMachine может установить значение компонента, которое Animation считывает, чтобы знать, что делать (или оно может инициировать Событие). Подход словаря звучит как Шаблон свойств, который также может работать. Компоненты - это то, что связанные свойства сгруппированы вместе, и их можно статически проверять. Никаких причудливых ошибок нет, потому что вы добавили «Ущерб» в одном месте, но попытались получить его с помощью «Ущерб» в другом
Майкл
6

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

  1. Отправить сообщение.
  2. Читайте непосредственно данные из компонента.

1) Проверьте, подключен ли компонент AnimationComponent (внутри кода компонента или на стороне двигателя).

Для этого я использовал: 1. функцию HasComponent в GameObject или 2. когда вы присоединяете компонент, вы можете проверить зависимости в какой-либо конструктивной функции, или 3. если я точно знаю, что объект имеет этот компонент, я просто использую его.

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

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

3) Напишите «HealthWithAnimationComponent», «HealthNoAnimationComponent» и т. Д., Что опять-таки противоречит всей идее разработки компонентов.

Это плохая идея, чтобы писать такие компоненты. В своем приложении я создал компонент Health, наиболее независимый. Теперь я думаю о некотором шаблоне Observer, который уведомляет подписчиков о каком-то конкретном событии (например, «попадание», «исцеление» и т. Д.). Поэтому AnimationComponent должен сам решить, когда воспроизводить анимацию.

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

Евгений
источник
1
Хорошо, google.no/… @ slide 16
hayer
@bobenko, пожалуйста, дайте ссылку на статью о CBES. Мне это тоже очень интересно;)
Edward83
1
И lambdor.net/?p=171 @ bottom, это своего рода краткое изложение моего вопроса. Как можно определить различные функциональные возможности в терминах относительно сложных неэлементарных компонентов? Какие самые элементарные компоненты? Чем элементарные компоненты отличаются от чистых функций? Как существующие компоненты могут автоматически связываться с новыми сообщениями от новых компонентов? Какой смысл игнорировать сообщение, о котором компонент не знает? Что случилось с моделью ввода-процесса-вывода?
Hayer
1
Вот хороший ответ на CBES stackoverflow.com/a/3495647/903195 Большинство статей, которые я исследовал, взяты из этого ответа. Я начал и вдохновился cowboyprogramming.com/2007/01/05/evolve-your-heirachy тогда В Gems 5 (как я помню) была хорошая статья с примерами.
Евгений
Но что касается другой концепции функционально-реактивного программирования, для меня этот вопрос все еще открыт, но, может быть, для вас это хорошее направление для исследований.
Евгений
3

Это как Майкл, говорит Патрик Хьюз и Блецки. Чтобы избежать простого перемещения проблемы, нужно в первую очередь отказаться от идеологии, которая вызывает проблему.

Его меньше OOD и больше похоже на функциональное программирование. Когда я начал экспериментировать с компонентным проектированием, я заметил эту проблему в будущем. Я погуглил еще немного и обнаружил, что «Функциональное реактивное программирование» является решением.

Теперь мои компоненты - это не что иное, как набор переменных и полей, которые описывают его текущее состояние. Затем у меня есть куча «системных» классов, которые обновляют все компоненты, которые имеют к ним отношение. Реактивная часть достигается путем запуска систем в четко определенном порядке. Это гарантирует, что какая бы система ни была следующей в очереди, чтобы выполнять свою обработку и обновление, и какие бы компоненты и объекты она ни собиралась читать и обновлять, она всегда работает с самыми последними данными.

Тем не менее, в некотором смысле вы все еще можете утверждать, что проблема снова изменилась. Потому что, если не ясно, в каком порядке должны работать ваши системы? Что делать, если существуют циклические отношения, и это только вопрос времени, прежде чем вы будете смотреть на беспорядок if-else и операторов switch? Это неявная форма обмена сообщениями, нет? На первый взгляд, я думаю, что это небольшой риск. Обычно вещи обрабатываются по порядку. Примерно так: ввод игрока -> позиции объекта -> обнаружение столкновений -> логика игры -> рендеринг -> начать заново. В этом случае у вас будет по одной Системе для каждой, предоставьте каждой Системе метод update (), а затем запустите их последовательно в вашей игровой петле.

uhmdown
источник