Чому я обрав серверну розробку в геймдеві

статті
11.10.2021

Оригінал статті опубліковано на DOU.

Мене звати Арсен, я .NET Developer у компанії Plarium, займаюся серверною частиною проєкту Raid: Shadow Legends, мобільного RPG у жанрі темного фентезі.

article image

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

Як я прийшов до .NET

Я почав кар'єру з посади фулстек-девелопера в невеликій IT-компанії: бекенд – на .NET, фронтенд – на Angular. Мені подобалося самому відповідати за весь цикл розробки й знати, як і що реалізовано на клієнті та сервері, як зберігається в базах даних. Але були й мінуси – величезний спектр завдань і мільйон фреймворків, які щодня оновлювалися. Коли намагаєшся встежити за одним, пропускаєш інше й неминуче десь просідаєш. Я захотів стати вузькоспрямованим фахівцем, тому почав замислюватися про перехід на .NET або фронтенд.

Коли я почувся впевненіше в професії, мені захотілося працювати в більшій компанії, з цікавішими проєктами. Я перейшов до іншої аутсорс-компанії, де займався тільки сервером. Працюючи там, потрапив на конференцію uDev і дізнався більше про Plarium. На той час я вже трохи розбирався в Unity – дивився уроки на YouTube, робив pet projects, – тому вирішив спробувати себе як Unity Developer і отримав офер.

Оскільки мені подобається писати чистий код, без усіляких юайних штук, то, попрацювавши певний час у Plarium на клієнті, я зрозумів, що сервер мені все-таки ближчий. Коли мені запропонували написати тулзу для гейм-дизайнерів на WPF, я одразу вхопився за можливість і відчув: .NET – це моє. Перейшов на серверний бік геймдеву й працюю тут уже понад 3 роки.

Що вирізняє роботу в геймдеві

Сприйняття продукту своїм

У продуктовому геймдеві я почав інакше ставитися до проєктів: вони більше сприймаються «своїми». Працюючи перший тиждень, я на дейліку почув фразу: «Ми робимо це для себе», – і якось пройнявся. На аутсорсі я звик давати естимейти в годинах, а тут – у днях: ми багато часу приділяємо код-рев'ю і можемо дозволити собі пошук найоптимальнішого рішення.

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

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

Для мене важливо, що я можу впливати на продукт. Коли ми робили цю мобільну гру, гейм-дизайнери, розробники, тестувальники, менеджери збиралися разом і сперечалися, зайде ця фіча чи ні. Бували навіть запеклі баталії – напевно, така атмосфера в стартапі. Ось ти розробник, перед тобою стоїть директор, і ти йому розповідаєш, чому це може злетіти. І він каже: авжеж, це круто, робимо.

Швидке вирішення проблем

Гадаю, геймдев не підійде тим, хто вважає, що розробка ігор – це суцільний фан і розваги. Але це зовсім не так.

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

Термінові зміни в спринті – теж стресовий фактор. Реліз фічі міг бути запланований через три місяці, але аналітики порахували, що вигідніше перенести її в найближчий спринт замість іншого завдання. Або змінилася політика стору – потрібно відкласти інші таски й за два-три дні «залізно» внести оновлення у гру.

Відповідальність за фічі та фідбек від гравців

Коли зробив фічу, люди ставлять 5 зірок, пишуть «класно», і це надихає. Але, якщо фіча подобається гравцям, це насамперед заслуга гейм-дизайнерів. Як серверний розробник я відповідаю за те, щоб фіча працювала коректно і стабільно, щоб гра не вилітала й не було проблем із продуктивністю.

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

На одному з проєктів якось почали відкочуватися користувачі. Це була офлайн-гра: кожну дію користувача ми зберігали на девайсі й раз на 30–40 секунд надсилали на сервер. Сервер валідував, зберігав і надсилав на клієнт запит, що все гаразд. Але деколи дії на клієнті та сервері відрізнялися. Якщо в сервера не виходило повторити дію клієнта, він автоматично вважав, що клієнт чітер, і все відкочував. Клієнт знову намагався надіслати запит на сервер – і все наново.

Після чергового релізу почало падати багато помилок такого типу, посипалися скарги гравців. Ми тимчасово вимкнули валідацію на сервері, щоб усе з'ясувати. Виявилося, що проблема крилася у зворотній сумісності. У нас дублювалася логіка на сервері та клієнті. Під час зміни коду на сервері потрібно переконатися, що старий клієнт на старому коді коректно працюватиме й на новому сервері. Ми повинні це враховувати, але того разу проґавили. Я добре запам'ятав це відчуття, коли дивишся, як помилки на сервері падають і падають…

Під капотом серверної розробки

У нашого сервера дуже тонкий стек. Він побудований так: чистий .NET, ASP.NET і MS SQL Server. Жодних складних і довгих entity-фреймворків, усе відбувається просто та швидко. Наші сервери обробляють 16 тисяч запитів за секунду і 140 мільйонів внутрішньоігрових битв на день.

Коли працюєш із високонавантаженою системою, то маєш бути на рівень глибше, знати всі вузькі місця і розуміти, як усе влаштовано всередині платформи. На невеликих проєктах я не замислювався, як працює якийсь там збирач сміття. А на таких проєктах, як Raid: Shadow Legends, навіть він може працювати довго – ми побачили це, коли зібрали аналітику. Я не знав, що таке можливо: у .NET збирач сміття реалізований автоматично і тобі не треба за ним стежити.

Глибоке знання системи

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

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

Для нашого проєкту іноді вигідніше написати кастомне рішення. Наприклад, ми самі зробили серіалізатор JSON: у плані продуктивності було неефективно використовувати third-party фреймворки. Вони складні й мають об'ємний функціонал: валідація даних, збирання аналітики, діагностика проблем. Але нас цікавило тільки одне завдання, яке слід було виконувати максимально швидко: серіалізувати модель даних у JSON-рядок і назад. Застосовувати свій простий інструмент для нас виявилося в кілька разів ефективніше.

Завдання, які неможливо заґуґлити

Я вважаю, що значна частина проблем, із якими зіштовхується серверний програміст у геймдеві, пов'язані не з особливостями системи, а з бізнес-завданнями. Починаючи працювати фулстек-розробником, я постійно ґуґлив: як щось додати на сторінку, як щось порахувати у вебі, як щось реалізується в ангулярі. А тут – чистий .NET. Так, я можу поґуґлити, як Apple реалізує якийсь API, але це все.

У мене був таск на збереження прогресу гравців без прив'язаного ID. Що станеться, якщо гравець на 100-му або 200-му рівні ненароком видалить гру? Ми дбаємо про наших користувачів, тому для нас було важливо спробувати зберегти й відновити прогрес.

Складність полягала не в технічному виконанні, а в пошуку рішення. На інших наших проєктах не було такого функціоналу, ми робили все з нуля. Оскільки це офлайн-гра, то ми мали придумати, як зберігати дані до наступного інтернет-підключення. У підсумку ми вирішили використовувати Google Drive або iCloud. Коли користувач установлював гру, у хмарному сховищі автоматично створювався токен. Без підключення до інтернету дані зберігалися на телефоні. Щойно підключення з'являлося, дані синхронізувалися з хмарою. Якщо гру було видалено, ми втрачали токен на сервері, але він зберігався у хмарному сховищі. Під час повторної установки гри ми перевіряли токен і пропонували гравцеві вибір: відновити прогрес або грати наново.

Але виникла ще одна проблема: виявилося, що токен міг прилітати з драйву не одразу. Google Drive працює рандомно, iCloud стабільніше, та все одно токен міг не прийти або не записатися. У нас був сервіс, який постійно крутився в циклі й перевіряв, є токен чи ні. Google синхронізував токен кожні 24 години: виходить, протягом доби прогрес гравця міг бути недоступним. Користувач міг завантажити гру заново, отримати двадцятий рівень. А потім токен з’являвся, підтягувався з драйву й таким чином у користувача виходило два прогреси. У такому разі ми показували гравцеві обидва прогреси: зароблений рівень і монети в кожному. Гравець сам обирав, який прогрес зберегти, а другий ми видаляли.

Для мене геймдев – це технічний виклик. На мій погляд, якщо ти кайфуєш від ігор, це не означає, що будеш крутішим розробником. Головне, щоб тобі подобалося вирішувати саме такі завдання.

Корисні ресурси

Розібратись у високонавантажених системах, ігровій і мобільній розробці мені допомогли кілька книг і курсів. Якщо вам теж цікаво, велкам :)

Що послухати:

  • Brackeys – канал про геймдев, тут я починав вивчати Unity;
  • Unit Testing for C# Developers – курс для початківців із юніт-тестів на C#;
  • React Native – The Practical Guide – курс, який я пройшов, щоб «помацати» мобільну розробку. Було настільки цікаво, що протягом місяця я вставав о 6 ранку, аби дивитися уроки.

Що почитати:

  • «CLR via C#. Програмування на платформі Microsoft .NET», Джеффрі Ріхтер;
  • «C# для професіоналів. Тонкощі програмування», Джон Скіт;
  • «Ефективна робота з успадкованим кодом», Майкл Фізерс;
  • «Прийоми об'єктно-орієнтованого проєктування. Патерни проєктування», Еріх Гамма, Річард Хелм, Ральф Джонсон, Джон Вліссідес;
  • «Unity в дії. Мультиплатформенна розробка на C#», Джозеф Хокінг;
  • «Високонавантажені додатки. Програмування, масштабування, підтримка», Мартін Клеппман.