Пользовательские плагины в JavaScript играх
Кто не знает, Wargaming сейчас разрабатывает тактическую карточную игру WoT: Generals. Web-версия написана на JS, используются LibCanvas и AtomJS. Я принимал непосредственное участие и хочу рассказать про функционал, который мне кажется интересным и может быть полезным во всех веб-играх. А именно система плагинов игры, которая вдохновлялась пакетными менеджерами в Линуксе и имеет следующие возможности:
— История изменений плагинов
— Автоматическое обновление плагина при обновлении версии игры
— Разработка плагинов на localhost
— Неограниченное количество веток, например для нестабильных версий
— Зависимости (плагин А автоматически подключает плагин Б)
— Следствие предыдущего пункта — встроенная возможность делать паки
— Легкое изменение любой части клиента игры
— Полный административный контроль авторов игры над всеми плагинами
— Поиск по базе плагинов
При этом простая установка юзером и удобная работа для плагинописцев.
Игра
Генералы — это тактическая карточная игра. Думаю, многие знакомы с Magic The Gathering. Основной геймплей — это поле боя 5×3, где задача — карточками танков, взводов и приказов уничтожить штаб противника.
Вне боя есть такие экраны как Ангар для выбора колоды, с которой вы пойдёте в бой, дерево исследований, редактор колод и так далее.
Объекты, где множество анимаций вроде карусели в ангаре, дерева исследований и, конечно, боя написаны на LibCanvas и отрисовываются на html5 canvas. Интерфейсы попроще пишутся на html.
Про систему плагинов
Плагины — однозначно полезный функционал.
Во-первых, хардкорные игроки могут использовать их для улучшения собственного впечатления от игры.
Во-вторых, сами по себе разработчики могут почерпнуть кое-какие идеи.
В-третьих, некоторые плагинописцы уже работают у нас в команде.
В-четвёртых, некоторые уязвимости были найдены именно благодаря сторонним авторам.
И хотя официального релиза у нас ещё не было, но система плагинов работает у нас уже полтора года. Я долго думал над тем, какими они должны быть. И ниже я расскажу историю как и к чему мы пришли в итоге.
Конечно, можно написать API, но мне не нравилась ограниченость такого подхода фантазией программиста и увеличенными затратами на поддержку. Хотелось именно возможности изменить любую часть клиента игры.
К счастью, это довольно просто по двум причинам:
— Клиент очень тонкий и занимается только отображением — вся логика вынесена на сервер, который никак не подвергается влиянию плагинами
— Все чувствительные операции вынесены на сторонние сервера — авторизация, где мог бы утечь пароль или платежка, где юзер мог потерять деньги.
В итоге у нас остался только чистый клиент игры, который можно безопасно менять.
Имеется три способа изменения поведения игры:
1. Использование ограниченного API
При создании плагина ему передается инстанс объекта Wotg.Plugins.Simple
с базовыми методы, которые позволяют самые простые операции — подмена картинок, смещение элементов, изменение звуков и т.д. Такое используется для простых плагинов:
2. Подписка на события
Так же у нас можно подписаться на огромное количество событий — получение сообщения с сервера, нажатие кнопки, открытие нового экрана. Это позволяет реагировать на соответсвующие события и, например, при нажатии кнопки «пробел» атаковать врага всем имеющимся арсеналом как в плагине «Катюша».
3. Агрессивное изменение
Это самый сложный, но и самый глубокий метод (если вы понимаете о чём я ;)). Он позволяет изменить любой метод любого класса с возможностью вызова предыдущего варианта. Примерно в коде это выглядит так (часть плагина, которая позволяет сохранять реплеи на стороннем сервере). В данном случае изменяется метод save у ReplayManager.
plugin.refactor( Wotg.Utils.ReplayManager, {
save: function method (battle) {
method.previous.call(this, battle);
var replay, xhr;
replay = this.getCompiledDataFrom(battle);
xhr = new XMLHttpRequest();
xhr.open("POST", MY_OWN_REPLAYS_SERVER, true);
xhr.send(
"player=" + replay.player.name +
"&opponent=" + replay.opponent.name +
"&replay=" + JSON.stringify(replay)
);
}
});
Это дает возможность изменить любое поведение на любое другое поведение вплоть до написания нового функционала, как внутриигровой генератор карт:
Вот такая она с точки зрения кода плагина. Но как все это организовать в плане подключения?
История
Изначально (ещё пару лет назад) было решение использовать встроенные в браузер аддоны и официальные магазины вроде chrome.google.com/webstore и addons.mozilla.org/uk/firefox. Но с этим были проблемы. Такие плагины было тяжело разрабатывать, пока игра не зарелизилась — их нельзя добавить в магазин и пользователям приходилось копипастом кода их добавлять с темы на форуме игры Использование unsafeWindow вместо классического window вносило путаницу для разработчиков. А потом с unsafeWindow вообще появились дополнительные запреты. В общем — темные времена и стало ясно, что необходимо куда-нибудь двигаться.
Хотелось чего-то прогрессивного и удобного. И потому было решение использовать GitHub. Один репозиторий на коммиты и пул-реквесты в репозиторий игры. Удобность работы выросла значительно, но были две проблемы — очень долгое обновление GitHub Pages и отсуствие возможности удобного администрирования. Зато было очевидно, что направление движения правильное. И уже было понятно, где наша Земля Обетованная.
GitLab
Мы подняли GitLab на нашем сервере и выделили его полностью под плагины. И это было божественно. Схема следующая:
— Есть пользователи GitLab — наши плагинописцы
— У каждого пользователя есть репозитории — по одному на каждый плагин
— Репозитории могут иметь несколько бранчей. Например, master и unstable. Master включается по-умолчанию.
В итоге мы получаем следующие возможности:
— Самое главное — весь репозиторий находится под нашим административным контролем и имеет общий с нашим сервером Uptime.
— Формат имени Owner:Title:Branch
. Например Shock:MyCoolPlugin
— основной плагин, а Shock:MyCoolPlugin:Unstable
— версия для разработки, которая мерджится в мастер по факту полной готовности. Путь к файлу определяется шаблоном https://gen-git.socapp.net/{author}/{title}/raw/{branch}/{title}.js
. Это дополнительно облегчает совместную работу над плагинами — один разработчик ответвляет от репозитория другого, получает плагин с таким же именем, но другим автором, вносит свои изменения, может даже дать установить свой плагин другим пользователям, а потом создает пул-реквест в основной плагин.
— При установке информация о плагине записывается в localStorage и его основной файл подключается каждый раз после загрузки всех классов, но до вызова точки входа.
— Каждый плагин имеет версию игры для которой он предназначен и при выходе обновления автоматически выключается пока автор не изменит версию в соответствующей ветке и тогда плагин снова автоматически включится для всех пользователей, у которых он установлен. При этом следующую версию можно заранее подготовить на супертесте и просто во время выхода обновления вмерджить её через веб-интерфейс одной кнопкой.
— В коде плагина достаточно написать require
и автоматически подтянутся необходимые плагины (зависимости). Они подтянутся в корректном порядке и будут доступны из тела плагина как показано ниже.
— Так же можно при помощи include
включать дополнительные классы с плагина. Приблизительно так:
new Wotg.Plugins.Simple({
version: '0.6.0',
require: [ 'Another:Plugin' ],
include: [ 'AnotherClass' ],
}, function (plugin, events, required) {
console.log( required['Another:Plugin'] ); // Ссылка на необходимый плагин
console.log( plugin.included['AnotherClass'] ); // Ссылка на необходимый класс
});
— При помощи команды plugin.addStyles( 'my.css' )
подключить свои стили.
— Каждый плагин имеет свои собственные настройки, которые можно получить командой plugins.getConfig( 'index' )
.
— Самые простые плагины можно изменять через веб-интерфейс GitLab, а более сложные благодаря git clone
разрабатывать локально. Для этого достаточно по шаблону сконфигурировать nginx и благодаря настройке в игре соответствующая директория станет использоваться как репозиторий плагинов. И тогда любое изменение в этой директории подключенного плагина будет отображаться без коммитов в игре разработчика.
— У GitLab есть API. Был зарегистрирован фейковый пользователь и благодаря его приватному токену любой веб-клиент игры может отправлять запросы в API. Это позволяет сделать поиск плагинов, валидацию названий и т.п.
# plugins add Test:CardCreate
No such plugin. Did you mean:
- Shock:CardCreate
# plugins add Test:Ca
Min plugin title length is 4: Test:Ca
# plugins add Test:Card
No such plugin. Did you mean:
- Shock:CardCreate
# plugins add Test:Exa
Min plugin title length is 4: Test:Exa
# plugins add Test:Exam
No such plugin. Did you mean:
- Shock:Example
- Isk1n:Example
# plugins add Shock:Unknown
No such plugin, I dont know, what you want to install
# plugins add Shock:Example:Test213
No such plugin. Did you mean:
- Shock:Example:master
- Shock:Example:new-test
Console vs GUI
На данный момент всё управление и установка плагинов производится через внутриигровую консоль, которая открывается по Ctrl ~
. Всем достаточно удобно (хотя иногда пользователи спрашивают, где тильда). Но есть далекие планы и возможности создать GUI — из localStorage получаем список установленных плагинов, а благодаря GitLab API реализуем их поиск и отображение информации о них.
Если кому интересно — можно почитать (на форуме необходимо авторизироваться) документацию для разработчиков, посмотреть раздел плагинов, или посмотреть как всё это выглядит в WoT: Generals.
Я специально сюда не включил реализацию именно в плане кода, т.к. она довольно проста. Как на меня, самое интересное — это именно использование GitLab как пакетного менеджера. Но если есть вопросы к технической реализации или к идее — жду в комментариях.