Я працюю над ізометричною 2D грою з середньомасштабним багатокористувацьким гравцем, приблизно 20-30 гравців, підключених одночасно до стійкого сервера. У мене виникли певні труднощі з можливістю гарного виконання прогнозування руху.
Фізика / Рух
Гра не має справжньої фізичної реалізації, але використовує основні принципи для здійснення руху. Замість того, щоб постійно запитувати вхід, зміни стану (тобто / миша вниз / вгору / переміщення) використовуються для зміни стану символьної сутності, якою керує гравець. Напрямок гравця (тобто на північний схід) поєднується з постійною швидкістю і перетворюється на справжній 3D-вектор - швидкість сутності.
У головному циклі гри перед оновленням називається "Оновити". Логіка оновлення викликає "завдання оновлення фізики", яке відстежує всі сутності з ненульовою швидкістю, використовуючи дуже базову інтеграцію для зміни положення сутності. Наприклад: сущность.Position + = суть.Velocity.Scale (ElapsedTime.Seconds) (де "Seconds" - це значення з плаваючою комою, але той самий підхід буде працювати і для цілих значень мілісекунд).
Ключовим моментом є те, що жодна інтерполяція не використовується для руху - рудиментарний фізичний двигун не має поняття "попередній стан" або "поточний стан", лише положення і швидкість.
Пакет змін і оновлення штату
Коли швидкість сутності символу гравця контролює зміни, на сервер надсилається пакет "переміщення аватара", що містить тип дії сутності (стояти, ходити, бігати), напрям (північний схід) та поточну позицію. Це відрізняється від того, як працюють 3D-ігри від першої особи. У 3D-грі швидкість (напрямок) може змінювати кадр на кадр, коли гравець рухається. Відправлення кожної зміни стану ефективно передасть пакет за кадром, що було б занадто дорого. Натомість, 3D-ігри, здається, ігнорують зміни стану та надсилають пакети "оновлення стану" через фіксований інтервал - скажімо, кожні 80-150 мс.
Оскільки оновлення швидкості та напряму трапляються набагато рідше в моїй грі, я можу втекти, надсилаючи кожну зміну стану. Хоча всі фізичні симуляції відбуваються з однаковою швидкістю і детерміновані, затримка все ще залишається проблемою. З цієї причини я надсилаю звичайні пакети оновлення позицій (подібні до 3D-гри), але набагато рідше - зараз кожні 250 мс, але я підозрюю, що при хорошому передбаченні я можу легко збільшити його до 500 мс. Найбільша проблема полягає в тому, що я зараз відхилився від норми - вся інша документація, путівники та зразки в Інтернеті надсилають звичайні оновлення та інтерполювати між двома державами. Це здається несумісним з моєю архітектурою, і мені потрібно придумати кращий алгоритм прогнозування руху, який ближче до (дуже базової) архітектури "мережевої фізики".
Потім сервер отримує пакет і визначає швидкість гравців від його типу руху на основі сценарію (Чи може гравець працювати? Отримати швидкість бігу гравця). Як тільки він має швидкість, він поєднує його з напрямком для отримання вектора - швидкості сутності. Відбувається деяке виявлення читів і основна перевірка, і сутність на стороні сервера оновлюється поточною швидкістю, напрямком і положенням. Базове дроселювання також виконується для того, щоб гравці не заполонили сервер запитами про рух.
Після оновлення власної сутності, сервер транслює пакет "оновлення положення аватара" всім іншим гравцям, що знаходяться в межах діапазону. Пакет оновлення позиції використовується для оновлення фізичних симуляцій на стороні клієнта (стан світу) віддалених клієнтів та виконання прогнозування та компенсації відставання.
Прогнозування та компенсація відставання
Як було сказано вище, клієнти авторитетні для своєї власної позиції. За винятком випадків обману або аномалій, аватар клієнта ніколи не буде перестановлений сервером. Немає екстраполяція ( «рухатися зараз і правильно пізніше») не потрібно для клієнта аватарі - то , що гравець бачить це правильно. Однак якась екстраполяція чи інтерполяція потрібна для всіх віддалених об'єктів, які рухаються. Якийсь тип прогнозування та / або компенсації відставання очевидно необхідний у локальному механізмі моделювання / фізики клієнта.
Проблеми
Я боровся з різними алгоритмами і маю ряд питань і проблем:
Чи слід екстраполювати, інтерполювати чи обидва? Моє "відчуття кишки" полягає в тому, що я повинен використовувати чисту екстраполяцію на основі швидкості. Зміна стану отримує клієнт, клієнт обчислює "передбачувану" швидкість, яка компенсує відставання, а звичайна фізична система робить все інше. Однак це протиставляється всім зразкам коду та статей - вони, схоже, зберігають ряд станів і виконують інтерполяцію без механізму фізики.
Коли пакет приходить, я намагався інтерполювати позицію пакета зі швидкістю пакета протягом певного періоду часу (скажімо, 200 мс). Потім я приймаю різницю між інтерпольованою позицією та поточною позицією "помилка", щоб обчислити новий вектор, і розміщую його на сутності замість швидкості, що була надіслана. Однак припущення полягає в тому, що інший пакет прийде в цей часовий проміжок, і "неймовірно важко" здогадатися ", коли наступний пакет надійде, тим більше, що всі вони не надходять через фіксований інтервал (тобто / зміни станів). Чи принципово хибна концепція, чи вона правильна, але потребує певних виправлень / коригувань?
Що відбувається, коли віддалений плеєр зупиняється? Я можу негайно зупинити сутність, але вона буде розміщена у «неправильному» місці, поки вона не рухатиметься знову. Якщо я оцінюю вектор або намагаюся інтерполювати, у мене виникає проблема, оскільки я не зберігаю попередній стан - фізичний двигун не може сказати "вам потрібно зупинитися після досягнення позиції X". Він просто розуміє швидкість, нічого складнішого. Я неохоче додаю інформацію про стан руху пакетів до сутностей або фізичного двигуна, оскільки це порушує основні принципи проектування та кровоточить мережевий код у решті ігрового двигуна.
Що повинно статися, коли суб'єкти стикаються? Існує три сценарії - контрольний гравець зіштовхується локально, два об'єкти стикаються на сервері під час оновлення позиції або віддалене оновлення сутички стикається на локальному клієнті. У всіх випадках я не впевнений, як вчинити зіткнення - окрім обману, обидва стани є "правильними", але в різні періоди часу. У випадку з віддаленою сутністю не має сенсу малювати його, проходячи через стіну, тому я виконую виявлення зіткнень на локальному клієнті і змушую його "зупинитися". Виходячи з пункту №2 вище, я можу обчислити "виправлений вектор", який постійно намагається перемістити об'єкт "крізь стіну", який ніколи не матиме успіху - віддалений аватар застрягає там, поки помилка не стане занадто високою, і вона "забивається" в положення. Як обробляють ігри навколо цього?