Jump to content
Korean Random
GPCracker

[Гайд] Создание Flash объектов и управление ими. Основы DAAPI.

Recommended Posts

Создание Flash объектов и управление ими. Основы DAAPI.


Автор: GPCracker
Версия клиента: 0.9.10, 0.9.17*

Дисклеймер

Данный гайд подразумевает наличие у читателя достаточного для самостоятельного анализа и написания кода уровня знаний Python и ActionScript. Я не буду подробно описывать побочные моменты, которые могут быть решены самостоятельно или решения которых уже обсуждались в других темах или гайдах. В любом случае, в гугл банов не раздает, и наверняка найдутся люди, способные ответить на ваш вопрос или поделиться ссылкой на готовый ответ. Также я не буду дублировать код, надуюсь что читатель сам поймет, что и откуда нужно взять. Я лишь опишу, что и для чего нужно, на примере простейшего боевого Flash. Если где-то я допускаю ошибки, прошу поправить. Дополнения и комментарии приветствуются.

Предисловие

Некоторое время назад по просьбе населения, так сказать, я написал небольшое руководство по использованию средств коммуникации Python<-->ActionScript для управления Flash объектами. Гайд по сути является комментарием к коду и написан на английском. Но поскольку информация достаточно интересная и полезная для многих начинающих (и не только) мододелов, не думаю, что пост в одной из тем - подходящее место для гайда. Однако и в новую тему выделить его нельзя, по причине отсутствия нормального оформления. Чем, собственно, и решил заняться, тем более появилось немного свободного времени.

"Примечание об актуальности после перевода боевого интерфейса на AS3"

Сразу извиняюсь за некоторое злоупотребление транслит-терминологией и вообще некоторыми "упрощениями" терминов, которые для новичков могут быть абсолютно незнакомыми, а также то, что не указываю точных адресов классов, поскольку не имею декомпилированного флеша и питона под рукой, но надеюсь приведенный под данным спойлером текст все же удастся понять, да и, если вы дошли до этого момента, надеюсь дисклеймер в самом начале тоже был прочитан. Поэтому очень рекомендую сначала ознакомиться с гайдами для ангарных интерфейсов, а уже потом приступать к боевым.
Поскольку в одном из недавних патчей, точно уже не помню в каком, боевой интерфейс был переведен на AS3, код, приведенный в данном гайде уже не совсем актуален. Однако представленная тут информация о механизмах коммуникации флеша и питона остается в силе. В целом после перехода можно пользоваться гайдами для ангарного флеша, но с рядом некоторых моментов, которые нужно учитывать. Постараюсь привести все, которые мне известны, хотя если честно ангарными интерфейсами я никогда не занимался толком (так пытался одно время окно создать, изучал примеры), из-за чего точное сравнение мне будет сделать сложновато, но в настоящее время большинство боевых и ангарных классов используют общие классы-прототипы, а значит и поведение у них примерно сходное.
1. Так же как и ангарная флешка, боевая должна содержать основным классом (классом документа) класс, наследующий класс View. Точный адрес уже сходу не вспомню, но вы без труда его отыщете сами, от него тянутся все флешки, которые грузятся как вьюшки. Это требование лоадера, иное он может просто не пропустить.
2. Так же как и ангарная, боевая грузится через app.loadView(), так же, как и для ангарной нужно прописывать сеттинги, а потом грузить по алиасу.
3. Не знаю как ангарная, но боевая флешка может иметь на борту компоненты, которые для AS класса являются child`ами, а для питона - компонент-объектами. Компонент питона это почти как питон-вьюшка, только наследуется от более низкоуровневого класса компонента, и так же как вьюшка забивается в сеттинги по алиасу, но к компоненту не аттачится флешка, поскольку это часть вьюшки. И запрос на регистрацию (именно регистрацию, поскольку флеш-часть грузится вьюшкой) отправляет обычно уже флешка на питон, при этом на питоне к объекту вьюшки аттачится в компоненты объект компонента - экземпляр класса, прописанный в сеттингах. Использование компонентов позволяет использовать классы-обработчики для упрощения управления флешкой. К примеру, BattlePage - это View, а DamagePanel или Minimap, скажем - компонент BattlePage. Найти классы этих компонентов на питоне и флеше вы можете самостоятельно, большого труда поиск по строке в файле думаю не составит. На вопрос о том, где брать исходники питона и флеша - декомпилить клиент. При желании можно уже найти разобранные, например в репо XVM.
4. Это особенности контейнеров. В боевом интерфейсе тру вьюшка (ViewType.View) может быть только одна, и это BattlePage. Не надо пытаться прописать вьюшку тру вьюшкой в сеттингах, ибо она просто перегрузит весь боевой интерфейс и будет... ну вы поняли уже наверное, пустой экран и ваша вьюшка.
4.1. Как решение проблемы можно использовать ViewType.Window, но тут всплывает другой геморрой в виде того, что при наличие вьюшки в контейнере окон BattlePage не может получить фокус и тупо не работает чат и все остальное, что работает по контролу (зажатие клавиши Ctrl + мышка), ибо этот контейнер изначально предназначен для диалогов, которые перекрывают интерфейс, вроде диалога на выход в конце реплея. Поэтому вьюшку нужно сразу после загрузки выгрузить, например поставить ивент с дестроем на ее попадание в контейнер.
4.2. Из предыдущего пункта вытекает другая проблема - это то, что при деструкте флешки улетает все ее содержимое... Но можно приаттачить ваш интерфейс к BattlePage, тогда при деструкте вашей флешки-лоадера оно никуда уже не денется. Все заюзанные где-то классы не деструктятся до тех пор, пока на них что-то ссылается.
5. Не нужно вешать загрузку вьюшки на регистрацию компонентов, типа дамаг панели и тому подобных вещей - при воспроизведении реплея может произойти релоад питон-компонентов, а как следствие их повторная регистрация, а вы получите пару лишних фантомов вашей панельки на флеше. Загрузку можно ставить постхуком (хук должен вызываться после оригинала) на BattlePage._populate(), что конечно не так удобно как постановка ивента на регистрацию компонента, но по крайней мере нормально работает, вроде как, толком еще этот момент не оттестил, ибо данный баг нашел совсем недавно. Более козырного варианта найти пока не удалось.
6. Из последних нескольких пунктов вы наверное уже поняли, что не все так просто и гладко, я описываю только особенности, еще некоторый напряг может создать сама процедура создания классов, но в принципе у меня нет времени и желания полностью копировать то, что пишут люди для ангара в гайдах и патчить, да и смысла особого не вижу, т.к. ИМХО в боевом интерфейсе делать нечего, пока не разберетесь с ангарным, т.к. там все несколько проще, и нет подобных заборов, обход которых те еще танцы, да и стабильность в боевом интерфейсе куда важнее, чем в ангаре.
7. Точнее это продолжение предыдущего пункта. Есть еще вариант подгрузить флешку как требуемую либу, просто хукнув на return метод, который возвращает их список. Минусы весьма очевидны - создать свой интерфейс через нее не получится, поскольку в момент загрузки либ не существует вообще ничего на флеше. Максимум - немного пропатчить классы через прототипы и добавить свой класс, например для использования скажем в миникарте или маркерах техники. Но это отдельная тема, не буду тут ее расписывать, иначе гайд будет размером с... ну думаю подставите слово сами. Хотя в принципе есть workaround в виде дописывания метода к классу через прототипы, который создаст нужный child для экземпляра класса, к которому он приаттачен... Но это тоже та еще тема, требующая весьма тщательного выкуривания документации на AS3 по части прототипов и того, как они работают. Вообще на питоне по части патчинга все в разы проще, на AS3 нельзя просто так взять и хукнуть что-нибудь. Нельзя поменять класс на свой, нельзя хукнуть метод класса. Хотя может конечно и можно запилить какой-нить финт через те же прототипы, но опять же, документацию придется неслабо так покурить. Вообще в AS не стоит лезть тем, кто не готов убить весьма приличное количество времени на то, чтобы разобраться с тем, что и как работает. Если вам нужна просто панелька - возьмите тот же BattleFlash Gambiter`а и сделайте через него, сэкономите кучу времени и сил. Что многие питонисты и делают. Как говорится, чем проще ваш код, тем проще его адаптировать после очередного хорошего патча... Но не всегда готовое решение подходит, но оно подходит в 99% всем начинающим мододелам.
8. Вообще для меня конечно после того как я сделал свою флешку для AAS со всей обвязкой и подтянул свои маркеры на миникарту (обнова для MGM с кастомной графикой, на момент редактирования поста была на стадии альфа-теста) это не кажется прям таким уж и сложным, но на раскуривание всего этого ушло весьма приличное количество времени. Как живой пример можно взять ту же AAS (все ссылки в подписи), конкретно там все по части GUI вынесено в отдельную папку gui, часть еще есть в хуках. В AAS используется методика с вьюшкой-лоадером. В MGM используется методика загрузки через подстановку флешки как библиотеки. Да, MGM - это модификация Minimap Gun Markers, AAS - Advanced Aiming System, ссылки на темы у меня в подписи, но смотреть нужно на гитхабе. К сожалению, написание и поддержка (наша с вами любимая картошка любит периодически все менять, в результате чего ваш код с таким же успехом будет отваливаться) рабочего кода в качестве туториала не такая простая задача, учитывая что у меня в силу загруженности IRL не так уж и много свободного времени. Но ответить на вопросы и помочь с чем-то в пределах данной темы в принципе вполне возможно.
9. С классами в движке игры (конкретно AS3) тема вообще весьма интересная. Они не перегружаются. Т.е. кто первый загрузил, того и тапки. Поэтому чтобы не крашить игру нужно подключать классы WG как External Library. В принципе готовую swc с классами WG, точнее того, что от них осталось после декомпилятора, а потом опять компилятора можно взять в найтликах XFW, это файл wg_battle.swc. Цепляете его external_lib и будет вам счастье - можно спокойно наследовать классы WG, но в вашу флешку они включаться не будут. Что же касается примеров - все довольно сильно зависит от среды, в которой все делается. Кто-то использует FD, он же FlashDevelop, например разрабы XVM, а кто-то предпочитает что-то типа Adobe Flash или Adobe Animate (не работал с последней и вообще не уверен, что ее упоминание тут вообще в тему). В них используются несколько разные подходы, я даже вроде уже и писал об этом тут, и код будет несколько отличаться. Да и в разделах по ангарному флешу думаю уже не один раз писали об этом.
10. Если у кого-то все-же найдутся достаточные знания, силы, время и желание написать хороший рабочий бланк-пример для новых боевых флешек - могу без проблем разместить ссылку на пост в шапке. Цель данного гайда больше состоит в том, чтобы объяснить теорию происходящих процессов, чтобы было проще самостоятельно разрабатывать интерфейс, а не копипастить примеры.
11. Да и вся эта тема с переходом на AS3 и систему с вьшками и компонентами больше актуальна для "зависимых" (загружаемых через специальный код-лоадер WG) флешек, рутовые по идее особо затронуть не должно было. Но чтобы использовать их (рутовые), нужно иметь весьма весомые на это причины, т.к. в большинстве случаев вполне хватает обычных "зависимых".

"Основы коммуникации Python и ActionScript"

В настоящее время в WoT доступно два метода коммуникации: ExternalInterface (для которого GameDelegate по сути является оберткой) и DAAPI. Первый имеет неплохую документацию, которою можно без особых сложностей нагуглить, найти же что-то на второй мне не удалось, разве что попалась на глаза статья на хабре. В ней довольно неплохо описана структура взаимодействия Python и ActionScript, поэтому я не буду особо концентрировать внимание на этом моменте. Оба метода коммуникации реализованы на уровне движка, поэтому количество кода, необходимого для их использования, минимально. В то же время это значительно усложняет процесс отладки, поскольку никаких отладчиков и подробных логов там нет. Так что вам придется в большей степени полагаться на интуицию, чем на логи. Отмечу, что ошибки в DAAPI могут вызывать некорректное завершение приложения, по простонародному - вылет. Так что будьте аккуратны и осторожны, если не хотите потом полдня искать элементарную ошибку в коде. Также не стоит забывать одно простое правило: нужно уметь не только хорошо строить, но и правильно разрушать. Некорректно уничтоженный объект может вызывать ошибки, порой даже критические, а также расходует ресурсы.

"Отладка Flash и логи ActionScript"

Для вывода логов ActionScript используется класс net.wargaming.utils.DebugUtils. Этот класс при помощи ExternalInterface передает логи в Python, которые затем выводятся в python.log при помощи модуля debug_utils. Для вывода логов GUI может потребоваться снизить LogLevel Python модуля.
Создание, добавление и удаление а также уничтожение Flash объектов не моментальные операции. Будьте осторожны с последовательным выполнением кода из категории управления. Методы могут вернуть результат до фактического окончания процесса, а запросив выполнение следующей операции можно словить ошибку, или даже вылет.

ExternalInterface

Начну, пожалуй, с самого простого и самого старого из методов - ExternalInterface. ExternalInterface реализует принцип "один расшарил - другой заюзал". Т.е. одна из сторон делает доступной для вызова другой стороной некоторую функцию. Другая сторона ее соответственно вызывает.
В WoT ExternalInterface напрямую не используется, для него существует обертка. Со стороны ActionScript это класс gfx.io.GameDelegate, со стороны Python управление идет через экземпляр класса gui.Scaleform.Flash.Flash. Соответственно, все вызовы и alias'ы работают относительно корневого Flash (см. Python часть). Преобразование численных и строковых типов происходит автоматически, для передачи других типов потребуется сериализация. Обертка GameDelegate не позволяет использовать возврат (return) функций. Использование ExternalInterface напрямую может потребовать патчей класса gui.Scaleform.Flash.Flash, что при выполнении неопытными программистами может вызывать критические ошибки, поэтому не рекомендуется.

ActionScript
import gfx.io.GameDelegate; // Класс-обертка ExternalInterface

// ActionScript не требует определения типов аргументов, но в данном случае это все же рекомендуется сделать
function as_externalInterfaceMethod(number:Number, string:String)
{
	// Эта функция будет вызываться из Python. Для примера я выполняю обратный вызов, вы же можете написать здесь нужный вам код.
	GameDelegate.call("DemoBattleFlash.py_externalInterfaceMethod", [number, string]);
	// Каждая функция, вызываемая через ExternalInterface должна иметь свое уникальное кодовое имя (alias). Аргументы - alias, argument list.
}

// Сделаем функцию доступной из Python.
GameDelegate.addCallBack("DemoBattleFlash.as_externalInterfaceMethod", this, "as_externalInterfaceMethod");
// Аргументы: alias, object, method name. Используемая функция может быть методом какого-либо объекта.

// Данный код может быть расположен в любом месте, например в первом кадре.
Python
# Определяем функцию
def py_externalInterfaceMethod(callID, number, string):
	# В данном примере я просто вывожу строковое представление полученных аргументов, вы можете написать здесь нужный вам код
	print repr(number), repr(string)

# Корневая Flash

# Делаем функцию доступной из ActionScript. Это нужно сделать до того, как противоположная сторона попытается ее вызвать.
demoBattleFlash.addExternalCallback("DemoBattleFlash.py_externalInterfaceMethod", py_externalInterfaceMethod)
# Аргументы: alias, function
# demoBattleFlash является экземпляром gui.Scaleform.Flash.Flash. Иными словами, наша Flash является корневой.

# ActionScript уже расшарил нужную нам функцию при инициализации. Поэтому мы можем вызвать ее.
demoBattleFlash.call("DemoBattleFlash.as_externalInterfaceMethod", [10, '1234'])
# Аргументы: alias, argument list.

# Перед уничтожением объекта необходимо удалить ссылку на нашу функцию.
demoBattleFlash.removeExternalCallback("DemoBattleFlash.py_externalInterfaceMethod", py_externalInterfaceMethod)
# Это необходимо делать в Python, поскольку объект существует, пока на него есть хотя бы одна "жесткая" ссылка.
# Игнорирование данного правила и неаккуратное обращение с объектами может быть причиной серьезных багов.

# Вложенная Flash

battleWindow.addExternalCallback("DemoBattleFlash.py_externalInterfaceMethod", py_externalInterfaceMethod)
battleWindow.call("DemoBattleFlash.as_externalInterfaceMethod", [10, '1234'])
battleWindow.removeExternalCallback("DemoBattleFlash.py_externalInterfaceMethod", py_externalInterfaceMethod)
# Корневой Flash (экземпляром gui.Scaleform.Flash.Flash) в данном случае является battleWindow, Python сторона battle.swf.
Плюсы ExternalInterface:
  • Простота.
  • Стабильность, простая отладка взаимодействия.
  • Не требуется класс, работа ведется с функциями.
Минусы ExternalInterface:
  • У вас нет прямого доступа к объектам. Для совершения любого действия нужна специфичная функция, что усложняет отладку компонентов.
  • Обертка GameDelegate не поддерживает словари и списки. Для их передачи требуется ручная сериализация. При большом количестве аргументов функции создает некоторые неудобства.
  • Обертка GameDelegate не поддерживает возврат (return).
  • Вызов через корневую Flash.

"Direct Access API (DAAPI)"

DAAPI является более сложным алгоритмом взаимодействия. Здесь реализуется т.н. прямой доступ, т.е. вы можете работать с ActionScript объектом, как будто это обычный Python объект. Правда, с некоторыми ограничениями. Тем не менее, используя данный метод взаимодействия, вы можете изменять из Python любые свойства объектов ActionScript, а также вызывать различные методы. Преобразование численных и строковых типов происходит автоматически, кроме того, можно передавать списки и кортежи (массивы). Для передачи других типов требуется ручная сериализация. Подробнее разобраться с алгоритмом работы DAAPI можно ознакомившись со статьей на хабре (ссылка выше). Для реализации DAAPI требуется пара Python и ActionScript классов, экземпляры которых будут впоследствии связаны между собой.
Насколько я понимаю, DAAPI может базироваться на любых объектах (вы можете наследовать любой класс), и абсолютно не обязательно использовать главный MovieClip документа, хотя все же рекомендуется. В случае использования AS2 может потребоваться перенести "мостик" DAAPI на какой-то другой объект, поскольку в AS2 класс документа невозможно задать нормальным образом, что вполне нормально выполняется на AS3. Костыли из серии замены прототипа главного MovieClip документа и ручного инициализирования я не тестировал, однако это абсолютно не значит, что проблему с классом документа AS2 не получится решить подобным образом. Тестируйте самостоятельно.
Вы можете попробовать на свой страх и риск использовать AS3 для боевого интерфейса, но я не уверен, что оно будет работать, в то же время не вижу причин для утверждения обратного.
Здесь стоит упомянуть, что объекты ActionScript могут создаваться двумя способами. Один из них известен пользователям Adobe (Macromedia) Flash. Вы создаете объект в редакторе, затем назначаете ему класс. Второй способ - это создание объекта с помощью конструктора, т.е. вы в ActionScript создаете каждый компонент а затем добавляете его к уже существующему. В зависимости от используемого способа классы будут несколько отличаться. В любом случае, наследуя какой-либо класс нужно объявить все свойства, которые вы используете и которые при этом не объявлены в наследуемом классе. В данном случае, нужно объявить потомков. Если вы используете объект, созданный в редакторе, вы оставляете эти свойства неопределенными.
Несмотря на то, что Flash имеет размеры документа, вы можете перемещать ваши объекты по всему экрану. Исходя из этого стоит выбирать размеры документа опираясь на размеры ваших компонентов, а не на размеры экрана.

ActionScript
import net.wargaming.utils.DebugUtils; // Необходимо для передачи логов.

class net.GPCracker.DemoBattleFlash.BaseClass extends MovieClip
{
	// DAAPI в AS2 требуется изменение свойств методов, которые будут перегружены.
	var DAAPIMethods:Array = ["py_daapiMethod"];

	// Необходимо определить потомков, иначе мы не сможем получить к ним доступ.
	var textField:TextField;

	function BaseClass()
	{
		super(); // Мы наследуем класс, поэтому не забываем про super, перегружая методы

		// Разрешаем перегрузку ActionScript методов Python методами. Это относится только к AS2.
		_global.ASSetPropFlags(this.__proto__, this.DAAPIMethods, 6, 1);

		// Поскольку по умолчанию Flash растягивается на весь экран, можно использовать подобный код для сохранения размеров элементов.
		// Это документированные элементы ActionScript, гугл без проблем находит документацию.
		Stage.scaleMode = "noScale";
		Stage.align = "TL";

		return;
	}
	// Это метод-заглушка, который будет перегружен Python методом при инициализации DAAPI. Его можно также определить как
	// var py_daapiMethod:Function;
	function py_daapiMethod(number:Number, string:String, array:Array):Number
	{
		// Метод-заглушка никогда не будет вызван, если DAAPI инициализировано корректно.
		// В противном случае это необходимо записать в лог.
		DebugUtils.LOG_WARNING("Python did not override net.GPCracker.DemoBattleFlash.BaseClass.py_daapiMethod flash method.");
		return;
	}
	// Методы DAAPI могут возвращать какое-либо значение или не возвращать ничего.
	// А это уже ActionScript метод, который будет вызываться из Python через DAAPI.
	// Он ничем не отличается от обычного ActionScript метода.
	// Префиксы py_ и as_ нужны исключительно для визуального определения назначения методов.
	function as_daapiMethod(number:Number, string:String, array:Array):Number
	{
		// Здесь вы можете написать свой код. Я буду использовать обратный вызов.
		DebugUtils.LOG_WARNING("as_daapiMethod was called");
		return number + string.length + array.length + this.py_daapiMethod(14, "abc", [1, 4]);
	}
}
Python (Корневая Flash)
import GUI
# BigWorld GUI необходим для добавления контейнера на экран.

from gui.Scaleform.Flash import Flash
# Все корневые Flash наследуют этот класс

class DemoBattleFlash(Flash):
	def __init__(self):
		# Поскольку мы наследуем класс, не забываем про super
		super(DemoBattleFlash, self).__init__('DemoBattleFlash.swf', path='.')
		# Пути в gui.Scaleform.Flash.Flash реализованы весьма криво, поэтому танцы с бубном и точкой, чтобы загрузить флешку из корневой директории
		# Походу писали индусы за еду.
		# По умолчанию gui/scaleform/*.swf
		# Корневая директория менеджера ресурсов - это пути поиска /res_mods/<patch>, /res, /res_bw

		# Здесь component это BigWorld GUI контейнер, а movie - Scaleform movie.

		self.movie.backgroundAlpha = 0.0
		# Сделаем фон прозрачным.

		self.component.focus = False
		self.component.heightMode = 'PIXEL'
		self.component.widthMode = 'PIXEL'
		self.component.size = (200, 50)
		# Документированные параметры BigWorld. Читайте соответствующую документацию.
		# Последний параметр устанавливается равным размеру Flash документа.

		self.afterCreate()
		# Это нужно выполнить как минимум для правильной работы логов, а может и еще для чего.

		return

	def populate(self):
		# Получаем доступ к Flash объекту.
		self.__flashObject = self.getMember('_root._level0.baseElement')
		# Вообще DAAPI обычно устанавливается на главный MovieClip. Но AS2 не имеет класса документа, поэтому перенес на другой объект.
		# Тем не менее, через _root._level0 можно получить доступ к функциям из 1 кадра или экземпляру класса документа.
		# Выше я прокомментировал данную ситуацию.

		# Устанавливаем объект, предоставляющий методы для перегрузки заглушек ActionScript.
		self.__flashObject.script = self
		# По сути можно использовать любой объект, имеющий необходимые методы.

		# Выводим BigWorld GUI контейнер на экран.
		GUI.addRoot(self.component)

		return

	@property
	def flashObject(self):
		#	Вообще-то Flash объект - это приватная сущность, и давать к нему доступ вне класса не есть хорошо.
		#	Целесообразнее использовать функции-обертки. Однако в процессе отладки вполне может пригодиться.
		return self.__flashObject

	def destroy(self):
		# Этот метод необходимо вызвать перед фактическим удалением ссылки на объект. Иначе объект не будет удален.
		# Вспоминаем пункт про аккуратность.

		# Удаляем контейнер  с экрана.
		GUI.delRoot(self.component)

		# Сбрасываем скрипт, удаляем ссылку на объект.
		self.__flashObject.script = None

		# Освобождаем Flash объект.
		self.__flashObject = None

		return

	def __del__(self):
		# Этот метод вызывается при фактическом удалении объекта. Читаем документацию к Python.
		# Совершаем противоположные __init__ действия.
		self.beforeDelete()

		# Не забываем про super
		super(DemoBattleFlash, self).__del__()

		return

	def py_daapiMethod(self, number, string, array):
		# Поскольку мы используем экземпляр данного класса как перегрузку для заглушек, определяем методы, которые будут перегружены.
		# Вы можете написать здесь свой код, я для примера просто верну сумму аргументов.
		import debug_utils
		debug_utils.LOG_WARNING("py_daapiMethod was called.")
		return number + len(string) + len(array)

	def as_daapiMethod_wrapper(self, number, string, array):
		# Функция-обертка. Не стоит давать внешним модулям доступ к flashObject.
		return self.__flashObject.as_daapiMethod(number, string, array)

	# Префиксы py_ и as_ просто показывают назначение методов.
Python (управление корневой Flash)
# Создаем объект
demoBattleFlash = DemoBattleFlash()
demoBattleFlash.populate()
# Меняем свойства
demoBattleFlash.flashObject._x = 500
demoBattleFlash.flashObject._y = 500
# Получаем значения атрибутов
print demoBattleFlash.flashObject.textField.text
# Вызываем метод, выводим результат
print demoBattleFlash.as_daapiMethod_wrapper(4, 'abcd', (1, 2))
# Уничтожаем флешку
demoBattleFlash.destroy()
demoBattleFlash = None
Python (Вложенная Flash)
# Вложенные Flash не требуется наследовать от gui.Scaleform.Flash.Flash
# По сути вложенная Flash является частью боевого интерфейса.

class DemoBattleFlash(object):
	def __init__(self, parentUI):
		# сохраняем слабую ссылку (proxy) на корневую Flash.
		# Она требуется для выполнения некоторых операций, например для вызова через ExternalInterface.
		self.__parentUI = parentUI
		return

	def populate(self):
		self.__flashObject = self.__parentUI.getMember('_level0.demoBattleFlash.baseElement')
		self.__flashObject.script = self
		return

	@property
	def flashObject(self):
		return self.__flashObject

	def destroy(self):
		self.__flashObject.script = None
		self.__flashObject = None
		return

	def __del__(self):
		return

	def py_daapiMethod(self, number, string, array):
		import debug_utils
		debug_utils.LOG_WARNING("py_daapiMethod was called.")
		return number + len(string) + len(array)

	def as_daapiMethod_wrapper(self, number, string, array):
		return self.__flashObject.as_daapiMethod(number, string, array)

# Методы populate и destroy не добавляют Flash на экран, а всего лишь получают и освобождают ссылку на Flash объект и инициализируют DAAPI.
# На свой страх и риск вы можете перенести в них соответствующий код, но не забывайте, что функция может вернуть результат раньше, чем закончится процесс.
# Используйте это очень осторожно.
Python (управление вложенной Flash)
# Загружаем Flash
from gui.app_loader import g_appLoader
battleWindow = g_appLoader.getDefBattleApp()
# Flash появляется на экране сразу после загрузки
battleWindow.getRoot().loadSwf('../../DemoBattleFlash.swf', 'demoBattleFlash', None)
# Функция может вернуть результат до окончания процесса.
# Инициализируем Python часть.
battleWindow.demoBattleFlash = DemoBattleFlash(battleWindow.proxy)
battleWindow.demoBattleFlash.populate()
# Выполняем те же самые действия
battleWindow.demoBattleFlash.flashObject._x = 500
battleWindow.demoBattleFlash.flashObject._y = 500
print battleWindow.demoBattleFlash.flashObject.textField.text
print battleWindow.demoBattleFlash.as_daapiMethod_wrapper(4, 'abcd', (1, 2))
# Уничтожаем Python часть
battleWindow.demoBattleFlash.destroy()
battleWindow.demoBattleFlash = None
# Удаляем Flash объект
battleWindow.getRoot().demoBattleFlash.removeMovieClip()
battleWindow.getRoot().demoBattleFlash = None
Плюсы Direct Access API:
  • Полный контроль над объектом со стороны Python.
  • Алгоритм основан на классах. Эксперты одобряют.
  • Корректно работает с массивами.
  • Может возвращать результат.
  • Нет прямой зависимости от корневой Flash.
Минусы Direct Access API:
  • Достаточно сложен в понимании и отладке для "чайников".
  • Ошибки могут быть критичными и вызывать вылеты. Необходимо работать очень аккуратно.
  • Работа с классами. "Чайники" могут закипеть.

"Видео по теме"


Исходники а также пример собранной Flash. Комментарии на английском.
Flash Interaction.zip
Автор выражает благодарность всем мододелам, кто прямо или косвенно помог в написании данного гайда. Edited by GPCracker
  • Upvote 16

Share this post


Link to post

Short link
Share on other sites

Минусы Direct Access API:

К минусам DAAPI можно добавить то, что он довольно тормозной.

Лучше им не увлекаться.

Share this post


Link to post

Short link
Share on other sites

К минусам DAAPI можно добавить то, что он довольно тормозной.

Не думаю, что ExternalInterface будет работать значительно быстрее. Хотя так может иногда казаться, если функция возвращает результат до окончания процесса. Тем не менее, GUI прицелов обновляется с интервалом около 0,03 секунды, ЕМНИП. Так что в разумных пределах все работает нормально.

Share this post


Link to post

Short link
Share on other sites

А где мона на пальцах почитать как работать с этими swf из чего состоят почему, зачем, как работает и как править редактировать?

Share this post


Link to post

Short link
Share on other sites

А где мона на пальцах почитать как работать с этими swf из чего состоят почему, зачем, как работает и как править редактировать?

То, что я описал в гайде, относится к WoT, возможно также и к WoWp, WoWs. Все остальное - самый обычный флеш, на который гайдов и инете хоть вагонами грузи. Я не слышал про какие-либо ограничения Scaleform, хотя правда иногда приходится потанцевать с бубном, чтобы все корректно работало.

А вообще для создания используются FlashDevelop, Macromedia Flash 8 или любой Adobe вполне спокойно тянет AS2, для AS3 нужно брать Adobe поновее. Вообще инструментов море, что-то прямо очень специфичное для сборки под WoT не требуется.

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

Share this post


Link to post

Short link
Share on other sites

 

 

# Flash появляется на экране сразу после загрузки battleWindow.getRoot().loadSwf('../../DemoBattleFlash.swf', 'demoBattleFlash', None)
 

 

А как узнать, что оно загрузилось?

Share this post


Link to post

Short link
Share on other sites

А как узнать, что оно загрузилось?

У функции loadSwf, определенной в первом фрейме battle.swf, есть в аргументах колобок на загрузку, ЕМНИП. Декомпильни, глянь код. Насчет подкинуть Py функцию - не знаю конечно, но можно попробовать, возможно и покатит. Хотя вряд ли сериализация справится с функцией... DAAPI у них в этом плане немного кривое. Нельзя к примеру получить ссылку DAAPI на AS объект, а потом отдать ее в качестве аргумента AS функции через DAAPI. Там еще кучу косяков найти можно. Вобщем, картоха как всегда, одни дыры, костыли и велосипеды. Но их видно такое вполне устраивает.

Share this post


Link to post

Short link
Share on other sites

 

Подскажи пожалуйста, а в 0.9.15.1 на что заменить getRoot().loadSwf? С остальным надеюсь проблем не возникнет, главное найти новый способ загрузки вложенной флешки.

Share this post


Link to post

Short link
Share on other sites

Подскажи пожалуйста, а в 0.9.15.1 на что заменить getRoot().loadSwf? С остальным надеюсь проблем не возникнет, главное найти новый способ загрузки вложенной флешки.

Эта тема потеряла актуальность. Edited by ShadowHunterRUS

Share this post


Link to post

Short link
Share on other sites

Эта тема потеряла актуальность.

Ну почему же...

Корневая флешка грузится и в 0.9.15.1 по аналогичному сценарию с небольшими изменениями в python'е и созданием флешки на as3.

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

Share this post


Link to post

Short link
Share on other sites

 

 

Мой невысокий уровень познаний в программировании пока не позволяет найти новый способ , но уверен, что

 

Не высокие уровни познаний часто порождают ложные уверенности =)

Share this post


Link to post

Short link
Share on other sites

Не высокие уровни познаний часто порождают ложные уверенности =)

Это да. :-) Но...

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

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

Edited by Tester

Share this post


Link to post

Short link
Share on other sites

Это да. :-) Но...

Сейчас реализована костыльная подгрузка через встроенную в главную флешку battle.swf функцию loadSwf. Нет единого сценария для флешек боевого интерфейса. В 0.9.15.1 добавили алгоритм их загрузки, который прослеживается при ковырянии новых исходников. Можно увидеть отталкиваясь, например, от addViewComponents (сейчас могу ошибаться в точности имени функции). Вот и возникла ложная уверенность, что по этому алгоритму можно загрузить и свои флешки.

from gui.app_loader.loader import g_appLoader
from gui.Scaleform.framework.entities.View import View
from gui.Scaleform.framework import g_entitiesFactories, ViewSettings, ViewTypes, ScopeTemplates
from gui.app_loader.settings import APP_NAME_SPACE
from gui.shared import events, g_eventBus

class NewFragCorrelationBar(View):
    def __init__(self):
        super(NewFragCorrelationBar, self).__init__()

g_entitiesFactories.addSettings(
        ViewSettings(
        'NewFragCorrelationBar',
        NewFragCorrelationBar,
        'NewFragCorrelationBar.swf',
        ViewTypes.WINDOW,
        None,
        ScopeTemplates.DEFAULT_SCOPE
    )
)

def onAppInitialized(event):
    if event.ns == APP_NAME_SPACE.SF_BATTLE:
        app = g_appLoader.getApp(event.ns)
        if app is not None:
            app.loadView('NewFragCorrelationBar')

g_eventBus.addListener(events.AppLifeCycleEvent.INITIALIZED, onAppInitialized)
  • Upvote 1

Share this post


Link to post

Short link
Share on other sites
, мне кажется, или это то же самое, что раньше делали для ангара?

Share this post


Link to post

Short link
Share on other sites

 

from gui.app_loader.loader import g_appLoader
from gui.Scaleform.framework.entities.View import View
from gui.Scaleform.framework import g_entitiesFactories, ViewSettings, ViewTypes, ScopeTemplates
from gui.app_loader.settings import APP_NAME_SPACE
from gui.shared import events, g_eventBus

class NewFragCorrelationBar(View):
    def __init__(self):
        super(NewFragCorrelationBar, self).__init__()

g_entitiesFactories.addSettings(
        ViewSettings(
        'NewFragCorrelationBar',
        NewFragCorrelationBar,
        'NewFragCorrelationBar.swf',
        ViewTypes.WINDOW,
        None,
        ScopeTemplates.DEFAULT_SCOPE
    )
)

def onAppInitialized(event):
    if event.ns == APP_NAME_SPACE.SF_BATTLE:
        app = g_appLoader.getApp(event.ns)
        if app is not None:
            app.loadView('NewFragCorrelationBar')

g_eventBus.addListener(events.AppLifeCycleEvent.INITIALIZED, onAppInitialized)
 

 

Спасибо большое, попробую позже.

 

, мне кажется, или это то же самое, что раньше делали для ангара?

Ага, уже тоже сравнил, принцип тот же.

Share this post


Link to post

Short link
Share on other sites

Ага, уже тоже сравнил, принцип тот же.

М.б., если будет время, разберу DAAPI на новых примерах. Там все в принципе то же самое, принципы те же, подходы тоже, различия незначительные в коде будут только. Местами даже проще будет. Edited by GPCracker

Share this post


Link to post

Short link
Share on other sites

Местами даже проще будет.

Проще только с костылями. Красиво только XVM сделан. Edited by ShadowHunterRUS

Share this post


Link to post

Short link
Share on other sites

Проще только с костылями.

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

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

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

А не просто слить кусок кода в стиле "разбирайтесь сами что к чему и для чего".

 

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

Но прикол-то в том, что реализация мода на основе XFW не всегда оправдана.

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

Edited by GPCracker

Share this post


Link to post

Short link
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...