Я реалізую варіант системної системи, який має:
Клас сутностей , який трохи більше , ніж ID , який пов'язує компоненти разом
Купа компонентних класів, що не мають "логіки компонентів", лише дані
Купа системних класів (також "підсистеми", "менеджери"). Вони виконують всю логічну обробку сутності. У більшості основних випадків системи просто повторюють список об'єктів, які їх цікавлять, і роблять дії по кожному з них
Об'єкт класу MessageChannel , який є загальним для всіх ігрових систем. Кожна система може підписатися на певний тип повідомлень для прослуховування, а також може використовувати канал для трансляції повідомлень іншим системам
Початковий варіант обробки системних повідомлень був приблизно таким:
- Запускайте оновлення для кожної ігрової системи послідовно
Якщо система робить щось з компонентом і ця дія може зацікавити інші системи, система надсилає відповідне повідомлення (наприклад, система дзвінки
messageChannel.Broadcast(new EntityMovedMessage(entity, oldPosition, newPosition))
щоразу, коли об'єкт переміщено)
Кожна система, яка підписалася на конкретне повідомлення, отримує назву методу обробки повідомлень
Якщо система обробляє подію, а логіка обробки подій вимагає трансляції іншого повідомлення, повідомлення отримує трансляцію відразу, а інший ланцюжок методів обробки повідомлень викликається
Цей варіант був у порядку, поки я не почав оптимізувати систему виявлення зіткнень (вона стала дуже повільною, оскільки кількість об'єктів збільшувалася). Спочатку це просто повторить кожну пару сутностей, використовуючи простий алгоритм грубої сили. Потім я додав "просторовий індекс", який має сітку комірок, яка зберігає сутності, що знаходяться всередині області певної комірки, таким чином дозволяючи робити перевірки лише сутностей у сусідніх осередках.
Кожен раз, коли суб'єкт рухається, система зіткнення перевіряє, чи стикається суб'єкт із чимось у новій позиції. Якщо це так, зіткнення виявляється. І якщо обидва зіштовхуються об'єкти - це "фізичні об'єкти" (вони обидва мають компонент RigidBody і покликані відштовхувати один одного, щоб не займати одне і те саме простір), спеціальна система жорсткого розділення тіла просить систему руху перемістити об'єкти до деяких конкретні позиції, які б їх розділили. Це, в свою чергу, змушує систему руху надсилати повідомлення, що сповіщають про змінені позиції сутності. Система виявлення зіткнень покликана реагувати, оскільки їй необхідно оновити її просторовий індекс.
У деяких випадках це спричиняє проблеми, оскільки вміст комірки (загальний список об'єктів Entity у C #) змінюється під час їх перегляду, тим самим викликаючи ітератор, який викидає виняток.
Отже ... як я можу запобігти перериванню системи зіткнення під час перевірки на зіткнення?
Звичайно, я міг би додати деяку "розумну" / "хитру" логіку, яка забезпечує вміст комірок правильно ітератуватися, але я думаю, що проблема полягає не в самій системі зіткнення (у мене також були подібні проблеми в інших системах), а в тому, як повідомлення обробляються, коли вони переходять із системи в систему. Що мені потрібно - це певний спосіб переконатися, що конкретний метод обробки подій виконує свою роботу без будь-яких перерв.
Що я спробував:
- Черги на вхідні повідомлення . Кожен раз, коли деяка система транслює повідомлення, повідомлення додається до черг повідомлень зацікавлених у ньому систем. Ці повідомлення обробляються, коли оновлення системи викликається кожним кадром. Проблема : якщо система A додає повідомлення до черги B системи, вона працює добре, якщо система B має бути оновлена пізніше системи A (у тому ж ігровому кадрі); інакше це призводить до того, що повідомлення обробляє наступний ігровий кадр (небажано для деяких систем)
- Черги на вихідні повідомлення . Поки система обробляє подію, будь-які повідомлення, які вона транслює, додаються до черги вихідних повідомлень. Повідомлення не потрібно чекати, коли буде оброблено оновлення системи: вони отримують обробку "відразу" після того, як початковий обробник повідомлень закінчить роботу. Якщо обробка повідомлень призводить до трансляції інших повідомлень, вони також додаються у вихідну чергу, тому всі повідомлення обробляються в одному кадрі. Проблема: якщо система життєдіяльності суб'єкта господарювання (я реалізував управління життям сутності за допомогою системи) створює сутність, вона повідомляє про це деякі системи A і B. У той час як система A обробляє повідомлення, це призводить до знищення ланцюга повідомлень, які врешті-решт призводять до знищення створеної сутності (наприклад, об'єкт кулі, створений прямо там, де він стикається з деякою перешкодою, через що куля саморуйнується). Поки ланцюжок повідомлень вирішується, система B не отримує повідомлення про створення сутності. Отже, якщо система B також зацікавлена у повідомленні про знищення сутності, вона отримує її, і лише після того, як «ланцюжок» закінчиться розв’язуванням, отримує початкове повідомлення про створення сутності. Це призводить до ігнорування повідомлення про знищення, повідомлення про створення - "прийнято",
РЕДАКТУВАТИ - ВІДПОВІДИ НА ЗАПИТАННЯ, КОМЕНТАРІ:
- Хто модифікує вміст комірки, коли система зіткнення перетворюється на них?
У той час як система зіткнення проводить перевірку на зіткнення з якоюсь сутністю та її сусідами, зіткнення може бути виявлено, і система сутності відправить повідомлення, на яке негайно відреагують інші системи. Реакція на повідомлення може спричинити створення інших повідомлень та їх обробку негайно. Тож якась інша система може створити повідомлення про те, що система зіткнення потім повинна буде обробитись одразу (наприклад, об'єкт перемістився, щоб система зіткнення потребує оновлення свого просторового індексу), хоча попередні перевірки зіткнення ще не були закінчені.
- Ви не можете працювати з глобальною чергою вихідних повідомлень?
Нещодавно я спробував одну глобальну чергу. Це спричиняє нові проблеми. Проблема: я переміщую об'єкт танка в настінну сутність (цистерною керує клавіатура). Тоді я вирішую змінити напрямок танка. Щоб відокремити резервуар і стінку кожного кадру, система CollidingRigidBodySeparationSystem відсуває танк від стіни на найменшу можливу кількість. Напрямок розділення повинен бути протилежним напрямку руху танка (коли починається ігровий розіграш, танк повинен виглядати так, ніби він ніколи не переміщувався в стіну). Але напрямок стає протилежним напрямку НОВОГО, таким чином переміщуючи танк на іншу сторону стінки, ніж це було спочатку. Чому виникає проблема: Ось як зараз обробляються повідомлення (спрощений код):
public void Update(int deltaTime)
{
m_messageQueue.Enqueue(new TimePassedMessage(deltaTime));
while (m_messageQueue.Count > 0)
{
Message message = m_messageQueue.Dequeue();
this.Broadcast(message);
}
}
private void Broadcast(Message message)
{
if (m_messageListenersByMessageType.ContainsKey(message.GetType()))
{
// NOTE: all IMessageListener objects here are systems.
List<IMessageListener> messageListeners = m_messageListenersByMessageType[message.GetType()];
foreach (IMessageListener listener in messageListeners)
{
listener.ReceiveMessage(message);
}
}
}
Код протікає так (припустимо, це не перший ігровий кадр):
- Системи починають обробку TimePassedMessage
- InputHandingSystem перетворює натискання клавіш на дію сутності (у цьому випадку ліва стрілка перетворюється на дію MoveWest). Дія сутності зберігається в компоненті ActionExecutor
- SystemExecutionSystem , реагуючи на дію сутності, додає MovementDirectionChangeRequestedMessage в кінець черги повідомлень
- MovementSystem переміщує позицію об'єкта на основі даних компонента швидкості та додає повідомлення PositionChangedMessage до кінця черги. Рух здійснюється за допомогою напрямку руху / швидкості попереднього кадру (скажімо, на північ)
- Системи зупиняють обробку TimePassedMessage
- Системи починають обробляти MovementDirectionChangeRequestedMessage
- MovementSystem змінює швидкість / напрямок руху сутності за потребою
- Системи зупиняють обробку MovementDirectionChangeRequestedMessage
- Системи починають обробку PositionChangedMessage
- CollisionDetectionSystem виявляє, що через те, що сутність перемістилося, вона наткнулася на іншу сутність (танк зайшов всередину стіни). Він додає CollisionOcuredMessage до черги
- Системи припиняють обробку PositionChangedMessage
- Системи починають обробку CollisionOcuredMessage
- CollidingRigidBodySeparationSystem реагує на зіткнення поділом бака і стінки. Оскільки стінка статична, переміщується лише бак. Напрямок руху танків використовується як індикатор, звідки взявся танк. Він зміщений у зворотному напрямку
БУГ: Коли танк перемістив цей кадр, він перемістився, використовуючи напрямок руху від попереднього кадру, але коли він був відокремлений, був використаний напрямок руху від ЦЬОГО кадру, хоча він вже був іншим. Це не так, як має працювати!
Щоб запобігти цій помилці, старий напрямок руху потрібно десь зберегти. Я міг би додати його до якогось компонента просто для виправлення цієї конкретної помилки, але чи не в цьому випадку вказується якийсь принципово неправильний спосіб обробки повідомлень? Чому система розділення повинна дбати, який напрямок руху вона використовує? Як можна вирішити цю проблему елегантно?
- Ви можете прочитати gamadu.com/artemis, щоб побачити, що вони зробили з Aspects, яка сторона вказує на деякі проблеми, які ви бачите.
Насправді я вже досить давно знайомий з Артемідою. Досліджував його вихідний код, читав форуми тощо. Але я бачив, як "Аспекти" згадуються лише в кількох місцях, і, наскільки я це розумію, вони в основному означають "Системи". Але я не можу бачити, як сторона Артеміди вирішує деякі мої проблеми. Він навіть не використовує повідомлення.
- Дивіться також: "Спілкування між особами: Черга повідомлень проти Опублікувати / Підписатися проти сигналу / Слоти"
Я вже читав усі питання gamedev.stackexchange щодо систем сутності. Цей, схоже, не обговорює проблем, з якими я стикаюся. Я щось пропускаю?
- Поводьтеся з двома випадками по-різному, оновлення сітки не покладається на повідомлення про рух, оскільки це частина системи зіткнення
Я не впевнений, що ти маєш на увазі. Старіші впровадження системи CollisionDetectionSystem просто перевіряли б на колізії під час оновлення (коли TimePassedMessage оброблявся), але мені довелося мінімізувати перевірки настільки, наскільки я міг завдяки продуктивності. Тому я перейшов до перевірки зіткнень, коли сутність рухається (більшість об'єктів моєї гри є статичними).