За мрежовата модел в игри за начинаещи

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

В това ръководство бих искал да споделя с вас различните концепции, които трябва да изучите, преди да напишете собствен игрови двигател, както и най-добрите ресурси и статии за тяхното изучаване.

Общо взето, съществуват два основни типа мрежови архитектури: peer-to-peer и клиент-серверна. В архитектурата peer-to-peer (p2p) данните се предават между всяка двойка свързани играчи, а в клиент-серверната архитектура данните се предават само между играчите и сървъра.

Въпреки че архитектурата peer-to-peer все още се използва в някои игри, стандартът е клиент-серверната: тя е по-лесна за реализиране, изисква по-малко канално пространство и улеснява защитата от измами. Затова в това ръководство ще се фокусираме върху клиент-серверната архитектура.

Конкретно, най-много ни интересуват авторитетните сървъри: в такива системи сървърът винаги е прав. Например, ако играчът смята, че се намира в координати (10, 5), а сървърът му казва, че е в (5, 3), клиентът трябва да замени своята позиция с тази, която предава сървърът, а не обратно. Използването на авторитетни сървъри опростява откритията на измамници.

В игровите мрежови системи има три основни компонента:

  • Транспортен протокол: как се предават данните между клиентите и сървъра.
  • Протокол на приложението: какво се предава от клиентите на сървъра и от сървъра на клиентите и в какъв формат.
  • Логика на приложението: как предаваните данни се използват за актуализиране на състоянието на клиентите и сървъра.

Много е важно да разберете ролята на всяка част и свързаните с тях трудности.

Транспортен протокол

Първата стъпка е да изберете протокол за транспортиране на данни между сървъра и клиентите. За това съществуват два интернет протокола: TCP и UDP. Но можете да създадете и собствен транспортен протокол на базата на един от тях или да приложите библиотека, в която те се използват.

Сравнение на TCP и UDP

И TCP, и UDP са основани на IP. IP позволява предаване на пакет от източника до получателя, но не гарантира, че изпратеният пакет рано или късно ще достигне до получателя, че ще стигне до него поне веднъж и че последователността на пакетите ще пристигне в правилния ред. Освен това, пакетът може да съдържа само ограничен размер на данни, задаван от величината. MTU.

UDP е просто тънък слой над IP. Следователно, той притежава същите ограничения. За разлика от него, TCP има множество характеристики. Той осигурява надеждно подредено съединение между два възела с проверка за грешки. Следователно, TCP е много удобен и се използва в множество други протоколи, например, в HTTP, FTP и SMTP. Но всички тези функции имат своя цена: забавяне.

За да разберем защо тези функции могат да причинят забавяне, трябва да разберем как работи TCP. Когато изпращащият възел предава пакет на получаващия възел, той очаква да получи потвърждение (ACK). Ако след определено време не получи такова (поради загуба на пакета или потвърждението, или по други причини), то изпраща пакета отново. Освен това, TCP гарантира получаване на пакетите в правилния ред, така че докато изгубеният пакет не бъде получен, всички останали пакети не могат да бъдат обработени, дори ако вече са били получени от получаващия възел.

Но както вероятно разбирате, забавянето в многопотребителските игри е много важно, особено в активни жанрове като FPS. Затова много игри използват UDP със собствен протокол.

Собственият протокол на базата на UDP може да бъде по-ефективен от TCP по различни причини. Например, той може да маркира някои пакети като надеждни, а други — като ненадеждни. Следователно, не му пука дали ненадеждният пакет е достигнал до получателя. Или може да обработва няколко потока от данни, така че изгубеният в един поток пакет да не забавя останалите потоци. Например, може да има поток за входа на играча и още един поток за съобщения в чата. Ако съобщението в чата, което не е спешно, бъде изгубено, то няма да забави активирането на входа, който е спешен. Или собствен протокол може да реализира надеждността по различен начин от TCP, за да бъде по-ефективен в условията на видеоигрите.

И така, ако TCP е толкова лош, ще създадем свой собствен транспортен протокол на базата на UDP?

Ситуацията е малко по-сложна. Дори и да е така, TCP почти не е оптимален за игрови мрежови системи, може да работи доста добре конкретно за вашата игра и да спести ценно време. Например, закъснението може да не бъде проблем за игра на ходове или игра, която може да се играе само в LAN мрежи, където закъсненията и загубата на пакети са много по-малко в сравнение с интернет.

Много успешни игри, включително World of Warcraft, Minecraft и Terraria, използват TCP. Въпреки това, повечето FPS игри прилагат собствени протоколи на базата на UDP, така че по-долу ще говорим за тях по-подробно.

Ако решите да използвате TCP, уверете се, че е изключен алгоритъмът на Нейгъл, защото той буферира пакетите преди изпращане, което увеличава закъснението.

За да научите повече за разликите между UDP и TCP в контекста на многопотребителските игри, можете да прочетете статията на Глен Фидлър UDP vs. TCP.

Собствен протокол

И така, искате да създадете свой собствен транспортен протокол, но не знаете откъде да започнете? Имате късмет, защото Глен Фидлър е написал две страхотни статии по този въпрос. В тях ще намерите много умни идеи.

Първата статия, Networking for Game Programmers от 2008 година е по-лесна от втората, Building A Game Network Protocol от 2016 година. Препоръчвам ви да започнете с по-старата.

Имайте предвид, че Глен Фидлър е голям привърженик на използването на собствен протокол на базата на UDP. След прочитане на статиите му, със сигурност ще възприемете мнението му, че TCP има сериозни недостатъци в видеоигрите и ще пожелаете да реализирате свой собствен протокол.

Но ако сте начинаещ в работата с мрежи, направете се услуга и използвайте TCP или библиотека. За успешното реализиране на собствен транспортен протокол трябва предварително да се научите на много.

Мрежови библиотеки

Ако ви трябва нещо по-ефективно от TCP, но не искате да се занимавате с реализирането на собствен протокол и да се задълбочавате в много подробности, можете да използвате мрежова библиотека. Има много такива:

  • yojimbo на Глен Фидлър
  • RakNet, която вече не се поддържа, но нейната версия с разширения SLikeNet все още изглежда активна.
  • ENet е библиотека, създадена за многопотребителски FPS Cube
  • GameNetworkingSockets на компанията Valve

Не съм опитвал всички тях, но предпочитам ENet, тъй като е лесна за употреба и надеждна. Освен това, разполага с ясна документация и уроци за начинаещи.

Транспортен протокол: заключение

В обобщение: съществуват два основни транспортни протокола: TCP и UDP. TCP притежава множество полезни характеристики: надеждност, поддържане на реда на пакети, откриване на грешки. UDP няма нито едно от тези неща, но затова TCP по природа има увеличени закъснения, недопустими за някои игри. Тоест, за осигуряване на ниски закъснения можете да създадете собствен протокол на база UDP или да използвате библиотека, реализираща транспортен протокол на UDP, адаптирана за многопотребителски видеоигри.

Изборът между TCP, UDP и библиотеката зависи от няколко фактора. Първо, от нуждите на играта: нужны ли са й ниски закъснения? Второ, от изискванията на протокола на приложението: необходим ли му е надежден протокол? Както ще видим в следващата част, можете да създадете протокол на приложението, за който напълно подходящ е ненадежден протокол. Накрая, трябва да вземете предвид и опитността на разработчика на мрежовия двигател.

Имам два съвета:

  • Максимално абстрахирайте транспортния протокол от останалата част на приложението, за да може да бъде лесно заменен, без да преоправяте целия код.
  • Не се занимавайте с преждевременна оптимизация. Ако не сте специалист в мрежите и не сте сигурни, нужен ли ви е собствен транспортен протокол на база UDP, можете да започнете с TCP или библиотека, осигуряваща надеждност, а след това да тествате и измервате производителността. Ако се появят проблеми и сте уверени, че причината е в транспортния протокол, то може би е време да създадете собствен транспортен протокол.

В заключение на тази част, ви препоръчвам да прочетете Introduction to Multiplayer Game Programming на Брайан Хук, в която се разглеждат много от темите, обсъдени тук.

Протокол на приложението

Сега, когато можем да обменяме данни между клиентите и сървъра, трябва да решим какви точно данни да предаваме и в какъв формат.

Класическата схема е, че клиентите изпращат на сървъра вход или действия, а сървърът изпраща на клиентите текущото състояние на играта.

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

Сериализация

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

В ума веднага идва мисълта да се използва читим формат, например JSON или XML. Но това ще бъде напълно неефективно и ще заеме голяма част от канала без причина.

Вместо това се препоръчва да се използва двоичен формат, който е много по-компактен. Тоест пакетите ще съдържат само няколко байта. Тук трябва да се вземе предвид проблемът с реда на байтовете, който може да се различава на различни компютри.

За сериализация на данни можете да използвате библиотека, например:

  • FlatBuffers на компанията Google
  • Cap’n Proto на компанията Sandstorm
  • cereal на Шейн Грант и Рандолф Вурхис

Просто се уверете, че библиотеката създава преносими архиви и се грижи за реда на байтовете.

Алтернативно решение може да бъде самостоятелната реализация, тя не е особено сложна, особено ако в кода използвате подход, ориентиран към данните. Освен това, тя ще ви позволи да правите оптимизации, които не винаги са възможни при използването на библиотека.

Глен Фидлер е написал две статии за сериализация: Четене и писане на пакети и Стратегии за сериализация.

Сжатие

Количество данни, предадени между клиентите и сървъра, е ограничено от пропускателната способност на канала. Компресията на данните ще позволи предаване на повече данни в всеки момент, да се увеличи честотата на обновление или просто да се намалят изискванията към канала.

Битова опаковка

Първата техника е битово опаковане. Тя се състои в използването на точно толкова битове, колкото са необходими за описване на желаната стойност. Например, ако имате изброяване, което може да има 16 различни стойности, вместо цял байт (8 бита), можете да използвате само 4 бита.

Глен Фидлър обяснява как да реализирате това във втората част на статията. Четене и писане на пакети.

Битовото опаковане работи особено добре с дискретизация, която ще бъде темата на следващия раздел.

Дискретизация

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

Глен Фидлър (отново!) показва как да приложите дискретизация на практика в своята статия. Snapshot Compression.

Алгоритми за компресия

Следващата техника ще бъдат алгоритми за беззагубна компресия.

Ето три от най-интересните алгоритми, които трябва да знаете:

  • Кодиране на Хаффман с предварително изчислен код, който е изключително бърз и може да дава добри резултати. Той е използван за компресия на пакети в мрежовия енджин Quake3.
  • zlib е общо предназначение алгоритъм за компресия, който никога не увеличава обема на данните. Както можете да видите, тук., той е приложен в множество области на приложение. За актуализиране на състоянията той може да се окаже излишен. Но може и да бъде полезен, ако ви трябва да изпращате активи на клиентите от сървъра, дълги текстове или релеф.
  • Копиране на дълги серии е вероятно най-простият алгоритъм за компресия, но е много ефективен за определени видове данни и може да се използва като етап на предварителна обработка преди zlib. Особено е подходящ за компресия на релефи, състоящи се от тайлове или воксели, в които множество суседни елементи се повтарят.

Делта-компресия

Последната методика за компресия е делта-компресия. Тя се състои в предаване само на разликите между текущото игрово състояние и последното състояние, получено от клиента.

Тя за първи път беше приложена в мрежовия енджин Quake3. Ето две статии, обясняващи начина на нейното използване:

Гленн Фидлер също я използва втората част на статията си Snapshot Compression.

Шифриране

Освен това, може да се наложи да шифровате предаването на информация между клиентите и сървъра. Има няколко причини за това:

  • лична тайна/конфиденциалност: съобщенията могат да бъдат прочетени само от получателя, и никой друг, който подслушва мрежата, не може да ги прочете.
  • идентификация: човек, който иска да играе ролята на играч, трябва да знае неговия ключ.
  • предотвратяване на измамите: злонамерените играчи ще им бъде много по-трудно да създават свои собствени пакети за измама, тъй като ще трябва да възпроизведат схемата на шифроване и да намерят ключа (който се променя при всяко свързване).

Препоръчвам настоятелно да използвате библиотека за това. Предлагам да се възползвате от libsodium, защото е особено лесна за работа и предлага отлични уроци. Особено интересен е урокът за разменяне на ключове, позволяващ генерирането на нови ключове при всяко ново свързване.

Приложен протокол: заключение

С това приключваме с приложния протокол. Смятам, че компресията е напълно ненужна и решението за нейното използване зависи само от играта и необходимата честотна лента. Шифроването, от моя гледна точка, е задължително, но в първия прототип може да се мине без него.

Логика на приложението

Сега можем да обновим състоянието на клиента, но можем да срещнем проблеми със закъснението. Играчът, след като е направил вход, трябва да чака обновление на състоянието на играта от сървъра, за да види какво влияние е оказал на света.

Освен това, между двете обновления на състоянието светът е напълно статичен. Ако честотата на обновление на състоянието е ниска, движенията ще бъдат много рязки.

Съществуват няколко техники, които могат да намалят влиянието на този проблем, и в следващия раздел ще ви разкажа за тях.

Техники за изглаждане на закъсненията

Всички описани в този раздел техники са подробно разгледани в серия от Fast-Paced Multiplayer на Габриел Гамбетта. Настоятелно препоръчвам да прочетете тази великолепна серия статии. В нея има и интерактивно демо, което позволява да видите как тези техники работят на практика.

Първата техника се състои в директно прилагане на резултата от входа, без да се чака отговор от сървъра. Това се нарича предсказване от страна на клиента. Въпреки това, когато клиентът получи обновление от сървъра, той трябва да се увери, че прогнозата му е била правилна. Ако не е така, трябва просто да промени своето състояние в съответствие с полученото от сървъра, тъй като сървърът е авторитарен. Техниката е използвана за първи път в Quake. Повече за нея може да се прочете в статия Преглед на кода на Quake Engine Фабьен Санглар [превод на Хабре].

Вторият набор техники се използва за изглаждане на движението на други същности между две обновления на състоянието. Има два начина за решаване на този проблем: интерполация и екстраполация. При интерполация се взимат последните две състояния и се показва преходът от едно в друго. Недостатъкът е, че тя предизвиква малка степен на забавяне, тъй като клиентът винаги вижда това, което се е случвало в миналото. Екстраполацията се състои в прогнозиране на това, къде сега трябва да се намират съществата на базата на последното състояние, получено от клиента. Недостатъкът е, че ако съществото напълно промени посока на движение, ще се получи значителна грешка между прогнозата и реалната позиция.

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

Глен Фидлър (както винаги!) написа през 2004 година статия Network Physics (2004), в която полага основите на синхронизацията на симулацията на физиката между сървъра и клиента. През 2014 година той написа нова поредица статии Networking Physics, в която описва други техники за синхронизация на симулацията на физиката.

Също така в уики на компанията Valve има две статии, Source Multiplayer Networking и Latency Compensating Methods in Client/Server In-game Protocol Design and Optimization , в които се разглежда компенсацията на забавянията.

Предотвратяване на измамите

Има две основни техники за предотвратяване на измамите.

Първата: усложнява изпращането на злонамерени пакети от хакери. Както споменахме по-горе, добър начин за реализирането му е криптиране.

Втората: авторитарният сървър трябва да получава само команди/вход/действия. Клиентът не трябва да има възможността да променя състоянието на сървъра, освен чрез изпращане на вход. Следователно, сървърът всеки път, когато получи вход, трябва да провери неговата допустимост, преди да го приложи.

Логика на приложението: заключение

Препоръчвам ви да реализирате метод за симулиране на големи забавяния и ниски честоти на обновление, за да можете да тествате поведението на играта си в лоши условия, дори когато клиентът и сървърът се изпълняват на същия компютър. Това ще опрости значително реализацията на методи за неутрализиране на забавяния.

Други полезни ресурси

Ако искате да проучите други ресурси, посветени на мрежови модели, можете да ги намерите тук:

  • Блогът на Глен Фидлър — струва си да прочетете целия му блог, в него има много отлични статии. Тук събрани са всички статии по мрежови технологии.
  • Awesome Game Networking на автора М. Фатих МАР — това е подробен списък на статии и видеоклипове по мрежови игрови енджини.
  • В wiki на сабредита r/gamedev също има много полезни връзки.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster