Як сконструювати систему повтору


75

Тож як би я спроектував систему повтору?

Ви можете знати це з певних ігор, таких як Warcraft 3 або Starcraft, де ви можете переглянути гру знову після її гри.

У вас виходить порівняно невеликий файл для відтворення. Тому мої запитання:

  • Як зберегти дані? (спеціальний формат?) (невеликий розмір файлів)
  • Що врятувати?
  • Як зробити його загальним, щоб його можна було використовувати в інших іграх для запису періоду часу (а не повного поєднання, наприклад)?
  • Зробити можливість перемотування вперед і назад (WC3 не міг перемотати назад, наскільки я пам’ятаю)

3
Хоча наведені нижче відповіді дають багато цінного розуміння, я просто хотів наголосити на важливості розвитку вашої гри / двигуна як дуже детермінованого ( en.wikipedia.org/wiki/Deterministic_algorithm ), оскільки це важливо для досягнення вашої мети.
Арі Патрік

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

5
Більшість двигунів фізики є детермінованими до тих пір, поки ви користуєтесь фіксованим часовим кроком, що ви все одно повинні робити. Я був би дуже здивований, якби Хавок не був. На детермінізм досить важко підійти за комп’ютерами ...

4
Детерміновано означає однакові входи = однакові виходи. Якщо у вас є плаваючі на одній платформі та подвійні на іншій (наприклад) або навмисно вимкнено стандартну реалізацію IEEE з плаваючою точкою, це означає, що ви не працюєте з тими самими входами, не що це не детерміновано.

3
Це я, чи це питання отримує щедрість через день?
Комуністична качка

Відповіді:


39

Ця відмінна стаття охоплює багато питань: http://www.gamasutra.com/view/feature/2029/developing_your_own_replay_system.php

Кілька речей, про які згадується у статті, і добре:

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

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

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

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


якщо є задіяний РНГ, то включіть результати зазначеного РНГ в потоки
храповик виродка

1
@ratchet freak: Завдяки детермінованому використанню PRNG ви можете обійтися, зберігаючи лише його насіння під час пропускних пунктів.
NonNumeric

22

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

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


2
+1 За допомогою розумного стиснення ви дійсно можете зменшити кількість даних, які потрібно зберігати (наприклад, не зберігайте стан, якщо він не змінився порівняно з останнім станом, який ви зберігали для поточного об'єкта) . Я вже пробував це з фізикою, і це працює дуже добре. Якщо ви не маєте фізики і не хочете перемотувати повну гру, я б пішов з рішенням Джо просто тому, що воно створить найменші можливі файли, і в цьому випадку, якщо ви хочете перемотати назад, ви можете зберігати лише останні nсекунди гра.
Самаурса

@Samaursa - Якщо ви використовуєте стандартні бібліотеки стиснення (наприклад, gzip), ви отримаєте таку ж (можливо, кращу) компресію, не потребуючи вручну робити такі дії, як перевірити, чи змінився стан чи ні.
Джастін

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

1
@ ZorbaTHut Теоретично так, але чи на практиці це дійсно варто докласти зусиль?
Джастін

4
Чи варто докладати зусиль, повністю залежить від того, скільки у вас є даних. Якщо у вас є RTS із сотнями чи тисячами одиниць, це, мабуть, має значення. Якщо вам потрібно зберегти повтори в пам'яті, як Braid, це, мабуть, має значення.

21

Ви можете переглядати свою систему так, ніби вона складається з ряду станів і функцій, де функція f[j]з введенням x[j]перетворює стан системи s[j]в стан s[j+1], наприклад:

s[j+1] = f[j](s[j], x[j])

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

Функція - це все, що може впливати на світ. Зміна кадру, натискання клавіші, мережевий пакет.

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

Заради цього пояснення я буду робити такі припущення:

Припущення 1:

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

Припущення 2:

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

Припущення 3:

Тимчасова вартість (час) подання стану аналогічна або просто на один-два порядки більше, ніж обчислення функції над станом.

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

Спосіб 1:

Магазин s[0]...s[n]. Це дуже просто, дуже просто. Через припущення 2, просторова вартість цього є досить високою.

Для шахів це можна було б виконати, намалювавши всю дошку за кожен хід.

Спосіб 2:

Якщо вам потрібно лише повторне відтворення, ви можете просто зберігати s[0], а потім зберігати f[0]...f[n-1](пам’ятайте, це лише назва ідентифікатора функції) та x[0]...x[n-1](який був вхід для кожної з цих функцій). Щоб відтворити, ви просто починаєте з s[0]і обчислюєте

s[1] = f[0](s[0], x[0])
s[2] = f[1](s[1], x[1])

і так далі...

Я хочу зробити тут невелику анотацію. Кілька інших коментаторів сказали, що гра "повинна бути детермінованою". Кожен, хто каже, що потрібно знову взяти Computer Science 101, оскільки, якщо ваша гра не призначена для запуску на квантових комп'ютерах, ВСІ КОМП'ЮТЕРНІ ПРОГРАМИ ДЕТЕРМІНІСТИЧНІ¹. Ось що робить комп’ютери настільки приголомшливими.

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

Якщо ви використовуєте псевдовипадкові числа, ви можете або зберігати згенеровані числа як частину свого введення x, або зберігати стан функції prng як частину вашого стану s, а також його реалізацію як частину функції f.

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

Спосіб 3:

Тепер, ви, швидше за все, хочете мати можливість шукати свою повтор. Тобто, обчислити s[n]для довільного n. Використовуючи метод 2, вам потрібно зробити розрахунок, s[0]...s[n-1]перш ніж ви зможете розрахувати s[n], що, за припущенням 2, може бути досить повільним.

Для реалізації цього методу 3 є узагальненням методів 1 і 2: магазин f[0]...f[n-1]і x[0]...x[n-1]так само , як метод 2, але і зберігати s[j], для всіх j % Q == 0для заданої постійної Q. Простіше кажучи, це означає, що ви зберігаєте закладку в одному з усіх Qштатів. Наприклад, для Q == 100, ви зберігаєтеs[0], s[100], s[200]...

Для того , щоб обчислити s[n]для будь-якого n, спочатку завантажити раніше збережене s[floor(n/Q)], а потім обчислити всі функції від floor(n/Q)до n. Щонайбільше, ви будете обчислювати Qфункції. Менші значення Qшвидше обчислити, але вони споживають набагато більше місця, тоді як великі значення Qспоживають менше місця, але для їх обчислення потрібно більше часу.

Спосіб 3 з Q==1є таким же, як метод 1, тоді як метод 3 з Q==infтаким же, як метод 2.

Для шахів це можна було б виконати, намалювавши кожен хід, а також по кожній з 10 дощок (для Q==10).

Спосіб 4:

Якщо ви хочете повернути повторне відтворення, ви можете зробити невелику варіацію способу 3. Припустимо Q==100, і ви хочете обчислити за s[150]допомогою s[90]зворотного. За немодифікованого методу 3 вам потрібно буде зробити 50 обчислень, щоб отримати, s[150]а потім ще 49 розрахунків, щоб отримати s[149]тощо. Але оскільки ви вже розраховували s[149]отримати s[150], ви можете створити кеш, s[100]...s[150]коли ви s[150]вперше обчислюєте , а потім вже s[149]в кеш, коли вам потрібно його відобразити.

Вам потрібно лише відновлювати кеш-пам'ять щоразу, коли потрібно обчислити s[j], j==(k*Q)-1для будь-якого даного k. Цього разу збільшення Qпризведе до менших розмірів (лише для кешу), але більш тривалих разів (лише для відтворення кешу). Оптимальне значення для Qможе бути обчислено, якщо ви знаєте розміри та час, необхідні для обчислення станів та функцій.

Для шахів це можна зробити, намалювавши кожен хід, а також кожну з 10 дощок (для Q==10), але також потрібно було б намалювати в окремому аркуші паперу, останні 10 дощок, які ви обчислили.

Спосіб 5:

Якщо штати просто споживають занадто багато місця, або функції займають занадто багато часу, ви можете створити рішення, яке фактично реалізує (не підробляє) зворотне відтворення. Для цього потрібно створити зворотні функції для кожної з функцій, які у вас є. Однак для цього потрібно, щоб кожна з ваших функцій була ін'єкцією. Якщо це можливо, тоді для f'позначення зворотної функції fобчислення s[j-1]є простим, як

s[j-1] = f'[j-1](s[j], x[j-1])

Зауважте, що тут і функція, і вхід є обома j-1, а не j. Ця ж функція та вхід були б тими, які ти використовував би, якби робив розрахунок

s[j] = f[j-1](s[j-1], x[j-1])

Створення зворотних цих функцій є складною частиною. Однак зазвичай це не вдається, оскільки деякі дані про стан зазвичай втрачаються після кожної функції в грі.

Цей спосіб, як є, може обчислити зворотно s[j-1], але лише за наявності s[j]. Це означає, що ви можете спостерігати за повтором лише назад, починаючи з того моменту, коли ви вирішили повторно відтворити. Якщо ви хочете відтворити назад з довільної точки, ви повинні змішати це з методом 4.

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

Спосіб 6:

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

s[j+1], r[j] = f[j](s[j], x[j])

Де r[j]викинуті дані. А потім створіть свої зворотні функції, щоб вони взяли відкинуті дані, наприклад:

s[j] = f'[j](s[j+1], x[j], r[j])

Крім f[j]і x[j], ви також повинні зберігати r[j]для кожної функції. Ще раз, якщо ви хочете мати можливість шукати, ви повинні зберігати закладки, наприклад, за допомогою методу 4.

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

Впровадження:

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

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

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

Я сподіваюся, що це пояснення було не надто нудним.

¹ Це не означає, що деякі програми діють як би недетермінованими (наприклад, MS Windows ^^). Тепер серйозно, якщо ви можете зробити недетерміновану програму на детермінованому комп’ютері, ви можете бути впевнені, що одночасно виграєте медаль «Філдс», премію Тьюрінга і, мабуть, навіть «Оскар» та «Греммі» за все, що варто.


У розділі "ВСІ КОМП'ЮТЕРНІ ПРОГРАМИ ДЕТЕРМІНІСТИЧНІ" ви нехтуєте розглядом програм, які покладаються на нарізку. Хоча нитка в основному використовується для завантаження ресурсів або для розділення циклу візуалізації, є винятки з цього, і в цей момент ви більше не зможете заявити про справжній детермінізм, якщо тільки ви не будете належним чином суворим щодо дотримання детермінізму. Одних механізмів блокування буде недостатньо. Ви не зможете ділитися будь-якими змінними даними без додаткових додаткових робіт. У багатьох сценаріях грі не потрібен такий рівень суворості заради себе, але це може бути для речей, як повтори.
krdluzni

1
@krdluzni Ниткання, паралелізм та випадкові числа з справжніх випадкових джерел не роблять програми недетермінованими. Тимчасові нитки, тупикові місця, неініціалізована пам’ять і навіть умови перегонів - лише додаткові входи, які приймає ваша програма. Ваш вибір відмовитись від цих входів або взагалі їх не розглядати (з будь-якої причини) не вплине на те, що ваша програма буде виконувати точно так само, враховуючи однакові входи. "недетермінований" - дуже точний термін "Інформатика з інформатики", тому, будь ласка, уникайте його використання, якщо ви не знаєте, що це означає.

@oscar (Може бути дещо лаконічним, може бути відредаговано пізніше): Хоча в якомусь суворому, теоретичному сенсі ви можете заявити про терміни потоку тощо як вхідні дані, це не корисно в будь-якому практичному сенсі, оскільки їх загалом не можна спостерігати сама програма або повністю контролюється розробником. Крім того, програма, яка не є детермінованою, суттєво відрізняється від недетермінованої (у розумінні стану машини). Я розумію значення цього терміна. Я б хотів, щоб вони обрали щось інше, а не перевантажували раніше існуючий термін.
krdluzni

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

9

Одне, що інші відповіді ще не висвітлювали - це небезпека поплавців. Ви не можете зробити повністю детерміновану програму за допомогою плавців.

Використовуючи поплавці, ви можете мати повністю детерміновану систему, але лише якщо:

  • Використання точно такого ж двійкового
  • Використання точно такого ж процесора

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

Цілком очевидно, як це вплине на біт AMD vs Intel - скажімо, один використовує 80 бітових плавців, а інший 64, наприклад, - але навіщо однакові бінарні вимоги?

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

Ви можете допомогти в цьому, встановивши прапорці _control87 () / _ controlfp () для використання мінімальної точності. Однак деякі бібліотеки можуть також торкнутися цього (принаймні, якась версія d3d).


3
За допомогою GCC ви можете використовувати -ffloat-store, щоб витіснити значення з регістрів і скоротити до 32/64 біт точності, не потрібно турбуватися про те, щоб інші бібліотеки псували ваші контрольні прапори. Очевидно, це негативно позначиться на вашій швидкості (але це буде впливати на будь-яке інше квантування).

8

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

Повторно встановіть RNG і відтворіть вхід. Це воно.

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

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


4

Я б проголосував проти детермінованого відтворення. Це FAR простіше і FAR менш схильний до помилок, щоб зберегти стан кожної сутності кожні 1 / Nth секунди.

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

Налаштуйте кодування. Використовуйте якомога менше бітів для всього. Повторення не повинно бути ідеальним, доки воно виглядає досить добре. Навіть якщо ви використовуєте float для, скажімо, заголовка, ви можете зберегти його в байті і отримати 256 можливих значень (точність 1,4º). Це може бути достатньо або навіть занадто багато для вашої конкретної проблеми.

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

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

Нарешті, gzip все це :)


1
Це трохи залежить від типу гри.
Jari Komppa

Я був би дуже обережний з цим твердженням. Тим більше, що для великих проектів із сторонніми залежностями економія держави може бути неможливою. Під час скидання та відтворення вводу завжди можливо.
TomSmartBishop

2

Це важко. Перший і найбільше прочитав відповіді Ярі Компи.

Перезапис, зроблений на моєму комп’ютері, може не працювати на вашому комп’ютері, оскільки результат поплавця НЕБЕЗПЕЧНО відрізняється. Це велика справа.

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

Щоб перестрибувати файли (що набагато важче), вам потрібно буде скинути ПАМ'ЯТЬ. Мовляв, де є кожна одиниця, гроші, тривалість часу проходить, весь стан гри. Потім швидке переадресація, але відтворення всього, крім пропускання візуалізації, звуку тощо, поки ви не дістанетесь до потрібного часу. Це може статися щохвилини або 5 хвилин, залежно від того, наскільки швидко вперед.

Основні моменти - Робота зі випадковими числами - Копіювання вводу (програвача (-ів) та віддаленого плеєра)) - Стан демпінгу для перестрибування файлів і ... - НЕ ПОВТОРЕННЯ НЕ ПОЛУЧАЄТЬСЯ (так, мені довелося кричати)


2

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

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

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

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


0

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

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

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

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

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

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

ПРИМІТКА. Якщо ваша гра передбачає динамічну трансляцію вмісту або у вас є логіка гри на декількох потоках або на різних ядрах ... удачі.


0

Щоб увімкнути як запис, так і перемотування, запишіть усі події (створені користувачем, створені таймером, створені зв’язки, ...).

Для кожної події записуйте час події, що було змінено, попередні значення, нові значення.

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

Збережені дані - це список змін.
Зміни можна зберегти в різних форматах (бінарний, xml, ...).
Зміна складається з ідентифікатора сутності, назви властивості, старого значення, нового значення.

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

Приклад:

  • час від початку = t1, сутність = гравець 1, властивість = позиція, змінено з а на b
  • час від початку = t1, сутність = система, властивість = режим гри, змінено з c на d
  • час від початку = t2, сутність = гравець 2, властивість = стан, змінено з е на f
  • Щоб увімкнути швидке перемотування / швидке перемотування вперед або запис лише певних часових діапазонів, необхідні
    ключові кадри - якщо ви записуєтеся весь час, раз у раз і зберігайте весь стан гри.
    Якщо запис записується лише певні часові інтервали, на початку збережіть початковий стан.


    -1

    Якщо вам потрібні ідеї, як реалізувати вашу систему відтворення, шукайте в Google, як реалізувати скасування / повтор у програмі. Для когось це може бути очевидним, але, можливо, не всім, що скасувати / повторити концептуально те саме, що відтворення для ігор. Це просто особливий випадок, коли ви можете перемотати назад і залежно від програми шукати конкретний момент часу.

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


    Скасувати / повторити трапляється в додатках, які самі по собі є детермінованими, керованими подіями та світлом стану (наприклад, стан текстового текстового документа - це виключно текст і виділення, а не весь макет, який можна перерахувати).

    Тоді очевидно, що ви ніколи не використовували програми CAD / CAM, програмне забезпечення для проектування схем, програмне забезпечення для відстеження руху або будь-яку програму, яка скасовує / повторює більш складніші, ніж текстовий процесор. Я не кажу, що код для скасування / повтору може бути скопійований для відтворення в грі, тільки що це концептуально те саме (збережіть стани та повторіть їх пізніше). Однак основна структура даних - це не черга, а стек.
    Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
    Licensed under cc by-sa 3.0 with attribution required.