Організація системи організації із зовнішніми менеджерами компонентів?


13

Я розробляю ігровий двигун для багатокористувацької гри 2D-шутерів зверху вниз, який хочу бути використаним для інших ігор з шутерами зверху вниз. На даний момент я замислююся над тим, як має бути спроектовано щось на зразок системи сутності в ньому. Спочатку я подумав про це:

У мене є клас під назвою EntityManager. Він повинен реалізувати метод, який називається Update, та інший під назвою Draw. Причина, по якій я розмежовую логіку та візуалізацію, полягає в тому, що тоді я можу опустити метод Draw, якщо запускається окремий сервер.

EntityManager володіє списком об'єктів типу BaseEntity. Кожен суб'єкт господарювання має перелік компонентів, таких як EntityModel (репрезентативне представництво об'єкта), EntityNetworkInterface та EntityPhysicalBody.

EntityManager також має список менеджерів компонентів, таких як EntityRenderManager, EntityNetworkManager та EntityPhysicsManager. Кожен менеджер компонентів зберігає посилання на компоненти сутності. Існують різні причини для переміщення цього коду з власного класу сутності та замість цього. Наприклад, я використовую для гри зовнішню бібліотеку фізики Box2D. У Box2D ви спочатку додаєте тіла та форми до світу (у даному випадку належить EntityPhysicsManager) та додаєте зворотні виклики зіткнення (які будуть відправлені до самого об'єкта сутності в моїй системі). Потім ви запускаєте функцію, яка імітує все в системі. Мені важко знайти краще рішення для цього, ніж робити це у зовнішньому менеджері компонентів, як це.

Створення сутності робиться так: EntityManager реалізує метод RegisterEntity (entitClass, завод), який реєструє, як створити сутність цього класу. Він також реалізує метод CreateEntity (entitClass), який би повертав об'єкт типу BaseEntity.

Тепер виникає моя проблема: як би реєструвати посилання на компонент до менеджерів компонентів? Я поняття не маю, як би я посилався на менеджерів компонентів із заводу / закриття.


Я не знаю, чи можливо це мається на увазі гібридна система, але це звучить, як ваші «менеджери» - це те, що я, як правило, називаюсь як «системи». тобто об'єкт є абстрактним ідентифікатором; Компонент - це пул даних; і те, що ви називаєте "менеджером", - це те, що загалом називають "Система". Чи правильно я тлумачу лексику?
BRPocock


Погляньте на gamadu.com/artemis і дізнайтеся, чи відповідають їхні методи на ваше запитання.
Патрік Х'юз

1
Не існує жодного способу проектування системи сутності, оскільки щодо її визначення мало консенсусу. Те, що @BRPocock описує, а також те, що Артеміда використовує ХЕС, було детальніше описано в цьому блозі: t-machine.org/index.php/category/entity-systems разом із вікі: entit-systems.wikidot.com
user8363

Відповіді:


6

Системи повинні зберігати пару ключових значень Entity to Component в якомусь мапі, об’єкті словника або асоціативному масиві (залежно від мови, що використовується). Крім того, коли ви створюєте свій об'єкт Entity, я б не переймався зберіганням його в менеджері, якщо вам не потрібно мати можливість відреєстрації його в будь-якій із систем. Entity - це складова компонентів, але вона не повинна обробляти жодне з оновлень компонента. Цим повинні займатися Системи. Натомість трактуйте свою сутність як ключ, який відображається на всіх компонентах, які вони містять у системах, а також як центр зв'язку для цих компонентів, щоб спілкуватися один з одним.

Велика частина моделей Entity-Component-System полягає в тому, що ви можете легко реалізувати можливість передачі повідомлень від одного компонента до інших компонентів сутності. Це дозволяє компоненту спілкуватися з іншим компонентом, не знаючи, хто цей компонент або як поводитися з компонентом, який він змінює. Замість цього він передає повідомлення і дозволяє компоненту самому змінюватися (якщо він існує)

Наприклад, система позицій не мала б у ній великого коду, лише слідкуючи за об'єктами сутності, відображеними на їх компонентах позиції. Але коли позиція змінюється, вони можуть надіслати повідомлення суб'єкту, що займається, і, в свою чергу, передається всім компонентам цієї організації. Позиція змінюється з будь-якої причини? Система позицій надсилає Суб'єкту повідомлення, що позиція змінилася, і десь компонент відображення зображення цього об'єкта отримує це повідомлення та оновлює, де воно буде малюватись далі.

І навпаки, фізична система повинна знати, що всі її об'єкти роблять; Він повинен мати можливість бачити всі світові об’єкти для перевірки зіткнень. Коли відбувається зіткнення, він оновлює компонент напряму суб'єкта господарювання, надсилаючи якесь "Повідомлення про зміну напрямку" суб'єкту, а не прямуючи безпосередньо до компонента Суб'єкта. Це відв'язує менеджера від необхідності знати, як змінювати напрямки, використовуючи повідомлення, замість того, щоб покладатися на певний компонент, який там є (якого його взагалі не може бути. У такому випадку повідомлення просто потраплятиме на глухі вуха замість деякої помилки виникає тому, що очікуваний об’єкт відсутній).

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

Коротко:

Сутність - це складова Компонентів, яка може приймати повідомлення. Організація може отримувати повідомлення, делегуючи вказані повідомлення всім їх компонентам, щоб оновити їх. (Повідомлення змінено позицію, напрямок зміни швидкості тощо) Це як центральна поштова скринька, яку всі компоненти можуть чути один від одного, а не говорити безпосередньо один одному.

Компонент - це невелика частина суб'єкта, що зберігає деякий стан сутності. Вони здатні розбирати певні повідомлення та викидати інші повідомлення. Наприклад, "Компонент напряму" дбає лише про "Повідомлення про зміну напрямку", але не про "Повідомлення про зміну позиції". Компоненти оновлюють власний стан на основі повідомлень, а потім оновлюють стан інших компонентів, надсилаючи повідомлення зі своєї Системи.

Система керує всіма компонентами певного типу і відповідає за оновлення зазначених компонентів кожного кадру, а також за відправлення повідомлень з компонентів, якими вони керують, до об'єктів, до яких належать компоненти

Системи зможуть паралельно оновлювати всі свої компоненти та зберігати всі повідомлення у міру їх надходження. Потім, коли виконання методів оновлення всіх систем завершиться, ви попросите кожну систему відправити їх повідомлення в певному порядку. Спочатку управління, можливо, потім - фізика, а потім напрямок, положення, візуалізація тощо. Має значення, в якому порядку вони надсилаються, оскільки зміна напряму фізики завжди повинна зважувати зміну напрямку управління.

Сподіваюсь, це допомагає. Це пекло зразка дизайну, але він смішно потужний, якщо зробити все правильно.


0

Я використовую подібну систему в своєму двигуні, і те, як я це робив, кожне об'єднання містить перелік компонентів. Від EntityManager я можу запитувати кожну з сутностей і бачити, які з них містять даний компонент. Приклад:

class Component
{
    private uint ID;
    // etc...
}

class Entity
{
    List<Component> Components;
    // etc...
    public bool Contains(Type type)
    {
        foreach(Component comp in Components)
        {
            if(typeof(comp) == type)
                return true;
        }
        return false;
    }
}

class EntityManager
{
    List<Entity> Entities;
    // etc...
    public List<Entity> GetEntitiesOfType(Type type)
    {
        List<Entity> results = new List<Entity>();
        foreach(Entity entity in Entities)
        {
            if(entity.Contains(type))
                results.Add(entity);
        }
        return results;
    }
}

Очевидно, що це не точний код (вам фактично потрібна функція шаблону для перевірки різних типів компонентів, а не використання typeof), але концепція є. Тоді ви можете взяти ці результати, посилатися на шуканий компонент та зареєструвати його на своїй фабриці. Це запобігає прямій зв'язці між Компонентами та їх менеджерами.


3
Caveat emptor: у точці, в якій ваша Сутність містить дані, це об’єкт, а не сутність ... В цій структурі ви втрачаєте більшість паралелізуючих (sic?) Переваг ECS. Системи "чисто" E / C / S є реляційними, не об'єктно-орієнтованими ... Не те, що це обов'язково "погано" для деяких випадків, але, безумовно, "зламає реляційну модель"
BRPocock

2
Я не впевнений, що тебе розумію. Моє розуміння (і, будь ласка, виправте мене, якщо я помиляюсь) - це базовий Entity-Component-System, який має клас Entity, в якому розміщені компоненти та може бути ідентифікатор, ім’я чи якийсь ідентифікатор. Я думаю, що ми можемо мати непорозуміння в тому, що я маю на увазі під "типом" Сутності. Коли я кажу Entity "type", я маю на увазі типи компонентів. Тобто Entity - це тип "Sprite", якщо він містить компонент Sprite.
Майк Клук

1
У чистій системі Сутність / Компонент Сутність зазвичай є атомною: наприклад typedef long long int Entity; Компонент - це запис (він може бути реалізований у вигляді об’єктного класу або просто а struct), який має посилання на Сутність, до якої він додається; і Система буде методом або колекцією методів. Модель ECS не дуже сумісна з моделлю OOP, хоча Компонент може бути (в основному) Об'єктом лише для даних, а Система - єдиним єдиним об'єктом, стан якого живе в компонентах ... хоча "гібридні" системи є Частіше, ніж "чисті", вони втрачають багато вроджених благ.
BRPocock

2
@BRPocock повторно "чисті" системи сутності. Я думаю, що об'єкт як об'єкт ідеально чудовий, він не повинен бути простим ідентифікатором. Одне - це серіалізоване представлення, інше - макет в пам'яті об'єкта / концепції / сутності. Поки ви можете підтримувати керованість даними, не слід прив'язуватися до неідіоматичного коду лише тому, що це "чистий" спосіб.
Дощ

1
@BRPocock це справедливе попередження, але для систем, що нагадують "t-machine". Я розумію, чому, але це не єдині способи моделювання компонентів на основі компонентів. актори - цікава альтернатива. Я схильний цінувати їх більше, особливо для суто логічних сутностей.
Дощ

0

1) Ваш заводський метод повинен бути переданий посиланням на EntityManager, який його викликав (я буду використовувати C # як приклад):

delegate BaseEntity EntityFactory(EntityManager manager);

2) Нехай CreateEntity також отримує ідентифікатор (наприклад, рядок, ціле число, це залежить від вас), крім класу / типу сутності, і автоматично зареєструє створене об'єкт у словнику, використовуючи цей ідентифікатор як ключ:

class EntityManager
{
    // Rest of class omitted

    BaseEntity CreateEntity(string id, Type entityClass)
    {
        BaseEntity entity = factories[entityClass](this);
        registry.Add(id, entity);
        return entity;
    }

    Dictionary<Id, BaseEntity> registry;
}

3) Додайте Getter до EntityManager, щоб отримати будь-яку сутність за ідентифікатором:

class EntityManager
{
    // Rest of class omitted

    BaseEntity GetEntity(string id)
    {
        return registry[id];
    }
}

І це все, що вам потрібно, щоб посилатися на будь-який ComponentManager з вашого фабричного методу. Наприклад:

BaseEntity CreateSomeSortOfEntity(EntityManager manager)
{
    // Create and configure entity
    BaseEntity entity = new BaseEntity();
    RenderComponent renderComponent = new RenderComponent();
    entity.AddComponent(renderComponent);

    // Get a reference to the render manager and register component
    RenderEntityManager renderer = manager.GetEntity("RenderEntityManager") as RenderEntityManager;
    if(renderer != null)
        renderer.Register(renderComponent)

    return entity;
}

Крім Id, ви також можете скористатися якоюсь властивістю Type (користувальницький enum або просто покластися на систему типів мови) та створити getter, який повертає всі BaseEntities певного типу.


1
Не бути педантичним, але знову ж таки… у системі сутої (реляційної) сутності суб'єкти не мають іншого типу, за винятком того, що їм
надано

@BRPocock: Чи можете ви створити приклад, який слід за чистою чеснотою?
Золомон

1
@Raine Можливо, я не маю досвіду з перших рук з цим, але це я читав. І є оптимізації, які ви можете впровадити, щоб скоротити витрачений час на пошук компонентів на id. Що стосується узгодженості кешу, я думаю, що це має сенс, оскільки ви одночасно зберігаєте дані одного типу в пам’яті, особливо коли ваші компоненти легкі або прості. Я читав, що один пропуск кешу на PS3 може бути таким же дорогим, як тисяча інструкцій процесора, і такий підхід постійного зберігання даних подібного типу є дуже поширеною технікою оптимізації в сучасній розробці ігор.
Девід Гувейя

2
У довідці: «чиста» система сутності: ідентифікатор особи, як правило, щось подібне typedef unsigned long long int EntityID;:; ідеальним є те, що кожна система може жити на окремому процесорі чи хості та вимагати лише отримання компонентів, що мають відношення до / активні в цій системі. З об’єктом Entity, можливо, доведеться створювати один і той же об'єкт Entity на кожному хості, що ускладнює масштабування. Чиста модель-система-компонент-система розбиває обробку через вузли (процеси, процесори або хости) зазвичай системою, а не сутністю.
BRPocock

1
@DavidGouveia згадав "оптимізацію ... пошук об'єктів за ідентифікатором". Насправді (декілька) систем, які я реалізував таким чином, як правило, не роблять цього. Частіше вибирайте компоненти за деяким шаблоном, що вказує на те, що вони представляють інтерес для певної системи, використовуючи об'єкти (ідентифікатори) лише для перехресних компонентів.
BRPocock
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.