Як обробити мережевий код?


10

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

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

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

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

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

Відповіді:


13

Ігноруйте чуйність. У локальній мережі пінг незначний. В Інтернеті 60-100 м. Туди і назад - це благо. Моліться богів, що відстають, щоб у вас не було шипів> 3K. Програмне забезпечення повинно працювати з дуже низькою кількістю оновлень / с, щоб це було проблемою. Якщо ви знімаєте 25 оновлень / с, то у вас є максимум 40 мс часу, коли ви отримуєте пакет і дієте на нього. І це однонитковий корпус ...

Створіть свою систему для гнучкості та коректності. Ось моя ідея, як підключити мережеву підсистему до ігрового коду: Повідомлення. Рішенням багатьох проблем може стати "обмін повідомленнями". Я думаю, що обмін повідомленнями вилікував рак у лабораторних щурів один раз. Повідомлення заощаджують мені 200 доларів або більше на страхуванні мого автомобіля. Але серйозно, обмін повідомленнями - це, мабуть, найкращий спосіб приєднати будь-яку підсистему до ігрового коду, зберігаючи дві незалежні підсистеми.

Використовуйте обмін повідомленнями для будь-якого зв’язку між мережевою підсистемою та ігровим механізмом, а також для будь-яких двох підсистем. Повідомлення між підсистемами може бути простим, як переповнення даних, переданих вказівником, використовуючи std :: list.

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

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

З вищого рівня, я б сказав, окрема мережа від самого ігрового двигуна. Ігровий движок не дбає про сокети або буфери, він дбає про події. Події - такі речі, як "Гравець X вистрілив у постріл" "У грі T трапився вибух". Це можна інтерпретувати ігровим двигуном безпосередньо. Звідки вони генеруються (сценарій, дії клієнта, програвач AI тощо) не має значення.

Якщо ви розглядаєте свою підсистему мережі як засіб надсилання / прийому подій, то ви отримуєте ряд переваг перед просто викликом recv () на сокет.

Ви можете оптимізувати пропускну здатність, наприклад, взявши 50 невеликих повідомлень (1-32 байти в довжину) і змусивши мережну підсистему упакувати їх в один великий пакет і надіслати його. Можливо, це могло б стиснути їх перед відправкою, якщо це було великою справою. З іншого боку, код може розпакувати / розпакувати великий пакет у 50 дискретних подій, щоб ігровий движок прочитав. Це все може відбуватися прозоро.

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


Ви згадували, використовуючи простий std :: list або щось подібне для передачі повідомлень. Це може бути темою для StackOverflow, але чи правда, що всі потоки розділяють один і той же адресний простір, і якщо я одночасно не можу декілька потоків накручувати пам’ять, що належить моїй черзі, я повинен бути добре? Я можу просто виділити дані для черги на купі, як звичайні, і просто використати в ній кілька файлів?
Стівен Лу

Так, це правильно. Мутекс, що захищає всі виклики до std :: list.
PatrickB

Дякуємо за відповідь! Я до сих пір домігся великого прогресу в моїх нитках. Це таке чудове відчуття, маючи власний ігровий движок!
Стівен Лу

4
Це почуття зношиться. Хоча великі мідні, які ви отримуєте, залишаються з вами.
ChrisE

@StevenLu Дещо [надзвичайно] пізно, але я хочу зазначити, що запобігання потоку з гвинтами пам’яті одночасно може бути надзвичайно важким, залежно від того, наскільки ви намагаєтеся це зробити та наскільки ефективно ви хочете бути. Ви робили це сьогодні, я хотів би вказати на одну з безлічі відмінних реалізацій одночасних черг з відкритим кодом, тому вам не потрібно винаходити складне колесо.
Фонд позову Моніки

4

Мені потрібно (принаймні) мати окрему нитку для обробки мережевих розеток

Ні, ти цього не робиш.

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

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

наприклад, він може миттєво повернути пакет ACK після отримання оновлення стану від сервера.

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

Для більшості мережевих ігор цілком можливо мати такий цикл гри:

while 1:
    read_network_messages()
    read_local_input()
    update_world()
    send_network_updates()
    render_world()

Ви можете від'єднати оновлення від візуалізації, що дуже рекомендується, але все інше може залишатися таким простим, якщо у вас немає конкретної потреби. Яку саме гру ви робите?


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

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

2

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

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


0

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

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

Прочитайте гафтер у навчальному посібнику щодо ігор або просто використовуйте ENet, який реалізує багато ідей Глена Фідлера.

Або ви можете просто використовувати TCP, якщо у грі не потрібні реакції смикань. Тоді всі проблеми з перезавантаженням, повторно відправляються та проблеми АСК минають. Однак ви все ще хочете, щоб NSS керував пропускною здатністю та каналами.


0

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

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

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