Як найкраще структурувати / керувати сотнями персонажів у грі


10

Я робив просту гру RTS, яка містить сотні персонажів, таких як Crusader Kings 2, в Unity. Для їх зберігання найпростішим варіантом було б використання об'єктів, що проглядаються, але це не дуже вдале рішення, оскільки ви не можете створювати нові під час виконання.

Тому я створив клас C # під назвою "Характер", який містить усі дані. Все працює добре, але, як гра імітує, вона постійно створює нових персонажів та вбиває деяких персонажів (як це відбувається в грі). Оскільки гра безперервно імітує, вона створює 1000 персонажів. Я додав простий чек, щоб переконатися, що персонаж "Живий" під час обробки його функції. Так це допомагає продуктивності, але я не можу видалити "Характер", якщо він / вона мертвий, тому що мені потрібна його інформація під час створення сімейного дерева.

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


9
Одне, що я робив у минулому, коли мені потрібна підмножина даних персонажа після його смерті, - це створити для цього персонажа об'єкт "надгробний камінь". Надгробний камінь може містити інформацію, яку мені потрібно шукати пізніше, але вона може бути меншою і повторюватися рідше, тому що вона не потребує постійного моделювання, як живий характер.
DMGregory


2
Чи така гра, як CK2, чи це лише частина про те, що має багато символів? Я зрозумів, що вся гра схожа на CK2. У цьому випадку багато відповідей тут є невірними і містять хороші ноу-хау, але вони пропускають суть питання. Це не допоможе, що ви назвали CK2 стратегічною грою в реальному часі, коли вона насправді є грандіозною стратегічною грою . Це може здатися непосидючим, але це дуже актуально для проблем, з якими ви стикаєтеся.
Рафаель Шмітц

1
Наприклад, коли ви згадуєте "1000 тисяч персонажів", люди думають про 1000 тисяч 3D-моделей або спрайтів на екрані одночасно - так, в Unity, 1000s GameObjects. У CK2 максимальна кількість персонажів, яких я бачив водночас, коли я дивився на свій двір і бачив там 10-15 людей (я не дуже грав). Так само добре, що армія з 3000 солдатами є лише одна GameObject, показуючи цифру "3000".
Рафаель Шмітц

1
@ R.Schmitz Так, я повинен був би зробити цю частину зрозумілою, щоб кожен персонаж не мав до них ігрового об'єкта. За необхідності подобається переміщення персонажа з однієї точки в іншу. Створюється окрема сутність, яка містить всю інформацію цього символу з логікою Ai.
paul p

Відповіді:


24

Слід розглянути три речі:

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

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

  3. Розглянемо дизайн, орієнтований на дані. Замість того, щоб мати 1000 символьних об'єктів і викликати одну і ту ж функцію для кожного, створіть масив даних для 1000 символів і один цикл функцій над 1000 символами, що оновлюються кожен по черзі. Цей вид оптимізації може значно підвищити продуктивність.


3
Сутність / компонент / система добре працює для цього. Створіть кожну "систему" для того, що вам потрібно, нехай вона зберігає тисячі або десятки тисяч символів (їх компонентів) та надайте системі "ідентифікатор символів". Це дозволяє тримати різні моделі даних окремими та меншими, а також можна видаляти мертві символи із систем, які їм не потрібні. (Ви також можете повністю розвантажити систему, якщо наразі не використовуєте її.)
Der Kommissar

1
За рахунок зменшення кількості оновлюваних символів, які отримуються поза екраном, ви можете значно збільшити час обробки. Ви не маєте на увазі зниження?
Техас Кале

1
@TejasKale: Так, виправлено.
Джек Едлі

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

1
Завжди найкраще перевірити, але це, як правило, безпечне робоче припущення, що римляни хочуть кастрати один одного;)
curiousdannii

11

У цій ситуації я б запропонував використовувати склад :

Принцип, згідно з яким класи повинні досягати поліморфної поведінки та повторного використання коду за своїм складом (шляхом містити екземпляри інших класів, які реалізують бажану функціональність)


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

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


Рішення полягає в тому, щоб розділити окремі частини вашого Characterпідкласу, яким Characterналежить екземпляр. Наприклад:

  • CharacterInfo може бути простою структурою даних з іменем, датою народження, датою смерті та фракцією,

  • Equipmentможуть містити всі предмети, які має ваш персонаж, або їхні поточні активи. Це може також мати логіку, яка керує ними у функціях.

  • CharacterAIабо, CharacterControllerможливо, є вся інформація, необхідна про поточну мету персонажа, їх корисні функції тощо. Також може бути фактична логіка оновлення, яка координує прийняття рішення / взаємодію між його окремими частинами.

Після того, як ви розділите символ, вам більше не потрібно перевіряти прапор Alive / Dead в циклі оновлення.

Замість цього, ви б просто зробити , AliveCharacterObjectщо має CharacterController, CharacterEquipmentі CharacterInfoприкладені сценарії. Щоб "вбити" персонажа, ви просто видаліть деталі, які вже не є актуальними (як-от CharacterController), - це тепер не втратить пам'ять чи час на обробку.

Зауважте, наскільки CharacterInfo, ймовірно, єдині дані, фактично потрібні для родинного дерева. Розклавши свої класи на більш дрібні фрагменти функціональності - ви можете легше зберігати цей невеликий об’єкт даних після смерті, не потребуючи збереження всього символу, керованого AI.


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


8

Якщо у вас є велика кількість даних для обробки, і не кожна точка даних представлена ​​фактичним ігровим об'єктом, тоді, як правило, не погана ідея відмовитися від класів, характерних для Unity, і просто перейти з простими старими об'єктами C #. Таким чином ви мінімізуєте накладні витрати. Отже, ви начебто тут на правильному шляху.

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

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

CKII « чорнослив » персонажі зі своєї бази даних , коли вона вважає їх неважливими для економії ресурсів. Якщо ваша купа мертвих персонажів витрачає занадто багато ресурсів у тривалій грі, то, можливо, ви захочете зробити щось подібне (я не хочу називати це "збиранням сміття". Можливо, "шанобливим некроматом"?).

Якщо у вас фактично є ігровий об’єкт для кожного персонажа в грі, то нова система Unity ECS and Jobs може бути вам корисною. Він оптимізований для ефективної обробки великої кількості дуже схожих ігрових об’єктів. Але це змушує вашу архітектуру програмного забезпечення до дуже жорстких моделей.

До речі, мені дуже подобається CKII і те, як він імітує світ з тисячами унікальних персонажів, контрольованих AI, тому я з нетерпінням чекаю, щоб грати на ваш жанр.


Привіт, дякую за відповідь. Усі розрахунки проводиться одним єдиним диспетчером GameObject. Я призначаю ігрові об’єкти окремим акторам лише в разі необхідності (наприклад, показ армії персонажів переміщується з однієї позиції на іншу).
paul p

1
Like "all living characters in a specific location" or "all living or dead ancestors of a specific character". It might be beneficial to create some more secondary data-structures optimized for these kinds of queries.З мого досвіду роботи з моделюванням CK2, це близько до того, як CK2 обробляє дані. CK2, здається, використовує індекси, які по суті є базовими індексами бази даних, що дозволяє швидше знаходити символи для конкретної ситуації. Замість того, щоб мати список символів, схоже, є внутрішня база даних персонажів із усіма недоліками та перевагами, які тягне за собою.
Морфілдур

1

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

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


це РТС. Ми повинні припустити, що в будь-який момент часу на екрані фактично значне число одиниць.
Том

У RTS світ повинен продовжуватися, поки гравець не дивиться. Велике оновлення зайняло б стільки ж часу, але під час переміщення камери буде сильно сплеск.
PStag

1

Уточнення питання

Я робив просту гру RTS, яка містить сотні персонажів, таких як Крестоносці Королі 2 в Єдності.

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

CharacterКласи без функціональності

Тому я створив клас C # під назвою "Характер", який містить усі дані.

Добре, адже персонаж у вашій грі - це лише дані. Те, що ви бачите на екрані, є лише поданням цих даних. Ці Characterзаняття є самим серцем гри, і тому вони загрожують стати " об'єктами бога ". Тому я б порадив екстремальні заходи проти цього: Видаліть усі функціональні можливості з цих класів. Метод, GetFullName()який поєднує ім’я та прізвище, добре, але не код, який насправді "щось робить". Покладіть цей код у виділені класи, які виконують одну дію; наприклад, клас Birtherіз методом Character CreateCharacter(Character father, Character mother)виявиться набагато чистішим, ніж функціонування цього Characterкласу.

Не зберігайте дані в коді

Для їх зберігання найпростішим варіантом було б використання об'єктів, написаних на скриптах

Ні. Зберігайте їх у форматі JSON, використовуючи JsonUtility Unity. З цими нефункціональними Characterкласами це потрібно робити тривіально. Це буде працювати для початкової установки гри, а також для її зберігання в ігрових іграх. Однак це все-таки нудно робити, тому я просто дав найпростіший варіант у вашій ситуації. Ви також можете реально використовувати XML або YAML або будь-який формат, якщо він все ще може читатися людьми, коли він зберігається у текстовому файлі. CK2 робить те саме, що насправді робить більшість ігор. Це також відмінна настройка для того, щоб люди могли модифікувати вашу гру, але це думка набагато пізніше.

Думай абстрактно

Я додав просту перевірку, щоб переконатися, що персонаж є "Живим" під час обробки [...], але я не можу видалити "Характер", якщо він мертвий, тому що мені потрібна його інформація під час створення сімейного дерева.

Це легше сказати, ніж зробити, адже він часто стикається з природним способом мислення. Ти думаєш "природним" способом, "характером". Однак, що стосується вашої гри, то, схоже, є як мінімум два різних типи даних, які "є персонажем": я буду називати це ActingCharacterі FamilyTreeEntry. Мертвого персонажа FamilyTreeEntryне потрібно оновлювати, і, ймовірно, потрібно набагато менше даних, ніж активний ActingCharacter.


0

Я говорю з невеликого досвіду, переходячи від жорсткої конструкції OO до дизайну Entity-Component-System (ECS).

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

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

  1. Сутність : це річ , гравець, тварина, NPC, що завгодно . Це річ, яка потребує приєднаних до неї компонентів.
  2. Компонент : це атрибут або властивість , наприклад, "Ім'я" або "Вік" або "Батьки" у вашому випадку.
  3. Система : це логіка компонента чи поведінки . Зазвичай ви будуєте одну систему на компонент, але це не завжди можливо. Крім того, інколи системи потребують впливу на інші системи.

Отже, ось де я б пішов з цим:

Перш за все, створіть IDдля своїх персонажів. An int, Guidщо завгодно. Це "Сутність".

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

Так само ми хочемо побудувати систему для "Чи персонаж живий чи мертвий?" Це одна з найважливіших систем у вашому дизайні, оскільки вона впливає на всі інші. Деякі системи можуть видалити "мертві" символи (наприклад, система "спрайт"), інші системи можуть внутрішньо впорядкувати речі, щоб краще підтримувати новий статус.

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

Крім того, система «AI», яка може підказати персонажу, що робити, куди йти і т. Д. Це повинно взаємодіяти з багатьма іншими системами та приймати рішення на їх основі. Знову, мертві персонажі, ймовірно, можуть бути видалені з цієї системи, оскільки вони вже нічого не роблять.

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

Це також дає перевагу зміни, коли система реагує ефективніше: у системи є власний таймер. Деякі системи потребують швидкого запуску, деякі - ні. Тут ми починаємо вникати в те, що робить гру ефективно ефективної. Нам не потрібно перерахувати погоду кожні мілісекунди, ми, мабуть, можемо робити це кожні 5 чи більше.

Це також дає вам більше творчих важелів: ви можете побудувати систему "Pathfinder", яка може обробляти обчислення шляху від A-B до, і може оновлювати за необхідності, дозволяючи системі Movement сказати "куди мені потрібно йти далі? " Зараз ми можемо повністю розділити ці проблеми і міркувати про них ефективніше. Руху не потрібно знаходити шлях, він просто повинен вас дістати.

Ви хочете викрити деякі частини системи зовні. У вашій Pathfinderсистемі вам, мабуть, захочеться Vector2 NextPosition(int entity). Таким чином, ви можете зберігати ці елементи в жорстко контрольованих масивах або списках. Ви можете використовувати більш дрібні structтипи, що допоможе вам зберегти компоненти в менших, суміжних блоках пам'яті, що може зробити оновлення системи набагато швидшим. (Особливо якщо зовнішні впливи на систему мінімальні, тепер їй потрібно дбати лише про її внутрішній стан, наприклад Name.)

Але я не можу наголосити на цьому достатньо, тепер Entityце просто просто ID, включаючи плитки, об'єкти тощо. Якщо суб'єкт не належить до системи, система не відстежує його. Це означає, що ми можемо створювати наші «Дерево» об’єкти, зберігати їх у системах Spriteі Movement(дерева не рухатимуться, але вони мають компонент «Позиція») і не захищати їх від інших систем. Нам більше не потрібен спеціальний список для дерев, оскільки візуалізація дерева не відрізняється від символів, окрім паперових робіт. (Яка Spriteсистема може керувати, або Paperdollсистема може керувати.) Тепер наша NextPositionможе бути трохи переписана:, Vector2? NextPosition(int entity)і вона може повернути nullпозицію для суб'єктів, які її не цікавлять. Ми також застосовуємо це до нашого NameSystem.GetName(int entity), воно повертається nullдля дерев та скель.


Я закінчу це на завершення, але ідея тут полягає в тому, щоб дати вам деяку інформацію про ECS, і як ви дійсно можете використовувати це, щоб ви мали кращий дизайн вашої гри. Ви можете збільшити продуктивність, роз’єднати непоєднувані елементи та зберегти речі більш організовано. (Це також добре поєднується з функціональними мовами / налаштуваннями, такими як F # і LINQ. Я настійно рекомендую перевірити F #, якщо ви цього ще не зробили, він дуже добре поєднується з C #, коли ви використовуєте їх спільно.)


Привіт, Дякую за таку детальну відповідь. Я використовую лише один диспетчер GameObject, який містить посилання на всі інші персонажі в грі.
paul p

Розвиток в Єдності обертається навколо названих організацій GameObject, які не дуже роблять, але мають список Componentкласів, які виконують фактичну роботу. Був зрушення парадигми щодо ECSS обхідних десять років тому , як покласти акторський код в окремих класах системи чиста. Нещодавно Unity впровадив таку систему, але їхGameObject система є і завжди була дуже багато. В ОП вже використовується ECS.
Рафаель Шмітц

-1

Коли ви робите це в Unity, найпростіший підхід такий:

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

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

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

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