Русский
Русский
English
Статистика
Реклама

Шутер

Автоматизация или смерть как управлять тысячами единиц игрового контента с помощью гугл-таблиц

10.06.2021 18:12:02 | Автор: admin

Несколько лет назад в нашем онлайн-шутере столкнулись с немного абсурдной проблемой: контента стало так много, что мы уже не могли с ним работать вручную. Сотни единиц оружия, карт, механик, гаджетов и много чего еще все нужно балансировать между собой, иначе геймплей развалится на части. Можно нанять больше людей, но лучше прибегнуть к алгоритмам, формулам и автоматизации.

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

Под катом на примерах расскажу, как автоматизировать работу с контентом, если в проекте его уже свыше тысячи единиц, как внедрить новые механики и фичи, не ломая при этом баланс и игровой опыт, и даже, как избавить QA-отдел и программистов от лишней работы.

Баланс в онлайн-шутере крайне важная штука. Нужно, чтобы геймплей нравился всем игрокам не было тех, кто не может вообще никого убить или, наоборот, тех, кто доминирует и фрустрирует всех остальных.

На первых годах жизни Pixel Gun 3D использовалась простая схема: весь контент добавлялся и редактировался вручную. Нужно поменять урон пушке? Заходишь в Unity, открываешь нужный файл и правишь руками. Дело на пару минут.

Потом индустрия начала переходить на freemium. От рынка отставать не стали и проект тоже изменил модель монетизации переработали экономику, прогрессию и многое другое, но сейчас не об этом. В результате DAU полетело вверх, а мы усиленно начали добавляли больше режимов, оружия, персонажей и так далее.

Проблемы начались, когда только оружия в каждой категории накопилось по 20-30 единиц, появились гибридные механики, а данных стало так много, что их уже невозможно было разобрать руками. Вместе с этим повысился порог входа в процессы для новых сотрудников только что пришедший в команду геймдизайнер не мог ориентироваться в контенте, так как банально не знал его на уровне названий.

Нужно было глобально что-то менять.

Сначала просто хотели сэкономить немного времени на балансировке контента, а в итоге автоматизировали большинство процессов, избавились от лишней ручной работы и разгрузили сотрудников. И все это с помощью гугл-таблиц.

Из гугл-таблиц в Unity

В первую очередь стали разбираться, как упростить работу с данными и можно ли их загружать в Unity из обычных гугл-таблиц. Оказалось, можно. Это был ключевой момент и первый шаг на пути к автоматизации назвали его удаленным контролем.

Для получения данных с таблиц мы используем Google Apps Script. Первое время заводили отдельные скрипты на каждую таблицу, в которых обрабатывали данные в JSON. Затем, получая в редакторе JSON, применяли их по назначению.

Сейчас пришли к тому, что опубликовали один скрипт. С его помощью получаем сырые данные с таблиц и уже в проекте конвертируем их в необходимый формат на С#. Этот подход более быстр и удобен.

Так выглядит наш скрипт:
function doGet(e){ if (e === undefined || e.parameter === undefined) {   return FailWithMessage("nullable parameters"); } var tableId = e.parameter["table"]; var listName = e.parameter["list"]; if (listName !== undefined && listName !== "" && listName !== "null") {   var startRow = parseInt(e.parameter["startRow"]);   var startColumn = parseInt(e.parameter["startColumn"]);   var numRow = parseInt(e.parameter["numRow"]);   var numColumn = parseInt(e.parameter["numCol"]);   return GetSigleList(tableId, listName, startRow, startColumn, numRow, numColumn); } else {   return GetAllTable(tableId); }}  function GetSigleList(tableId, listName, startRow, startColumn, numRow, numColumn){ var ss = SpreadsheetApp.openById(tableId); if (ss == null) {   return FailWithMessage("table with name: " + tableId + "not found"); } var sheet = ss.getSheetByName(listName); if (sheet == null) {   return FailWithMessage("list with name: " + listName + "not found"); }  if (numRow < 1) numRow = sheet.getLastRow(); if (numColumn < 1) numColumn = sheet.getLastColumn(); var range = sheet.getRange(startRow, startColumn, numRow, numColumn); var data = range.getValues(); var str = JSON.stringify(data);  var resultObject = {   "resultCode": 2,   "message": str }; var result = JSON.stringify(resultObject); return ContentService.createTextOutput(result);}  function GetAllTable(tableId){ var ss = SpreadsheetApp.openById(tableId); if (ss == null) {   return FailWithMessage("table with name: " + tableId + "not found"); }  var result = {};  var listModes = ss.getSheets(); for(var i = 0; i< listModes.length; i++) {   var sheet = listModes[i];   var sheetName = sheet.getSheetName();     var range = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn());   var data = range.getValues();   result[sheetName] = data; }  var str = JSON.stringify(result);  var resultObject = {   "resultCode": 2,   "message": str };  var result = JSON.stringify(resultObject); return ContentService.createTextOutput(result);} function FailWithMessage(message){ var result = {   "resultCode": 1,   "message": message };   var str = JSON.stringify(result);  return ContentService.createTextOutput(str);}

Для его использования необходимо создать новый скрипт, вставить содержимое из примера, опубликовать и в разрешениях указать, что запускать могут все, даже анонимные пользователи.

После публикации получится ссылка такого формата:

https://script.google.com/macros/s/WwlCZODTDRXJaHhdjfwFRcKtHRQOHqzYisjndduZzDihMpXehLrNxdi/exec

Ее нужно использовать для запуска скрипта. Чтобы скрипт знал, с какой таблицы нужны данные, в get-запрос подставляем ID таблицы. Получить его можно из URL таблицы. Например, в https://docs.google.com/spreadsheets/d/example_habr/edit#gid=0, ID будет example_habr.

Если нужно получить данные с конкретного листа, можно в запрос добавить его название тогда данные вернутся только по нему. То же самое и с диапазоном интересующих ячеек.

Полный запрос будет выглядеть так:

https://script.google.com/macros/s/WwlCZODTDRXJaHhdjfwFRcKtHRQOHqzYisjndduZzDihMpXehLrNxdi/exec?table=example_habr&list=MyList&startRow=1&startColumn=2&numRow=10&numCol=5

У Google есть ограничения на количество запросов и время выполнения. Поэтому нельзя использовать этот инструмент, например, для подтягивание конфигов напрямую в клиент у пользователей при превышении лимита скрипт начнет выдавать ошибку.

Теперь большинство параметров контента редактируется не внутри проекта, а во вне. Движок просто получает данные из таблицы в реальном времени. Например, нужно всем пушкам добавить 5 урона: вбиваем в ячейку, протягиваем по столбцу и нажимаем загрузить. Готово все данные ушли в клиент. По сути, мы с помощью нескольких гугл-таблиц управляем всей игрой.

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

Но мы пошли дальше.

Из Unity в гугл-таблицы

Когда пользователь играет матч, вся статистика отправляется на наш сервер: по ID игрока в базу данных сохраняется, с какой пушкой он ходил, какие результаты показал и прочее. Таких необработанных данных очень много по каждому пользователю и единице контента: киллрейты, результаты матчей, частота взятие пушек и другое. Их мы давно использовали для аналитики но все было вручную, долго и сложно.

Почему бы не использовать таблицы не просто для загрузки данных в Unity, но и в обратную для обработки собираемой статистики и метрик или поиска проблемных мест? Да и вообще для генерации самого контента.

Начали с простеньких задачек. Например, мы часто превышали лимит в 500 символов на описание апдейта в Google Play. Стор такое отклоняет, нужно переписывать и отправлять заново. Задались вопросом, а есть ли формула для подсчета символов в ячейке? Разумеется, в гугл-таблицах большой перечень базовых формул, которые можно комбинировать как угодно и решать практически любые задачи. Написали в ячейке, чтобы описание апдейта автоматически проверялось на количество символов =ДЛСТР(номер ячейки). Теперь проблемы нет.

Гугл-таблицы закрывают большинство наших потребностей. Формулы состоят из простых операторов, но сочетания такие, что иногда не помещаются на экран 27-дюймового монитора. Есть отдельные специалисты, которые их составляют, знают разные фишки, специальные округления, сложные погрешности, выборки.

В гугл-таблицах огромное количество функций, ознакомиться со всеми можно на сайте саппорта Google.

Например, решили составить коллекции для галереи оружия (это такие подборки оружия по схожим признакам: цвет, сеттинг, сезон). Чтобы составить одну коллекцию, нужно оценить визуал, смотрятся ли они вместе, схожий ли у них класс и другие детали. Для этого сделали таблицу, в которую из базы данных выгрузили список всех пушек, написали формулу и подгрузили превьюшки, с которыми можно визуально работать. А уже из таблиц по балансу подтягиваем названия, теги и остальную информацию.

В результате получили таблицу, которую можно подгрузить в любую другую и взаимодействовать с пушками наглядно они автоматом подтягиваются по тегу. Например, по тегу Weapon970 сразу подгружается ее название, превью и так далее. С этим уже можно спокойно работать.

Еще пример. Решили поменять баланс дробовиков (скорострельность, дальность и так далее). Надо понимать, что механик много, а позиций еще больше. Каждую нужно учитывать в формулах в виде отдельных коэффициентов. Геймдизайнер не может зайти и перепечатать параметры руками их придется менять у каждого дробовика, которых может быть несколько десятков.

В формулах есть множество коэффициентов. Есть даже коэффициент крутости добавляем его, если, например, текстура пушки получилась особенно красивой. В таком подходе мы уже думаем через переменные, а не через частные случаи. Уже не хочется вручную переделывать руками 40 позиций, хочется иметь один коэффициент, который сделает все сам.

В итоге таблицы экономят колоссальное количество времени. На подготовку уйдет максимум пару часов (если совсем что-то нестандартное), зато потом можно ворочать огромным количеством данных или сущностей. Вручную те же задачи могут спокойно занять неделю.

Где используем автоматизацию

Мы применяем ее практически везде, где это возможно. И всегда ищем новые способы, чтобы сделать себе жизнь проще и переложить часть работы на формулы.

Балансировка контента

Самые базовый способ применения, о котором уже упоминали выше. Так мы ищем проблемные места, добавляем новый контент, не ломая старый, нерфим или бафаем оружие и так далее.

Если есть ошибка в балансе пушки, то апдейтим ее прямо на сервере, после того, как пересчитали в таблице. Такие правки стараемся переводить в формат удаленного контроля, чтобы не выпускать отдельные патчи в сторы.

Например, если на сервере не контролируется размер обоймы, а в коде добавили лишний нолик и у пулемета стало 50 тысяч патронов без патча такое не пофиксить. Пока он выйдет, пулемет купит большинство, баланс посыпется, появится куча негатива в соцсетях. Но если сделать это на сервере, то достаточно убрать одну цифру все наладится, а игроки даже не заметят. По максимуму вводить удаленный контроль очень хорошая практика.

Генерация сущностей

Автоматизацию можно использовать не только для баланса, но и для генерации нового контента. Так, в каждом батлпассе у нас есть порядка 140 разных челленджей (например, сыграть 10 игр в определенном режиме или убить 30 противников из пистолета). Каждый раз придумывать это вручную долго и нудно, поэтому сделали генерацию. Собрали подробный список условий и прямо из гугл-таблицы создаем квесты теперь одна формула придумывает нам все задачи на каждый сезон.

Естественно, со сгенерированным пулом работает уже человек и настраивает кривую сложности. Игрок должен плавно войти в ивент, тем самым повысив конвертацию в платеж или в более длинную игровую сессию.

Подобным образом мы генерируем задачи и для клановых войн.

Пример формулы:

=ifs(I2="EndMatch";ifs(AE2<=TasksData!$O$34+TasksData!$N$34;"TeamDuel";AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34;ЕСЛИ(A2=0;"TeamDuel";"Spleef");AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34;"Duel";AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34;ЕСЛИ(A2=0;"Duel";"BattleRoyale");AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34+TasksData!$J$34;ЕСЛИ(A2=0;"TeamFight";"DeadlyGames");AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34+TasksData!$J$34+TasksData!$I$34;"CapturePoints";AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34+TasksData!$J$34+TasksData!$I$34+TasksData!$H$34;"FlagCapture";AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34+TasksData!$J$34+TasksData!$I$34+TasksData!$H$34+TasksData!$G$34;"Deathmatch";AE2>TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34+TasksData!$J$34+TasksData!$I$34+TasksData!$H$34+TasksData!$G$34;"TeamFight"); I2="killPlayer";ifs(AE2<=TasksData!$N$35;"TeamDuel";AE2<=TasksData!$N$35+TasksData!$L$35;"Duel";AE2<=TasksData!$N$35+TasksData!$L$35+TasksData!$K$35;ЕСЛИ(A2=0;"TeamFight";"BattleRoyale");AE2<=TasksData!$N$35+TasksData!$L$35+TasksData!$K$35+TasksData!$J$35;"DeadlyGames";AE2<=TasksData!$N$35+TasksData!$L$35+TasksData!$K$35+TasksData!$J$35+TasksData!$I$35;"CapturePoints";AE2<=TasksData!$N$35+TasksData!$L$35+TasksData!$K$35+TasksData!$J$35+TasksData!$I$35+TasksData!$H$35;"FlagCapture";AE2<=TasksData!$N$35+TasksData!$L$35+TasksData!$K$35+TasksData!$J$35+TasksData!$I$35+TasksData!$H$35+TasksData!$G$35;"Deathmatch";AE2>TasksData!$N$35+TasksData!$L$35+TasksData!$K$35+TasksData!$J$35+TasksData!$I$35+TasksData!$H$35+TasksData!$G$35;"TeamFight"); I2="killPet";ifs(AE2<=TasksData!$G$36;"Deathmatch";AE2>TasksData!$G$36;"TeamFight"); I2="killPlayerThroughWall";ifs(AE2<=TasksData!$I$37;"CapturePoints";AE2<=TasksData!$I$37+TasksData!$H$37;"FlagCapture";AE2<=TasksData!$I$37+TasksData!$H$37+TasksData!$G$37;"Deathmatch";AE2>TasksData!$I$37+TasksData!$H$37+TasksData!$G$37;"TeamFight"); I2="killPlayerFlying";ifs(AE2<=TasksData!$I$38;"CapturePoints";AE2<=TasksData!$I$38+TasksData!$H$38;"FlagCapture";AE2<=TasksData!$I$38+TasksData!$H$38+TasksData!$G$38;"Deathmatch";AE2>TasksData!$I$38+TasksData!$H$38+TasksData!$G$38;"TeamFight");I2="ramEscort";"Siege";I2="escortDestroyGate";"Siege";I2="winBrNoChest";"BattleRoyale";I2="crashChest";"BattleRoyale";I2="winBrInParty";"BattleRoyale";I2="flagCapture";"FlagCapture";I2="pointCapture";"CapturePoints")

Пример таблицы с вводными для генератора:

Пример формулы (в ней представлена часть с генерацией описаний задач, Key_номер это ключи локализаций, из которых составляются задачи):

=IFS(I2="endMatch";(ЕСЛИ(T2=0;"Key_7220";"Key_7234"));I2="killPet";ЕСЛИ(W2="None";"Key_7228";"Key_7224");I2="killPlayer";ЕСЛИ(Q2=1;"Key_7227";(ЕСЛИ(W2="NONE";ЕСЛИ(R2=1;"Key_7232";ЕСЛИ(S2=1;"Key_7233";"Key_7221"));"Key_7216")));I2="killPlayerFlying";"Key_7225";I2="killPlayerThroughWall";"Key_7226";I2="ramEscort";"Key_7235";I2="escortDestroyGate";"Key_7236";I2="winBrNoChest";"Key_7229";I2="crashChest";"Key_7230";I2="winBrInParty";"Key_7231";I2="flagCapture";"Key_7237";I2="pointCapture";"Key_7238";I2="";"")

И еще более эпичная, уже проходящая через ячейки параметров и рандомайзеров:

=ifs(L3="DeadlyGames";0;L3="BattleRoyale";0;L3="TeamDuel";0;1=1;ЕСЛИ(I3="killPlayer";ifs(A3=0;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")>=(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38)*6;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")<(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38+TasksData!$B$47)*6;1;0);0);A3=1;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")>=(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38+TasksData!$B$47+TasksData!$B$48+TasksData!$C$34+TasksData!$C$36+TasksData!$C$38)*6;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")<(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38+TasksData!$C$34+TasksData!$C$36+TasksData!$C$38+TasksData!$B$47+TasksData!$B$48+TasksData!$C$47)*6;1;0);0);A3=2;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")>=(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38+TasksData!$B$47+TasksData!$B$48+TasksData!$C$34+TasksData!$C$36+TasksData!$C$38+TasksData!$C$47+TasksData!$C$48+TasksData!$D$34+TasksData!$D$36+TasksData!$D$38)*6;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")<(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38+TasksData!$B$48+TasksData!$C$34+TasksData!$C$36+TasksData!$C$38+TasksData!$D$34+TasksData!$D$36+TasksData!$D$38+TasksData!$B$47+TasksData!$C$47+TasksData!$C$48+TasksData!$D$47)*6;1;0);0));0))

Симуляция процессов

Другой пример. Добавляем в игру сундук с предметами: игрок открывает и получает приз. У сундука есть множество правил, как и что будет выпадать. Можно написать код и отдать QA-отделу, чтобы это проверить, но есть способ лучше. Прямо в гугл-таблице делаем симулятор дропа: вносим правила, условия, открываем 1000 сундуков и корректируем всю модель под нужный исход. Для этого не нужен ни код, ни программист достаточно таблицы и самой концепции.

Пример части вводных данных:

Пример использованных формул в ячейках:

=СЛУЧМЕЖДУ(1;100)
=СРЗНАЧ(13;15)*6
=СУММ(B4:F4)
=IFS($A32<=G32;"Mythic";$A32<=F32;"Legend";$A32<=E32;"Epic";$A32<=D32;"Rare";$A32<=C32;"Common")

Сами по себе формулы простые, но при правильном построении связей ячеек и порядка выполнения действий получается нужный результат.

Пример результатов дропа по номерам открытий:

Разумеется, в самом проекте все пишет программист, но проверить это можно заранее. По сути все, что есть в игре, можно симулировать в таблице. И если результаты устраивают передавать в разработку.

Воркфлоу создания таблицы

Рассказать, как именно происходит работа с данными и создание таблиц, лучше всего на примере. Допустим, мы решили выяснить, какие пушки сейчас самые популярные в игре. А для этого нужно подтянуть множество дополнительных данных.

Расскажу подробнее по шагам (цифры в примере изменены в рамках конфиденциальности):

Эти же данные можно брать из таблицы баланса. Разница в том, что таблица баланса дает актуальные параметры (и не дает метрики), а на сервере информация хранится с пометкой даты ее можно выгрузить за нужный период (и вместе с метриками).

2. Затем создается гугл-таблица, в которой будет происходить основная работа. И в нее загружается полученный CSV-файл.

3. Для удобства из другой таблицы с помощью формулы подтягиваются картинки пушек. Арты предварительно загружены на сервис для получения прямой ссылки в формате (важно именно расширение в конце ссылки):

http://адрес_сервиса/путь/номер/имя картинки.jpg

Для добавления в таблицу используем простую функцию:

=IMAGE("https://files.fm/u/wdrhemgnk#/view/special_offer_pixelman_reward_big.png")

Пример формулы для подтягивания артов с перебором:

=ЕСЛИ(ЕНД(ВПР(A4;importrange(имя_таблицы;Лист1!B:F);5;ЛОЖЬ))=ЛОЖЬ;ВПР(A4;importrange(имя_таблицы;Лист1!B:F);5;ЛОЖЬ); ЕСЛИ(ЕНД(ВПР(A4;importrange(имя_таблицы;Лист1!C:F);4;ЛОЖЬ))=ЛОЖЬ;ВПР(A4;importrange(имя_таблицы;Лист1!C:F);4;ЛОЖЬ); ЕСЛИ(ЕНД(ВПР(A4;importrange(имя_таблицы;Лист1!D:F);3;ЛОЖЬ))=ЛОЖЬ;ВПР(A4;importrange(имя_таблицы;Лист1!D:F);3;ЛОЖЬ); ЕСЛИ(ЕНД(ВПР(A4;importrange(имя_таблицы;Лист1!E:F);2;ЛОЖЬ))=ЛОЖЬ;ВПР(A4;importrange(имя_таблицы;Лист1!E:F);2;ЛОЖЬ);НИМА ТАКОГО))))

Теперь можно работать не с абстрактными строками, а наглядно. Если пушек 30 штук, можно зайти в игру и посмотреть, но когда их 700 так уже не сделаешь.

Важный момент: после подгрузки данных мы их вырезаем (ctrl+x) и вставляем без привязки к формуле (ctrl+x+v). Формулу затем удаляем, иначе после каждого обновления страницы она будет пересчитывать все строки. В данном случае более 800.

Далее одну базу можно использовать для всех задач, где требуется отображение картинок оружия.

4. При желании можно выгрузить название пушки в игровом магазине (а также все необходимые для задачи параметры, например, дополнительные свойства).

5. Также используем форматирование ячеек. Оно может выполнить множество полезных преобразований. Например, покрасить ячейки, в которых значение больше 2 или меньше 1.

В применении к данным о киллрейте пушки (соотношение числа убийств из пушки к смертям игрока с ней) видно превышение обозначенного предела, а также низкие значения, которые явно являются поводом передать эти данные в отдел балансирования.

6. Добавляем столбец с категориями оружия (у нас их 6) и прибегая к тому же условному форматированию красим каждый в свой цвет. Стало видно, что в топе нет пушек категории Special, что уже наталкивает на анализ механик этой категории оружия.

В итоге, чтобы собрать такую таблицу с категориями, свойствами, превью и прочим пришлось вытащить эту информацию из нескольких разных мест. И на это ушло около 15 минут, а не весь день. Так как формулы операций типовые, а таблицы с рендерами и данными есть уже готовые.

Теперь можно сортировать, строить графики, добавлять продажи или другую информацию, чтобы быстро и обоснованно сделать нужные выводы или проверить теории.

Для наглядности на основе данных можно построить и график. Делается это через простую операцию добавления: добавить диаграмма (сразу можно выделить нужный диапазон ячеек).

График популярности оружияГрафик популярности оружия

Жизнь после автоматизации

На самом деле сплошные профиты. Проблемы дисбаланса значительно сократились, стало проще следить за контентом и предугадывать возможные проблемы, потому что общая картина всегда перед глазами, а проблемные места подсвечиваются.

Теперь легче что-то поменять, перезалить или вообще обновить целый класс оружия, в котором 100+ позиций. Достаточно вбить новые параметры все автоматически и с учетом механик пересчитается под норму (а часть параметров пересчитаются сами под новые вводные).

Мы избавились от мелкого ручного менеджмента, освободили время сотрудников для более интересных задач, сняли дополнительную нагрузку на технический отдел и теперь программисты не отвлекаются от написания кода. И что еще важно новички в команде теперь легко вливаются в процессы и намного быстрее разбираются с проектом на реальных задачах.

Переход к автоматизации непрерывный эволюционный процесс, и во многом непаханное поле. Теперь постоянно думаем, как оптимизировать ресурсы и упростить задачи, пробуем новые варианты, формулы и всегда ищем более удобные решения. Например, сейчас работаем над добавлением всех этих процессов прямо в админку.

P.S. Данный подход меняет сам принцип работы с различными инструментами. Например, в том же Slack можно видеть BB-коды наглядно с помощью простой команды #:

Полезные ссылки

Подробнее..

Как мы вырастили и победили читеров в своем онлайн-шутере

02.03.2021 22:15:14 | Автор: admin

Часто сижу на форумах читеров, не потому что нравится, а просто разработчику PvP-шутера всегда нужно быть в тонусе. Некоторые из взломщиков в прямом смысле слова выросли на моих глазах вместе с проектом. Вспоминал эти истории после очередного апдейта в проекте и захотелось ими поделиться.

Несколько лет назад мы написали тестовый прототип шутера, который быстро стал популярным защиты никакой не было, и некоторые побежали этим пользоваться. Мы затыкали пробоины скотчем, читеры находили новые дыры, а мы учились у них с помощью социальной инженерии и повышения технических навыков. И в итоге пересмотрели отношение к защите будущих мобильных проектов (об этом в конце статьи).

До появления мобильной Pixel Gun 3D в 2013 году команда Lightmap писала мини-игры на движке Cocos2d. То есть опыта ни в трехмерных, ни тем более мультиплеерных играх у нас тогда не было. Мы посещали конференции, очень много общались с другими разработчиками и, посмотрев на их опыт, все-таки решили попробовать себя в 3D на Unity.

Изучать движок решили на прототипе шутера, чтобы посмотреть, как отреагируют игроки. В итоге проект выстрелил так, что собирать опытную команду (а на старте было всего 3 разработчика) и разрабатывать качественный продукт с нуля уже не было времени. Всему пришлось учиться на своих ошибках после релиза.

Читеры из младших классов

С самого начала база игроков росла достаточно быстро, а с ними приходили первые читеры. Но из-за ограниченных ресурсов в приоритете стояли совсем другие задачи на старте было мало контента, игровых механик, да и цельной меты как таковой не было. Только когда в обзорах на YouTube слишком часто стали всплывать упоминания о взломах, пришлось заняться этим вопросом.

То есть изначально мы даже не рассматривали проблему появления читеров, а думали только о том, как сделать интересную мобильную игру на Unity. Возможно это и была первая ошибка, но напомню, что мы впервые собирали подобный прототип.

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

Сначала они находили элементарные дыры это использование простых переменных для данных в оперативной памяти и хранение данных на устройстве в незашифрованном виде. Поэтому тот же GameGuardian легко находил слабые места: валюту, время до конца матча, здоровье и так далее.

Сразу же решили передать хранение прогресса в защищенное зашифрованное значение и переделать все переменные, в которых хранились ответственные данные, на классы, где для скрытия используется рандомная соль. Не обошлось без факапов.

В месте, где валюта переносилась из старого хранилища в новое, мы допустили баг, и когда пользователь, который не входил в игру больше недели, обновлялся до новой версии то вся его валюта обнулялась. Самым сложным было понять, в чем дело, потому что при наших тестах такой сценарий не воспроизводился. Оказалось, что место отправки аналитики входа в игру после долгого отсутствия (в которой косвенно использовалась валюта по коду) располагалось раньше нашей миграции.

Насчет прогресса тоже оставалась дыра можно было ломать предыдущие версии и накатываться на новые. Тогда мы параллельно ввели новые валюты и сущности в игре, рассчитывая, что хотя бы их не будут ломать. Ценность старой валюты постепенно снижалась, так как новый контент на нее уже было не приобрести.

Также поставили хоть и локальную, но, все-таки, защиту на взлом инапов через LuckyPatcher. Когда позакрывали места, с которыми мог справиться и первоклассник, к взлому подключились ученики средних классов.

Читеры постарше

Они начали менять код приложения, дописывать свои менюшки для взлома различных параметров, переподписывать и выкладывать apk, которые пользовались успехом на форумах.

Все старания спрятать в коде проверку подписи быстро находились и вырезались, как и другие проверки на взломы приложения. Для обфускации мы стали использовать появившуюся в Unity IL2cpp, но в итоге выяснилось, что движок хранит в проекте дамп всех методов с адресами памяти чем стали пользоваться уже старшеклассники.

Ситуация по сути не менялась, так как мы все время боролись с симптомами, а не с болезнью. Даже не смотря на то, что иногда нам удавалось закрыть уязвимости еще до их использования для этого приходилось выдавать себя за тех самых читеров.

Социальная инженерия против читеров

Мы следили за взломщиками на форумах, общались, изучали, откуда они черпают знания и старались работать на опережение. У нас натурально были постоянные взломщики, которые на глазах перешли из средней школы в институт, набирались опыта и знаний.

Однажды один из таких ребят начал активно общаться с читером, который ломал другие игры. Мы это заметили, скачали его взломы, изучили принцип работы и заранее расставили необходимые детекты по своему приложению. Позже на YouTube мы увидели, что наш читер действительно пытался сделать аналогичный мод для Pixel Gun, но ничего рабочего в итоге не получилось.

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

Но в итоге все равно различные взломы регулярно выпускались разными известными и не очень взломщиками. Все-таки дыра с валютой была лишь одной из многих на тот момент и далеко не самой опасной, потому что за ее использование надо было платить.

А тут случился еще один серьезный факап с нашей стороны. В сеть утекла версия для разработчиков годовалой давности с дев-консолью, в которой можно было легко намешать любую сущность. К этом моменту CCU в пике доходил до 70 тысяч игроков, а DAU стабильно переваливал за миллион пользователей.

Стало понятно, что дальнейшее развитие приложения невозможно при таком состоянии дел со взломами. Все старания с поднятием уровня основных метрик нивелировались, а часто даже занижались вывешенными взломанными версиями.

Приоритетной задачей стало: убрать возможность читерства из приложения.

Решение

Итак, что мы имели на входе:

  • Огромное приложение с несколькими годами активной разработки и большим легаси.

  • Возможность изменения версии и выкладки пользователям.

  • Хранение прогресса локально на девайсе. Как следствие отсутствие контроля над обновлениями с прошлых версий, в том числе взломанных.

  • Синхронизация прогресса между девайсами через сторонние сервисы прогресс можно было беспрепятственно суммировать между игроками.

  • Отсутствие надежной возможности бана читера в измененных версиях его тоже вырезали.

  • Отсутствие надежного отслеживания фактов читерства.

  • В качестве игрового сервера Photon Unity Networking (Cloud), который не имеет серверной логики и, следовательно, не дает возможности контролировать игровую логику.

  • Наличие измененных версий игры в китайских сторонних сторах.

  • Связь с сервером через www-запросы, которые легко отследить и подделать.

  • Возможность подставить из кода любой id, который мы не сможем идентифицировать как подставной.

В итоге: безбожное ломание всего кора и меты игры на тот момент. Поэтому решение тоже должно было быть комплексным от обфускации кода и переноса хранения прогресса на наш сервер до защиты от переподписывания версий и введения собственной аналитической системы.

Причем эффект от всего этого был бы только в случае одномоментного выпуска всех мер защиты, потому что постепенный ввод изменений в проект значительно облегчил бы отслеживание апдейтов взломщиками, которые и так уже хорошо ориентировались в проекте. Другими словами, время, потраченное на взлом, не должно быть сопоставимо с выгодой.

Началась масштабная работа, для чего практически удвоили команду разработчиков, привлекая их с другого проекта.

Пока кратко о том, что мы сделали для перелома ситуации с читерами:

  1. Для обфускации ввели новые стандарты написания кода, позволяющие плагину обфускации сделать свое дело, и переписали под них все приложение. Задача не особо сложная, но ресурсоемкая, так как кодовая база к этому времени уже была довольно большой.

  2. Переработали хранение данных еще более глобальная и ресурсоемкая задача. Сущностей к этому времени было несчетное количество, данные сохранялись беспорядочно в произвольных форматах, что никак нам не подходило. Для синхронизации прогресса с сервером реализовали постоянное сокетное соединение, переведя на него все функционалы, связанные с метой игры (ну и, само собой, сам сервер, но это отдельная история). Для игроков оставили возможность поиграть оффлайн в несколько режимов, правда без наград и изменения прогресса.

  3. Чтобы мигрировать прогресс игроков, на время разработки глобальной защиты стали выдавать специальные ключи. Перенести прогресс в результате смогли только те, кто заходил в приложение в этот временной интервал. После релиза новой версии выдачу ключей остановили. И хотя эти ключи выдавались всем и честным игрокам и читерам основная цель была: исключить взломы через прошлые версии, так как без ключа прогресс не принимался сервером при миграции.

  4. Реализовали надежную систему бана, которую нельзя было вырезать из клиента, так как у забаненного игрока блочится весь серверный функционал (при том что большая часть логики была перенесена на сервер).

  5. Добавили подсчет хеша всех библиотек и начали сравнивать его с разрешенными при авторизации на сервере, куда они добавляются при сборке релизных билдов.

  6. Расставили в дополнительные места защиту от переподписывания версий, лаунчеров (на Android), твиков (на iOS), спрятав уже в обфусцированном коде.

  7. Для предотвращения взлома игровых параметров серверной логикой ввели Photon Plugin, доступный на тарифе Enterprise Cloud. Он позволяет мониторить пересылаемый между пользователями игровой трафик, чтобы вычислять тех, у кого действия выходят за рамки допустимого (жизни, урон, скорость перемещения/стрельбы, использование запрещенных предметов и так далее). Для возможности отслеживания взломов переписали сетевое взаимодействие игроков.

  8. Добавили серверную валидацию инапов.

  9. Добавили защиту от взлома оперативной памяти.

  10. Практически одномоментно выкатили все в продакшен.

  11. Продолжили реализовывать и улучшать собственную систему аналитики.

Сейчас аналитика основной инструмент постоянного отслеживания попыток взлома. Мы можем отслеживать все действия каждого конкретного пользователя и реагировать даже на единичные попытки взлома, не давая распространиться лазейкам. А когда-то эту роль для команды играли YouTube и профильные сайты.

Путь проделан не маленький и можно еще многое расписать про подходы, инструменты, нашу архитектуру и инхаус-разработки, вроде аналитики. Но рассказ обо всем сразу будет слишком объемный, поэтому лучше оставлю место для вопросов в комментариях и для будущих материалов.

ААА-защита для мобильных игр

Сейчас игра надежно защищена от взломов, у нее, как минимум, нет распространенных методов взлома. Бывают единичные, но их оперативно отслеживаем и реагируем.

К новым проектам в разработке тоже стали относиться по-другому и сразу пишем их с защитой, почти как у полноценных ААА-игр. Максимально возможное количество защит на старте приложения означает, что у взломщиков слишком большой порог входа, который надо преодолеть это отпугивает абсолютное большинство.

Также всегда проще писать сразу под необходимые меры защиты, а не переделывать проект под них в будущем.

На текущий момент наша программа-минимум для новых проектов это:

  • Плагин обфускации для осложнения понимания кода приложения.

  • Хранение и синхронизация прогресса через наш сервер с использованием валидации всех основных операций.

  • Зашифрованное хранилище на диске, чтобы исключить перенос данных от девайса к девайсу копированием данных.

  • Надежная система бана.

  • Photon Plugin, как минимум, для валидации действий пользователя.

  • Защита от изменения, переподписывания версий, лаунчеров и твиков.

  • Серверная валидация инапов.

  • Повсеместное использование засоленных данных как защита от изменений в оперативной памяти.

  • Собственная система аналитики с отслеживанием подозрительных действий конкретных пользователей.

И продолжаем сидеть на форумах под видом читеров, общаться и мониторить новые уязвимости. Например, одна из последних это скрытие использования тиков после выхода джейлбрейка на свежие версии iOS. Об этом тоже узнали из форумов, изучили механизмы работы и оперативно выкатили обновление но об этом и других кейсах уже в следующий раз.

Подробнее..

Первые пять шагов для перелома ситуации с читерами в PvP-шутере

18.03.2021 20:06:33 | Автор: admin

Мы прошли долгий путь от появления в игре первых читеров до полного пересмотра подхода к разработке, чтобы создавать защищенные по умолчанию проекты. О том, как в игре появились читеры, я рассказал в прошлом материале. И там же привел список подзадач, которые выкатили одновременно, чтобы закрыть вопрос со взломами, от обфускации кода до подсчета хеша всех библиотек и надежной системы бана.

Итак, эти шаги:

  • Обфускация.

  • Хранение данных.

  • Миграция прогресса.

  • Система бана.

  • Подсчет хеша всех библиотек.

  • Защита от переподписывания версий.

  • Photon Plugin.

  • Серверная валидация инаппов.

  • Защита от взлома оперативной памяти.

  • Собственная аналитика.

  • И одновременный релиз всех решений.

Сегодня поговорим про первые пять пунктов.

Для удобства я пронумеровал все разделы, но напомню, что приведенные ниже решения мы разрабатывали и выкатили одновременно. Потому что постепенный ввод изменений сильно облегчит взломщикам отслеживание апдейтов.

Шаг 1. Обфускация

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

Для защиты приложения от взлома обфускация играет большую роль, так как на всех платформах можно изменять код приложения, подменяя результаты выполнения различных свойств и методов тем или иным способом. И чем проще он читается, тем ниже порог для входа. Цена инапов, жизни, урон скорость и так далее все это нужно обязательно скрывать.

Мы используем плагин Beebyte Obfuscator. Но для работы плагинов обфускаторов надо придерживаться определенных правил оформления кода, иначе названия свойств и методов не будут скрываться.

Какие-то из приведенных ниже правил были выведены изучением материалов о работе обфускаторов, что-то получили экспериментальным путем, ну а что-то нам подсказал разработчик плагина, с которым мы связывались при внедрении. Наш набор:

  • internal вместо **public** и **private** или **internal** вместо **protected**.

  • Все члены классов, помеченных [Serializable], не обфусцируются.

  • Свести к минимуму использование Parse/ToString для enum (при обфускации результат, как правило, бесполезен), но если это все-таки необходимо, то помечаем атрибутом **[Obfuscation(Exclude = true)]**.

    Например:

[Obfuscation(Exclude = true)]public enum GameEventItemContainerContentType{   None = 0,   SingleItem = 1,   ItemsCollection = 2,   Start = 3,}
  • По возможности заменяем `const` на `static`. Статики нельзя использовать в switch. Например, вместо internal const string A_B = "my_constant" делать internal static readonly string A_B = "my_constant" либо internal static string A_B { get{...} }.

  • События анимаций их нужно оставлять/делать public или помечать атрибутом [Obfuscation(Exclude = true)].

  • Лямбды имя лямбды включает имя того метода/класса, в котором она определена, вне зависимости от модификатора доступа метода или класса. По возможности нужно заменять лямбды методами.

  • Kорутины в IL не обфусцируются. Поэтому их можно обфусцировать вручную, например, назвать vfg45_00.

Сейчас, когда под плагин переделано абсолютное большинство нашего кода, весь новый мы пишем согласно этим правилам. Также периодически пересматриваем дамп сборки со списком методов в проекте на предмет, нет ли каких-то важных необфусцированных методов.

Отмечу минус использования обфускации время сборки неминуемо увеличивается (у нас примерно на 30%), но польза несравнимо выше.

На случай, когда надо получить сборку максимально быстро, мы добавили на билд-сервере возможность сборки без обфускации. Но обычно стараемся собирать с ней, потому что есть опасность пропустить баги связанные с обфускацией (обычно это места, где результат выполнения кода зависит от имени метода или энума).

Шаг 2. Хранение данных

При реализации общего плана по защите от взломов игры это был один из самых важных и в то же время масштабных пунктов. Важным, потому что без полного контроля над прогрессом игроков невозможно полноценно защититься от взломов. А масштабным, потому что более чем за пять лет жизни проекта в игре было огромное количество сущностей и функционала, каждый из которых хранил данные в собственном формате, сохранялся беспорядочно и не имел никаких ограничений по количеству обращений к диску на чтение и запись.

Для реализации этой задачи необходимо было переработать все функционалы, в которых что-либо сохранялось.

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

Что ж, для начала составили критерии, которым должна удовлетворять наша система хранения данных:

  • Пользовательский опыт, когда все действия в игре происходят моментально без задержек на серверную валидацию, не должен пострадать.

  • При запуске приложения одним пользователем на нескольких устройствах игрок должен видеть абсолютно одинаковые состояния.

  • Нельзя допустить, чтобы какие-либо проблемы с сервером приводили к запрету входа в игру.

  • Минимизировать трафик и нагрузку на сервер.

  • После реализации основного этапа поддержка и развитие новых функционалов должны быть максимально простыми и быстрыми.

  • Все должно быть надежным и исключать возможность несанкционированного накручивания прогресса.

  • Желательно оставить возможность пользоваться игрой оффлайн, так как такие режимы востребованы среди наших игроков.

Потом начали составлять план работ. Выделили несколько глобальных этапов:

  1. Введение постоянного сокетного соединения клиент-сервер. Используемая до этого связь через https-запросы сильно ограничивала нас в реализации необходимых функционалов. Виделось, что при ней реализовать систему по всем требованиям не получится.

    Тогда опыта использования постоянного сокетного соединения у нас еще не было (предполагалось, что в дальнейшем без соединения нельзя будет находиться в основных разделах игры), поэтому решили обкатать соединение постепенно.

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

    Затем ввели полноценную работу с аккаунтами и добавили поддержку сокетного соединения у большего количества функционалов. До этого момента мы поддерживали оба канала связи. Когда убедились, что новая архитектура держит постоянную связь с сервером и справляется с нагрузками, то можно было выкатывать полноценную работу с прогрессом и все остальные переработанные функционалы.

  2. Формат хранения данных определили JSON. Может он и не самый оптимизированный по трафику и работе, но удобен в использовании. Его мы расширяем и часто используем по проекту.

    Глобально прогресс представляет собой Dictionary<int, object>, где каждая пара это так называемые слоты для хранения данных. Каждый слот служит для хранения своего типа данных: слот валюты, слот инвентаря, слот ачивок и так далее. Ключ он же номер слота данных решили сделать интовым, чтобы сократить использование трафика, значение в каждом слоте может иметь свой формат, но это всегда JSON.

    Чтобы все действия на клиенте отрабатывались моментально, прогресс хранится и на клиенте, и на сервере (команды по изменению прогресса пишутся и там, и там). Отрабатывают сначала на клиенте: визуально пользователь видит, что все окей, а в это время на сервер отправляются параметры для команды. Там они проходят необходимые проверки, и если все хорошо, то прогресс меняется аналогичным способом, как и в клиенте. По результату сравниваются итоговые хеши изменившихся слотов на сервере и клиенте, если не сходятся рвется соединение с клиентом, клиент переавторизовывается и подтягивает последнее валидное состояние слотов.

    Чтобы сократить трафик между клиентом и сервером, для сравнения слотов отправляется хеш слота, а не слот целиком. И только при несовпадении хешей пересылается состояние слота от сервера клиенту при авторизации.

    На случай временной потери интернета (или если, например, приложение закроется до отправки команды на сервер), команды сохраняются локально и при следующем запуске перед началом сравнения слотов на сервере и клиенте сначала отправляются они и только потом хеши имеющихся слотов.

    Чтобы прогресс всегда был в консистентном состоянии, добавили возможность отправки нескольких команд на изменение прогресса в виде одной и назвали это снапшотом. Если не проходит валидацию одна из команд в снапшоте, то все команды в нем фейлятся и не применяются. Где это необходимо использовать: при добавлении опыта повышается уровень игрока, а за это ему необходимо выдать награду. Если не сойдется слот уровня (например, в случае локального его изменения до команды), то и добавление опыта не запишется, не создав ситуацию, когда у игрока опыта больше, чем может быть на уровне.

  3. Для сохранения возможности входа в Pixel Gun 3D в те моменты, когда необходимо по каким-либо причинам остановить сервер прогресса, реализовали для него аварийный режим. При его включении все команды отрабатывают только локально. А когда включается штатный режим, клиент присылает серверу текущие слоты. В эти моменты, конечно, есть возможность что-либо накрутить через какой-нибудь мод. Но аварийный режим мы включаем крайне редко, да и визуально на клиенте никак этого не понять, поэтому вероятность, что этим воспользуются минимальна.

  4. Оставили возможность входа в игру при отсутствии интернета. Игрокам доступны одиночные режимы без наград и ряд других локальных возможностей, которые не требуют изменений прогресса.

Шаг 3. Миграция прогресса

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

Для этого мы добавили выдачу уникальных ключей всем, кто заходил в игру во время реализации новой схемы прогресса (примерно полгода). В дальнейшем, как только выпустили версию с обновленным прогрессом, мы отключили на сервере выдачу этих ключей, а принимали мигрированный прогресс только от тех, у кого был ключ и только один раз (чтобы нельзя было вернуться в прошлую версию и начислить прогресс тем, кто этого не сделал ранее).

Так мы мигрировали прогресс всем активным игрокам и решили проблему взлома через старые незащищенные версии.

Шаг 4. Система бана

Реализовали надежную систему бана, которую нельзя вырезать из клиента, так как у забаненного игрока блокируется весь серверный функционал (при том, что большая часть логики уже была перенесена на сервер).

Ранее, когда не было постоянного соединения с сервером, не было возможности надежно забанить клиент информация о бане запрашивалась отдельным http-запросом с сервера, и в случае положительного ответа в клиенте выводился баннер, что аккаунт забанен. Хоть мы и старались это спрятать в коде, появлялись различные моды, где весь мешающий жизни функционал был нейтрализован.

Сейчас, когда реализована постоянная связь с сервером, большинство функционалов просто не работает без соединения. Для бана достаточно запретить авторизацию при открытии соединения с отдельной ошибкой (для показа баннера, что игрок забанен), чтобы сделать невозможным пользование приложением с заблокированным аккаунтом.

Для защиты онлайна на серверах фотона от забаненных мы также сделали проверку с плагина фотона на факт бана. Если там выясняется, что такой id забанен, то игрока выкидывает из комнаты.

Шаг 5. Подсчет хеша всех библиотек

Одним из традиционных способов взлома является модификация библиотек приложения напрямую. В случае с приложениями на Unity это libil2cpp.so (при билде через IL2CPP).

Для обнаружения таких изменений может использоваться проверка на несовпадение контрольных сумм (хеша библиотек). Самым контролируемым способом будет вычисление текущего хеша на клиенте и отправка его на сервер (где он будет сравнен с эталонным).

Получить путь до наших библиотек можно так:

public string GetLibraryDirectory(){var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");if (unityPlayer == null)throw new InvalidOperationException("unityPlayer == null");var _currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");if (_currentActivity == null)throw new InvalidOperationException("_currentActivity == null");AndroidJavaObject packageManager = _currentActivity.Call<AndroidJavaObject>("getPackageManager");if (packageManager == null)throw new InvalidOperationException("packageManager == null");string packageName = _currentActivity.Call<string>("getPackageName");if (string.IsNullOrEmpty(packageName))throw new InvalidOperationException("string.IsNullOrEmpty(packageName)");const int GetMetaData = 128;AndroidJavaObject packageInfo = packageManager.Call<AndroidJavaObject>("getPackageInfo", packageName, GetMetaData);if (packageInfo == null)throw new InvalidOperationException("packageInfo == null");AndroidJavaObject applicationInfo = packageInfo.Get<AndroidJavaObject>("applicationInfo");if (applicationInfo == null)throw new InvalidOperationException("applicationInfo == null");string nativeLibraryDir = applicationInfo.Get<string>("nativeLibraryDir");if (string.IsNullOrEmpty(nativeLibraryDir))throw new InvalidOperationException("string.IsNullOrEmpty(nativeLibraryDir)");return nativeLibraryDir;}

Для автоматизации процесса при сборке билдов можно использовать OnPostprocessBuild в Unity и производить расчет эталонного хеша. Обратите внимание на то, что при сборке с включением нескольких платформ (ARM, x86) необходимо вычислять хеш по каждой платформе.

Что дальше

В следующий раз поговорим про остальные решения, а именно: защиту от переподписывания версий, Photon Plugin, серверную валидацию инапов, защиту от взлома оперативной памяти, одновременный релиз всех решений и собственную аналитику. А про некоторые объемные пункты уже готовим отдельные, более подробные материалы.

Подробнее..

Еще пять инструментов против читеров на мобильном проекте с DAU 1 млн пользователей

27.04.2021 20:11:49 | Автор: admin

Когда-то нам пришлось полностью переработать защиту популярного PvP-шутера. Результатом стал ряд инструментов, которые мы готовили и зарелизили одновременно, чтобы не дать читерам возможность постепенно отслеживать апдейты.

Про первые пять решений обфускацию, хранение данных, миграцию прогресса и систему бана шла речь в этойстатье. Сегодня расскажу про остальные, а именно:

  • Защита от измененных версий.

  • Photon Plugin.

  • Серверная валидация инаппов.

  • Защита от взлома оперативной памяти.

  • Собственная аналитика.

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

Чтобы не возникло путаницы, продолжим нумерации из прошлой статьи, где уже были опубликованы пять пунктов.

Решение 6. Защита от измененных версий

В дополнительные места мы расставили защиту от переподписывания версий, лаунчеров (на Android) и твиков (на iOS), спрятав уже в обфусцированном коде.

Проверка на твики (iOS)

На устройствах с Jailbreak с помощью Cydia пользователи могут устанавливать твики, которые способны внедрять свой код в системные и установленные приложения. Каждый твик имеет информацию (файл *.plist), с какими бандлами они должны работать.

Механизм детекта осуществляется проверкой этих файлов в папке /Library/MobileSubstrate/DynamicLibraries/ (на наличие внутри нашего бандла).

Большинство твиков, скрывающих Jailbreak, способно спрятать эту папку от просмотра, поэтому есть способ просматривать ее через созданный нами симлинк.

string finalPath = string.Empty;string substratePath = "/Library/MobileSubstrate/DynamicLibraries/";bool bySymlink = false;if (!Directory.Exists(substratePath)) //Если папки не существует (скрыт твиком xCon), то пытаемся получить доступ к файлам через созданный нами симлинк{string symlinkPath = CreateSymlimk(substratePath);if (!string.IsNullOrEmpty(symlinkPath)){bySymlink = true;finalPath = symlinkPath;}}else{finalPath = substratePath;}bool detected = false;string detectedFile = string.Empty;try{if (!string.IsNullOrEmpty(finalPath)){string[] plistFiles = Directory.GetFiles(finalPath, "*.plist"));foreach (var plistFile in plistFiles){if (File.Exists(plistFile)){StreamReader file = File.OpenText(plistFile);string con = file.ReadToEnd();string bundle = "app_bundle"; if (con.Contains(bundle)){detectedFile = plistFile;detected = true;break;}}}}}catch (Exception ex){Debug.LogError(ex.ToString());}

Но также есть твики, которые запрещают создание симлинков по проверяемому нами пути (KernBypass, A-Bypass). При их наличии мы не можем осуществить проверку, поэтому считаем это за возможное читерство.

Общего механизма детекта таких твиков нет, тут нужен индивидуальный подход.

Детект KernBypass (который был активен в отношении нашего бандла):

if (File.Exists("/var/mobile/Library/Preferences/jp.akusio.kernbypass.plist") {StreamReader file = File.OpenText("/var/mobile/Library/Preferences/jp.akusio.kernbypass.plist"); string con = file.ReadToEnd();if (con.Contains("app_bundle") {//detected}}

Определение запуска через лаунчер (Android)

Запуск приложения через лаунчер это, по сути, запуск вашего приложения внутри другого приложения (по типу Parallel Space). Некоторые реализации взломов используют такой механизм для внедрения своего кода, и для этого на устройстве не требуется root-доступ. Обычно они имитируют всю среду: выделяют папку под файлы приложения, возвращают фейковый Application Info и так далее.

При таком запуске у нас все равно сохраняется доступ ко всем файлам, к которым имеет доступ сам лаунчер. Самый простой способ детекта это проверить доступ к материнской папке от нашего приложения (dataDir в applicationInfo) через функцию access (в нативном коде). В обычном случае операционная система не предоставит доступ, а в случае лаунчера это будет папка, которая все еще находится внутри Persistent Data приложения.

Код для плагина на C:

JavaVM*java_vm;jint JNI_OnLoad(JavaVM* vm, void* reserved) {        java_vm = vm;    return JNI_VERSION_1_6;}int CheckParentDirectoryAccess(){    JNIEnv* jni_env = 0;    (*java_vm)->AttachCurrentThread(java_vm, &jni_env, NULL);    jclass uClass = (*jni_env)->FindClass(jni_env, "com/unity3d/player/UnityPlayer");    jfieldID activityID = (*jni_env)->GetStaticFieldID(jni_env, uClass, "currentActivity", "Landroid/app/Activity;");    jobject obj_activity = (*jni_env)->GetStaticObjectField(jni_env, uClass, activityID);    jclass classActivity = (*jni_env)->FindClass(jni_env, "android/app/Activity");        jmethodID mID_func = (*jni_env)->GetMethodID(jni_env, classActivity,                                                      "getPackageManager", "()Landroid/content/pm/PackageManager;");        jobject pm = (*jni_env)->CallObjectMethod(jni_env, obj_activity, mID_func);        jmethodID pmmID = (*jni_env)->GetMethodID(jni_env, classActivity,                                                      "getPackageName", "()Ljava/lang/String;");        jstring pName = (*jni_env)->CallObjectMethod(jni_env, obj_activity, pmmID);    jclass pm_class = (*jni_env)->GetObjectClass(jni_env, pm);    jmethodID mID_ai = (*jni_env)->GetMethodID(jni_env, pm_class, "getApplicationInfo","(Ljava/lang/String;I)Landroid/content/pm/ApplicationInfo;");    jobject ai = (*jni_env)->CallObjectMethod(jni_env, pm, mID_ai, pName, 128);    jclass ai_class = (*jni_env)->GetObjectClass(jni_env, ai);        jfieldID nfieldID = (*jni_env)->GetFieldID(jni_env, ai_class,"dataDir","Ljava/lang/String;");    jstring nDir = (*jni_env)->GetObjectField(jni_env, ai, nfieldID);        const char *nDirStr = (*jni_env)->GetStringUTFChars(jni_env, nDir, 0);    char parentDir[200];    snprintf(parentDir, sizeof(parentDir), "%s/..", nDirStr);    if (access(parentDir, W_OK) != 0)    {         return 1;    }else{ return 0;}}

Защита от переподписи apk (Android)

При любом вмешательстве внутрь apk-файла пакет необходимо переподписать, иначе система не позволит его установить. Поэтому можно определить модифицированную игру, если хеш подписи не совпадает с нашим.

Однако такую системную защиту можно отключить, если на устройстве есть root-доступ, и устанавливать пакеты с невалидной подписью. Поэтому данный пункт не является каким-то особенно важным, но и не будет лишним в общей массе.

Получение хеша подписи в С# через обращение в Java-код:

Lazy<byte[]> defaultResult = new Lazy<byte[]>(() => new byte[20]);            if (Application.platform != RuntimePlatform.Android)                return defaultResult.Value;#if UNITY_ANDROIDvar unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");if (unityPlayer == null)throw new InvalidOperationException("unityPlayer == null");var _currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");if (_currentActivity == null)throw new InvalidOperationException("_currentActivity == null");            var packageManager = _currentActivity.Call<AndroidJavaObject>("getPackageManager");            if (packageManager == null)                throw new InvalidOperationException("getPackageManager() == null");            // http://developer.android.com/reference/android/content/pm/PackageManager.html#GET_SIGNATURES            const int getSignaturesFlag = 64;            var packageInfo = packageManager.Call<AndroidJavaObject>("getPackageInfo", PackageName, getSignaturesFlag);            if (packageInfo == null)                throw new InvalidOperationException("getPackageInfo() == null");            var signatures = packageInfo.Get<AndroidJavaObject[]>("signatures");            if (signatures == null)                throw new InvalidOperationException("signatures() == null");            using (var sha1 = new SHA1Managed())            {                var hashes = signatures.Select(s => s.Call<byte[]>("toByteArray"))                    .Where(s => s != null)                    .Select<byte[], byte[]>(sha1.ComputeHash);                var result = hashes.FirstOrDefault() ?? defaultResult.Value;                return result;            }#else            return defaultResult.Value;#endif

Решение 7. Photon Plugin

Для организации сетевого взаимодействия между пользователями в игровых комнатах мы используем Photon Cloud, который сам по себе предполагает отсутствие серверной логики и отвечает только за пересылку пакетов между пользователями. А для защиты кора нам нужна была именно серверная логика.

Переделывать всё на Photon Server было бы достаточно долгой и сложной задачей, так как в игре уже было много различных режимов, механик и прочего. Поэтому, пообщавшись с ребятами из Photon, мы решили попробовать Photon Plugin.

Photon Plugin доступен на тарифе Enterprise Cloud и пишется на С#. Он запускается на серверах Photon и позволяет мониторить пересылаемый между пользователями игровой трафик, добавлять серверную логику, которая может:

  • блокировать или добавлять сетевые сообщения;

  • контролировать изменения свойств комнат и игроков;

  • кикать из комнаты;

  • взаимодействовать при помощи http-запросов со сторонними серверами.

Мы переписали сетевое взаимодействие игроков так, чтобы можно было отследить тех, у кого действия выходят за рамки допустимого (например, количество здоровья, урон, скорость перемещения, использование запрещенных предметов и так далее), или код изменен так, что нарушается логика режимов.

Про это решение хотелось рассказать подробнее, но тогда бы статья разрослась слишком сильно. Поэтому эту тему оставили для отдельного материала.

Решение 8. Серверная валидация иннапов

В нашей игре сотни видов оружия и предметов. Несмотря на баланс и большое влияние личного скилла игроков на результат матча, все инаппы нужно защитить от взлома. Для этого мы ввели валидацию покупок.

Валидация на сервере состоит из двух этапов:

  1. Превалидация. Когда данные по платежу отправляются на сервер соответствующей платформы для проверки валидности.

  2. Начисление. В случае успешно пройденной валидации купленных позиций.

Сначала сервер получает в качестве входных параметров данные, необходимые для проведения валидации (например, на Android это id инаппа и токен).

Отдельной командой/набором команд происходит начисление купленных позиций. Одна позиция может содержать разнотипные итемы (например, деньги и оружие) и для каждого типа итема на сервере существует отдельная команда начисления.

Чтобы логически объединить несколько команд, привязанных к одному действию игрока, на клиенте и на сервере мы ввели понятие снапшота. Это специальная конструкция из последовательности команд, которые одновременно выполняются или не выполняются на сервере.

Команда валидации проверяет транзакцию в случае, если есть данные превалидации, то используются они. В противном случае данные отправляются на сервер валидации для соответствующей платформы.

В случае успешного начисления id транзакции сохраняется в соответствующий слот игрока запись в БД, которая хранит данные по платежным транзакциям данного игрока. Во избежание взлома платежки методом, когда одну валидную транзакцию используют для многократного начисления, в рамках валидации осуществляется проверка на существование данного id транзакции.

Кроме того, в завершение начисления отправляется соответствующая статистика на сервер аналитики.

Подробнее про интеграцию инаппов и серверную валидацию вместе с кодом расскажем в отдельной статье.

Решение 9. Защита от взлома оперативной памяти

Локальные данные, которые не защищены валидацией с сервера, можно взломать в памяти (например, с помощью GameGuardian на Android). Механизм взлома заключается в поиске значений в памяти путем отсеивания. Ищется текущее значение, затем оно изменяется в игре, а среди найденных адресов в памяти они отсеиваются по новому значению, пока не будет найден нужный адрес.

Для их защиты они засаливаются при помощи случайно сгенерированной соли:

 internal int Value{get { return _salt ^ _saltedValue; }set { _saltedValue = _salt ^ value; }}

Когда пользователь не в состоянии изменить искомое значение для отсеивания, он может попытаться заменить все найденные значения на свои. Для их детекта используется простая ловушка. Следующий пример показывает, как можно определить вмешательство в память с числами от 0 до 1000 (заранее храним массив чисел, которые никогда не должны измениться, кроме как после редактирования памяти).

private static int[] refNumbers;internal static void Start(){refNumbers = new int[1000];for (int i = 0; i < refNumbers.Length; i++) {refNumbers[i] = i;}}internal static bool Check(){for (int i = 0; i < 1000; i++) {if (!refNumbers [i].Equals(i))return true;}}

Решение 10. Собственная аналитика

Изначально мы пользовались платным решением от devtodev и бесплатным от Flurry. Основная проблема была в отсутствии детализации происходящих в игре событий. Мы собирали только агрегированные данные и поверхностные метрики.

Но с ростом экспертизы внутри команды стала очевидной необходимость писать собственное решение с нужными именно нам фичами. Основная цель была в повышении продуктовых метрик и повороте компании в сторону Data-Driven подхода. Но в итоге аналитика также стала незаменимым инструментом в борьбе с читерами.Например, раньше система не привязывалась к пользователю, а просто считала ивенты. То есть мы знали, что 500 человек совершили какие-то действия, но кто эти 500 человек, и что они делали до этого нет. Сейчас можно посмотреть все действия каждого конкретного игрока и, соответственно, отследить подозрительные операции.

Все пользовательские ивенты отправляются в одну большую SQL-евскую базу. Там есть как элементарные ивенты (игрок залогинился, сколько раз в день он залогинился и так далее), так и другие. Например, прилетает ивент, что игрок покупает оружие за столько-то монет, а вместо суммы написано 0. Очевидно, что он сделал что-то неправомерное.Большинство выгрузки с подозрительными действиями нарабатываются с опытом. Например, у нас есть скрипт, который показывает, что столько-то людей с конкретными id получили определенное большое количество монет. Но это не всегда читеры обязательно нужно проверять.

Также читеров опознаем по несоответствию значений начисления валют. Аналитик знает, что за покупку инаппа начисляется конкретное количество гемов. У читеров часто это количество бывает 9999 значит, что-то взломали в памяти. Еще бывают игроки с аномальными киллрейтами. По ним у нас тоже есть специально обученное поле, и когда появляется пользователь, у которого киллрейт 15 или 30, становится понятно, что, скорее всего, это читер.В основном отслеживанием занимается один скрипт, который пачкой прогоняет по детектам и сгружает все в таблицу. Аналитики получают id и видят игроков, которые залогинились утром с огромным количеством голды, в соседнем листе лежат игроки, открывшие 1000 сундуков, в следующем игроки с тысячей гач и так далее. Затем вариантов несколько.

Предполагаемых читеров можно прогнать по флагам, а так как подобные способы читерства задетекчены и пофиксены, то подобные игроки уже могли ранее получить значок читера и попасть в отдельную базу. Если они там есть, то бан прилетает автоматически с помощью скрипта.

Если нет детекта, данные передаются разработчикам. Они думают, как читер смог обойти защиту и решают, как закрыть эти дыры, как таких игроков детектить и банить.А про причины перехода на собственную аналитическую систему и внутренние фичи тоже расскажем в отдельном материале.

Одновременный релиз всех решений

Чтобы достичь максимального эффекта от всех новых защит, было решено выкатить их одномоментно. Так значительно повышался входной порог для тех взломщиков, которые уже были хорошо знакомы с нашим необфусцированным кодом.

Кроме того, мы не просто применили обфускацию приложения, но и изменили структуру большинства значимых частей приложения. В корне была изменена работа внутри, и все это свело к минимуму имеющиеся к тому моменту у читеров наработки. А разбираться в новом стало куда гораздо сложнее.

Всего на глобальный ввод большинства защит ушло около семи месяцев.

Самым масштабным пунктом была реализация системы хранения на наших серверах именно она определяла запуск в продакшен всех решений из списка. Кроме аналитики, которая развивалась самостоятельно.

Также было важно развивать приложение в целом и релизить апдейты для игроков, чтобы не потерять их интерес, пока глобальные решения против читеров складывались отдельно в ожидании своего момента.

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

Подробнее..

Чего не хватает современным шутерам?

03.08.2020 08:09:56 | Автор: admin
Маленький дисклеймер
Заметка нашлась в записках, относящихся к той самой, ещё весной обещанной тут статье, которая, к сожалению всё ещё не готова, хотя на дворе почти осень.
На сегодняшний день статья без иллюстраций выглядит так, кроме того удалось определиться с её конечным названием:

image
image
Одной очень простой, но важной вещи!
Случайно генерируемых на каждый матч (не раунд) локаций.
Именно это добавит киберспортивным соревнованиям азарта, предъявит к спортсменам более высокие требования и подогреет зрительский интерес. Тем более, что реализовать это сегодня с технической точки зрения не сложно!
Конкретно для спортсменов повысится требование к навыку спортивного ориентирования.
Остаётся только дать командам 10-15 минут до начала матча на ознакомление с зоной!

П.С.
Это же решение технически приблизит многие соревновательные шутеры к реальным спецоперациям, когда отряд быстрого реагирования ознакамливается с театром предстоящих боевых действий зачастую в уже движущемся транспорте!
image
Подробнее..

Recovery mode Чего не хватает современным шутерам?

03.08.2020 10:14:39 | Автор: admin
Маленький дисклеймер
Заметка нашлась почему-то в записках, относящихся к той самой, ещё весной обещанной тут статье, которая, к сожалению всё ещё не готова, хотя на дворе почти осень.
На сегодняшний день статья без иллюстраций выглядит так, кроме того удалось определиться с её конечным названием:

image
image
Одной очень простой, но важной вещи!
Случайно генерируемых на каждый матч (не раунд) локаций.
Именно это добавит киберспортивным соревнованиям азарта, предъявит к спортсменам более высокие требования и подогреет зрительский интерес. Тем более, что реализовать это сегодня с технической точки зрения не сложно!
Конкретно для спортсменов повысится требование к навыку спортивного ориентирования.
Остаётся только дать командам 10-15 минут до начала матча на ознакомление с зоной!
В киберспортивном плане обсуждение новой карты игроками могло бы стать интереснейшей частью трансляции! Только мат запикать, пустив поток с задержкой в пару минут

П.С.
Это же решение технически приблизит многие соревновательные шутеры к реальным спецоперациям, когда отряд быстрого реагирования ознакамливается с театром предстоящих боевых действий зачастую в уже движущемся транспорте!
image
Подробнее..

Перевод А мог побороться с CoD История создания шутера Black

04.01.2021 16:19:24 | Автор: admin

На закате эры PS2, студия Criterion, известная своим мастерством в гоночных играх, сделала ставку на другой популярный жанр FPS. Редакция журнала Retro Gamer расспросила бывшего руководителя студии Алекса Уорда о создании популярного консольного шутера. Зрелищный боевик от первого лица показал всё на что способно железо PS2. Здесь есть разрушаемое окружение, огромные и детализированные локации, а также потрясающие визуальные эффекты. Тем не менее разработчик рисковал, выпуская накануне старта нового поколения игру для прошлого и без сетевой игры

Сейчас в это трудно поверить, но раньше играть в шутеры от первого лица на консолях было не комфортно. Хороший шутан на приставках считался лишь исключением из правил, поэтому такая классика, как GoldenEye, TimeSplitters и Halo, пользуются всеобщим почтением. После Halo разработчики и издатели поняли, что не только начинка систем вытягивает жанр, но на этом ещё можно заработать. Шестое поколение (PS2-Xbox-Gamecube) подарило игрокам множество отличных FPS. Геймер мог испытать острые ощущений в TimeSplitters 2, окунуться в мрачную атмосферу Chronicles Of Riddick: Escape From Butcher Bay, сыграть в историческую Medal Of Honor или опробовать переосмысление серии Metroid Prime. Этот период стал самым значимым в формировании консольных FPS.

К всему перечисленному стоит добавить появившийся на закате поколения Black боевик от одной из самых талантливых студий того времени. Мы тогда работали над Burnout 3, а Black представили на закрытом показе шоу E3, вспоминает Алекс Уорд, бывший глава Criterion и нынешний основатель собственной студию Three Fields Entertainment. Мы полагали, что хорошо разбираемся в архитектуре PS2 и способны создать отличный шутер. Единственным конкурентом была Medal Of Honor, которую плохо оптимизировали на PS2, что добавило нам уверенности. Мы построили простой уровень, который показали за закрытыми дверьми на E3 журналистам. Помню как бегал между двумя залами: в одном была презентации Burnout, а в другом Black. Это было весело, хоть и порядком меня вымотало

В тот год мы хотели показать всем на что способна Criterion! Вдобавок студия была в процессе покупки EA. Когда мы поехали на выставку с Black, новый издатель предупредил, что не стоит показывать там прототип. Но я подумал, что сделка может сорваться, поэтому не стоит упускать возможность показать на E3 ещё один проект. Считаю, надо устраивать выставку два раза в году, чтобы разработчики показали идеи, а потом вернулись с альфа-версией. Но вместо этого, приходится кранчить, дабы представить на выставке ранний геймплей.

Кино как источник вдохновения

В основе игры концепции разрушения окружающей среды (по примеру Red Faction), напряжённые перестрелки и зрелищные взрывы как в голливудских блокбастерах. Такой проект станет амбициозным для любой студии, что уж говорить о Criterion, которая была известна лишь гоночными играми. На стадии прототипа над Black работала очень маленькая команда, рассказывает Алекс. Мы раздумывали о применении оружия, и сможем ли это воплотить. Потом нас купила EA, но издатель не смог бы отменить проект, потому что мы уже показали игру и продолжали делиться новостями о разработке. После завершения работы над Burnout 3, команду ответственную за гонку разделили: одна половина приступила к Burnout Revenge, а другая занялась Black. В студии было принято начинать разработку с создания рипоматики (раскадровка) это собранные вместе отрывки из фильмов, чтобы создать представление о будущей игре. Алекс показал эту нарезку, в ней есть сцены из таких фильмов как Хищник, Солдат Джейн, В осаде, В тылу врага и Три короля. Картины формируют тон и ощущения создаваемой игры задолго до начала реальной разработки.

На самом деле замысел возник гораздо раньше. Идея возникла, когда мы делали первый Bumout, объясняет Алекс. Мы хотели сделать стрелялку, но возникла проблема: в кого игрок будет стрелять? Я решил, что действие надо перенести в Россию. Потому что любил пересматривать картину Святой на LaserDisc (предшественник DVD) фильм Филлипа Нойса с Вэлом Килмером (дешёвая клюква, если захотите посмеяться, можете глянуть). Я подумал, что если в кинематографе можно делать русских злодеями, значит они станут противниками и в нашей игре. В фильмах времён холодной войны часто строили на этом сюжет.

Я купил через интернет русские наручные часы, потому что являюсь поклонником трилогии Назад в будущее. Во второй части на рубашке Дока изображены ковбои и поезда это отсылка к следующему фильму. Таким же образом русские часы намекали на будущий проект студии. Глупо конечно, но я носил их около года, засветившись на всех интервью по Burnout 3.

Чувствую, что решение сделать нас злодеями, накалит многих читателей, поэтому предлагаю ненадолго отвлечься и почитать про создание звука в игре.

Вдохновляясь боевиками, разработчики брали звуковые эффекты для оружия из известных фильмов. MP5 Джона Макклейна из картины Крепкий орешек, пистолет Джека Бауэра в сериале 24 и Узи героя Арнольда Шварценеггера из Правдивой лжи. Понимая, что в хаосе перестрелки все звуки смешиваются и начинают резать слух, звукорежиссёры придумали концепцию под названием хор орудий. Традиционно в шутерах каждой модели оружия назначается отдельный звук. В Black каждому врагу присвоен собственный звук выстрела, подобно тому, как каждый член хора наделён собственным голосом. Например, стреляют три врага: одному их них присвоят низкий голос, другому средний, а третьему высокий. Это позволяет добиться гармонии и обеспечивать отличный звук в игре. За что проект номинировали на награду BAFTA Video Games Awards 2006 в категории лучший звук. Также совместно с Burnout Revenge шутер получил награду Best Art & Sound на конкурсе Develop Industry Excellence Awards 2006.

А теперь вернёмся к интервью с создателем.

Criterion переоценила свои силы, взявшись за разработку сразу двух игр. Пришлось попотеть. Закончив Burnout 3, мы сразу перешли к Revenge, вспоминает Алекс. Команда трудилась семь дней в неделю. Нас недавно купила EA и мы хотели показать всё, на что способны. Black надо было закончить в январе, а это означало, что предстоит работать все новогодние праздники. Мы сидели в офисе в канун Рождества, тестируя проект. Кажется, были готовы шесть уровней. Но код сыроват там всё было сломано. Мы с Крейгом Салливаном (ведущий дизайнер) сидели ухватившись за головы. Поспешно отправив команду домой, мы рассчитывали, что проблем не возникнет. Думали, мол, быстро пройдём пять уровней и по домам В итоге дошли до четвёртого, и здесь игра ломалась! И это не первая проблема, с которой команда сталкивалась.

Создание повествования

У Criterion были грандиозные планы по созданию крутого сюжета, но стало очевидно, что в студии нет тех, кто умеет выстраивать повествование. Мы провели несколько экспериментов, чтобы проверить, сможем ли реализовать подачу истории на движке игры. Но у нас не хватало опыта и знаний. Поэтому решили записать пререндеренные ролики. Но даже кат-сцены с живым видео не впишутся в игру, если нет альфа-версии. Мы построили уровни и спланировали нить повествования, объясняет Алекс. Игра это воспоминания героя. В роликах его допрашивают, и они связывают историю в единое целое.

Здесь команда столкнулась с трудной задачей. У нас не было возможности снимать ролики самостоятельно. Займись мы этим сами, пришлось бы нанимать актёров и всю съёмочную команду. Но мы делаем игру в офисе и там нет места для съёмочной площадки! Поэтому требовалась помощь со стороны, Алекс продолжает объяснять, как они решили задачу. В команде был Нило Родис, с опытом работы в киноиндустрии. Он познакомил меня с художником-постановщиком Джозефом Ходжесом, который работал над сериалом 24. Бюджет разработки не потянул бы киносъёмки, поэтому мы позвонили ему и попросили сделать одолжение. Джозеф согласился.

Criterion сначала экспериментировала с абстрактными роликами и закадровым голосом, но результат не впечатлял. Требовалось другое решение. Джо сказал: Я сниму это завтра вживую на площадке "24" до того как начнутся съёмки сериала. Они даже об этом не узнают! Я спросил насчёт костюмов, а он ответил, что договорится и об этом. Актёрам сказали явиться в 5 утра на съёмочную площадку в Чатсуорте, Лос-Анджелес. Он отснял сцены в комнате для допросов, которая часто фигурирует в сериале. Съёмки проходили тайно. Джозеф гений, мы остались довольны отснятым материалом.

Удивительно, но игра таки попала в сериал! В одном из эпизодов персонаж шоу играет в видеоигру там лишь пара кадров с геймплеем, но я знаю, на каком уровне он находится, потому что помню, как подписывал разрешение на показ игры в сериале, ухмыляется Алекс. В конечном итоге Джон Мур (режиссёр В тылу врага) и кинокомпания 20th Century Fox решили снять фильм по мотивам Black. Алекс рассказал о встрече с продюсером Хищника Джоном Дэвисом и Алексом Янгом, продюсировавшим Команду А. Подготовка к съёмкам уже началась, как и создание второй части игры, но оба проекта не увидели свет. Fox была заинтересована в создании фильма. Я не знаю подробностей и причин отмены картины, но немного расскажу про сиквел игры.

Концепт-артыКонцепт-арты

Уйти в закат на волне популярности

Предстояло осваивать архитектуру некстген-консолей, поэтому мы отказались от решения разбивать сильную команду на две более слабые. Вновь собрав сотрудников вместе, все переключились на Burnout Paradise, поэтому разработка Black 2 прекратилась. Мы работали над ней около полугода. Был готов прототип с несколькими уровнями, а также модели персонажей. Это отняло много времени, усилий и требовало больших вложений реши мы продолжать работу. Выбор стал между Burnout и Black выжить мог только один проект. Оглядываясь назад, считаю такое решение правильным. Black стала успешной игрой и полюбилась многим, но фанатов Burnout гораздо больше нельзя оставлять их ни с чем

Бывший сотрудник Criterion называет другие причины закрытия проекта. Дизайнер Стюарт Блэк в интервью рассказал, что Black 2 отменили из-за разногласий с Electronic Arts. Стюарт и другие разработчики из Criterion ушли в Codemasters, где работали над духовным наследником Bodycount. Игру выпустили для Xbox 360 и PlayStation 3 в 2011 году. Боевик получил смешанные отзывы и не достиг высоких показателей продаж. В результате Codemasters закрыла студию в Гилфорде.

Но вернёмся к Алексу Уорду и его студии Three Fields Entertainment, основанной вместе с другими сотрудниками Criterion: сооснователем Фионой Сперри и разработчиком Полом Россом. Компания разрабатывает игры с 2014 года и уже выпустила по две части гоночных аркад в стиле Burnout Danger Zone и Dangerous Driving. Дела у студии идут хорошо, поэтому можно ожидать, что разработчики вернутся к идее создания зрелищного боевика с оглядкой на кинематограф Но это не точно.

Подробнее..

Перевод Как создавали Побег из Бухты Мясника

18.01.2021 18:09:31 | Автор: admin

Мы привыкли к проходным играм по фильмам. Но время от времени студии выпускают годные продукты. В 2004 году неизвестные шведские разработчики из Starbreeze Studios создали не просто игру в киновселенной о Риддике, а инновационный проект на движке, который превзошел технологии id Software и Valve.

Профайл разработчиков

Прежде чем начать рассказ, представлю участников интервью.

Микаэль Сакер

Микаэль работал в Starbreeze вплоть до Assault On Dark Athena, а затем перешёл в DICE, где приложил руку к серии Battlefield. В настоящее время отстранился от игровой индустрии, чтобы закончить свой первый роман.

Йенс Андерссон

Сейчас Йенс работает на два фронта: трудится в своей инди-студии Collecting Smiles и Villa Gorilla (которая выпустила неплохой пинбол-платформер Yokus Island Express). Недавно он успешно собрал средства на Кикстартере для проекта Colors Live, заточенного под Nintendo Switch (приложение позволяет использовать портативку как планшет для рисования благодаря специальному стилусу).

Пит Ванат

Пит был исполнительным продюсером Dark Athena, затем продюсировал другие игры, например, Lego Jurassic World и некоторые проекты студии Telltale Games. После стал соучредителем разработчика мобильных спортивных игр Nifty Games.

А теперь перейдём к истории создания хита

Вступление

Не нужно проводить много времени в шкуре Риддика, чтобы понять насколько Escape From Butcher Bay крутая игра. После короткого пролога, игрока бросают в тюрьму Бухта Мясника неприступного сооружения из стали и камня на пустынной планете в забытом уголке космоса.

Первые пять минут мы движемся по рельсам в сопровождении охранников через будущее место заключения. Играет драматическая музыка, титры исчезают и нас знакомят с голосами таких звёзд, как Рон Перлман (Джаггер Вэланс), Xzibit (Эббот) и Вин Дизель (Риддик, которым управляет игрок). Затем бросают в камеру, из которой предстоит сбежать.

Сцена напоминает знаменитый пролог из Half-Life (та самая поездка на монорельсе). И такие кинематографические моменты редко встречались в играх того времени. Сравнение с Half-life приведено не просто так: как поездка Гордона Фримена открыла новое поколение игр, так и прогулка Риддика в сопровождении представила новую эру игровых движков. Проект опередил Doom 3 с ядром id Tech 4 и Half-Life 2 на их Source Engine, продемонстрировав новейшие графические технологии.

Рождение хита

В 2002 году Starbreeze Studios только делала первые шаги. Студия выпустила свой первый крупный релиз ролевую игру в жанре тёмного фэнтези Enclave. Ролевой боевик хорошо приняла публика (к чёрту низкие оценки критиков что они понимают в играх), но ведущий сценарист Побега из Бухты Мясника Микаэль Сакер вспоминает, что для команды главным был полученный опыт. Игра стала для нас скорее обучением или закалила в бою, признаётся он. После релиза мы почувствовали себя сильнее и были готовы взяться за следующую работу.

Сначала разработчики хотели заняться чем-то оригинальным, но из-за отсутствия идей, им приглянулся проект по вселенной Риддика. Тогда антигерой Вина Дизеля был известен только по культовому научно-фантастическому фильму Чёрная дыра (или Кромешная Тьма). Сиквелу ещё не дали добро, что предоставило Starbreeze много творческой свободы вселенная Риддика только зародилась и вписать туда новый сюжет не составляло труда.

Продюсер Бухты Мясника и один из основателей Starbreeze Йенс Андерссон рассказывает, что им, по сути, предложили оригинальную интеллектуальную собственность. Всем понравилась картина Чёрная дыра, вспоминает он. Там есть атмосфера фильмов категории B, но нет детального описания мира, что развязало нам руки. Вселенная Риддика как чистый лист.

Разработка началась в 2002 году. Первоначальная концепция заключалась в том, чтобы сделать открытый мир по типу GTA. Vice City только вышла, а песочницы тогда казались жанром с безграничными возможностями. Концепт просуществовал недолго, поскольку студия не стремилась соответствовать трендам, а решила ориентироваться на свои вкусы. Команда внедрила элементы скрытности (стелса) и ролевой игры, благодаря чему проект сравнивают с такой классикой, как Deus Ex и Thief.

Правда, такой подход не понравился издателю Vivendi Universal. Геймплей требовал перехода к виду от первого лица, а это означало, что звезда проекта Вин Дизель не будет светить лысиной на протяжении всей игры. Решением стали сцены взаимодействия с объектами, например, когда вы поднимаетесь по лестнице или входите в вентиляционное отверстие обзор переключается на вид от третьего лица, поясняет Микаэль.

Кинематография

Собственная студия Вина Дизеля Tigon Studios работала над Побегом из Бухты Мясника совместно со Starbreeze. Пит Ванат продюсер Vivendi Universal рассказывает о преданности Вина Дизеля своему антигерою. Вин считает игры больше, чем развлечением для него это искусство, рассказывает он. Однажды во время записи Дизель приостановил работу, чтобы вместе с Флинтом Дилле (сценарист проекта) переписать диалог. На это ушло пять часов! И команду это накалило. Но когда у вас в студии чёртов Риддик, а не Вин Дизель приходится смиренно подчиняться А получившуюся тогда запись считаю одной из лучших в игре!

Вин понимает своего персонажа лучше, поэтому запись часто не соответствовала сценарию, поясняет Йенс. Дизель не просто актёр озвучки. Он вжился в роль для фильма, и этот практический подход проявился в диалогах. Риддик тихий убийца, поэтому говорит мало и так, что запоминается

Игровой процесс

Тем временем Starbreeze трудилась над механиками игры. Нам приходилось буквально воевать с кодом, Йенс сравнивает программирование с борьбой. Первым испытанием стал рукопашный бой. Нужно было предложить игрокам различные варианты схваток без применения огнестрельного оружия. Риддик заключённый в тюрьме, поэтому нельзя сразу вручить ему пистолет. Требовалось разнообразить геймплей, чтобы стрельба стала лишь дополнением к основным механикам.

Другим элементом геймплея стала RPG-составляющая. Даже сейчас редко встретишь шутер с квестами, головоломками, несколькими маршрутами к цели и системой диалогов с NPC (тут должна быть шутка про Киберпанк). Мы решили ввести элементы ролевой игры: древо навыков, продвинутых NPC, которые реагируют на действия игрока, рассказывает Микаэль.

Эти RPG-механики не выделяются, грамотно дополняя игровой процесс. Между сценами экшена, в которых вы стреляете в охранников или вас преследуют мутанты, есть длинные отрезки, где Риддик живёт жизнью заключённого. Можно выполнять одолжения в обмен на оружие, участвовать в рукопашных турнирах и зарабатывать репутацию среди сокамерников. Разумеется, всё это не для забавы ради, а чтобы устроить побег.

Такой подход очень смелый игра, способная быть отличным шутером, внезапно лишает игрока оружия. Но рукопашный бой, стелс и симуляция открытого мира способны затмить более привлекательную для масс стрельбу, которая впечатляет зрелищностью, но всё-таки довольно линейна и однообразна.

Йенс и Микаэль признаются, что предпочитают более размеренные этапы игры, а не стрельбу. Нам такой геймплей нравится больше, не скрывает предпочтений Йенс. Когда разрабатываешь игру, то нет представления, что у тебя получится, пока не приблизишься к финальной стадии. Поэтому над балансом между разными жанрами мы трудились уже в самом конце разработки.

Мощный собственный движок радовал возможностями, но программировать на этом ядре было трудно. Команда вложила в Escape From Butcher Bay столько разнообразных идей, но донести до издателя крутость проекта не получалось. Даже непосредственные участники разработки не могли предоставить Vivendi Universal вертикальный срез и рассказать, что из этого получится, рассказывает Йенс.

Ох, уж эти издатели вечно подрезают крылья создателям!

В 2003 году студия работала ещё над двумя проектами. Но их издатели обанкротились, и Starbreeze поджидала та же участь. Это вынудило разработчиков попросить у издателя Escape From Butcher Bay дополнительные деньги на завершение проекта. Пришлось поставить их перед фактом, мол, потребуется дополнительное финансирование, иначе игра не будет завершена, вспоминает Йенс.

К концу года подтвердили дату выхода фильма Хроники Риддика, и стало очевидно, что игра не поспевает к релизу картины. Проект находился в стадии альфа-тестирования. Другими словами, игра была поломана: низкая производительность, неисправная система ближнего боя и куча багов.

Йенс вспоминает, что в какой-то момент проект почти отменили пока не вмешался Пит Ванат. Был момент, когда один конкретный руководитель из Vivendi Universal очень хотел закрыть проект, вспоминает Пит. Издателя больше заботили игры Van Helsing и Fight Club, которые тоже находилась в разработке.

В итоге команде пришлось пойти на жертвы версию для PS2 отменили. Консоль от Sony была (и остаётся) более успешной, чем Xbox, но из-за слабого железа оптимизация такой технически продвинутой игры вытянула бы слишком много ресурсов студии. Многие элементы дизайна там недоступны, например, тени и динамическое освещение, но мы нашли другой способ реализовать освещение, отмечает Йенс. Уверен, что версию для PS2 возможно было сделать и получился бы отличный порт!

Возможно, однажды наработки всплывут в сети интересно посмотреть на версию для пастгенаВозможно, однажды наработки всплывут в сети интересно посмотреть на версию для пастгена

Технические подробности разработки

Больше всего времени команда потратила на работу с собственным движком, который создал основатель Starbreeze Магнус Хогдал. Йенс описывает Хогдала как технического гения, одержимого движком id Tech Джона Кармака и взявшего оттуда всё, что смог.

Микаэль вспоминает, как Магнус показывал сотрудникам ролики, демонстрирующие возможности id Tech. Помню, как однажды он показал нам клип, где объясняется тесселяция в реальном времени. Если у вас есть модель изогнутой трубы, вы можете построить её с большим количеством полигонов, но ограничивайте их количество по мере удаления от объекта, рассказывает Микаэль. Уже на следующий день Магнус реализовал это в нашем движке. Хогдал человек очень целеустремлённый!

Несмотря на сложность работы с движком, примерно за полгода до релиза всё наконец встало по своим местам. Команда полностью освоила ядро и применяла его технологии и механику. Например, на Xbox движок способен отображать на экране только четыре-пять персонажей одновременно. Вот почему двор в Тюремной Зоне 2 разделен, поясняет Микаэль. Мы постарались, чтобы это выглядело естественным, а причина такого разграничения ограничение количества персонажей на экране.

Релиз не за горами

Код начал обретать форму, превратив Escape From Butcher Bay из сломанной игры в новаторскую. Такие визуально простые трюки, как динамические карты освещения, начали работать, а рукопашные схватки очень увлекали, говорит Йенс. Прорыв в механике кулачных боёв сподвиг Starbreeze добавить в игру целую локацию, ориентированную на такой вид геймплея (Территория Аквила). Это моя любимая локация в игре, делится с нами Йенс. Она соответствует тюремному менталитету и создаёт атмосферу заключения!

Йенс и Микаэль признаются, что на самом деле не знают, настолько ли хороша релизная версия, которую отправили в печать. Дело в том, что к концу разработки штат Starbreeze сократился с 80 до 18 сотрудников, и всё внимание сосредоточили на поиске следующего проекта, который поддерживал бы студию на плаву. После провала в прокате Хроник Риддика, речи о продолжении игры по вселенной не было Вплоть до 2009 года, когда вышла Assault On Dark Athena. Сейчас такие проекты именуют ремейком: к переработанному оригиналу добавили мультиплеер и локацию Dark Athena (довольно линейная глава, не соответствующая геймплею основной игры).

В ремейке по глазам бьёт замыленная картинка, поэтому лучше найти оригинал с патчем 1.1, а потом браться за продолжение Assault On Dark Athena.В ремейке по глазам бьёт замыленная картинка, поэтому лучше найти оригинал с патчем 1.1, а потом браться за продолжение Assault On Dark Athena.

The Chronicles of Riddick: Escape from Butcher Bay хорошо продалась (правда, большая часть заработанных денег ушла издателю) и стала одной из лучших игр своего поколения. Благодаря переносу Half-life 2 и Doom 3, это стал первый проект, демонстрирующей инновационные графические технологии, которые геймеры только позже увидели в играх на движках Source и id Tech 4.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru