Выполнение бонусов в компонентной системе

29

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

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

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

Что меня смущает, так это то, что у щита, очевидно, есть три компонента, связанные с ним.

  • Снижение ущерба / фильтрация
  • Спрайт
  • Коллайдер

Что еще хуже, различные варианты щитов могут иметь еще большее поведение, все из которых могут быть компонентами:

  • увеличить максимальное здоровье игрока
  • регенерация здоровья
  • отклонение снаряда
  • так далее

  1. Я обдумываю это? Должен ли щит быть просто супер компонентом?
    Я действительно думаю, что это неправильный ответ. Поэтому, если вы думаете, что это путь, пожалуйста, объясните.

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

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

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

  5. Что-то еще, что очевидно для кого-то с большим опытом компонентов?

deft_code
источник
Я позволил себе сделать ваш титул более конкретным.
Тетрад

Ответы:

11

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

Изменить: я думаю, что не достаточно "автономного поведения" для отдельной сущности. В этом конкретном случае щит следует за целью, работает на цель и не переживает цель. Хотя я склонен согласиться с тем, что в концепции «объекта-щита» нет ничего плохого, в этом случае мы имеем дело с поведением, которое прекрасно вписывается в компонент. Но я также сторонник чисто логических сущностей (в отличие от полноценных систем сущностей, в которых вы можете найти компоненты Transform и Rendering).

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

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

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

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

Что-то еще, что очевидно для кого-то с большим опытом компонентов?

Я собираюсь уточнить немного.

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

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

Когда компонент щита «установлен», обработчики сообщений компонента щита связываются с определенным (более высоким) порядком.

Handler Stage    Handler Level     Handler Priority
In               Pre               System High
Out              Invariant         High
                 Post              AboveNormal
                                   Normal
                                   BelowNormal
                                   Low
                                   System Low

In - incoming messages
Out - outgoing messages
Index = ((int)Level | (int)Priority)

Компонент «stats» устанавливает обработчик сообщений «повреждение» по индексу In / Invariant / Normal. Каждый раз, когда получено сообщение «повреждение», уменьшайте HP на величину «стоимости».

Довольно стандартное поведение (добавить некоторую естественную устойчивость к урону и / или расовые черты, что угодно).

Компонент защиты устанавливает обработчик сообщений «повреждение» по индексу In / Pre / High.

Every time a "damage" message is received, deplete the shield energy and substract
the shield energy from the damage value, so that the damage down the message
handler pipeline is reduced.

damage -> stats
    stats
        stats.hp -= damage.value

damage -> shield -> stats
    shield
        if(shield.energy) {
            remove_me();
            return;
        }
        damage.value -= shield.energyquantum
        shield.energy -= shield.energyquantum;

     stats
        stats.hp -= damage.value

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

Имеет смысл? Дайте мне знать, если я могу добавить больше деталей.

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

Raine
источник
Вы ответили «Нет» на первый вопрос без объяснения причин. Обучение других заключается в том, чтобы помочь им понять причину любого решения. IMO, того факта, что в RL силовое поле будет отдельным «физическим объектом» от вашего собственного тела, достаточно, чтобы оно стало отдельным объектом в коде. Можете ли вы предложить веские причины, чтобы предположить, почему идти по этому пути плохо?
инженер
@ Ник, я ни в коем случае не пытаюсь научить кого-либо чему-либо, а делюсь тем, что я знаю по этому вопросу. Однако я собираюсь добавить обоснование этого «нет», которое, мы надеемся, удалит это неприятное понижение :(
Рейн
Ваша точка автономии имеет смысл. Но обратите внимание: «в этом случае мы имеем дело с поведением». True - поведение с участием совершенно отдельного физического объекта (форма столкновения щита). Для меня одна сущность связана с одним физическим телом (или составным набором тел, связанных, например, суставами). Как вы примиряете это? Со своей стороны, я чувствовал бы себя некомфортно, добавляя «фиктивный» физический прибор, который активировался бы, только если игрок использовал щит. ИМО негибкий, трудно поддерживать во всех организациях. Также рассмотрим игру, в которой пояса щитов держат щиты даже после смерти (Дюна).
Инженер
@ Ник, большинство систем сущностей имеют как логические, так и графические компоненты, так что в этом случае наличие сущности для щита абсолютно разумно. В чисто логических системах сущностей «автономность» является продуктом того, насколько сложен объект, его зависимости и время жизни. В конце концов, главное требование - и, учитывая, что нет единого мнения о том, что такое система сущностей, есть много места для проектных решений :)
Raine
@deft_code, пожалуйста, дайте мне знать, если я могу улучшить свой ответ.
Рейн
4

1) Я обдумываю это? Должен ли щит быть просто супер компонентом?

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

2) Должен ли щит быть его собственной сущностью, которая отслеживает местонахождение игрока?

Нет, если только этот щит не является каким-то существом, которое может ходить независимо на каком-то этапе.

3) Должен ли экран быть компонентом, в котором размещены другие компоненты?

Это очень похоже на сущность, поэтому ответ - нет.

4) Должен ли щит быть просто набором компонентов, которые добавляются в плеер?

Это вероятно.

«Снижение ущерба / фильтрация»

  • функциональность компонента основного экрана.

"Спрайт"

  • есть ли причина, по которой вы не можете добавить еще один SpriteComponent к своей сущности персонажа (другими словами, более одного компонента определенного типа на одну сущность)?

"Коллайдер"

  • ты уверен, что тебе нужен еще один? Это зависит от вашего физического движка. Можете ли вы отправить сообщение ColliderComponent персонажа и попросить его изменить форму?

«Увеличьте максимальное здоровье игрока, регенерацию здоровья, отклонение снаряда и т. Д.»

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

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

Дайте вашему щиту пару компонентов: физический / пространственный компонент, представляющий форму столкновения, и компонент DamageAffector, который содержит ссылку на некоторую сущность, к которой он будет применять увеличенный или уменьшенный урон (например, персонажа вашего игрока) каждый раз, когда сущность удерживая DamageAffector получает урон. Таким образом, ваш игрок получает урон "по доверенности".

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

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


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

Существует тонкая грань между логическими подкомпонентами (Spatial, AI, Слоты с оружием, обработка ввода и т. Д. И т. Д.) И физическими подкомпонентами. Вам нужно решить, на какой стороне вы стоите, так как это сильно определяет, какая у вас система сущностей. Для меня подкомпонент Физики моей сущности управляет физически-иерархическими отношениями (такими как конечности в теле - думайте узлы графа сцены), в то время как логические контроллеры, отмеченные выше, как правило, представлены компонентами вашей сущности, а не представляют индивидуальные физические "приспособления".

инженер
источник
3

Должен ли щит быть компонентом, в котором размещены другие компоненты?

Может быть, не содержит других компонентов, но контролирует срок службы вспомогательных компонентов. Так что в некотором грубом псевдокоде ваш клиентский код добавил бы этот «щитовой» компонент.

class Shield : Component
{
    void Start() // happens when the component is added
    {
        sprite = entity.add_component<Sprite>( "shield" );
        collider = entity.add_component<Collider>( whatever );
        //etc
    }

    void OnDestroy() // when the component is removed
    {
        entity.remove_component( sprite );
        entity.remove_component( collider );
    }

    void Update() // every tick
    {
        if( ShouldRemoveSelf() ) // probably time based or something
            entity.remove_component( this );
    }
}
тетрада
источник
Непонятно, что thisзначит в вашем ответе. Имеется в thisвиду компонент Shield или вы имели в виду сущность, которая использует щит, его родителя? Путаница может быть моей ошибкой. «Компонент на основе» является довольно расплывчатым. В моей версии сущностей, основанных на компонентах, сущность - это просто контейнер компонентов с некоторыми минимальными функциональными возможностями (имя объекта, теги, обмен сообщениями и т. Д.).
deft_code
Было бы менее запутанным, если бы я использовал gameObjectили что-то. Это ссылка на текущий игровой объект / сущность / все, что владеет компонентами.
Тетрад
0

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

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

camera template
    moveable
    {
        keyreaction = "scriptforcameramoves"

    }  

player template
    moveable
    {
        keyreaction = "scriptfroplayerkeypress"

    }  

затем в моем перемещаемом компоненте во время регистрации сообщения я регистрирую метод Do сценариев (код на C #)

Owner.RegisterHandler<InputStateInformation>(MessageType.InputUpdate, kScript.Do);

конечно, это зависит от моего метода Do, следующего за шаблоном функций, которые принимает мой RegisterHandler. В этом случае его (отправитель IComponent, аргумент типа ref)

так что мой «скрипт» (в моем случае также C # только скомпилированный runime) определяет

 public class CameraMoveScript : KeyReactionScript
{
 public override void Do(IComponent pSender, ref InputStateInformation inputState)
 {
    //code here
 }
}

и мой базовый класс KeyReactionScript

public class KeyReactionScript : Script
{
      public virtual void Do(IComponent pSender, ref InputStateInformation inputState);
}

затем позже, когда компонент ввода отправляет сообщение типа MessageTypes.InputUpdate с типом как таковым

 InputStateInformation myInputState = InputSystem.GetInputState();
 SendMessage<InputStateInformation>(MessageTypes.InputUpdate, ref myInputState);

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

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

exnihilo1031
источник