понедельник, 24 октября 2011 г.

ApplicationDomain Dynamic Instantiation (NResponder)

Одна из основных трудностей при разработке приложений заключается в копировании графики или доступ к загруженным из вне swf-файлам. К примеру, постройки на карте, юниты, квесты или ключевые персонажи в игре (NPC).  При повторном использовании загруженной графики необходим метод быстрого создания копии, к примеру можно использовать класс duplicateDisplayObject, написанный одним из разработчиков FP Trevor McCauley, aka senocular. Или же просто копировать нужную "часть экрана" как BitmapData


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

FP позволят создавать "экземпляры объекта" (instances) через создание копии зарегистрированного класса в массиве всех "объявленных объектов" внутри FP - ApplicationDomain, через метод getDefinition (в том числе и класса внедренной графики (embeded media) из Flash IDE).

Каждый раз когда мы загружаем сторонние данные в основную swf-ку, их объявление (имя класса, тип) регистрируются в специальном "древе" зарегистрированных объектов - ApplicationDomain. При загрузке контента классом new Loader() в параметрах метода загрузки load(request:URLRequest, context:LoaderContext) помимо адреса, откуда следует загружать данные request:URLRequest, можно указать, то где будет располагаться информация о загруженных данных и как ейо следует обрабатывать при загрузке - context:LoaderContext. Нас будет интересовать "то где будет располагаться информация о загруженных данных", за это как раз отвечает ApplicationDomain.


NResponder
Прежде всего хочу сделать небольшой отступление и предложить всем вам использовать вместо стандартного Flash Event Engine более продвинутую оболочку NResponder.




Ниже я приведу пример использования этого "ивент-движка".

1. Main Class - ApplicationDomainTest.as
Моей обычной средой для разработки является FlashDevelop, где я и начинаю с создания простого AS3.0 Project и основной класс я назову ApplicationDomainTest.as


Здесь я использую специально написанный для этого примера класс - ClassLoader (1). В котором происходит загрузка файлов. При создании экземпляра лоудера (строчка 42) loader = new ClassLoader("assets") - в конструктор передается относительный путь к папке с графикой, а в вызове метода loader.load("assets.swf") - имя загружаемого файла (строчка 62).

В качестве "событийного движка" здесь применен NResponder, также на рисунке есть небольшое описание того как он работает (2). Обработчик двух событий NResponder - функция loader_Handler принимает один параметр - params:Array, который представляет собой массив параметров переданных в "диспатче" от NResponder и обрабатывает оба события:
 - CLASS_LOADED - загрузка завершена, в качестве параметров передается массив.
 - LOAD_ERROR - загрузка прошла с ошибками, в этом случае передается событие о ошибке.

2. Flash IDE - assets.swf
В загружаемом файле всего один объект - "простенькая кнопочка" - MovieClip - mc_btn_home, которая ассоциируется с классом Button и экспортируется в первый кадр, так как этот объект не находиться на сцене, а это (Export in frame 1) позволяет внедрить объект в главный assets.swf-файл.

3. AppDomain.as
Вспомогательный класс где находятся все ApplicationDomain, в которых будут храниться "определения" о загруженных объектах.


В данном случае я создал две статические переменные: AppDomains.graphics - будет содержать отдельный ApplicationDomain для графики, а AppDomains.current - текущий ApplicationDomain для основного приложения.

4. ClassLoader.as
Теперь рассмотрим сам класс загрузчика. Буду постепенно давать комментарии к строчкам кода.


Здесь уже используется стандартный флэшовый загрузчик _loader = new Loader();

1) Строчки 36-42. Добавляем "слушателей событий", через
NResponder.addNative(        
target:Object, 
action:String,
listener:Function,
replies:uint,
delay:uint,
order:uint). 


Сам объект Loader не может быть "прослушен" на действия загрузки объектов, такие как Event.COMPLETE, LoaderEvent.PROGRESS и другие. Loader это всего лишь прокси объект, который имплементирует некоторые параметры загружаемых в него объектов DisplayObjects, он уже существует, загружен и инициализирован поэтому его нельзя "прослушать" на события.


Однако, у каждого визуального объекта (DisplayObject) есть параметр LoaderInfo (объект типа LoaderInfo, для Loader-а - LoaderInfo = contentLoaderInfo), в котором хранится откуда он был загружен, имя Loader-ра (loaderInfo.loader.name = "ClassLoader") и другие данные. Если объект не был загружен, а создавался в "реал тайме", он хранит имя основного файла и в этом случае loaderInfo.loader = null. Как раз LoaderInfo (contentLoaderInfo)  может быть "прослушан" на события, поскольку именно он содержит загружаемые данные (content) и информацию о них - bytes, bytesLoaded, bytesTotal.

Необходимые нам события это: Event.COMPLETE - завершение загрузки, IOErrorEvent.IO_ERROR - ошибка загрузки-сохранения (IO - InputOutput).

2) Строчки 49-60. Функция load принимает один параметр filename - имя файла, это сделано для будующего расширения класса, к примеру последовательной загрузке файлов из стэка.
Теперь самое интересное. Перед тем начать загрузку мы определяем ApplicationDomain для переменных класса AppDomain.graphics новый "домен", а .current - сохранит в себе ссылку на текущий (основной) домен (ApplicationDomain.currentDomain).

Строчки 55-57. Создаем контекст (LoaderContext) для loader-а. Здесь хочу рассмотреть три различных случая, которые позволят по разному создавать новые экземпляры загруженных объектов.

/* 1 */ var context:LoaderContext = new LoaderContext();
Данный случай тоже самое, что и не указывать context при вызове метода _loader.load(request). При этом Loader сам создает отдельный ApplicationDomain, в котором будут определены загруженные объекты, и доступ к этим объектам возможен только через:

 _loader.contentLoaderInfo.applicationDomain или
_loader.content.loaderInfo.applicationDomain


/* 2 */ var context:LoaderContext = new LoaderContext(false, AppDomains.current);
Второй вариант это ссылка на основной ApplicationDomain (ApplicationDomain.currentDomain). Тогда доступ к загруженный классам можно получить так:

AppDomains.current.getDefinition("ObjectClassName") as Class;
ApplicationDomain.currentDomain.getDefinition("ObjectClassName") as Class;

/* 3 */ var context:LoaderContext = new LoaderContext(false, AppDomains.graphics)
Случай с использованием заранее созданного ApplicationDomain, отделяющего загруженные объекты от объектов создаваемых в рантайме. По моему самый удобный способ сохранения загруженный данных, потому как доставать их очень удобно:

var runtimeClassRef:Class = AppDomains.graphics.getDefinition("Button") as Class;

3) Строчки 62-82. Обработчик событий загрузки данных loader_Handler принимает всего один параметр e:Event, потому как все события "флэша" расширяются от этого базового класса.
case Event.Complete. 

Если загрузка прошла хорошо, то мы отправляем сообщение о удачной загружке с помощью NResponder.dispatсh(action:String, params:Array, toTarget:*), который позволяет передать любые данные в массиве params, по событию action и привязанные к объекту toTarget. После передачи данных можно избавиться от Loader(_loader = null);

Отдельно скажу про специальную "тулсу" SWFExplorer для экспектирования (от слова expect - ожидание) внедренных классов в загруженном объекте. SWFExplorer написан одним разработчиками FP - Thibault Imbert и скачать "тулсу" можно с его сайта bytearray.org (там же можно посмотреть примеры как с ней работать).


Здесь я "разбираю" (parse) уже загруженные данные с помощью SWFExplorer, который возвращает массив с "определениями типов" всех объектов в загруженном файле. На рисунке выше представлены два варианта, с разделением на LoaderContext и без, которые выводят одно и тоже.

case (IOErrorEvent.IO_ERROR || SecurityErrorEvent.SECURITY_ERROR).
Если загрузка прошла с ошибками, то мы отправляет сообщение с событием, это позволит разделить данные от события в обработчике этого события.

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


Сначала нужно "достать" класс определяющий необходимый нам объект, сделать это можно вызовом метода getDefinition("InstanceObjectClassName") из того ApplicationDomain где были определены загружаемые данные (1,2,3), к примеру:

var runtimeClassRef:Class = AppDomains.graphics.getDefinition("Button") as Class;

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



6. Заключение
Таким образом вы можете сами выбрать удобный для себя метод создания копий объектов в FP, но по моему мнению ApplicationDomain является самым простым и понятным способом.
Спасибо за интерес. Желаю успехов!

Вы можете скачать проект отсюда

Комментариев нет:

Отправить комментарий