Теория AS3 модов
Пользовательский интерфейс игры состоит из совокупности окон (Windows), которыми управляет движок wulf. Сами окна могут быть реализованы на:
GF– Coherent GameFace, этоHTML+JavaScript+CSSUnbound– собственный фреймворк ЛестыScaleform– этоFlash, в котором используется язык программированияActionScript 3(AS3)
Наиболее распространённым способом создания графических модов является использование Scaleform.
Scaleform
Scaleform — это технология, которая позволяет запускать Flash‑приложения внутри игры, аналогично тому, как они раньше запускались в браузере с помощью плагина Adobe Flash Player.
Flash‑приложения пишутся на языке ActionScript 3 (AS3) и компилируются в файл с расширением .swf, который содержит скрипты, картинки, анимации и другие ресурсы, необходимые для работы приложения. Игра умеет работать с такими файлами и отображать их в интерфейсе.
У SWF‑файлов всегда есть точка входа — главный класс, экземпляр которого создаётся при загрузке.
DAAPI
Для взаимодействия AS3‑кода с Python‑скриптами используется механизм DAAPI (Direct Access API). Он позволяет связать AS3‑класс с Python‑классом, чтобы они могли обмениваться данными, вызывая методы друг друга. Связь образуется между указанным Python‑классом и основным классом AS3, указанным в точке входа SWF‑файла.
Пример классов
public class HelloWorldWindow extends AbstractWindowView
{
public var py_DemoFunction:Function;
public function as_DemoFunction(message:String):void {
trace("Message from Python: " + message);
}
public function sendMessageToPython(message:String):void {
py_DemoFunction(message);
}
}class HelloWorldWindow(AbstractWindowView):
def py_DemoFunction(self, message):
print("Message from AS3: {}".format(message))
def as_DemoFunction(self, message):
self.flashObject.as_DemoFunction(message)Префиксы as_ и py_ не являются обязательными, но они помогают отличать методы, которые вызываются из другого языка.
Если создать связку между этими двумя классами, то можно будет в Python‑коде обратиться к self.flashObject и вызывать на нём метод, объявленный в AS3. И наоборот, в AS3‑коде можно объявить переменную типа Function, которая будет ассоциирована с методом из Python‑класса.
Создание связки
Чтобы связать AS3‑класс с Python‑классом, необходимо в g_entitiesFactories зарегистрировать ViewSettings, указав класс Python и путь к SWF‑файлу.
from frameworks.wulf.gui_constants import WindowLayer
from gui.Scaleform.framework import g_entitiesFactories, ScopeTemplates, ViewSettings
viewSettings = ViewSettings(
'MY_MOD_HELLO_WORLD_WINDOW', # уникальный ID окна
HelloWorldWindow, # управляющий класс Python
"HelloWorldWindow.swf", # путь к SWF файлу
WindowLayer.TOP_WINDOW,
None,
ScopeTemplates.VIEW_SCOPE,
)
g_entitiesFactories.addSettings(viewSettings)Добавление окна в интерфейс
После регистрации SWF‑файла его можно открыть в интерфейсе, вызвав метод loadView на текущем Scaleform‑приложении. В качестве аргумента передаётся ViewLoadParams, в котором указывается уникальный ID окна, указанный при регистрации.
from helpers import dependency
from skeletons.gui.app_loader import IAppLoader
from gui.Scaleform.framework.managers.loaders import SFViewLoadParams
appLoader = dependency.instance(IAppLoader)
app = appLoader.getApp()
app.loadView(SFViewLoadParams('MY_MOD_HELLO_WORLD_WINDOW'))Тестирование SWF без перезапуска игры
Во время разработки SWF не обязательно каждый раз упаковывать в .mtmod и перезапускать игру. После сборки копируйте файл в res_mods, сохраняя его путь относительно каталога ресурсов (gui\flash\...):
<GameRoot>\res_mods\<GAME_VERSION>\gui\flash\reload-exmaple\HelloWorldWindow.swfНа время разработки зарегистрируйте окно с путём к этому файлу относительно каталога gui/flash:
DEV_SWF = 'reload-exmaple/HelloWorldWindow.swf'
viewSettings = ViewSettings(
'MY_MOD_HELLO_WORLD_WINDOW',
HelloWorldWindow,
DEV_SWF,
WindowLayer.TOP_WINDOW,
None,
ScopeTemplates.VIEW_SCOPE,
)
g_entitiesFactories.addSettings(viewSettings)Файл не обязательно размещать в корне gui/flash: каталог reload-exmaple в примере нужен, чтобы получить отдельный виртуальный путь для разработки. Вы можете выбрать другое имя, главное — чтобы итоговый путь не совпадал с существующими ресурсами игры и с путём SWF внутри вашего .mtmod.
Регистрировать окно заново после каждой сборки не нужно. Чтобы загрузить обновлённый SWF, уничтожьте старое окно, дождитесь следующего кадра и только затем создайте его снова:
import BigWorld
from gui.Scaleform.framework.managers.loaders import SFViewLoadParams
from helpers import dependency
from skeletons.gui.app_loader import IAppLoader
VIEW_ALIAS = 'MY_MOD_HELLO_WORLD_WINDOW'
def loadMyView():
app = dependency.instance(IAppLoader).getApp()
app.loadView(SFViewLoadParams(VIEW_ALIAS))
def reload():
app = dependency.instance(IAppLoader).getApp()
app.containerManager.destroyViews(VIEW_ALIAS)
BigWorld.callback(0, loadMyView)После замены файла вызывайте reload() через WotREPL или по кнопке из самого мода. Важно уничтожить все открытые окна, использующие этот SWF: пока хотя бы одно из них существует, Scaleform может продолжить использовать предыдущую версию файла. Вызов loadMyView() в том же кадре также загрузит старую версию, поэтому BigWorld.callback(0, ...) здесь обязателен.
Andrei Soprachev