May 9, 2019

Дневник разработчиков CSTL #2. HUD для трансляций

Начнём с небольшого лингвистического дисклеймера. Мы знаем, что корректнее писать и читать не «ХУД», а «ХАД», но наша команда привыкла говорить и писать худ, так что сорян.

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

Графический интерфейс на стримах является весьма важным и приоритетным атрибутом, особенно если он уникальный и динамический. Отсюда мы и перейдём в скучную часть дневника. Допустим, что уникальность интерфейса мы можем достичь своими силами, используя графические редакторы и ограничиваясь исключительно своим воображением, но что делать с динамикой? К счастью, Valve об этом подумали сильно раньше нас, потому в Counter-Strike имеется Game State Integration(GSI).

Как работают HUD'ы

Нативный клиент CS:GO изначально умеет периодически отправлять некоторую игровую информацию на HTTP сервер. Естественно, передача этой информации просто так не происходит, игре нужно указать какие данные, куда и с какой частотой отправлять. Здесь дядя Гейб нас тоже не подвёл, потому сделано целое руководство о том, как необходимо настраивать GSI.

Тут стоит уточнить пару вопросов:

1: Могут ли забанить за использование GSI?

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

2: Можно ли использовать GSI, являясь игроком?

— Да, но игра различает наблюдателей и игроков, только наблюдателю CS:GO может отправлять всё игровое состояние.

Для большей развёрнутости стоит добавить что набор информации для наблюдателя и игрока сильно отличается. Эти отличия весьма критичны, даже оригинальный худ игрока повторить не удастся, так как не хватит информации, которую отдаёт игра. Да, игрок может юзать кастомный интерфейс, основанный на Game State Integration, но никакого превосходства игрок получить не сможет, если не считать превосходством более приятный вид игры.

А теперь вернёмся к настройкам, нам предлагается на выбор два примера конфигов.

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

Чтож, приступим

Естественно, уже существовало готовое решение разработчик osztenkurden вдохновился идеей RedSparr0w и написал кастомный худ, используя NW.js и jQuery. Как и с eBotEX, мы взяли что-то готовое и начали дописывать свои решения. Правда, в этот раз мы заранее знали что решение временное, нам не нравилось использование jQuery, да и сама систематизация проекта была изначально неудобная, всё усугублялось наличием кучи мусора, раскиданного по папкам проекта. Так или иначе, работа по доработке худа была начата.

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

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

Редизайн

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

Как только мы адекватно оценили масштабы работы, так сразу решили переходить на Vue.js, это решение в будущем сэкономило нам немало времени и нервов.

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

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

Сперва было решено сверстать новый дизайн худа, без привязки к GSI. Кстати, на этом этапе мы сразу исправили упущение первой версии — адаптивность. Первая версия нормально выглядела только на мониторе 1920x1080p, что помешало использовать её на лане во время ТКФ 2017.

Естественно останавливаться на достигнутом никто не собирался, мы начали добавлять недостающие элементы, которые видно исключительно во время фризтаймов и пауз. В итоге мы добавили показ K/A/D, расчёт количества денег у команд, расчёт стоимости их закупа и показ количества потраченных денег для нового раунда.

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

Уууупс

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

//ADR
//OK I admit this is some random bullshit, no idea how to get damage done in given round
var adr = 0;
if(map.round != adr_r){
  if(map.round != 0 && adr_g < 10){
    
    adr = Math.floor(((stats.kills*100 + stats.assists*58)/(map.round))*(Math.floor((Math.random() * 30) + 85)/100));
    adr_p["a_" + real_obs_sl] = adr;
    $("#stats_player" + real_obs_sl + " .adr_stat .adr").html(adr + " ADR");
    start_money[steamid] = stats.money;
    if(bestadr < adr){
        bestadr = adr;
    }
  
    $("#stats_player1 .adr_stat .adr_bar").css("width", (adr_p.a_1/bestadr)*75 + "%");
    $("#stats_player2 .adr_stat .adr_bar").css("width", (adr_p.a_2/bestadr)*75 + "%");
    $("#stats_player3 .adr_stat .adr_bar").css("width", (adr_p.a_3/bestadr)*75 + "%");
    $("#stats_player4 .adr_stat .adr_bar").css("width", (adr_p.a_4/bestadr)*75 + "%");
    $("#stats_player5 .adr_stat .adr_bar").css("width", (adr_p.a_5/bestadr)*75 + "%");
    $("#stats_player6 .adr_stat .adr_bar").css("width", (adr_p.a_6/bestadr)*75 + "%");
    $("#stats_player7 .adr_stat .adr_bar").css("width", (adr_p.a_7/bestadr)*75 + "%");
    $("#stats_player8 .adr_stat .adr_bar").css("width", (adr_p.a_8/bestadr)*75 + "%");
    $("#stats_player9 .adr_stat .adr_bar").css("width", (adr_p.a_9/bestadr)*75 + "%");
    $("#stats_player0 .adr_stat .adr_bar").css("width", (adr_p.a_0/bestadr)*75 + "%");
    adr_g++;
  } else {
    adr_r = map.round;
    bestadr = adr_g = 0;
  }
}

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

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

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

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

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

Как итог, мы получили то что нам было нужно. Мы конвертировали время в полезную фичу для стримов по CS:GO.

Трансляция с HUD в Yota Arena

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

Sergei Riabinin

Sergei Riabinin

@polarwooolf

Igor Tkachenko

Igor Tkachenko

@alonerus