Як просунути ігровий компонент, що складається з об'єктів, у покроковій грі?


9

Досі системи компонентів сутності, якими я користувався, працювали здебільшого як артеміди Java:

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

Зараз я намагаюся застосувати це до гри по черзі вперше, з тоннами подій та відповідей, які повинні відбуватися в наборі в порівнянні з іншими, перш ніж гра може продовжуватися. Приклад:

Гравець A отримує шкоду від меча. У відповідь на це броня A забиває і знижує отриману шкоду. Швидкість руху A також знижується внаслідок ослаблення.

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

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

Загалом це, здається, призводить до кількох проблем:

  1. Багато витрачених циклів обробки: Більшість систем (окрім речей, які завжди працюють, як-от рендерінг) просто не мають нічого гідного робити, коли це не їхня черга працювати, і витрачати більшу частину часу на очікування гри дійсна робота-стан. Це засмічує кожну таку систему з чеками, які постійно збільшуються в розмірі, чим більше станів додається в гру.
  2. Щоб дізнатись, чи може система обробляти об'єкти, які є в грі, їм потрібен певний спосіб контролювати інші незв'язані стану сутності / системи (система, відповідальна за нанесення шкоди, повинна знати, застосована чи ні броня). Це або заплутує системи з численними обов'язками, або створює потребу в додаткових системах, не маючи інших цілей, окрім сканування колекції сутностей після кожного циклу обробки та спілкування з набором слухачів, кажучи їм, коли добре щось робити.

Вищевказані два пункти передбачають, що системи працюють на одному і тому ж наборі сутностей, які в кінцевому підсумку змінюють стан, використовуючи прапорці в своїх компонентах.

Іншим способом вирішити це було б додавання / видалення компонентів (або створення абсолютно нових об'єктів) у результаті роботи однієї системи для просування стану ігор. Це означає, що всякий раз, коли система насправді має відповідну сутність, вона знає, що її дозволено обробляти.

Це, однак, робить системи відповідальними за запуск наступних систем, ускладнюючи міркування про поведінку програм, оскільки помилки не з’являться в результаті єдиної системної взаємодії. Додавання нових систем також стає складніше, оскільки їх неможливо реалізувати, не знаючи, як саме вони впливають на інші системи (і попередні системи, можливо, доведеться модифікувати, щоб викликати стани, в яких нова система зацікавлена), начебто перемагаючи мету створення окремих систем з одним завданням.

Це щось, з чим мені доведеться жити? Кожен приклад ECS, який я бачив, був у режимі реального часу, і це дуже просто зрозуміти, як працює ця цикл з однією ітерацією за гру в таких випадках. І мені це все ще потрібно для візуалізації, воно просто здається непридатним для систем, які призупиняють більшість аспектів себе кожного разу, коли щось відбувається.

Чи є якась схема дизайну для переміщення ігрового стану вперед, яка підходить для цього, або я повинен просто перемістити всю логіку з циклу і замість цього запустити її лише за потреби?


Ви дійсно не хочете опитуватись, чи відбудеться подія. Подія буває лише тоді, коли вона трапляється. Чи не дозволяє Артеміда системам спілкуватися між собою?
Сидар

Так, але лише шляхом з'єднання їх методами.
Aeris130

Відповіді:


3

Моя порада тут виходить із минулого досвіду проекту RPG, де ми використовували компонентну систему. Я скажу, що я ненавидів працювати в цьому ігровому коді, оскільки це був код спагетті. Тому я не пропоную багато відповіді тут, лише перспектива:

Логіка, яку ви описуєте, для обробки пошкодження меча гравцеві ... здається, одна система повинна відповідати за все це.

Десь є функція HandleWeaponHit (). Він отримає доступ до ArmorComponent об'єкта гравця, щоб отримати відповідну броню. Він отримав би доступ до WeaponComponent атакуючої зброї, щоб, можливо, розбити зброю. Після обчислення остаточної шкоди, він доторкнеться до MovementComponent для гравця, щоб досягти зниження швидкості.

Що стосується витрачених циклів обробки ... HandleWeaponHit () слід спрацьовувати лише в разі потреби (при виявленні удару меча).

Можливо, я намагаюся зробити таке: напевно вам потрібно місце в коді, де ви можете поставити точку розриву, вдарити його, а потім перейти до кроку по всій логіці, яка повинна працювати, коли трапляється удар меча. Іншими словами, логіка не повинна бути розповсюджена по всім функціям галочки ().


Якщо це зробити так, це зробить функцію hit () функційною кулькою, оскільки додається більше поведінки. Скажімо, є ворог, який сміється щоразу, коли меч потрапляє в ціль (будь-яку ціль) у межах своєї видимості. Чи повинен HandleWeaponHit дійсно нести відповідальність за те, що це викликало?
Aeris130

1
Ви маєте чітко переплетену бойову послідовність, так що так, удар викликає запускаючі ефекти. Не все має бути розбито на маленькі системи, нехай ця ця система впорається з цим, бо це справді ваша "Бойова система", і вона справляється ... Боротьба ...
Патрік Х'юз

3

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

Я не впевнений, що це порушує поняття ECS, але що робити:

  • Додайте EventBus, щоб дозволити Системам видавати / передплачувати об’єкти події (фактично чисті дані, але я не думаю, що це компонент)
  • Створіть компоненти для кожного проміжного стану

Приклад:

  • UserInputSystem запускає подію Attack за допомогою [DamageDealerEntity, DamageReceiverEntity, відомості про вміння / зброю]
  • CombatSystem підписаний на нього і обчислює шанс ухилення від DamageReceiver. Якщо ухилення не вдалося, то воно викликає пошкодження події з однаковими параметрами
  • DamageSystem підписується на таку подію і таким чином спрацьовує
  • DamageSystem використовує Strength, BaseWeapon збитки, його тип тощо та записує його в новий IncomingDamageComponent з [DamageDealerEntity, FinalOutgoingDamage, DamageType] та додає його до приймача пошкоджень Entity / Entities
  • DamageSystem запускає обчислений вихідний пошкодження
  • Система ArmorSystem запускається нею, піднімає приймач Entity або здійснює пошук за цим аспектом IncomingDamage в Entities, щоб забрати IncomingDamageComponent (останній може бути, мабуть, кращим для декількох атак із поширенням) та обчислює броні та шкоду, завдану йому. Необов’язково запускає події для розбиття меча
  • ArmorSystems видаляє IncomingDamageComponent у кожному об'єкті та замінює його на DamageReceivedComponent з кінцевими підрахунковими числами, які впливатимуть на HP та зменшення швидкості від ран
  • ArmorSystems надсилає подію IncomingDamageCalculated
  • Система швидкості передплачується і перераховує швидкість
  • HealthSystem підписується та зменшує фактичну кількість HP
  • тощо
  • Якось прибирати

Плюси:

  • Система запускає один одного, надаючи проміжні дані для складних подій ланцюга
  • Розв’язка EventBus

Мінуси:

  • Я відчуваю, що я змішую два способи передачі речей: у параметрах подій та у тимчасових компонентах. це може бути слабким місцем. Теоретично, щоб зберегти однорідність речей, я міг би запускати просто перерахунки подій без даних, щоб Системи знаходили неявні параметри в компонентах Entity за аспектом ... Не впевнений, чи все гаразд
  • Не знаєте, як дізнатись, чи всі потенційно зацікавлені SystemHave обробляли IncomingDamageCalculated, щоб його можна було очистити та не допустити наступного повороту. Можливо, якісь перевірки ще в CombatSystem ...

2

Опублікувавши рішення, я нарешті зупинився, подібно до Яковлєва.

В основному я застосував систему подій, оскільки мені стало дуже інтуїтивно слідувати її логіці за поворотами. Система відповідала за ігрові підрозділи, які дотримувалися покрокової логіки (гравець, монстри та все, з чим вони можуть взаємодіяти), а завдання в режимі реального часу, такі як візуалізація та введення опитування, розміщувалися в інших місцях.

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

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

Приклад: Гравець отримує шкоду, тому суб'єкту гравця надсилається подія збитку. DamageSystem підписується на пошкодження подій, що надсилаються будь-якій сутності з компонентом здоров’я, і має метод onEvent (сутність, подія), який зменшує стан здоров'я в компоненті суб'єктів на суму, вказану в події.

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

Іноді система повинна виходити за межі одержуючої особи. Щоб продовжити свою відповідь на Еріка Ундерсандера, було б тривіально додати систему, яка отримує доступ до карти гри та шукає сутності з FallsDownLaughingComponent в межах x проміжків суб'єкта, що отримує пошкодження, а потім надсилає їм FallDownLaughingEvent. Цю систему потрібно було запланувати на отримання події після системи пошкодження, якщо подія збитку не була скасована в цей момент, шкода була завдана.

Одне з проблем, яке виникло, полягало в тому, як переконатися, що події відповідей обробляються в порядку їх надсилання, враховуючи, що деякі відповіді можуть породити додаткові відповіді. Приклад:

Гравець рухається, підказуючи, що подія руху пересилається об'єкту гравців і приймається системою руху.

У черзі: рух

Якщо рух дозволений, система регулює положення гравців. Якщо ні (гравець намагався перейти на перешкоду), він позначає подію як скасовану, внаслідок чого менеджер сутності відкидає її, а не надсилає її наступним системам. Наприкінці списку систем, зацікавлених у події, є TurnFinishedSystem, який підтверджує, що гравець витратив свою чергу на переміщення персонажа, і що його / її черга зараз закінчилася. Це призводить до того, що подія TurnOver надсилається об'єкту гравця і розміщується в черзі.

У черзі: TurnOver

Тепер скажіть, що гравець наступив на пастку, завдаючи шкоди. TrapSystem отримує повідомлення про рух перед TurnFinishedSystem, тому подія пошкодження надсилається першою. Тепер черга замість цього виглядає так:

У черзі: Пошкодження, Поворот

Поки все добре, подія пошкодження буде оброблена, і тоді черга закінчується. Однак що робити, якщо додаткові події будуть надіслані як відповідь на шкоду? Тепер черга подій виглядатиме так:

У черзі: Пошкодження, Поворот, Відповідь, Пошкодження

Іншими словами, черга закінчиться до того, як будь-які відповіді на пошкодження будуть оброблені.

Для вирішення цього питання я застосував два способи надсилання подій: send (подія, сутність) та відповідь (подія, eventToRespondTo, сутність).

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

Крім цього, масив змінної довжини використовується, щоб містити кілька черг подій. Щоразу, коли подію отримує менеджер, подія додається до черги з індексом у масиві, який відповідає кількості подій у ланцюжку відповідей. Таким чином, початкова подія переміщення додається до черги в [0], а пошкодження, а також події TurnOver додаються до окремої черги за номером [1], оскільки вони обидва були надіслані як відповіді на рух.

Коли відповіді на пошкодження події будуть надіслані, ці події будуть містити як саму подію пошкодження, так і рух, ставлячи їх у чергу в індексі [2]. Поки індекс [n] має події у своїй черзі, ці події будуть оброблятися перед тим, як перейти до [n-1]. Це дає порядок обробки:

Рух -> Пошкодження [1] -> ResponseToDamage [2] -> [2] порожній -> Поворот [1] -> [1] порожній -> [0] порожній

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.