Название MagicObject, имхо, явно лучше E4XMap и подобных названий.
Intro.
В играх я частенько использую машины состояний и сложные структуры данных (коллекции, карты..) , сильно упрощающие разработку. Что актуально, кстати, не только для игр, но для риа вообще.
Сегодня хочу поделиться простенькой реализацией e4x в Объекте, где основной фишкой является расширение нативного e4x, который не позволяет callDescendants.
Например вот так:
object..method(args)
Зачем это нужно?
Допустим, дано:
- StateMachine и сложное дерево состояний;
- Анимация игрового объекта завязана на состояния, точнее, на смены состояний;
- Есть VO (ValueObjects) и есть рендеры, которые визуализируют изменения в VO;
- Рендеры имеют сложную иерархию, уровни вложенности.
Как происходит изменение состояния и отображение изменений при использовании MagicObject:
- Изменяем состояние в StateMachine;
- При удачном изменении состояния обновляем данные в VO;
- Вызываем обновление во всех рендерах внутри игрового уровня:
Метод update() вызовется последовательно у всех объектов, имеющих этот метод.
Поясню как работает:
magic..update достаёт со всех уровней вложенности значения всех полей с именем update. Если там есть экземпляры класса Function, то всё это хозяйство оборачивается noname-фукцией, а ей задаются все свойства массива (для итеративного пробега) и пары, типа ключ-значение, где значение - каждое найденое поле с именем update. На примере:
const result:Object = magic..update;
// можно вызвать:
result(); // or result(args);// можно получить определённый элемент:for(var key:Stringin magic)trace('in', magic, ':', key, '=', magic[key]),
// или вызвать:
magic[key]isFunction&& magic[key]();
History tracking:
const magic:MagicObject = new MagicObject();
magic.x = 0;
magic.x = 1;
// вернёт все изменения на первом уровней вложенности:trace(magic.changes); // :Vector.<MagicNode>// вернёт все изменения на всех уровнях вложенности:trace(magic..changes); // [:Vector.<MagicNode>,..] или Vector.<MagicNode> если элемент один.// чистим историю изменений:
magic.clearChanges();
// or magic..clearChanges();
Коротко о главном:
В комбинации AIR + iOS + {желание работать с текстом, клавой} есть одна большая проблема - это и есть большая проблема.
Нет событий от экранной клавиатуры (за некоторым исключением);
Нет раздельных стилей в режиме редактирования;
Нет обновлений фокуса и позиции мыши/пальца в режиме редактирования;
Ещё много чего нет..
Предлагаю вам, товарищи, мой детектор статуса, типа и представления экранной клавиатуры. Только для iOS. Актуализирован для работы под iOS 5.0.
Он скажет вам:
Тип представления экранной клавиатуры
Экранная клавиатура активирована;
Экранная клавиатура деактивирована;
Сменился relatedObject;
Пользователь изменил тип представления экранной клавиатуры (сдвинул или раздвинул).
Note:
Работает с обычными текстовыми полями и с новеньким StageText.
В симуляторе не работает и не должно, ибо он крив и только лживо симулирует. Только iOS.
Сегодня Apple сообщили, что ограничения в использовании средств разработки под iOS будут сняты.
"Мы снимаем все ограничения которые касаются средств разработки используемые для создания приложений под iOS, запрещенным остается только загрузка дополнительного кода созданным приложением. Это предоставит разработчикам больше свободы в выборе инструментов, в которых они нуждаются. В то же время безопасность приложений останется на том же уровне."
Позитивная новость для геймдива:
В полку сервисов всеми любимой Adobe прибыло. Точнее, прибудет.
Adobe анонсировала открытие Flash Platform Game Technology Center - ресурса для разработчиков игр на Flash Platform.
Для начала несколько слов прелюдии.
Примерно полтора года назад я накодил простенький передзагрузчик (аналог), стартующий до инициализации главного класса приложения, и периодически его дополнял и частично переписывал. Таким образом один класс вырос в целую систему, поддерживающую модульную архитектуру, полиморфизм, и предзагрузку необходимых ассетов.
Эту разработку я частенько сам использую в больших многомодульных проектах, где есть одна или несколько библиотек (swc) и огромное количество модулей, эти библиотеки использующие; Предзагрузчик у всех модулей - один, следовательно вынесен в библиотеку, а не клонируется по всей команде разработчиков от проекта к проекту. При этом (частая ситуация) хорошо было бы, что бы все модули приложения могли работать как самостоятельно, так и внутри любого другого модуля, - т.е. полиморфизм. Вот эту ситуацию и возьму за основу.
По-сути, мое решение представляет из себя неслабое универсализированное расширение уже давно описанных метод: тут (по-русски), тут и тут - настоятельно рекомендую ознакомиться перед продолжением.
мы получаем вот такой результат:
[Схема с нашим классом]
Но все было бы чудесно, если б не одна проблема: Наш предзагрузчик понятия не имеет об имени главного класса приложения, который ему надо инстанциировать, а заниматься жестким хардкодом в большом проекте - непозволительная роскошь. Первая мысль была такова:
В каждом проекте-модуле использовать в качестве предзагрузчика субкласс того основного, что находится в библиотеке и остается неизменным. В этом субклассе мы просто переопределяем значение protected переменной MAINCLASS_NAME, ранее заведенной в основном классе и далее используем ее при инстанциировании нужного нам главного класса приложения. Но это мне вскоре надоело (неудобно) и было придумано более интересное решение:
Предзагрузчик наш остается в библиотеке, не имеет никаких субклассов (можно даже сделать его final). После загрузки всей свифки он сам найдет нужное ему имя класса в метаданных, а в голове главного класса мы просто впишем свой кастомный метатэг:
[App(className='ИмяГлавногоКласса')]
Что бы компиллер "увидел" наш метатэг, в параметры компилятора (в свойствах проекта) впишем: -load-config cfg.xml
(про параметры компилятора подробности тут)
А в конфиге мы явно укажем, что на наш метатэг стоит обратить внимание:
package{importflash.display.FrameLabel;
importflash.display.MovieClip;
importflash.events.Event;
importflash.utils.*;
import ru.kozlovskij.core.Application;
import ru.kozlovskij.preload.Preloader;
/**
* SWF metadata
*/[SWF(backgroundColor='0xFFCC00', width='500', height='500')]/**
* The frameworks must be initialized by SystemManager.
* This factoryClass will be automatically subclassed by any
* MXML applications that don't explicitly specify a different
* factoryClass.
*
* NOTE: Minimize the non-Flash classes you import here.
* Any dependencies of SystemManager have to load in frame 1,
* before the preloader, or anything else, can be displayed.
*/[Frame(factoryClass='ru.kozlovskij.preload.Preloader')]//[Frame(factoryClass='exampleClasses.MyPreloader')]/**
* Custom metadata tags
*/[App(type='module', className='Main')][copyrights(firstName='Aleksandr', lastName='Kozlovskij', url='http://Kozlovskij.ru', blog='http://FlashImp.ru')]publicclass Main extends Application
{publicfunction Main(){super();
addEventListener(Preloader.PRELOADER_DONE, preloaderDoneHandler);
}privatefunction preloaderDoneHandler(e:Event):void{//initialize...for(var i:int; i < (parentasMovieClip).currentLabels.length; ++i)trace('FrameLabel:', '[', FrameLabel((parentasMovieClip).currentLabels[i]).name,
',', FrameLabel((parentasMovieClip).currentLabels[i]).frame, ']');
(parentasMovieClip).nextFrame();
parent.addChild(new(flash.utils.getDefinitionByName('exampleClasses.CustomFrame')asClass)());
}}}
Метод preloaderDoneHandler вызывается, когда от предзагрузчика мы получаем событие об окончании загрузки всего необходимого (+ассеты?). В preloaderDoneHandler мы, удовлетворения ради, выводим в трэйс все, имеющиеся в нашем распоряжении (в родителе-предзагрузчике) фрэймы. В последних двух строках мы переходим в третий кадр и инстанциируем класс, который был загружен последним - об этом чуток ниже.
Во многих моих реализациях данного подхода я либо создавал в Main классе метод, возвращающий массив урлов к тому, что еще необходимо загрузить, что было несколько криво и не гибко, либо записывал эту информацию в метаданные, что позволяло предзагрузчику все необходимое скачать еще до инициализации главного класса, а он в свою очередь рождался "на всем готовом". Нюансы реализацию такой инициализации приложения я покажу позже, а сейчас - простой пример того самого предзагрузчика:
Preloader.as - собственно, сам прелоудер:
package ru.kozlovskij.preload
{importflash.display.DisplayObject;
importflash.display.MovieClip;
importflash.display.StageAlign;
importflash.display.StageScaleMode;
importflash.events.Event;
importflash.system.ApplicationDomain;
importflash.utils.ByteArray;
import ru.etcs.utils.ClassExplorer;
[Event(name="preloaderDone", type="ru.kozlovskij.preload.Preloader")]publicclass Preloader extends MovieClip{public static const PRELOADER_DONE:String = 'preloaderDone';
protectedvar MAINCLASS_NAME:String;
protectedvar preloaderGraphics:IPreloaderGraphics;
privatevar Main:Class;
privatevar main:DisplayObject;
protectedvar _standalone:Boolean = true;
protectedfunctionget standalone():Boolean{return _standalone;}publicfunction Preloader(preloaderGraphics:IPreloaderGraphics = null){stop();
this.preloaderGraphics = preloaderGraphics ? preloaderGraphics :new PreloaderGraphics();
if(stage)stage.scaleMode = StageScaleMode.NO_SCALE,
stage.align = StageAlign.TOP_LEFT,
stage.frameRate = 31,
createGraphics(),
stage.addEventListener(Event.RESIZE, resizeHandler);
else
_standalone = false;
addEventListener(Event.ENTER_FRAME, enterFrameHandler);
resizeHandler();
}protectedfunction createGraphics():void{addChild(preloaderGraphics asDisplayObject);
}privatefunction enterFrameHandler(e:Event):void{//percent = current / totalvar percent:Number = root.loaderInfo.bytesLoaded/root.loaderInfo.bytesTotal;
preloaderGraphics.percent = percent *100;
//trace(Math.round(100 * percent) + '%');if(framesLoaded == totalFrames){removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
//Move to the next frame when we're done//Moving to frame 1(!)nextFrame();
preinit();
}}protectedfunction resizeHandler(e:Event = null):void{
preloaderGraphics.x = stage?stage.stageWidth/2:root.width/2,
preloaderGraphics.y = stage?stage.stageHeight/2:root.height/2;
}privatefunction preinit(mainclass_name_from_metadata:String = null):void{var needThrow:Boolean = true;
MAINCLASS_NAME = mainclass_name_from_metadata ? mainclass_name_from_metadata :
MAINCLASS_NAME ? MAINCLASS_NAME :null;
MAINCLASS_NAME ?null:(needThrow = false, getMETAMainClassName());
if(needThrow && !MAINCLASS_NAME){clear();
thrownewError(this.toString()+': Main Class Definition eq null');
}var has:Boolean = ApplicationDomain.currentDomain.hasDefinition(MAINCLASS_NAME);
if(has)//if main app
Main = ApplicationDomain.currentDomain.getDefinition(MAINCLASS_NAME)asClass;
else{
has = loaderInfo.applicationDomain.hasDefinition(MAINCLASS_NAME);
if(has)//if module app
Main = loaderInfo.applicationDomain.getDefinition(MAINCLASS_NAME)asClass;
}if(has){//and instantiate our Main class
main = new Main();
//get urls, whot need to additional preloading before starting application (in next post..)init();
clear();
}elseif(needThrow){clear();
thrownewError(this.toString()+': Main Class Definition NOT FOUND');
}}privatefunctioninit():void{//finally we add main class to the DisplayList and dispatching eventaddChild(main asDisplayObject).dispatchEvent(newEvent(PRELOADER_DONE));
//stage.addChild(main as DisplayObject) - only if we make a standalone app.}publicfunctionclear():void{//killing & clearing preloaderGraphics :)
IPreloaderGraphics(removeChild(preloaderGraphics asDisplayObject)).clear();
standalone ?(stage.removeEventListener(Event.RESIZE, resizeHandler), stage.removeChild(this)):null;
}//-----------------------------------------------------//privatefunction getMETAMainClassName():void{var bytes:ByteArray = newByteArray();
bytes.objectEncoding = 3;
bytes.writeBytes(loaderInfo.bytes);
bytes.position = 0;
var explorer:ClassExplorer = new ClassExplorer();
explorer.loadBytes(bytes);
explorer.addEventListener(Event.COMPLETE, explorerWorkCompleteHandler);
}privatefunction explorerWorkCompleteHandler(e:Event):void{var explorer:ClassExplorer = e.targetas ClassExplorer;
var xml:XML = explorer.getDefinitionInfo();
//trace(xml.toXMLString());var className:String;
className = xml.definition/*.(@name == 'frame2')*/.script.classTrait.metadata.(@name == 'App').arg.(@key == 'className').@value.toString();
preinit(className);
}}}
Как видно, в данной реализации я оставил возможность наследовать этот класс и переопределять значение переменной MAINCLASS_NAME. Так же в конструктор можно передать экземпляр визуализатора процесса загрузки, реализующий интерфейс IPreloaderGraphics. По умолчанию создается инстанс PreloaderGraphics - простейший субкласс DisplayObject'a, содержащий в себе текстовое поле для отображения процентов - это я тоже упростил:)
Вернемся к ru.kozlovskij.preload.Preloader. Как это все работает?
Запускается конструктор, подписываемся на событие Event.ENTER_FRAME и постоянно проверяем, не завершилась ли загрузка; Загрузка завершена, находим в метаданные и в них - имя главного класса (preinit->getMETAMainClassName->explorerWorkCompleteHandler->preinit), инициализируем главный класс, не забыв перед этим перескочить на второй кадр, - вот такая последовательность.
В демонстрируемой реализации в качестве "поисковика метадаты" я использовал ClassExplorer, за что отдельное спасибо автору.
Для наших целей пришлось немного его подправить. Не помню уже где и что исправлял - интересующимся поможет любой мержер, например, Araxis Merge, который я считаю очень достойным.
Единственный минус - это то, что при каждом добавлении нового метатэга, необходимо прописывать его в конфиге компиляции.
Теперь от третьим кадре:
Для того, чтобы в нашем рутовом классе (рутовый он только в случае, если приложение самостоятельное, не модуль), являющимся по-совместительству предзагрузчиком, при компиляции создался третий кадр, конфигурационный файл необходимо дополнить следующим образом:
Класс exampleClasses.CustomFrame будет экспортирован во фрэйм "newFrameName". Третьим этот фрэйм будет потому, что два уже есть.
По непонятной мне причине, добавлять ноды для четвертого, пятого и т.д. фрэймов - бесполезно.
Теперь смотрим в Main.as на метод preloaderDoneHandler. Последние два строки - по-сути, тоже самое, что и в ru.kozlovskij.preload::Preloader, т.е. переход на следующий кадр и инициализация соответствующего класса.
Написал VBS скриптец для пакетной компиляции .pbk файлов. Работает только под окнами.
Как работает: Принимает пачку входящих параметров - [fullUrlFile.pbk ...], выбрасывает все, что не .pbk, для каждого вызывает компилятор.
Принимает необязательные параметры:
-pb-C:\url\to\pbutil.exe
-bin-C:\url\to\
Написано кривовато, ибо в VBS я на момент написания абсолютно ничего не смыслел.
Применять эту штуку удобно в случае, если мы пишем и отлаживаем код в Pixel Bender Toolkit, а самих кернелов (.pbk) в проекте много. Приходится либо вызывать компиляцию для каждого обновленного ручками из Pixel Bender Toolkit-а, либо, если пользуемся плагином для Eclipse под названием PBDT, то сохранять каждый pbk, не, что тоже долго и неприятно.
Решение для Eclipse/FB: Просто подключаем этот vbs-скрипт как екстернал-компилятор и указываем в параметрах папку, куда следует складывать готовые .pbj. И все обновившиеся кернелы скомпилятся при следующем билде.
Конкретика:
Создаем в Project > Properties > Builders > .. новый билдер, называем его PBBCR;
Заменяем свой .externalToolBuilders\PBBCR.launch на аналог из скачанного архива;
Снова открываем Project > Properties > Builders > PBBCR > в поле Arguments первым параметром пишем/правим путь к PBBCR.vbs;
(опционально) Там же в поле Arguments пишем/правим путь к pbutil.exe (он есть в папке Pixel Bender-a начиная с 1.5.1 версии) и путь к папке, куда складывать бинарники .pbj.
Не забываем в списке билдеров Project > Properties > Builders > поставить наш PBBCR на самый верх, дабы он срабатывал первый.
Сам скрипт, пример .project, пример .launch в архиве.
Единственный нюанс: Если сами указываем output-папку и она реально не существует, ее необходимо создать самостоятельно - скрипт сам не умеет творить такие чудеса.