Как обновить состояния сущностей и анимации в компонентной игре?

10

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

Я не хочу иметь метод update () внутри компонента, чтобы предотвратить зависимости между компонентами.

В настоящее время я имею в виду, что компоненты содержат данные и компоненты обновления системы.

Итак, если у меня есть простая 2D-игра с некоторыми объектами (например, игрок, враг1, враг2), которые имеют компоненты Transform, Movement, State, Animation и Rendering, я думаю, что я должен иметь:

  • MovementSystem, которая перемещает все компоненты Movement и обновляет компоненты State.
  • И RenderSystem, которая обновляет компоненты Animation (компонент анимации должен иметь одну анимацию (то есть набор кадров / текстур) для каждого состояния и обновлять ее, означает выбор анимации, соответствующей текущему состоянию (например, jump, moving_left и т. Д.), И обновление индекса кадра). Затем RenderSystem обновляет компоненты Render с помощью текстуры, соответствующей текущему кадру анимации каждого объекта, и отображает все на экране.

Я видел несколько реализаций, таких как Artemis Framework, но я не знаю, как решить эту ситуацию:

Допустим, в моей игре есть следующие сущности. Каждый объект имеет набор состояний и одну анимацию для каждого состояния:

  • игрок: «холостой», «moving_right», «прыжки»
  • враг1: "перемещение_вверх", "перемещение_для"
  • враг 2: "движущийся влево", "движущийся вправо"

Каковы наиболее приемлемые подходы для обновления текущего состояния каждого объекта? Единственное, о чем я могу думать, - это иметь отдельные системы для каждой группы объектов и отдельные компоненты State и Animation, поэтому у меня будут PlayerState, PlayerAnimation, Enemy1State, Enemy1Animation ... PlayerMovementSystem, PlayerRenderingSystem ... но я думаю, что это плохо решение и ломает цель иметь систему на основе компонентов.

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

РЕДАКТИРОВАТЬ: Я думаю, что решение, чтобы сделать эту работу, как я намерен это следующее:

Вы делаете составные компоненты состояния и анимации достаточно общими, чтобы их можно было использовать для всех объектов. Содержащиеся в них данные будут модификатором изменений, например, какие анимации воспроизводятся или какие состояния доступны. - Byte56

Теперь я пытаюсь выяснить, как спроектировать эти два компонента достаточно универсально, чтобы я мог использовать их повторно. Хорошим решением может быть наличие UID для каждого состояния (например, ходьба, бег ...) и сохранение анимации на карте в AnimationComponent, заданном этим идентификатором?

miviclin
источник
Я предполагаю, что вы видели это: состояние изменений в объектах или компонентах ? Ваш вопрос принципиально отличается от этого?
MichaelHouse
@ Byte56 Да, я читал это несколько часов назад. Предлагаемое вами решение похоже на идею, которую я изложил здесь. Но моя проблема возникает, когда StateComponent и AnimationComponent не одинаковы для всех объектов в системе. Должен ли я разделить эту систему на более мелкие системы, которые обрабатывают группы объектов с одинаковыми возможными состояниями и анимациями? (см. последнюю часть моего исходного поста для лучшего разъяснения)
miviclin
1
Вы делаете statecomponentи animationcomponentдостаточно универсальным, чтобы использоваться для всех сущностей. Содержащиеся в них данные будут модификатором изменений, например, какие анимации воспроизводятся или какие состояния доступны.
MichaelHouse
Когда вы говорите о зависимости, вы имеете в виду зависимость данных или порядок выполнения? Кроме того, в вашем предлагаемом решении MovementSystem теперь необходимо реализовать все различные способы перемещения чего-либо? Похоже, что это нарушает идею системы на основе компонентов ...
АБР
@ADB Я говорю о зависимости данных. Чтобы обновить анимацию (например, перейти от анимации move_right к анимации move_left), мне нужно знать текущее состояние объекта и не вижу, как сделать эти 2 компонента более общими.
Мивиклин

Ответы:

5

IMHO, Movementкомпонент должен содержать текущее состояние ( Movement.state), а Animationкомпонент должен наблюдать за изменениями Movement.stateи обновлять свою текущую анимацию ( Animation.animation) соответственно, используя простой поиск идентификатора состояния для анимации (как это было предложено в конце OP). Очевидно, это средство Animationбудет зависеть от Movement.

Альтернативная структура могла бы иметь общий Stateкомпонент, который Animationнаблюдает и Movementмодифицирует, который в основном представляет собой модель-представление-контроллер (в этом случае состояние-анимация-движение).

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

Удачи.

Torious
источник
Анимация наблюдает за государством, а государство наблюдает за движением ... Зависимости все еще существуют, но я мог бы попробовать. Будет ли последняя альтернатива что-то вроде этого: Движение уведомляет об изменениях объекта, и объект отправляет событие в состояние, и затем тот же процесс будет повторяться для состояния и анимации? Как этот подход может повлиять на производительность?
Мивиклин
Первый случай: Movementбудет контролировать State (не наблюдать). Последний случай: Да Movement, entity.dispatchEvent(...);или около того, и все другие компоненты, прослушивающие событие такого типа, получат его. Производительность, конечно, хуже чистых вызовов методов, но не намного. Вы можете объединить объекты событий, например. Кстати, вам не нужно использовать сущность в качестве «узла событий», вы также можете использовать выделенную «шину событий», полностью исключая свой класс сущностей.
Великий
2

О вашей проблеме: если STATE используется только в анимации, вам даже не нужно показывать это другим компонентам. Если он имеет более одного использования, то вам нужно выставить его.

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

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

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

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

Итак, я бы начал с компонента State: PlayerStateComponent, Enemy1State, Enemy2State. Компонент состояния позаботится об изменении состояния в соответствующее время. State - это нечто, почти все ваши объекты, поэтому оно может находиться в GameObject.

Тогда будет AnimationCompoment. Это будет словарь анимаций, привязанный к состоянию. В update () измените анимацию, если состояние меняется.

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

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

  • замените переменную GameObject словарем. Каждый компонент использует словарь для хранения значений. (убедитесь, что правильно обработали столкновение ...)
  • замените словарь простых значений ссылками: class FloatVariable () {public value [...]}
  • Вместо компонента с несколькими состояниями создайте общий StateComponent, в котором вы можете создавать переменные конечные автоматы. Вам необходимо иметь общий набор условий, при которых состояние может меняться: нажатия клавиш, ввод с помощью мыши, изменения переменных (вы можете связать это с FloatVariable выше).
АБР
источник
Этот подход работает, я реализовал нечто подобное год назад, но проблема в том, что почти каждый компонент зависит от других компонентов, поэтому он кажется мне менее гибким. Я также думал о том, чтобы вставить самые общие компоненты (например, transform, render, state ...) в сущность, но я думаю, что это нарушает назначение компонентов, потому что некоторые из них привязаны к сущности, а некоторым сущностям они могут не понадобиться. Вот почему я пытаюсь изменить дизайн системы, отвечающей за обновление логики, чтобы компоненты ничего не знали друг о друге, поскольку сами не обновляются.
Мивиклин
0

В дополнение к ответу ADB вы можете использовать http://en.wikipedia.org/wiki/Dependency_injection , которое помогает, когда вам нужно создать множество компонентов, передавая их как ссылки на их конструкторы. Очевидно, что они все равно будут зависеть друг от друга (если это требуется в вашей кодовой базе), но вы можете поместить всю эту зависимость в одно место, где устанавливаются зависимости, а остальной части кода не нужно знать о зависимости.

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

Для простых систем вы можете обойтись без использования DI или чистого кода, ваши классы RenderingSystem звучат так, как будто вы должны вызывать их статически или, по крайней мере, иметь их в каждом компоненте, что в значительной степени делает их зависимыми друг от друга и трудными для изменения. Если вы заинтересованы в более чистом подходе, проверьте ссылки на ссылку DI wiki выше и прочитайте о Чистом коде: http://clean-code-developer.com/

exDreamDuck
источник
У меня уже есть одна система, где компоненты довольно сильно зависят друг от друга. Я активно использовал там внедрение зависимостей, и хотя я предпочитаю его более глубоким иерархиям, я пытаюсь создать новую, чтобы избежать связывания компонентов, если это было возможно. Я бы не стал ничего называть статически. Я хотел бы иметь ComponentManager, к которому у каждой системы есть доступ (каждая система должна иметь ссылку на него), а RendererSystem получит все компоненты анимации из диспетчера компонентов и отобразит текущее состояние каждой анимации.
Miviclin