Перші комп'ютерні ігри працювали так: запустив, пограв один, вимкнув. Або пограв на пару з кимось на одному комп'ютері у пінг-понг чи гонки. Але не можна було пограти з сусідом, який живе парою поверхів вище, або з друзями, що мешкають в іншому місті. Зараз все інакше — з'явилися мультиплеєрні ігри, в які можуть грати відразу багато людей, кожен на своєму комп'ютері, і при цьому вони можуть взаємодіяти і з грою, і один з одним.
Для цього розробникам потрібно було вирішити таку проблему: як синхронізувати те, що відбувається на екрані з діями кожного гравця. Наприклад, щоб кожен міг бачити, що роблять інші гравці, а гра враховувала їхні дії у загальному ігровому процесі. Сьогодні розповімо, які технології для цього потрібні і як працює мультиплеєр в іграх.
Як гравці зв'язуються один з одним у мультиплеєрних іграх
Щоб люди з різних пристроїв могли зіграти разом, потрібно зв'язати ці пристрої між собою. Для цього використовується кілька технологій, але загальний принцип такий: є якийсь загальний елемент синхронізації дій гравців, який відповідає за мультиплеєр, і всі пристрої орієнтуються на нього. Це може бути реалізовано кількома способами.
Клієнт – сервер. Вся ігрова логіка розміщується на окремому сервері, до якого підключаються гравці – часто для цього потрібно авторизуватись. Від сервера залежить швидкість і якість з'єднання:
Хост. У цій моделі сервером є комп'ютер одного з гравців, а інші підключаються до цього комп'ютера. В іншому все працює так само, як і в моделі «Клієнт-сервер».
Якщо не потрібна складна інфраструктура виділеного сервера, це найперший варіант.Хост-модель часто використовують у комп'ютерних клубах та при грі по локальній мережі:
Модель peer to peer. Як сервер виступає комп'ютер кожного гравця. Модель одночасно управляє зв'язками між гравцями та загальним станом гри. Така модель зручна тим, що якщо у когось погане з'єднання, то це не сильно позначається на загальній швидкодії гри, тому що дані для обробки перерозподіляються між швидшими комп'ютерами.
Раніше peer to peer використовували майже всі мультиплеєри, але з оптимізацією технологій розробники частіше віддають перевагу моделі «клієнт-сервер».
Асинхронний режим не вимагає одночасної присутності в мережі всіх учасників процесу. Так, наприклад, працюють шахи без обмеження на час ходу.
Як використовується модель OSI в іграх
Незалежно від того, чи спілкуються комп'ютери через інтернет або через локальну мережу, вони використовують один і той же протокол — мережеву модель передачі даних OSI.
Ця модель ділиться на 7 рівнів, кожному з яких відбувається своя частина роботи. Ось що знаходиться на цих рівнях для онлайн-мультиплеєра між гравцями та сервером у різних точках Землі:
- фізичний рівень. Спочатку інформація про гру на комп'ютері гравця відправляється на роутер через Wi-Fi або Ethernet-кабелем. Тут використовуються фізичні параметри зв'язку: частоти, Wi-Fi-протоколи, з'єднання по проводах і таке інше.
- на канальному рівні налаштовується з'єднання між вузлами – наприклад, комп'ютером та роутером.
- Мережевий рівень. Всі дані про гру поділяються на невеликі частини пакети. Пакети отримують номер, знаходять сервер за IP-протоколом і відправляються туди.
- Транспортний рівень працює для ігор у двох варіантах: протокол TCP чи UDP. Протокол TCP повільний, але точний: з ним всі дані точно дійдуть одержувача. UDP швидкий, але не такий акуратний: частина пакетів може загубитися дорогою. UDP зручніше для передачі даних у реальному часі, наприклад зображення та відео. Якщо ви граєте в онлайн-шутер – там точно буде UDP. TCP іноді використовується для встановлення з'єднання та передачі інформації користувача.
- на сеансовому рівні між сервером та гравцем встановлюється зв'язок. Наприклад, сервер може попросити приватника розрахованої на багато користувачів гри ввести логін і пароль.
- Рівень вистави відповідає за стиснення та шифрування даних, щоб обмінюватися ними швидко та безпечно.
- Прикладний рівень. Тут знаходяться програми та протоколи для відповідей та запитів. Наприклад, при спілкуванні із сайтом ми використовуємо інтернет-браузер та HTTP-протокол. В іграх замість браузера додаток гри.
Щоб два комп'ютери могли спілкуватися між собою, вони відкривають сокет: кінцеву точку з'єднання обміну даними. Сокет буде загальним для обох машин:
Для відкриття сокета комп'ютеру потрібна IP-адреса та номер порту. IP-адреса не завжди унікальна для кожного комп'ютера. Між домашнім комп'ютером та інтернетом є вузол NAT (Network Address Translator), який об'єднує багато машин в одну IP-адресу. Щоб дані не заблукали в мережі, при адресації використовують номер порту, який для кожного комп'ютера свій:
Що таке стан гри та як його передавати між учасниками
Стан гри — це інформація про поточний момент гри, яку можна перетворити на одному комп'ютері та передати на інший.Наприклад, у шахах ми можемо розбити весь процес на 64 елементи-клітини та передавати дані про кожну: вона порожня або яка постать там стоїть.
Щоб інформацію можна було передати через мережу або записати на диск, її серіалізують: перетворюють на рядок, з якого можна відновити дані у вихідному вигляді. Після цього комп'ютер і сервер просто обмінюються цими рядками та беруть із них потрібні дані. Так можна зробити з чим завгодно: із тривимірними моделями, звуками та координатами. Залежно від гри обсяг даних і частота оновлення будуть різними.
Частоту оновлення стану відображає показник tick rate, або simulation rate, що вимірюється у герцах. Tick rate в 60 герц означає, що за секунду комп'ютер-сервер 60 разів обробляє стан і відправляє його комп'ютерам-клієнтам. Клієнт бере дані та малює стан гри в себе. Сервер між відправкою стану та початком нової обробки не діє:
Як синхронізується стан ігор у реальному часі
В онлайн-шутерах та інших подібних іграх обмін даними має відбуватися швидко, і в цьому випадку використовується протокол UDP. Він передає пакети даних швидко, але необов'язково у тому порядку, як і їх відправляє сервер. На прикладі анімації – якщо клієнт почне відтворювати стан відразу після отримання кожного окремого пакета, зображення може почати стрибати вперед-назад, тому що спочатку прийшов пакет №5 і тільки потім пакет №3.
Щоб відео відтворювалося коректно та плавно, використовують буферизацію та прогнозування позиції об'єкта.Найчастіше в технологіях створення мультиплеєрних ігор це вже передбачено, і розробнику занурюватися в це не потрібно (але добре б розуміти при цьому, що саме там відбувається під капотом).
При відтворенні стану частина даних завантажується заздалегідь, щоб встигнути розташувати їх у правильному порядку або запитати повторно, якщо щось загубилося дорогою. Тоді у разі проблем із зв'язком відтворення не перерветься. Це і є буферизація.
Такий спосіб використовується при потоковій передачі відео на стрімінг. У плеєрах буфер видно під час перегляду, він показує, скільки відеоданих вже завантажилося:
Для створення буфера перевіряться пінг – час, за який дані надходять від сервера до клієнта. Об'єм буфера встановлюється приблизно вдвічі більше пінгу.
Щоб зробити анімацію ще плавнішою, на клієнті працює прогнозування позиції. Для цього програма гри бере дані сервера і заздалегідь прораховує, де (швидше за все) перебуватиме об'єкт у русі:
Віддалений виклик функцій на інших машинах
Іноді потрібно спеціально викликати синхронізацію гри інших машинах. Наприклад, після якоїсь дії гравця, що впливає на решту. Для цього є свій механізм – RPC, або Remote Procedure Call ("віддалений виклик процедур"). Це частина коду, що викликається однією машині, а виконується іншою. Сервер може викликати RPC для клієнта і навпаки.
Як показується стан гри на різних клієнтах
Якщо для переміщення персонажа гравцю потрібно надіслати дані на сервер і потім дочекатися, поки ці дані дійдуть до всіх учасників, грати буде незручно.Нам доведеться чекати частки секунди між натисканням на кнопки керування та видимою дією.
Один із варіантів вирішення цієї проблеми – спочатку малювати стан гри на екрані клієнта без затримки – а вже потім передавати стан на машини інших гравців.
Найчастіше невелика різниця в 100 мілісекунд між двома учасниками не відіграє ролі, але в деяких іграх змагань від цього залежить результат раунду або всієї гри. Наприклад, серверу доводиться вирішувати, хто вистрілив першим — зазвичай у такій ситуації переможцем стає гравець, який потрапив до іншого на своєму екрані. Через це в шутерах буває так, що ви начебто встигли сховатися або забігти за укриття, але все одно отримали попадання від іншого гравця.
Як все це використовувати
У більшості технологій для розробки мультиплеєра вже вбудовані всі необхідні можливості. Наприклад, для движка Unity є мережеві продажі Mirror, Netcode або Fishnet. Не можна сказати, що якийсь краще чи гірше, але їх можливості трохи різняться, і для конкретної гри потрібно вивчити існуючі варіанти та вибрати відповідний.
Але основна ідея мультиплеєра завжди одна – потрібно синхронізувати стан гри для всіх і передавати його на клієнти найшвидшим способом.
Що далі
Наступного разу подивимося, як влаштований мультиплеєр у якійсь грі та розберемо його плюси та мінуси. Або напишемо свій.
Ігри з розрахованим на багато користувачів режимом значно цікавіше аналогічних ігор без нього. Але реалізація мультиплеєра має на увазі під собою наявність свого сервера, коду, що реалізує мережеву взаємодію, матчмекінг та багато іншого.
На щастя, є безліч готових рішень. Під час розробки своєї гри я випробував такі варіанти:
- Game Center (ігри під iOS)
- Steamworks (ігри для Steam)
- GameSparks (кросплатформне рішення)
Перелічені варіанти розташовані в порядку зростання можливостей та гнучкості. Складність реалізації у своїй теж зростає.
1. GameCenter
Найпростіший і, в той же час, наймізерніший варіант. Гравець натискає кнопку, після чого з'являється вікно пошуку супротивника. Після набрання певної кількості гравців матч запускається. У цей момент гравці мають можливість обмінюватися повідомленнями. Після того, як усі гравці залишили матч, гра завершується.
2. Steamworks
Функціонал поділено на дві незалежні частини: матчмейкінг та обмін повідомленнями. Перша половина складається з можливості створювати кімнати, в яких гравці можуть обговорювати майбутній матч.
Друга половина – це функціонал для безпосереднього обміну повідомленнями. Обидві частини є абсолютно незалежними. Можна будь-коли надіслати повідомлення будь-якому гравцеві незалежно від того, в яких кімнатах він знаходиться.
3. GameSparks
Насправді, GameSparks — це повноцінний сервер, з усіма плюсами, що звідси випливають. Ви можете зберігати дані гравця, верифікувати всі його дії. Є вбудовані механізми матчмекінгу, лідербордів, ачивок та багато іншого.
Перші два варіанти підійдуть для простенької гри з невеликими вимогами до безпеки. А для великої серйозної гри вам, звичайно, потрібний свій сервер.
Для мене, останній варіант виявився найприємнішим у розробці, оскільки вдалося позбавитися безлічі милиць.
GalaxyAdmirals – це покрокова космічна стратегія. Геймплей складається з коротких боїв на гексогональному ігровому полі. Гравці ходять по черзі, елемент рандому відсутній. По суті це шахи в космосі.
У грі є кампанія із 30 місій. Розрахований на багато користувачів режим – це бій віч-на-віч з випадковим противником.
Гра зроблена на Unity3D.
Загальний опис
Game Center – це така програма під iOS. У ньому відображаються ваші здобутки в інших іграх: ачивки, таблиці рекордів. Можна додавати друзів, кидати виклики, порівнювати ваші здобутки. Функціонал не великий, а юзабіліті всього цього, як на мене, досить сумнівний. Але найголовніше те, що через Game Center можна продати простенький мультиплеєр.
Інтеграція
Можливо, є якісь готові плагіни, але я писав свій на Objective C. Кода не багато, і він досить простий.
Реалізація
Існує три варіанти реалізації мультиплеєра через Game Center:
Це для покрокових ігор типу шашок, шахів. Гравці роблять хід по черзі. Після кожного ходу, гравцеві прилітає повідомлення у Game Center, що тепер його черга. При цьому гра не обов'язково має бути запущена. Стан гри зберігається у Game Center. Гравці не спілкуються один з одним, вони спілкуються лише з сервером, щоб оновити стан гри.
Game Center здійснює лише матчмейкінг. Ви маєте самостійно реалізувати мультиплеєр через свій сервер.
При запуску матчмейкінгу, гравець передає такі параметри:
- Максимальна, мінімальна кількість гравців
- Група
- Атрибут
Атрибут – це 32-бітове число, яке означає роль, яку ви хочете відіграти. По суті це бітова маска.Game Center підбирає гравців таким чином, щоб їх бітові маски, об'єднані по "або", були повністю заповнені одиницями.
Наприклад, ви хочете грати в шахи лише за білих. Тоді ви передаєте маску 0xFFFF0000. Гравець, який бажає грати за чорних, має передати 0x0000FFFF. Якщо гравцеві все одно, за кого грати, він передає заповнену одиницями маску 0xFFFFFFFF. Тоді гравцю, який бажає грати за білих, підбереться противник, який бажає грати за чорних, або противник, якому все одно, за кого грати.
Після того, як ворогів знайдено, запускається матч. Гравцю повертається список противників, яким він може надсилати повідомлення. Усі гравці вступають у гру на рівних правах. Їм треба домовитись, хто з них гравець №1, а хто гравець №2. Наприклад, можна відсортувати гравців по айдішниках. Тоді в обох вийде той самий список.
Якогось спеціального стану «матч завершено» не існує. Гравцям спочатку потрібно між собою домовитися, що гра закінчена, і потім кожен з них дисконектується.
Існує можливість надіслати запрошення своєму другові. Це запрошення висвітиться у нього в Game Center, навіть якщо гра не запущена. На кліку на повідомлення гра запускається. Для того, щоб запрошення працювали коректно, доводиться окремо обробляти два варіанти, коли програма запущена і коли ні.
На жаль, якщо гравець не прийняв запрошення, то гравець, який його послав, про це ніколи не дізнається. Він чекатиме його доти, доки йому не набридне.
Проблеми
1. Проблема із завершенням матчу
Функціонал працює таким чином, що ти не можеш надіслати повідомлення «гра закінчена, я пішов», а потім одразу дисконектуватися.В цьому випадку противник не отримає твого повідомлення, а побачить тільки, що ти дисконектувався. Вирішивши, що ти відвалився, він надасть собі перемогу.
Тому доводиться надсилати додаткові підтвердження, типу «я отримав твоє повідомлення, що гра закінчена, можеш йти».
2. Проблема дисконнекту одного з гравців
З'єднання між гравцями встановлюється peer-to-peer. Тому, якщо в одного гравця пропадає інтернет, то обидва одержують повідомлення, що противник відвалився. За ідеєю, ми маємо присудити поразку гравцю, який відвалився, та перемогу гравцю, який залишився у грі. І найнеприємніше, що не існує способу запитати у Game Center, хто залишився в грі, а хто дисконектувався.
Щоб це з'ясувати, доводиться вставляти милиці. Наприклад, перевіряти з'єднання з Game Center відразу після дисконнекту противника (наприклад, я запитував стан очівок). Якщо Game Center не відповідає, можна вважати, що відвалився я, а чи не противник.
Але навіть це може не спрацювати. Наприклад, гравець може згорнути програму. Коли він розгорне додаток, з'єднання з гравцем буде розірвано, але з'єднання з інтернетом збережеться і гравець може вирішити, що це противник відвалився, а не він сам. Коротше, доводиться вставляти додаткові милиці.
Мінуси
- Занадто простий функціонал матчмейкінгу.
- Доводиться створювати неприємні милиці.
Загальний опис
Steamworks – це Api для інтеграції вашої гри з магазином Steam. Він дозволяє реалізувати у вашій грі мультиплеєр, ачивки, лідерборди, покупки, зберігання даних користувача у хмарі.
Інтеграція
Steamworks SDK написаний С++. Тому для інтеграції в Unity3D необхідний враз.З кількох варіантів мною навмання був обраний Steamworks.NET. В принципі, врапер не поганий, все інтегрувалося добре, але пізніше я помітив деяку дивну поведінку.
Кожна функція повертає результат через callback приблизно такого виду.
protected void OnResult(LobbyEnter_t pCallback, bool failure) Як видно, до функції передається додатковий булевий параметр, який повинен повідомляти про невдале виконання операції. Як виявилось, цей параметр ніколи не повертає true. Навіть якщо зробити щось свідомо неправильне. Не знаю, чи це косяк Steamworks SDK або Steamworks.NET. Від розробників якоїсь виразної відповіді я не отримав і довелося викручуватися за допомогою милиць.
Реалізація
Як я вже писав вище, Steamworks має гнучкіші можливості матчмейкінгу. Ось зразковий список того, що ви можете зробити:
- Створити кімнату (тут вони називаються "лобі")
- Присвоїти кімнаті який-небудь параметр (наприклад, вказати назву кімнати, і на якій карті ви гратимете)
- Відфільтрувати список кімнат за одним або декількома параметрами
- Присвоїти параметр гравцю у кімнаті
- Надіслати повідомлення всередині кімнати
Як видно зі скріншоту, перед початком матчу можна поспілкуватися у чатику, вибрати собі расу, вибрати картку та інші параметри матчу.
Не усі гравці рівні між собою. Гравець, який створив кімнату, є її власником (owner). Якщо власник покинув кімнату, то це звання перекидається випадково на іншого гравця в кімнаті. Цей гравець може виступати у ролі арбітра у спірних ситуаціях.
Теоретично весь ігровий функціонал можна реалізувати прямо всередині кімнати, зберігати стан гри прямо в ній. Але це все пахне збоченням.
У стимі немає спеціальних функцій для створення з'єднання між гравцями. Ви просто надсилаєте повідомлення будь-якому гравцю. Якщо з'єднання не було до цього встановлено, воно встановлюється автоматично (інша сторона повинна підтвердити, що хоче спілкуватися з вами). Якщо з'єднання раптом обірвалося, стим спробує відновити з'єднання наступного разу, коли ви надішлете повідомлення.
Як і в Game Center, немає такого стану, як «гра закінчена». Тобто. гравці повинні самі між собою домовитись, коли гра вважається закінченою.
У стимі існує ще такий цікавий функціонал. Допустимо, ви робите мультиплеєр не через стим, а через свої сервери. Ви можете зареєструвати свій сервер у стимі. Гравець може отримати список усіх зареєстрованих серверів. Таким чином стим виступає у ролі каталогу зовнішніх серверів.
Проблеми
1. Проблема дисконнекту одного з гравців.
На жаль, як і в Game Center, ви не можете так просто визначити, чи з'єднання з сервером пропало у вас або у вашого супротивника. Доводиться писати милиці.
Плюси
Мінуси
- Дивна поведінка Steamworks.NET за помилок.
- Проблема з дисконнектом гравців.
Загальний опис
GameSparks – це сервіс, який виступає в ролі backend-сервера для ігор. Він дозволяє зберігати дані, запускати скрипти, має вбудовані механізми матчмейкінгу, очівок, лідербордів та багато іншого.
Інтеграція
GameSparks інтегрується в Unity3D за допомогою офіційного плагіна. Все працює чудово.
Реалізація
Усі скрипти пишуться на JavaScript. Тому перше що я зробив, це налаштував оточення так, щоб писати нормальною строго типізованою мовою. Я використовував TypeScript.
Взаємодія із сервером дуже проста. Ми надсилаємо на сервер повідомлення.На сервері спрацьовує скрипт, який відповідає цьому повідомленню. Скрипт зчитує інформацію з бази даних, вносить зміни та повертає нам результат. Також скрипт може надіслати повідомлення будь-якому іншому користувачеві. Все дуже просто та логічно.
В принципі, GameSparks дозволяє вам реалізувати як завгодно складний матчмейкінг. Можна, наприклад, зробити кімнати як у стимі. Але я вирішив не морочитися, а скористатися вбудованим механізмом матчмейкінгу.
При запуску матчмейкінгу потрібно передати силу гравця (числовий параметр). Після чого GameSparks поверне супротивників, сила яких відрізняється від переданої на певне значення (в абсолютних чи відносних значеннях).
Для real-time мультиплеєра можна створювати окремі кімнати. У цій кімнаті може бути запущено скрипт, який постійно крутиться на стороні сервера. Але я не використав цей функціонал. Для передачі повідомлень між гравцями використовував звичайний скрипт, який просто ретранслював повідомлення потрібному гравцю.
Плюси
- Є можливість зберігати дані гравця на сервері
- Вдалося позбавитися милиць, які використовувалися в Game Center і Steam. Сервер завжди знає хто онлайн, хто ні, і завжди можна достовірно визначити, хто покинув гру.
Мінуси
- Трохи сумбурна документація.
- Обіцяли, що для інді-розробників можна запустити гру безкоштовно. Насправді довелося заплатити.
Створюйте додаткові тулзи, щоб візуалізувати процеси, що відбуваються всередині коду. Наприклад, я написав логіруючий сервер.
Зліва зображено лог одного гравця, праворуч – лог іншого. Стрілечками намальовані повідомлення, коли повідомлення було надіслано, у якій надійшло.