пятница, 23 декабря 2011 г.

Перевод документации по Starling

Представляю вашему вниманию перевод документации по фреймворку Starling (оригинал тут)

Что такое Starling?



Starling это фреймворк написанный на ActionScript 3, для работы с двухмерной графикой с использованием API Stage3D (это API доступно в средах Flash Player 11 и Adobe AIR 3). Starling в основном разработан для использования при разработке игр, но может быть использован и во многих других случаях. Starling делает возможным написание приложений, ускоренных GPU, без непосредственного взаимодействия разработчика с API Stage3D.


Почему именно Starling?



Большинство Flash-разработчиков хотят использовать аппаратное ускорение (с помощью Stage3D) без необходимости написания высокоуровневого фреймворка и погружения в низкоуровневневый API Stage3D. Starling спроектирован по образу Flash Player API и абстрагирует сложность Stage3D и делает доступным легкое и интуитивное программирование для каждого.
Очевидно, что Starling для разработчиков на ActionScript 3, особенно для тех, кто участвует в разработке 2D игр и конечно вам нужно иметь базовое понимание ActionScript 3. Благодаря своей легкой, гибкой и простой структуре, Starling может быть использован и в других случаях, например, при разработке пользовательского интерфейса. Другими словами, все спроектировано так, чтобы сделать разработку настолько интуитивной, насколько это возможно и любой Java или .Net разработчик мог бы быстро получить нужные навыки.

Философия



Интуитивность
Starling прост в освоении. Flash/Flex разработчики будут чувствовать себя как дома, т.к. заимствуется большинство догм ActionScript 3 и абстрагируется сложность низкоуровнего API Stage3D. Вместо того чтобы оперировать в разработке буферами вершин, матрицами, шейдерами и ассемблером вы будете использовать знакомые понятия, такие как список отображения DOM, событийная модель, MovieClip, Sprite, TextField и т.д.

Легкость
Starling (порода голубя) – легкая птичка во многих отношениях. Количество классов ограничено. Все что нужно для фреймворка это Flash Player 11 или AIR 3 (поддержка мобильных устройств планируется в будущем). Это позволяет быть вашим приложениям небольшими, а процессу разработки быть простым.

Бесплатность
Starling бесплатен и открыт. Распространяется под упрощенной лицензией BSD, вы можете использовать фреймворк даже в коммерческих приложениях. Мы работаем над ним каждый день и мы рассчитываем на активное участие сообщества в дальнейшем улучшении фреймворка.

Как это работает


Starling использует API Stage3D за кулисами которого используется низкоуровневый API GPU через OpenGL или DirectX на настольных платформах или OpenGL ES2 на мобильных устройствах. Starling это ActionScript 3 порт фреймворка Sparrow (http://www.sparrow‐framework.org), эквивалентной библиотеки IOS, использующей API OpenGL ES2:

Рисунок 1.1 – Слой Starling поверх Stage3D


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

Рисунок 1.2 – Диаграмма наследования DisplayObject


Многие люди думают, что API Stage3D предназначено строго для 3D контента, чтобы быть честным, признаемся, что название может запутать. Если в названии используется 3D, то как я это могу использовать для 2D?

Как мы можем использовать drawTriangles для отрисовки чего-то типа MovieClip?

Рисунок 1.3 - Как мы можем использовать drawTriangles для отрисовки чего-то типа MovieClip?


На самом деле, это очень просто, GPU чрезвычайно эффективно для отрисовки треугольников, так, с помощью drawTriangles будут отрисовываться два треугольника, затем мы выберем текстуру и применим ее к треугольникам, используя UV-преобразование, в конечном итоге мы получим текстурированный прямоугольник, который и будет нашим спрайтом. Обновляя текстуру на наших треугольниках каждый кадр мы бы в конечном итоге получим MovieClip.

Еще хорошей новостью является то, что нам даже не придется погружаться в эти детали при использовании Starling, мы просто поставляем наши спрайты в MovieClip Starling и вуаля!

Рисунок 1.4 – drawTriangles + текстура = 2D


Чтобы понять, насколько Starling снижает сложность, посмотрим на код, который мы написали для отображения простого текстурированного прямоугольника с помощью Stage3D API:


// create the vertices
var vertices:Vector. = Vector.([
-0.5,-0.5,0, 0, 0, // x, y, z, u, v
-0.5, 0.5, 0, 0, 1,
0.5, 0.5, 0, 1, 1,
0.5, -0.5, 0, 1, 0]);
// create the buffer to upload the vertices
var vertexbuffer:VertexBuffer3D = context3D.createVertexBuffer(4, 5);
// upload the vertices
vertexbuffer.uploadFromVector(vertices, 0, 4);
// create the buffer to upload the indices
var indexbuffer:IndexBuffer3D = context3D.createIndexBuffer(6);
// upload the indices
indexbuffer.uploadFromVector (Vector.([0, 1, 2, 2, 3, 0]), 0, 6);
// create the bitmap texture
var bitmap:Bitmap = new TextureBitmap();
// create the texture bitmap to upload the bitmap
var texture:Texture = context3D.createTexture(bitmap.bitmapData.width,
bitmap.bitmapData.height, Context3DTextureFormat.BGRA, false);
// upload the bitmap
texture.uploadFromBitmapData(bitmap.bitmapData);
// create the mini assembler
var vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler();
// assemble the vertex shader
vertexShaderAssembler.assemble( Context3DProgramType.VERTEX,
"m44 op, va0, vc0\n" + // pos to clipspace
"mov v0, va1" // copy uv
);
// assemble the fragment shader
fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT,
"tex ft1, v0, fs0 <2d data-blogger-escaped-linear="linear" data-blogger-escaped-nomip="nomip">;\n" +
"mov oc, ft1"
);
// create the shader program
var program:Program3D = context3D.createProgram();
// upload the vertex and fragment shaders
program.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);
// clear the buffer
context3D.clear ( 1, 1, 1, 1 );
// set the vertex buffer
context3D.setVertexBufferAt(0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
context3D.setVertexBufferAt(1, vertexbuffer, 3, Context3DVertexBufferFormat.FLOAT_2);
// set the texture
context3D.setTextureAt( 0, texture );
// set the shaders program
context3D.setProgram( program );
// create a 3D matrix
var m:Matrix3D = new Matrix3D();
// apply rotation to the matrix to rotate vertices along the Z axis
m.appendRotation(getTimer()/50, Vector3D.Z_AXIS);
// set the program constants (matrix here)
context3D.setProgramConstantsFromMatrix(Co
ntext3DProgramType.VERTEX, 0, m, true);
// draw the triangles
context3D.drawTriangles( indexBuffer);
// present the pixels to the screen
context3D.present();


В результате мы получим следующее:

Рисунок 1.5 – Простой текстурированный прямоугольник


Очень сложный код, правда? Это стоимость использования низкоуровневого API, вы можете контролировать многие вещи, но ценой низкой понятности.

C помощью Starling вы можете написать следующий код:


// create a Texture object out of an embedded bitmap
var texture:Texture = Texture.fromBitmap ( new embeddedBitmap() );
// create an Image object our of the Texture
var image:Image = new Image(texture);
// set the properties
quad.pivotX = 50;
quad.pivotY = 50;
quad.x = 300;
quad.y = 150;
quad.rotation = Math.PI/4;
// display it
addChild(quad);


Как разработчик, использующий Flash API, вы будете чувствовать себя как дома с этим API, все сложности Stage3D API скрыты за кулисами.

Если вы попытаетесь увидеть области перерисовки, то увидите, что Starling, как и ожидается, использует Stage3D, а не классический список отображения. Это показано на рисунке ниже. Мы поворачиваем прямоугольник в каждом кадре; перерисовка областей видна только для счетчиков, которые используют стандартный список отображения:

Рисунок 1.6 – Содержимое, отображаемое Stage3D


Нужно запомнить, что при использовании Stage3D, контент полностью отрисовывается с помощью GPU, в результате чего возможность увидеть области перерисовки списка отображения не может быть использована.


Ограничения расположения слоев



Как разработчик, использующий Starling (а следовательно и Stage3D), вы должны помнить об одном ограничении при разработке контента. Как упоминалось ранее, Stage3D в буквальном смысле новая архитектура рендеринга внутри Flash Player. Поверхность, отображаемая GPU, находится под списком отображения, а это означает, что любое содержимое, помещенное в список отображения, будет располагаться выше содержимого Stage3D.
Рисунок ниже иллюстрирует это:

Рисунок 1.7 – Расположение слоев Stage3D, StageVideo и списка отображения


Обратите внимание, что объект Stage3D не может быть прозрачным, хотя это позволило бы разработчикам проигрывать видео с использованием технологии StageVideo (введение в Flash Player 10.2 - http://www.adobe.com/devnet/flashplayer/articles/stage_video.html) и перекрывать видео содержимым, отображаемым с помощью Stage3D. Такая возможность, может быть, будет реализована в следующих релизах Flash Player и AIR.


Приступим



Скачать Starling можно по следующим ссылкам:

- Github - http://github.com/PrimaryFeather/Starling-Framework/
- официальный сайт - http://www.starling-framework.org

Заметим, что Starling распространяется под упрощенной лицензией BSD, поэтому чувствуйте себя свободно для использования его в любых проектах. Вы можете связаться с разработчиками Starling с помощью почтового ящика office@starling-framework.org.

После загрузки вы можете использовать Starling как любую другую ActionScript библиотеку. Чтобы использовать Stage3D API, необходимое для Starling, вам нужно установить SWF версии 13 как аргумент для компилятора Flex. Инструкции ниже:

Если вы используете Adobe Flex SDK:
- загрузите новый playerglobal.swc для Flash Player 11;
- загрузите Flex 4.5 SDK (4.5.0.20967);
- установите его в вашу среду разработки;
- В Flash Builder создайте новый ActionScript проект: File -> New -> ActionScript project.
- Откройте панель Properties (правой кнопкой и выбрать “Properties”).
- Выберите “ActionScript Compiler” из списка слева.
- Используйте опцию “Configure Flex SDK’s” в правом углу для указания скачанной SDK для вашего проекта.
- Установите для вашего проекта SWF 13 версии, для этого:
- Откройте панель Properties (правой кнопкой и выбрать “Properties”).
- Выберите “ActionScript Compiler” из списка слева.
- Добавьте в поле “Additional compiler arguments” следующее: -swf-version=13. Это гарантирует, что результирующий SWF будет версии 13. Если вы компилируете из командной строки, то нужно будет добавить такой же аргумент.
- Убедитесь, что у вас установлен Flash Player 11.
Чтобы получить доступ к API Flash Player 11 вам нужно обновить playerglobal.swc, который доступен по следующему адресу: http://labs.adobe.com/downloads/flashplayer11.html


Настройка сцены



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

С этого момента, ссылаясь на такие объекты как MovieClip, Sprite и т.д. мы будем иметь в виду объекты Starling, а не обычные объекты Flash Player, если это не указано в явном виде.
Итак, конструктор Starling принимает множество аргументов, вот его сигнатура:


public function Starling(rootClass:Class, stage:flash.display.Stage,
viewPort:Rectangle=null, stage3D:Stage3D=null,
renderMode:String="auto")


На самом деле только первые два используются наиболее часто. Аргумент rootClass ссылается на основной класс приложения, расширяющий starling.display.Sprite, а второй аргумент на сцену:


package
{
 import flash.display.Sprite;
 import flash.display.StageAlign;
 import flash.display.StageScaleMode;
 import starling.core.Starling;
 [SWF(width="1280",height="752",frameRate="60",backgroundColor="#002143")]
 
 public class Startup extends Sprite
 {
  private var mStarling:Starling;
  
  public function Startup()
  {
   // stats class for fps
   addChild(new Stats());
   stage.align = StageAlign.TOP_LEFT;
   stage.scaleMode = StageScaleMode.NO_SCALE;
   // create our Starling instance
   mStarling = new Starling(Game, stage);
   // set anti-aliasing (higher the better quality but slower performance)
   mStarling.antiAliasing = 1;
   // start it!
   mStarling.start();
  }
 }
}


Ниже наш класс Game, создающий простой прямоугольник и добавляющий его на сцену:


package
{
 import starling.display.Quad;
 import starling.display.Sprite;
 import starling.events.Event;
 
 public class Game extends Sprite
 {
  private var q:Quad;
  
  public function Game()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
   q = new Quad(200, 200);
   q.setVertexColor(0, 0x000000);
   q.setVertexColor(1, 0xAA0000);
   q.setVertexColor(2, 0x00FF00);
   q.setVertexColor(3, 0x0000FF);
   addChild(q);
  }
 }
}


Как обычно мы слушаем событие Event.ADDED_TO_STAGE и инициализируем приложение в приемнике события. Так мы получаем безопасный доступ к сцене.
Еще раз, обратите внимание на следующую деталь. Наш класс Game расширяет класс Sprite из фреймворка Starling – из пакета starling.display, а не из пакета flash.display. Хорошая практика в том, чтобы всегда проверять список импортов, чтобы использовались не нативные API, а API Starling. Вы быстро к этому привыкните, но поначалу будете, возможно, ошибаться.
Тестирая наш предыдущий код, мы получим следующий результат. Как и ожидалось, объекты по умолчанию помещаются в позицию 0, 0. Давайте добавим несколько строчек, чтобы отцентрировать наш квадрат:


q.x = stage.stageWidth - q.width >> 1;
q.y = stage.stageHeight - q.height >> 1;


А теперь результат:

Рисунок 1.8 – Наш первый квадрат


Обратите внимание, что можно установить любое значение сглаживания. Значения равного 1, достаточно в большинстве случаев, но если вам понадобятся другие, то вы можете использовать значения от 0 до 16. Вот список наиболее часто используемых значений:
- 0 – без сглаживания;
- 2 – минимальное сглаживание;
- 4 – высокое качество сглаживания;
-16 – очень высокое качество сглаживания.

Нам очень редко потребуются значения выше 2, особенно для 2D контента, но вы можете решать сами, в зависимости от конкретной ситуации. Ниже представлены одинаковые фигуры с разными значениями сглаживания (1 и 4):

Рисунок 1.9 – Различные значения сглаживания


Вы можете попробовать использовать значения выше 2 чтобы добиться желаемого качества. Конечно, выбирая большие значения, вы теряете в производительности.

Давайте быстренько посмотрим на другие свойства и методы объекта Starling:
- enableErrorChecking: Позволяет вам включить или отключить проверку ошибок. Определяет, будут ли отображаться сообщения об ошибках, обнаруженных при визуализации. Включение проверок ошибок снижает производительность, поэтому вы должны использовать это только при отладке.
- isStarted: Показывает, был ли вызван метод start().
- juggler: Juggler это простой объект, который хранит список объектов, реализующих интерфейс IAnimatable. Когда анимация объекта проигрывается до конца, он удаляется этот объект из списка.
- start: Начинает отрисовку и обработку событий.
- stop: Останавливает отрисовку и обработку событий. Этот метод можно использовать для остановки рендеринга, когда игра теряет фокус, чтобы сохранить ресурсы.
- dispose: Вызовите этот метод, если вы хотите очистить память GPU от текущего отображаемого контента. Этот метод удаляет шейдеры, текстуры и т.д.

Как только ваш объект Starling создан, автоматически выводится отладочное сообщение, сообщающее о типе рендеринга. По умолчанию, если SWF корректно встроен в страничку или тестируется в отдельно установленном плеере, выводится следующее:


[Starling] Initialization complete.
[Starling] Display Driver:OpenGL Vendor=NVIDIA Corporation Version=2.1 NVIDIA-7.2.9 Renderer=NVIDIA GeForce GT330M OpenGL Engine GLSL=1.20 (Direct blitting)


Конечно, информация об аппаратном обеспечении зависит от вашей конфигурации. Обратите внимание, что здесь нам сообщается, что мы используем аппаратное ускорение и сообщается версия установленного драйвера. Для отладки вам может понадобиться установить программный рендеринг. Чтобы сделать это, просто передайте в конструктор соответствующее значение Context3DRenderMode.SOFTWARE


mStarling = new Starling(Game, stage, null, null, Context3DRenderMode.SOFTWARE);


Если используется программный рендеринг, то отладочное сообщение сообщает нам, что мы запустились в программном режиме:


[Starling] Initialization complete.
[Starling] Display Driver:Software (Direct blitting)


Программный рендеринг может вам понадобиться, чтобы оценить производительность в этом режиме, т.к. некоторые пользователи могут использовать старые конфигурации и драйвера (в аппаратном режиме не работают все драйвера выпущенные до 2009 года) и им будет недоступен аппаратный рендеринг. Теперь давайте взглянем на требования Stage3D когда дело доходит до встраивания SWF на страничку.


Wmode



Вы должны помнить, что для того, чтобы включить Stage3D и GPU ускорение вы должны использовать wmode=direct при вставке SWF на страничку. Если вы не указываете никакого значения или указываете значение, отличное от “direct”, например “transparent”, “opaque” или “window”, то Stage3D будет недоступен и появиться сообщение, информирующее нас об ошибке при создании объекта Context3D, когда вызывается метод requestContext3D класса Stage3D. Ниже рисунок с таким сообщением:

Рисунок 1.10 – Ошибка при отсутствии Context2D


Очень важно обработать такую ситуацию, если ваше приложение встроено с неправильным значением wmode. Вам нужно отреагировать, показав сообщение, объясняющее проблему. К счастью, Starling обрабатывает это автоматически за вас и показывает следующее сообщение:

Рисунок 1.11 – Сообщение при некорректном внедрении приложения на страницу



Качество рендеринга сцены (свойство quality класса Stage)



Как Flash-разработчик вы уже знакомы с понятием качества рендеринга сцены (свойство quality класса Stage). Запомните, что если вы работаете с Stage3D, а следовательно и с Starling, quality не влияет на производительность.


Прогрессивное улучшение



Как говорилось раннее, если аппаратное ускорение не доступно, то Stage3D возвращается в программный режим и использует движок, называемый SwiftShader (Transgaming). Чтобы убедиться, что ваше приложение работает хорошо, вам нужно отследить ситуацию, когда оно работает в программном режиме, чтобы удалить эффекты, которые могут тормозить ваше приложение в программном режиме:


// are we running hardware of software ?
var isHW:Boolean = Starling.context.driverInfo.toLowerCase().indexOf("software") == -1;


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

Теперь перейдем к очень увлекательному разделу о списке отображения в Starling!


Список отображения



Так же, как и в нативном списке отображения Flash, Starling придерживается правила, что сцена (stage) не доступна объекту, пока он не добавлен в список отображения. Для безопасного доступа к stage, мы обычно полагаемся на некоторые важные события Flash, которые также доступны в Starling:

- Event.ADDED : объект был добавлен в родителя.
- Event.ADDED_TO_STAGE : объект был добавлен на сцену, поэтому он сейчас видим.
- Event.REMOVED : объект был удален из родителя.
- Event.REMOVED_FROM_STAGE : объект был удален со сцены, поэтому он сейчас невидим.

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

Ниже список методов, определенных в классе DisplayObject:
- removeFromParent : удаляет объект из родителя, если он есть.
- getTransformationMatrixToSpace : создает матрицу, которая представляет переход из локальных координат системы в другие.
- getBounds : возвращает прямоугольник, который представляет границы объекта.
- hitTest : возвращает объект, который пересекается с заданной точкой, иначе возвращает null.
- globalToLocal : Переводит координаты из глобальных в локальные.
- localToGlobal : Переводит координаты из локальных в глобальные.

Ниже список свойств, определенных в классе DisplayObject, очень приятно увидеть все те же свойства и некоторые небольшие улучшения как pivotX и pivotY для динамического изменения точки регистрации DisplayObject:
- transformationMatrix : матрица преобразования объекта относительно его родителя.
- bounds : границы объекта, в локальных координатах его родителя.
- width : ширина объекта в пикселях.
- height : высота объекта в пикселях.
- root : объект, находящийся выше всех в иерархии отображения.
- x : координата x объекта в локальных координатах родителя.
- y : координата y объекта в локальных координатах родителя.
- pivotX : координата x объекта в собственном координатном пространстве.
- pivotY : координата y объекта в собственном координатном пространстве.
- scaleX : коэффициент масштабирования по горизонтали, если задаются отрицательные значения, то объект отражается относительно оси y.
- scaleY : коэффициент масштабирования по вертикали, если задаются отрицательные значения, то объект отражается относительно оси х.
- rotation : поворот объекта в радианах. В Starling все углы измеряются в радианах.
- alpha : прозрачность объекта.
- visible : видимость объекта. Невидимые объекты не реагируют на прикосновения.
- touchable : показывает, может ли объект и его потомки реагировать на прикосновения.
- parent : отображаемый объект, содержащий в себе текущий объект.
- stage : ссылка на сцену, содержащую текущий объект.

Так же как и в нативном API Flash, объект Sprite это самый легкий контейнер, который вы можете использовать. Как подкласс DisplayObject он имеет доступ к его интерфейсу плюс возможность содержать другие отображаемые объекты. До сих пор мы не помещали одни отображаемые объекты в другие, кроме использования сцены, но вы должны помнить, что для этого объект должен наследовать DisplayObjectContainer.

Ниже интерфейс класса DisplayObjectContainer:
- addChild : добавляет потомка в объект.
- addChildAt : добавляет потомка на указанную позицию.
- dispose : удаляет всех слушателей, зарегистрированных данным объектом.
- removeFromParent : удаляет потомка из родителя.
- removeChild : удаляет потомка из контейнера.
- removeChildAt : удаляет потомка из контейнера с указанной позиции.
- removeChildren : удаляет всех потомков из контейнера.
- getChildAt : возвращает потомка с указанной позиции.
- getChildByName : возвращает потомка по указанному имени.
- getChildIndex : возвращает позицию потомка в контейнере.
- setChildIndex : меняет позицию потомка в контейнере.
- swapChildren : меняет местами потомков в контейнере.
- swapChildrenAt : меняет местами потомков с заданными позициями в контейнере.
- contains : определяет, содержится ли в контейнере заданный объект.
Как только у вас появился доступ к сцене, то вы можете вызвать любой метод интерфейса DisplayObjectContainer, но также вы можете применить любой цвет к сцене. По умолчанию Starling использует цвет фона по умолчанию, который задается следующим образом:


[SWF(width="1280", height="752", frameRate="60", backgroundColor="#990000")]


Вы также можете переопределить это поведение, устанавливая цвет объекту stage, получая к нему доступ через любой объект DisplayObject добавленный в список отображения:


package
{
 import starling.display.Quad;
 import starling.display.Sprite;
 import starling.events.Event;
 
 public class Game extends Sprite
 {
  private var q:Quad;
  
  public function Game()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
   // set the background color to blue
   stage.color = 0x002143;
   q = new Quad(200, 200);
   q.setVertexColor(0, 0x000000);
   q.setVertexColor(1, 0xAA0000);
   q.setVertexColor(2, 0x00FF00);
   q.setVertexColor(3, 0x0000FF);
   addChild(q);
  }
 }
}


Здесь мы не используем никаких текстур, у нас есть два треугольника сгруппированных в квадрат, и каждая точка нашей плоскости имеет свой цвет интерполированный GPU.
Если вы хотите получить сплошную заливку плоскости, просто используйте свойство color объекта Quad:


package
{
 import starling.display.Quad;
 import starling.display.Sprite;
 import starling.events.Event;
 
 public class Game extends Sprite
 {
  private var q:Quad;
  
  public function Game()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
   q = new Quad(200, 200);
   q.color = 0x00FF00;
   q.x = stage.stageWidth - q.width >> 1;
   q.y = stage.stageHeight - q.height >> 1;
   addChild(q);
  }
 }
}


В результате мы получим следующее:

Рисунок 1.12 – Прямоугольник, залитый сплошным зеленым


Теперь давайте используем событие Event.ENTER_FRAME, с помощью которого реализуем простой эффект перехода от одного значения цвета к другому:


package
{
 import starling.display.Quad;
 import starling.display.Sprite;
 import starling.events.Event;
 
 public class Game extends Sprite
 {
  private var q:Quad;
  private var r:Number = 0;
  private var g:Number = 0;
  private var b:Number = 0;
  private var rDest:Number;
  private var gDest:Number;
  private var bDest:Number;
  
  public function Game()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
   resetColors();
   q = new Quad(200, 200);
   q.x = stage.stageWidth - q.width >> 1;
   q.y = stage.stageHeight - q.height >> 1;
   addChild(q);
   stage.addEventListener(Event.ENTER_FRAME, onFrame);
  }
  
  private function onFrame(e:Event):void
  {
   r -= (r - rDest) * .01;
   g -= (g - gDest) * .01;
   b -= (b - bDest) * .01;
   var color:uint = r << 16 | g << 8 | b;
   q.color = color;
// when reaching the color, pick another one
   if (Math.abs(r - rDest) < 1 && Math.abs(g - gDest) < 1 && Math.abs(b - bDest))
    resetColors();
  }
  
  private function resetColors():void
  {
   rDest = Math.random() * 255;
   gDest = Math.random() * 255;
   bDest = Math.random() * 255;
  }
 }
}


Чтобы вращать этот квадрат, мы можем использовать свойство rotation, заметим, что Starling в отличие от Flash работает с радианами, а не с градусами. Этот выбор был сделан, чтобы сохранить однообразие между Sparrow и Starling. Всегда, когда вы хотите использовать для поворота градусы, просто используйте метод starling.utils.deg2rad:


sprite.rotation = deg2rad(Math.random()*360);


Все отображаемые объекты имеют свойства pivotX и pivotY, позволяющие менять точку регистрации в рантайме для любого объекта:


q.pivotX = q.width >> 1;
q.pivotY = q.height >> 1;


Квадрат может быть вложен в спрайт с текстовым полем и перемещаться вместе с ними совсем как в нативном списке отображения:


package
{
 import starling.display.DisplayObject;
 import starling.display.Quad;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.text.TextField;
 
 public class Game extends Sprite
 {
  private var q:Quad;
  private var s:Sprite;
  private var r:Number = 0;
  private var g:Number = 0;
  private var b:Number = 0;
  private var rDest:Number;
  private var gDest:Number;
  private var bDest:Number;
  
  public function Game()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
   resetColors();
   q = new Quad(200, 200);
   s = new Sprite();
   var legend:TextField = new TextField(100, 20, "Hello Starling!", "Arial", 14, 0xFFFFFF);
   s.addChild(q);
   s.addChild(legend);
   s.pivotX = s.width >> 1;
   s.pivotY = s.height >> 1;
   s.x = (stage.stageWidth - s.width >> 1) + (s.width >> 1);
   s.y = (stage.stageHeight - s.height >> 1) + (s.height >> 1);
   addChild(s);
   s.addEventListener(Event.ENTER_FRAME, onFrame);
  }
  
  private function onFrame(e:Event):void
  {
   r -= (r - rDest) * .01;
   g -= (g - gDest) * .01;
   b -= (b - bDest) * .01;
   var color:uint = r << 16 | g << 8 | b;
   q.color = color;
// when reaching the color, pick another one
   if (Math.abs(r - rDest) < 1 && Math.abs(g - gDest) < 1 && Math.abs(b - bDest))
    resetColors();
   (e.currentTarget as DisplayObject).rotation += .01;
  }
  
  private function resetColors():void
  {
   rDest = Math.random() * 255;
   gDest = Math.random() * 255;
   bDest = Math.random() * 255;
  }
 }
}


Сейчас мы вращаем наш спрайт, который содержит экземпляры Quad и TextField, вокруг точки регистрации спрайта:

Рисунок 1.13 – Спрайт, содержащий квадрат и текстовое поле


Наш код начинает выглядеть грязновато, давайте переместим код в новый класс CustomSprite, который инкапсулирует переход цветов и добавления потомков:


package
{
 import starling.display.Quad;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.text.TextField;
 
 public class CustomSprite extends Sprite
 {
  private var quad:Quad;
  private var legend:TextField;
  private var quadWidth:uint;
  private var quadHeight:uint;
  private var r:Number = 0;
  private var g:Number = 0;
  private var b:Number = 0;
  private var rDest:Number;
  private var gDest:Number;
  private var bDest:Number;
  
  public function CustomSprite(width:Number, height:Number, color:uint = 16777215)
  {
// reset the destination color component
   resetColors();
// set the width and height
   quadWidth = width;
   quadHeight = height;
// when added to stage, activate it
   addEventListener(Event.ADDED_TO_STAGE, activate);
  }
  
  private function activate(e:Event):void
  {
// create a quad of the specified width
   quad = new Quad(quadWidth, quadHeight);
// add the legend
   legend = new TextField(100, 20, "Hello Starling!", "Arial", 14, 0xFFFFFF);
// add the children
   addChild(quad);
   addChild(legend);
// change the registration point
   pivotX = width >> 1;
   pivotY = height >> 1;
  }
  
  private function resetColors():void
  {
// pick random color components
   rDest = Math.random() * 255;
   gDest = Math.random() * 255;
   bDest = Math.random() * 255;
  }
  
  /**
   * Updates the internal behavior
   *
   */
  public function update():void
  {
// easing on the components
   r -= (r - rDest) * .01;
   g -= (g - gDest) * .01;
   b -= (b - bDest) * .01;
// assemble the color
   var color:uint = r << 16 | g << 8 | b;
   quad.color = color;
// when reaching the color, pick another one
   if (Math.abs(r - rDest) < 1 && Math.abs(g - gDest) < 1 && Math.abs(b - bDest))
    resetColors();
// rotate it!
//rotation += .01;
  }
 }
}


А это наш новый класс Game:


package
{
 import starling.display.Sprite;
 import starling.events.Event;
 
 public class Game extends Sprite
 {
  private var customSprite:CustomSprite;
  
  public function Game()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
   // create the custom sprite
   customSprite = new CustomSprite(200, 200);
   // positions it by default in the center of the stage
   // we add half width because of the registration point of the custom sprite (middle)
   customSprite.x = (stage.stageWidth - customSprite.width >> 1) + (customSprite.width >> 1);
   customSprite.y = (stage.stageHeight - customSprite.height >> 1) + (customSprite.height >> 1);
   // show it
   addChild(customSprite);
   // need to comment this one ? ;)
   stage.addEventListener(Event.ENTER_FRAME, onFrame);
  }
  
  private function onFrame(e:Event):void
  {
   // we update our custom sprite
   customSprite.update();
  }
 }
}


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

Добавим немного интерактивности в наш маленький тест, мы сделаем перемещение квадрата за мышью:


package
{
 import flash.geom.Point;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.events.Touch;
 import starling.events.TouchEvent;
 
 public class Game extends Sprite
 {
  private var customSprite:CustomSprite;
  private var mouseX:Number = 0;
  private var mouseY:Number = 0;
  
  public function Game()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create the custom sprite
   customSprite = new CustomSprite(200, 200);
// positions it by default in the center of the stage
// we add half width because of the registration point of the custom sprite (middle)
   customSprite.x = (stage.stageWidth - customSprite.width >> 1) + (customSprite.width >> 1);
   customSprite.y = (stage.stageHeight - customSprite.height >> 1) + (customSprite.height >> 1);
// show it
   addChild(customSprite);
// we listen to the mouse movement on the stage
   stage.addEventListener(TouchEvent.TOUCH, onTouch);
// need to comment this one ? ;)
   stage.addEventListener(Event.ENTER_FRAME, onFrame);
  }
  
  private function onFrame(e:Event):void
  {
// easing on the custom sprite position
   customSprite.x -= (customSprite.x - mouseX) * .1;
   customSprite.y -= (customSprite.y - mouseY) * .1;
// we update our custom sprite
   customSprite.update();
  }
  
  private function onTouch(e:TouchEvent):void
  {
// get the mouse location related to the stage
   var touch:Touch = e.getTouch(stage);
   var pos:Point = touch.getLocation(stage);
// store the mouse coordinates
   mouseX = pos.x;
   mouseY = pos.y;
  }
 }
}


Обратите внимание, что мы не используем методы и свойства встроенного класса Mouse, фактически в Starling нет понятия мыши, мы к этому очень скоро вернемся.
Слушая событие TouchEvent.TOUCH, мы на самом деле слушаем любое перемещение мыши или пальца, так же как при классическом MouseEvent.MOUSE_MOVE. В каждом кадре, мы запоминаем текущую позицию мыши, используя методы getTouch и getLocation класса TouchEvent. Как только позиция мыши сохранена, мы используем небольшие простые вычисления в методе onFrame для перемещения квадрата.

Как сказано ранее, Starling облегчает не только программирование для GPU, но и очистку объектов. Скажем, мы хотим удалить этот квадрат со сцены при клике по нему, напишем следующее:


package
{
 import flash.geom.Point;
 import starling.display.DisplayObject;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.events.Touch;
 import starling.events.TouchEvent;
 import starling.events.TouchPhase;
 
 public class Game extends Sprite
 {
  private var customSprite:CustomSprite;
  private var mouseX:Number = 0;
  private var mouseY:Number = 0;
  
  public function Game()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create the custom sprite
   customSprite = new CustomSprite(200, 200);
// positions it by default in the center of the stage
// we add half width because of the registration point of the custom sprite (middle)
   customSprite.x = (stage.stageWidth - customSprite.width >> 1) + (customSprite.width >> 1);
   customSprite.y = (stage.stageHeight - customSprite.height >> 1) + (customSprite.height >> 1);
// show it
   addChild(customSprite);
// we listen to the mouse movement on the stage
   stage.addEventListener(TouchEvent.TOUCH, onTouch);
// need to comment this one ? ;)
   stage.addEventListener(Event.ENTER_FRAME, onFrame);
// when the sprite is touched
   customSprite.addEventListener(TouchEvent.TOUCH, onTouchedSprite);
  }
  
  private function onFrame(e:Event):void
  {
// easing on the custom sprite position
   customSprite.x -= (customSprite.x - mouseX) * .1;
   customSprite.y -= (customSprite.y - mouseY) * .1;
// we update our custom sprite
   customSprite.update();
  }
  
  private function onTouch(e:TouchEvent):void
  {
// get the mouse location related to the stage
   var touch:Touch = e.getTouch(stage);
   var pos:Point = touch.getLocation(stage);
// store the mouse coordinates
   mouseX = pos.x;
   mouseY = pos.y;
  }
  
  private function onTouchedSprite(e:TouchEvent):void
  {
// get the touch points (can be multiple because of multitouch)
   var touch:Touch = e.getTouch(stage);
   var clicked:DisplayObject = e.currentTarget as DisplayObject;
// detect the click/release phase
   if (touch.phase == TouchPhase.ENDED)
   {
// remove the clicked object
    removeChild(clicked);
   }
  }
 }
}


Заметим, что мы удалили потомка, но не удалили слушателя события Event.ENTER_FRAME. В этом мы можем в этом убедиться, используя метод hasEventListener:


private function onTouchedSprite(e:TouchEvent):void
{
// get the touch points (can be multiple because of multitouch)
 var touch:Touch = e.getTouch(stage);
 var clicked:DisplayObject = e.currentTarget as DisplayObject;
// detect the click/release phase
 if (touch.phase == TouchPhase.ENDED)
 {
// remove the clicked object
  removeChild(clicked);
// outputs : true
  trace(clicked.hasEventListener(e.type));
 }
}


Для безопасного удаления потомка, используйте второй параметр метода removeChild, который позволяет нам автоматически удалить всех слушателей, зарегистрированных этим объектом:


private function onTouchedSprite(e:TouchEvent):void
{
// get the touch points (can be multiple because of multitouch)
 var touch:Touch = e.getTouch(stage);
 var clicked:DisplayObject = e.currentTarget as DisplayObject;
// detect the click/release phase
 if (touch.phase == TouchPhase.ENDED)
 {
// remove and dispose all the listeners
  removeChild(clicked, true);
// outputs : false
  trace(clicked.hasEventListener(e.type));
 }
}


Обратите внимание, что этот параметр доступен в других методах, которые служат для удаления – removeChildren и removeChildAt.
Также можно удалить всех слушателей, используя метод dispose класса DisplayObject:


clicked.dispose()


Мы в первый раз использовали событийную модель Starling, которая очень похожа на нативную модель Flash Player. Давайте потратим больше времени на изучение API EventDispatcher в Starling.


Событийная модель



Как показано на рисунке 1.2, все объекты Starling являются подклассами класса EventDispatcher. Все объекты имеют методы для добавления и удаления слушателей:

- addEventListener : добавляет слушателя события.
- hasEventListener : проверяет, есть ли слушатель у события.
- removeEventListener : удаляет слушателя события.
- removeEventListeners : удаляет все слушатели определенного события или вообще все слушатели.

Обратите внимание на последний метод removeEventListeners. Всегда, когда вам нужно удалить все слушатели, зарегистрированные на получение определенного события, используйте removeEventListeners, передавая в него тип события:


button.removeEventListeners(Event.TRIGGERED);


Если нужно удалить все слушатели, независимо от типа события, просто вызовите removeEventListeners без передачи в него параметров:


button.removeEventListeners ();


Обратите внимание, что при вызове метода removeChildren, он принимает параметр, показывающий необходимость удаления слушателей, если он имеет значение true, то для удаляемого потомка вызывается метод removeEventListeners().


Распространение события



Как мы уже видели в начале этого руководства, Starling воссоздает концепцию списка отображения поверх Stage3D. Отличная новость в том, что вы можете использовать силу распространения событий с Starling. Распространение событий может быть реально полезным в том, чтобы ограничить количество слушателей которых вы регистрируете и сделать ваш код более понятным.
Если вы не знакомы с концепцией распространения событий, то вы можете познакомиться с ней по следующей ссылке: http://www.adobe.com/devnet/actionscript/articles/event_handling_as3.html
Интересная деталь – Starling обрабатывает распространение событий, но немного по-другому, чем это делается в нативном Flash. Starling поддерживает только фазу всплытия, здесь нет понятия фазы захвата. Мы используем события в следующих примерах, чтобы посмотреть, как это работает.


События прикосновения



Как упоминалось ранее – Starling это родственник Sparrow, вследствие чего, механизм события прикосновения в Starling на самом деле предназначен для взаимодействий с прикосновениями на мобильных платформах и его использование в настольных приложения с Starling выглядит на первый взгляд запутанным.
Во-первых, если вы посмотрите на рисунок 1.2, вы заметите, что в отличие от нативного списка отображения, в Starling нет класса InteractiveObject в иерархии, все отображаемые объекты по умолчанию интерактивные. Другими словами класс DisplayObject реализует интерактивность.
Мы использовали события прикосновений в предыдущих примерах. Мы начали с самого основного – реакции квадрата на прикосновения мыши. Для этого мы использовали событие TouchEvent.TOUCH:


// when the sprite is touched
_customSprite.addEventListener(TouchEvent.TOUCH, onTouchedSprite);


Вы можете подумать, что это очень ограниченно? На самом деле это очень мощное событие, с помощью которого вы можете различить множество различных состояний. Всякий раз, когда мышь или палец взаимодействуют с графическим объектом, отправляется событие TouchEvent.TOUCH.
В коде ниже мы трейсим свойство phase объекта Touch в обработчике onTouch:


private function onTouch(e:TouchEvent):void
{
// get the mouse location related to the stage
 var touch:Touch = e.getTouch(stage);
 var pos:Point = touch.getLocation(stage);
 trace(touch.phase);
// store the mouse coordinates
 _mouseY = pos.y;
 _mouseX = pos.x;
}


Когда мы начинаем взаимодействовать с квадратом и кликаем по нему, мы видим как переключаются различные фазы. Вот перечень всех фаз, которые хранятся как константы класса TochPhase:
• began : нажатие пальцем или левой кнопки мыши над объектом (соответствует нативному MOUSE_DOWN).
• ended : отпускание пальца или левой кнопки мыши над объектом (соответствует нативному CLICK).
• hover : перемещение курсора мыши или пальца над объектом. (соответствует нативному MOUSE_OVER)
• moved : перемещение курсора мыши или пальца над объектом в нажатом состоянии (соответствует MOUSE_DOWN + MOUSE_OVER).
• stationary : мышь или палец закончили взаимодействие с объектом и остались над ним.

Посмотрим на другие свойства и методы доступные в объекте TouchEvent:
• ctrlKey : булево значение, показывающее нажатие клавиши Ctrl.
• getTouch: возвращает первый объект типа Touch, который возник над заданным объектом и в заданной фазе.
• getTouches : возвращает набор объектов типа Touch, которые возникли над заданным объектом и в заданной фазе.
• shiftKey: булево значение, показывающее нажатие клавиши Shift.
• timestamp : время возникновения события (в секундах, с момента запуска приложения).
• touches : все прикосновения, которые происходят в настоящее время.

Использование ctrlKey и shiftKey полезно для определения комбинаций с клавишами клавиатуры. Всегда, когда есть взаимодействие с пальцем или мышью, есть объект Touch, связанный с этим взаимодействием.
Посмотрим на интерфейс класса Touch:
• clone : клонирует объект.
• getLocation: конвертирует текущие координаты прикосновения в локальные координаты отображаемого объекта.
• getPreviousLocation: конвертирует предыдущие координаты прикосновения в локальные координаты отображаемого объекта.
• globalX: координата х прикосновения в глобальной системе координат (координаты экрана).
• globalY : координата y прикосновения в глобальной системе координат.
• id: уникальный идентификатор объекта.
• phase : текущая фаза прикосновения.
• previousGlobalX : предыдущая координата х прикосновения в глобальной системе координат.
• previousGlobalY : предыдущая координата х прикосновения в глобальной системе координат.
• tapCount : количество нажатий за короткий промежуток времени, используйте это для определения двойного клика и т.д.
• target : отображаемый объект, к которому прикоснулись.
• timestamp : момент наступления события (в секундах с запуска приложения).


Симуляция мультитач



Когда разрабатывается контент для мобильных устройств, наиболее вероятно, что вам понадобится реализация мультитач, например, для масштабирования объекта. Starling предлагает отличный встроенный механизм симуляции мультитач.
Чтобы включить его нужно использовать свойство simulateMultiTouch объекта Starling:


package
{
 import flash.display.Sprite;
 import flash.display.StageAlign;
 import flash.display.StageScaleMode;
 import starling.core.Starling;
 [SWF(width="1280",height="752",frameRate="60",backgroundColor="#002143")]
 
 public class Startup extends Sprite
 {
  private var mStarling:Starling;
  
  public function Startup()
  {
// stats class for fps
   addChild(new Stats());
   stage.align = StageAlign.TOP_LEFT;
   stage.scaleMode = StageScaleMode.NO_SCALE;
// create our Starling instance
   mStarling = new Starling(Game, stage);
// emulate multi-touch
   mStarling.simulateMultitouch = true;
// set anti-aliasing (higher the better quality but slower performance)
   mStarling.antiAliasing = 1;
// start it!
   mStarling.start();
  }
 }
}


Когда симуляция включена, просто используйте клавишу Ctrl вместе с кликом мыши и появятся две маленькие точки, показывающие места прикосновения. На рисунке ниже это показано:

Рисунок 1.14 – Симуляция мультитач


В коде ниже мы масштабируем квадрат, если используется мультитач, например двумя пальцами. Мы получаем точки прикосновения и определяем расстояние между ними:


package
{
 import flash.geom.Point;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.events.Touch;
 import starling.events.TouchEvent;
 import starling.events.TouchPhase;
 
 public class Game extends Sprite
 {
  private var customSprite:CustomSprite;
  
  public function Game()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create the custom sprite
   customSprite = new CustomSprite(200, 200);
// positions it by default in the center of the stage
// we add half width because of the registration point of the custom sprite (middle)
   customSprite.x = (stage.stageWidth - customSprite.width >> 1) + (customSprite.width >> 1);
   customSprite.y = (stage.stageHeight - customSprite.height >> 1) + (customSprite.height >> 1);
// show it
   addChild(customSprite);
// we listen to the mouse movement on the stage
//stage.addEventListener(TouchEvent.TOUCH, onTouch);
// need to comment this one ? ;)
   stage.addEventListener(Event.ENTER_FRAME, onFrame);
// when the sprite is touched
   customSprite.addEventListener(TouchEvent.TOUCH, onTouchedSprite);
  }
  
  private function onFrame(e:Event):void
  {
// we update our custom sprite
   customSprite.update();
  }
  
  private function onTouchedSprite(e:TouchEvent):void
  {
// retrieves the touch points
   var touches:Vector. = e.touches;
// if two fingers
   if (touches.length == 2)
   {
    var finger1:Touch = touches[0];
    var finger2:Touch = touches[1];
    var distance:int;
    var dx:int;
    var dy:int;
// if both fingers moving (dragging)
    if (finger1.phase == TouchPhase.MOVED && finger2.phase == TouchPhase.MOVED)
    {
// calculate the distance between each axes
     dx = Math.abs(finger1.globalX - finger2.globalX);
     dy = Math.abs(finger1.globalY - finger2.globalY);
// calculate the distance
     distance = Math.sqrt(dx * dx + dy * dy);
     trace(distance);
    }
   }
  }
 }
}


Теперь разберемся с текстурами!


Текстуры



Объект текстур должень быть создан со связанным с ним объектом Image. Думайте об этом, как о связи Bitmap и BitmapData в нативном API. Ниже интерфейс объекта Texture:
• base : The Stage3D texture object the texture is based on.
• dispose : очищает данные текстуры.
• empty : возвращает сплошную текстуру с заданными размерами и цветом.
• frame : кадра текстуры.
• fromBitmap : возвращает объект Texture, созданный из Bitmap.
• fromBitmapData : возвращает объект Texture, созданный из BitmapData.
• fromAtfData : позволяет использовать сжатые текстуры ATF (Adobe Texture Format). Сжатые текстуры позволяют сохранить большое количество памяти, особенно это полезно для мобильных устройств.
• fromTexture : возвращает объект Texture, созданный из другого объекта Texture.
• height : высота текстуры в пикселях.
• mipmapping : индикатор mip-текстурирования.
• premultipliedAlpha : показывает, используется ли значение прозрачности в формате RGB.
• repeat : показывает, должна ли текстура повторятся на поверхности.
• width : ширина текстуры в пикселях.
Для текстур могут быть использованы различные форматы изображений. В списке ниже представлены форматы, которые могут быть использованы в ваших текстурах:
• PNG : если требуется прозрачность, то наиболее часто для текстур используется PNG.
• JPEG : классический JPEG также может быть использован. Нужно помнить, что в GPU изображение не сжато, поэтому используйте его, если использование памяти не ограничено и вам не нужна прозрачность в ваших текстурах.
• JPEG-XR : JPEG XR (аббривиатура для JPEG extended range[4]) это стандарт сжатия изображений и формат файла, основанный на технологии, разработанной и запатентованной Microsoft под названием HD Photo (ранее Windows Media Photo). Поддерживает сжатие с потерями и без потерь и является предпочтительным форматом изображения для документов, соответствующих Ecma-388 Open XML Paper Specification.
• ATF : Adobe Texture Format. Это лучший формат для лучшего сжатия. Файлы ATF это контейнеры для хранения сжатых данных текстур. Сжатие производится с применением двух технологий: сжатие JPEG-XR1 и блочное сжатие. Сжатие JPEG-XR1 обеспечивает снижение памяти для хранения текстуры, а блочное сжатие обеспечивает снижение памяти видеокарты для работы с текстурой, сжимает по сравнению с RGBA текстурой в 8 раз. ATF поддерживает три типа блочного сжатия: DXT12, ETC13 и PVRTC4.

Давайте рассмотрим концепцию текстуры и основы изображений в GPU и mip-текстурирования. Mip-текстурирование это очень важное и простое понятие. Набор текстур с размером от номинального до 1х1 называется mip-пирамидой. При работе с текстурами в GPU последней необходимо иногда масштабировать изображения в зависимости от размера контента. Это может случится, если ваша камера приближается к контенту или удаляется от него. В обоих случаях масштабируется контент, а в результате и его текстуры.

Обратите внимание, что размер текстуры должен быть кратным 2 (1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048), но она не обязательно должна быть квадратной. Если вы не будете соблюдать этого правила, Starling автоматически найдет ближайшее значение размера кратное двум для вашего изображения и создаст текстуру этого размера, что может привести к трате памяти. Чтобы оптимально использовать память рекомендуется делать текстурные карты (texture atlases) также известные как листы спрайтов (sprite sheets). Мы вернемся к этому позже.

Для достижения лучшего качества GPU необходимы вся mip-пирамида изображения. Без использования Starling вы можете вручную сгенерировать mip-пирамиду, используя BitmapData.draw() и масштабирование с помощью матрицы.

К счастью,Starling делает это автоматически. Вот код, который генерирует mip-пирамиду:


if (generateMipmaps)
{
 var currentWidth:int = data.width >> 1;
 var currentHeight:int = data.height >> 1;
 var level:int = 1;
 var canvas:BitmapData = new BitmapData(currentWidth, currentHeight, true, 0);
 var transform:Matrix = new Matrix(.5, 0, 0, .5);
 while (currentWidth >= 1 || currentHeight >= 1)
 {
  canvas.fillRect(new Rectangle(0, 0, currentWidth, currentHeight), 0);
  canvas.draw(data, transform, null, null, null, true);
  texture.uploadFromBitmapData(canvas, level++);
  transform.scale(0.5, 0.5);
  currentWidth = currentWidth >> 1;
  currentHeight = currentHeight >> 1;
 }
 canvas.dispose();
}


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

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


texture.frame = new Rectangle(5, 5, 30, 30);
var image:Image = new Image(texture);


Поговорим об объекте Image.


Image



В Starling объект starling.display.Image это эквивалент нативного объекта flash.display.Bitmap:


var myImage:Image = new Image(texture);


Чтобы показать изображение вам нужно создать объект Image и передать в него объект Texture:


package
{
 import flash.display.Bitmap;
 import starling.display.Image;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.textures.Texture;
 import starling.utils.deg2rad;
 
 public class Game2 extends Sprite
 {
  private var sausagesVector:Vector. = new Vector.(NUM_SAUSAGES, true);
  private const NUM_SAUSAGES:uint = 400;
  [Embed(source="../media/textures/sausage.png")]
  private static const Sausage:Class;
  
  public function Game2()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create a Bitmap object out of the embedded image
   var sausageBitmap:Bitmap = new Sausage();
// create a Texture object to feed the Image object
   var texture:Texture = Texture.fromBitmap(sausageBitmap);
   for (var i:int = 0; i < NUM_SAUSAGES; i++)
   {
// create a Image object with our one texture
    var image:Image = new Image(texture);
// set a random alpha, position, rotation
    image.alpha = Math.random();
// define a random initial position
    image.x = Math.random() * stage.stageWidth
    image.y = Math.random() * stage.stageHeight
    image.rotation = deg2rad(Math.random() * 360);
// show it
    addChild(image);
// store references for later
    sausagesVector[i] = image;
   }
  }
 }
}


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

Если мы протестируем код, то получим следующий результат.

Рисунок 1.15 – Наши сосиски расположенные случайно


Давайте заставим это двигаться, для этого создадим класс CustomImage:


package
{
 import starling.display.Image;
 import starling.textures.Texture;
 
 public class CustomImage extends Image
 {
  public var destX:Number = 0;
  public var destY:Number = 0;
  
  public function CustomImage(texture:Texture)
  {
   super(texture);
  }
 }
}


И используем класс CustomImage в нашем коде:


package
{
 import flash.display.Bitmap;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.textures.Texture;
 import starling.utils.deg2rad;
 
 public class Game2 extends Sprite
 {
  private var sausagesVector:Vector. = new Vector.(NUM_SAUSAGES, true);
  private const NUM_SAUSAGES:uint = 400;
  [Embed(source="../media/textures/sausage.png")]
  private static const Sausage:Class;
  
  public function Game2()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create a Bitmap object out of the embedded image
   var sausageBitmap:Bitmap = new Sausage();
// create a Texture object to feed the Image object
   var texture:Texture = Texture.fromBitmap(sausageBitmap, false);
   for (var i:int = 0; i < NUM_SAUSAGES; i++)
   {
// create a Image object with our one texture
    var image:CustomImage = new CustomImage(texture);
// set a random alpha, position, rotation
    image.alpha = Math.random();
// define a random destination
    image.destX = Math.random() * stage.stageWidth;
    image.destY = Math.random() * stage.stageWidth;
// define a random initial position
    image.x = Math.random() * stage.stageWidth
    image.y = Math.random() * stage.stageHeight
    image.rotation = deg2rad(Math.random() * 360);
// show it
    addChild(image);
// store references for later
    sausagesVector[i] = image;
   }
// main loop
   stage.addEventListener(Event.ENTER_FRAME, onFrame);
  }
  
  private function onFrame(e:Event):void
  {
   var lng:uint = sausagesVector.length;
   for (var i:int = 0; i < lng; i++)
   {
// move the sausages around
    var sausage:CustomImage = sausagesVector[i];
    sausage.x -= (sausage.x - sausage.destX) * .1;
    sausage.y -= (sausage.y - sausage.destY) * .1;
// when reached destination
    if (Math.abs(sausage.x - sausage.destX) < 1 && Math.abs(sausage.y - sausage.destY) < 1)
    {
     sausage.destX = Math.random() * stage.stageWidth;
     sausage.destY = Math.random() * stage.stageWidth;
     sausage.rotation = deg2rad(Math.random() * 360);
    }
   }
  }
 }
}


Как упоминалось ранее, обработка событий в Starling делает наш код понятней, если нам нужно обработать события прикосновения ко всем этим движущимся изображениям. Для этого добавим слушателя события TouchEvent.TOUCH к сцене:


// we listen to the mouse movement on the stage
stage.addEventListener(TouchEvent.TOUCH, onClick);


Если мы проверим значения свойств target и currentTarget, то мы увидим, что объект отправляющий событие (currentTarget) это Stage, а объект получающий событие это экземпляр CustomImage:


private function onClick(e:TouchEvent):void
{
// get the touch points (can be mulitple because of multitouch)
 var touches:Vector. = e.getTouches(this);
 var clicked:DisplayObject = e.currentTarget as DisplayObject;
// if one finger only or the mouse
 if (touches.length == 1)
 {
// grab the touch point
  var touch:Touch = touches[0];
// detect the click/release phase
  if (touch.phase == TouchPhase.ENDED)
  {
// outputs : [object Stage] [object CustomImage]
   trace(e.currentTarget, e.target);
  }
 }
}


Используя этот подход нам не нужно регистировать слушателя для каждого изображения. Мы просто слушаем событие от их контейнера, здесь это сцена и ловим события на фазе всплытия. Если мы используем свойство bubble объекта TouchEvent, мы увидим, что событие всплывающее:


// outputs : [object Stage] [object CustomImage] true
trace ( e.currentTarget, e.target, e.bubbles );


Ок, это то, что касается механизма распространения событий. Давайте посмотрим на объект Image и какие возможности он предлагает. Image имеет интерфейс, наследованный от DisplayObject и свойство smoothing для настройки сглаживания. Следующие значения сглаживания хранятся в константах класса TextureSmoothing:
- BILINEAR : применяет билинейную фильтрацию при масштабировании изображения (значение по-умолчанию).
- NONE : не применяет фильтрацию при масштабировании.
- TRILINEAR : применяет трилинейную фильтрацию при масштабировании.

Это работает следующим образом:


//disable filtering when scaled
image.smoothing = TextureSmoothing.NONE;


Ниже масштабированное изображение с применением билинейной фильтрации (TextureSmoothing.BILINEAR):

Рисунок 1.16 – TextureSmoothing.BILINEAR


Ниже масштабированное изображение с применением трилинейной фильтрации (TextureSmoothing.TRILINEAR):

Рисунок 1.17 - TextureSmoothing.TRILINEAR


Ниже масштабированное изображение без применения фильтрации (TextureSmoothing.NONE):

Рисунок 1.18 - TextureSmoothing.NONE


Обратите внимание, как здорово выглядит сосиска без применения фильтрации.

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

Рисунок 1.19


Вы хотите рисовать фигуры в рантайме с помощью Starling? Нет проблем, переходим к следующему разделу.


Рисование



Starling не предоставляет такого API рисования, какое есть в нативном flash.display.Graphics. Однако можно легко воспроизвести такую возможность с помощью рисования в Graphics, затем отрисовки его в объект BitmapData и использования BitmapData в качестве текстуры.

Скажем, вы хотите нарисовать звезду:


// create a vector shape (Graphics)
var shape:flash.display.Sprite = new flash.display.Sprite();
// pick a color
var color:uint = Math.random() * 0xFFFFFF;
// set color fill
s.graphics.beginFill(color,ballAlpha);
// radius
var radius:uint = 20;
// draw circle with a specified radius
s.graphics.drawCircle(radius,radius,radius);
s.graphics.endFill();
// create a BitmapData buffer
var bmd:BitmapData = new BitmapData(radius * 2, radius * 2, true, color);
// draw the shape on the bitmap
buffer.draw(s);
// create a Texture out of the BitmapData
var texture:Texture = Texture.fromBitmapData(buffer);
// create an Image out of the texture
var image:Image = new Image(texture);
// show it!
addChild(image);


Идея проста, вы используете интерфейс нативного Graphics для отрисовки линий, контуров и заливок используя CPU, затем растеризуете это и загружаете как текстуру.
На рисунке ниже представлен пример создания окружностей с помощью Starling:

Рисунок 1.20



Flat Sprites (плоские спрайты?)


Starling содержит очень мощный инструмент, называемый flat sprites (compiled sprites в Sparrow), который позволит вам существенно улучшить производительность.

Для лучшего понимания того, как список отображения работает по умолчанию, посмотрим на рисунок ниже, который иллюстрирует обычный список отображения в приложении, которое содержит множество вложенных объектов:

Рисунок 1.21 – Дети, имеющие собственные буфера вершин и индексов


Как видите на рисунке выше, каждый потомок имеет собственные вертексные и индексные буферы и все потомки отрисовываются независимо, что может потребовать больше вычислений и снизить производительность.

Starling может собрать всю геометрию потомков в один большой вертексный буфер и отрисовать весь контент за один вызов, как простую текстуру:

Рисунок 1.22 – потомки отрисовываются за один вызов


Это очень похоже на нативный метод casheAsBitmap. Отличие в том, что поверхность не перерисовывается автоматически, если потомок в дереве изменился. Вы должны явно вызвать метод перерисовки, чтобы увидеть изменения.

Ниже список доступных методов:
- flatten : вызовите этот метод если хотите отрисовывать контент так быстро, как это возможно. Если вызвать этот метод, то Starling соберет для отрисовки списка отображения всю требуемую геометрию в один буфер и весь контент будет отрисован за один вызов метода отрисовки, так, как будто вы отрисовываете простую текстуру. Чтобы отразить все изменения потомков в списке отображения нужно вызвать метод отрисовки еще раз.
- unflatten : отключает плоские спрайты.
- isFlatenned: булево значение, показывающее, сделан ли спрайт плоским или нет.

Давайте попробуем это, в коде ниже мы добавляем множество изображений в Sprite и поворачиваем контейнер в каждом кадре:


package
{
 import flash.display.Bitmap;
 import starling.display.Image;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.textures.Texture;
 import starling.utils.deg2rad;
 
 public class Game6 extends Sprite
 {
  private var container:Sprite;
  private static const NUM_PIGS:uint = 400;
  [Embed(source="../media/textures/pig-parachute.png")]
  private static const PigParachute:Class;
  
  public function Game6()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create the container
   container = new Sprite();
// change the registration point
   container.pivotX = stage.stageWidth >> 1;
   container.pivotY = stage.stageHeight >> 1;
   container.x = stage.stageWidth >> 1;
   container.y = stage.stageHeight >> 1;
// create a Bitmap object out of the embedded image
   var pigTexture:Bitmap = new PigParachute();
// create a Texture object to feed the Image object
   var texture:Texture = Texture.fromBitmap(pigTexture);
// layout the pigs
   for (var i:uint = 0; i < NUM_PIGS; i++)
   {
// create a new pig
    var pig:Image = new Image(texture);
// random position
    pig.x = Math.random() * stage.stageWidth;
    pig.y = Math.random() * stage.stageHeight;
    pig.rotation = deg2rad(Math.random() * 360);
// nest the pig
    container.addChild(pig);
   }
   container.pivotX = stage.stageWidth >> 1;
   container.pivotY = stage.stageHeight >> 1;
// show the pigs
   addChild(container);
// on each frame
   stage.addEventListener(Event.ENTER_FRAME, onFrame);
  }
  
  private function onFrame(e:Event):void
  {
// rotate the container
   container.rotation += .1;
  }
 }
}


В этом тесте, анимация отлично сглажена и выполняется со скоростью 60 кадров в секунду, но мы можем это оптимизировать, снизив количество вызовов метода отрисовки к минимуму. Вызовем метод flatten:


// freeze the children
container.flatten();


Теперь все потомки отрисовываются только один раз. Возможно, вы не увидите разницы на настольной платформе, но она заметна на мобильных устройствах. Конечно, вы можете использовать эту возможность динамически, вы можете изменить контент, вызвать unflatten, отрисовать его и вызвать flatten опять.


MovieClip



Мы рассмотрели, как работает Sprite, а что насчет анимации, похожей на MovieClip? Каждый Flash-разработчик знаком с концепцией MovieClip и уже несколько лет все больше разработчиков воссоздают функциональность MovieClip на основе BitmapData.

На рисунке ниже показана карта спрайтов, представляющих каждый кадр нашего MovieClip:

Рисунок 1.23 – Лист спрайтов для бегущего мальчика


Идея в том, что каждый кадр должен быть загружен в GPU и показан на сцене, обновляя текстуру в каждом кадре, мы воссоздаем концепцию MovieClip. Как же нам сделать такую анимацию? Ну, Flash Pro ваш лучший друг в этом случае, каждый кадр вашей анимации может быть экспортирован в последовательность изображений. Затем эти изображения загружаем в инструмент типа TexturePacker (http://www.texturepacker.com), который соединяет их в одну текстуру, которая используется для нашего MovieClip.

На рисунке ниже кадры на нашей карте спрайтов:

Рисунок 1.24 – Кадры на листе спрайтов


Если вы mip-текстурировали вашу карту, то вам нужно убедиться в том, что между кадрами есть расстояния в 2 пикселя, чтобы не было наложений. Т.е. чтобы когда в GPU будут загружаться эти кадры, то в них не попали пиксели из других кадров.

Запомните, что есть ограничения на размер текстур, вам нужно помнить, что Stage3D спроектирован с учетом ограничений мобильной платформы. В результате Stage3D учитывает ограничения OpenGL ES2, такие как кратность размеров текстур двум. В результате TexturePacker помогает вам учитывать эти ограничения и содержит возможность автоматического подбора размера для текстуры, ограничиваясь максимальным заданным размером (в Starling это 2048х2048).

Рисунок 1.25 – Выбор автоматического определения размеров в TexturePacker


Как мы уже говорили ранее, Starling автоматически проверяет кратность размеров текстур двум. Если это не так, то Starling позаботится об этом за вас и автоматически найдет следующее значение размера кратное двум и обрежет изображение.

Чтобы дать знать Starling, как располагаются кадры, вам нужно создать XML файл для объекта TextureAtlas, который генерирует из всего этого Vector текстур. TexturePacker генерирует этот файл автоматически, когда генерируется карта спрайтов. Кроме того вы можете выбрать формат: XML, JSON, etc.
Starling поддерживает обычный XML, ниже код такого XML:
























Прелесть этого в том, что вы получаете полный контроль над кадрами и следовательно частотой кадров, что позволяет вам иметь множество клипов с различной частотой кадров.

Конструктор MovieClip выглядит следующим образом:


public function MovieClip(textures:Vector., fps:Number=12)


В коде ниже, мы создаем текстуру содержащую кадры для нашего клипа:

[Embed(source = "../media/textures/running-sheet.png")]
private const SpriteSheet:Class;
var bitmap:Bitmap = new SpriteSheet();
var texture:Texture = Texture.fromBitmap(bitmap);

Затем мы передаем наш XML со значениями позиций кадров в карте спрайтов:


[Embed(source="../media/textures/running-sheet.xml", mimeType="application/octet-stream")]
public const SpriteSheetXML:Class;
var xml:XML = XML(new spriteSheetXML());
var sTextureAtlas:TextureAtlas = new TextureAtlas(texture, xml);


Теперь мы можем получить кадры нашего бегущего мальчика:


var frames:Vector. = sTextureAtlas.getTextures("running_");


Обратите внимание что мы передаем аргумент “running_” означающий, что нам нужны только определенные кадры из листа спрайтов, мы могли бы запросить другие кадры, например, “jump” или “fire” или любые другие, если они у нас определены.

Посмотрим на весь код целиком:


package
{
 import flash.display.Bitmap;
 import starling.core.Starling;
 import starling.display.MovieClip;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.textures.Texture;
 import starling.textures.TextureAtlas;
 
 public class Game3 extends Sprite
 {
  private var mMovie:MovieClip;
  [Embed(source="../media/textures/running-sheet.xml",mimeType="application/octet-stream")]
  public static const SpriteSheetXML:Class;
  [Embed(source="../media/textures/running-sheet.png")]
  private static const SpriteSheet:Class;
  
  public function Game3()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// creates the embedded bitmap (spritesheet file)
   var bitmap:Bitmap = new SpriteSheet();
// creates a texture out of it
   var texture:Texture = Texture.fromBitmap(bitmap);
// creates the XML file detailing the frames in the spritesheet
   var xml:XML = XML(new SpriteSheetXML());
// creates a texture atlas (binds the spritesheet and XML description)
   var sTextureAtlas:TextureAtlas = new TextureAtlas(texture, xml);
// retrieve the frames the running boy frames
   var frames:Vector. = sTextureAtlas.getTextures("running_");
// creates a MovieClip playing at 40fps
   mMovie = new MovieClip(frames, 40);
// centers the MovieClip
   mMovie.x = stage.stageWidth - mMovie.width >> 1;
   mMovie.y = stage.stageHeight - mMovie.height >> 1;
// show it
   addChild(mMovie);
  }
 }
}


Результат выглядит следующим образом:

Рисунок 1.26 – Бегущий мальчик


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

Чтобы отразить наш клип, просто используйте свойство scaleX и после скорректируйте позицию клипа:


mMovie = new MovieClip(frames, 40);
mMovie.scaleX = -1;
mMovie.x = (stage.stageWidth - mMovie.width >> 1) + mMovie.width;
mMovie.y = stage.stageHeight - mMovie.height >> 1;


Это даст нам следующий результат:

Рисунок 1.27 – Наш MovieClip отражен относительно вертикальной оси


Хранить все текстуры в одном файле – это хороший подход. Почему?

Во-первых, это удобно, все ресурсы вашей игры хранятся в одном файле. Использование одной текстуры минимизирует число загрузок в GPU. А это дорогая операция, особенно для мобильных устройств. Поэтому чем меньше загрузок, тем лучше. Наконец, переключение из одной текстуры в другую также затратно, поэтому лучше иметь ее одну.


Текстурная карта



Мы разобрались с понятием листа спрайтов, теперь давайте разберемся что такое текстурная карта. В текстурной карте, все наши ресурсы содержатся в одной текстуре.
На следующем рисунке мы добавили другую последовательность кадров в предыдущую карту:

Рисунок 1.28 – Текстурная карта


Наш XML файл теперь содержит еще текстуры мясника:







frameWidth="275" frameHeight="200"/>
frameWidth="275" frameHeight="200"/>


Теперь мы можем получить эти кадры с помощью метода getTextures класса TextureAtlas:


// retrieve the frames the running boy frames
var frames:Vector. = sTextureAtlas.getTextures("running_");
// retrieve the frames the running butcher
var framesButcher:Vector. = sTextureAtlas.getTextures("french-butcher_");
// creates a MovieClip playing at 40fps
mMovie = new MovieClip(frames, 40);
// creates a MovieClip playing at 25
mMovieButcher = new MovieClip(framesButcher, 25);
// positiomns them
mMovie.x = stage.stageWidth - mMovie.width >> 1;
mMovie.y = stage.stageHeight - mMovie.height >> 1;
mMovieButcher.x = mMovie.x + mMovie.width + 10;
mMovieButcher.y = mMovie.y;
// show them
addChild ( mMovie );
addChild ( mMovieButcher );


Теперь мясник рядом с маленьким мальчиком:

Рисунок 1.29


Заметьте, что на данный момент у нас есть клипы и они на сцене, но они не играют. Чтобы они заиграли нужно использовать объект Juggler.
По умолчанию он доступен в свойстве juggler объекта Starling. Для анимации мальчика и мясника нам нужно добавить следующие строчки:


// animate them
Starling.juggler.add ( mMovie );
Starling.juggler.add ( mMovieButcher );


Теперь наши клипы анимированы! Конечно, всегда вы можете остановить воспроизведение или поставить его на паузу:


// pause or stop the playback
mMovie.pause();
mMovie.stop();


Обратите внимание на различия между этими двумя методами. Pause останавливает на текущем кадре, а stop останавливает и переводит на первый кадр.

Посмотрим на интерфейс класса MovieClip, некоторые свойства и методы знакомы вам по нативному API Flash Player, другие, такие как возможность задавать частоту кадров, заменять или добавлять кадры в рантайме могут быть очень полезными.

- currentFrame : текущий кадр.
- fps : значение частоты кадров по-умолчанию.
- isPlaying : булево значение, показывающее состояние клипа проигрывается/не проигрывается.
- loop : булево значение зациклен/незациклен.
- numFrames : количество кадров в клипе.
- totalTime : продолжительность всех кадров.
- addFrame : добавляет кадры.
- addFrameAt : добавляет кадры в определенную позицию.
- getFrameDuration : возвращает продолжительность (в секундах) кадра с определенным индексом.
- getFrameSound : возвращает звук кадра с определенным индексом.
- getFrameTexture : возвращает текстуру кадра с определенным индексом.
- pause : ставит воспроизведение на паузу.
- play : начинает воспроизведение. Убедитесь, что клип добавлен в Juggler.
- removeFrameAt : удаляет кадры с определенной позиции.
- setFrameDuration : устанавливает продолжительность кадра в секундах.
- setFrameSound : устанавливает звук для кадра.
- setFrameTexture : устанавливает текстуру для конкретного кадра.

Мы не будем рассматривать здесь применение каждого метода, ограничимся наиболее полезными, например addFrameAt и removeFrameAt, позволяющими нам в рантайме добавлять или удалять кадры. Или даже устанавливать различную продолжительность для определенных кадров, и конечно хелперы isPlaying или loop.

В коде ниже, мы хотим для пятого кадра установить продолжительность в 2 секунды:


// frame 5 will length 2 seconds
mMovie.setFrameDuration(5, 2);


Мы можем динамически добавить звук к определенному кадру:


// frame 5 will length 2 seconds and play a sound when reached
mMovie.setFrameDuration(5, 2);
mMovie.setFrameSound(5, new StepSound() as Sound);


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


package
{
 import flash.display.Bitmap;
 import flash.ui.Keyboard;
 import starling.animation.Juggler;
 import starling.core.Starling;
 import starling.display.MovieClip;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.events.KeyboardEvent;
 import starling.textures.Texture;
 import starling.textures.TextureAtlas;
 
 public class Game3 extends Sprite
 {
  private var mMovie:MovieClip;
  private var j:Juggler;
  [Embed(source="../media/textures/running-sheet.xml",mimeType="application/octet-stream")]
  public static const SpriteSheetXML:Class;
  [Embed(source="../media/textures/running-sheet.png")]
  private static const SpriteSheet:Class;
  
  public function Game3()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// creates the embedded bitmap (spritesheet file)
   var bitmap:Bitmap = new SpriteSheet();
// creates a texture out of it
   var texture:Texture = Texture.fromBitmap(bitmap);
// creates the XML file detailing the frames in the spritesheet
   var xml:XML = XML(new SpriteSheetXML());
// creates a texture atlas (binds the spritesheet and XML description)
   var sTextureAtlas:TextureAtlas = new TextureAtlas(texture, xml);
// retrieve the frames the running boy frames
   var frames:Vector. = sTextureAtlas.getTextures("running_");
// creates a MovieClip playing at 40fps
   mMovie = new MovieClip(frames, 40);
// centers the MovieClip
   mMovie.x = stage.stageWidth - mMovie.width >> 1;
   mMovie.y = stage.stageHeight - mMovie.height >> 1;
// show it
   addChild(mMovie);
// animate it
   Starling.juggler.add(mMovie);
// on key down
   stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
  }
  
  private function onKeyDown(e:KeyboardEvent):void
  {
// repositions the boy accordingly
   if (mMovie.scaleX == -1)
    mMovie.x -= mMovie.width;
// if right key or left key
   var state:int;
   if (e.keyCode == Keyboard.RIGHT)
    state = 1;
   else if (e.keyCode == Keyboard.LEFT)
    state = -1;
// flip the running boy
   mMovie.scaleX = state;
// repositions the boy accordingly
   if (mMovie.scaleX == -1)
    mMovie.x = mMovie.x + mMovie.width;
  }
 }
}


Если нужно слушать окончание анимации, то вы можете слушать событие Event.MOVIE_COMPLETED:


// listen to the end of the animation
mMovie.addEventListener(Event.MOVIE_COMPLETED, onAnimationComplete);


В этом примере мы использовали объект Juggler, давайте посмотрим на его работ изнутри и что мы с этим можем делать.


Juggler



Интерфейс объекта Juggler позволяет анимировать любой объект, реализующий интерфейс IAnimatable. MovieClip реализует последний, но можно также определить собственный тип анимированного объекта в Starling, все что вам нужно, это реализовать интерфейс IAnimatable и переопределить метод advanceTime. Как это сделать мы посмотрим в конце этого руководства, когда будем работать с частицами.

Ниже вы увидите как анимация реализована в MovieClip, главная логика расположена здесь. В каждом кадре текстура меняется. Если сравнивать с нативным API, то это похоже на замену bitmapData у объекта Bitmap в каждом кадре:


// IAnimatable
public function advanceTime(passedTime:Number):void
{
 if (mLoop && mCurrentTime == mTotalTime)
  mCurrentTime = 0.0;
 if (!mPlaying || passedTime == 0.0 || mCurrentTime == mTotalTime)
  return;
 var i:int = 0;
 var durationSum:Number = 0.0;
 var previousTime:Number = mCurrentTime;
 var restTime:Number = mTotalTime - mCurrentTime;
 var carryOverTime:Number = passedTime > restTime ? passedTime - restTime : 0.0;
 mCurrentTime = Math.min(mTotalTime, mCurrentTime + passedTime);
 for each (var duration:Number in mDurations)
 {
  if (durationSum + duration >= mCurrentTime)
  {
   if (mCurrentFrame != i)
   {
    mCurrentFrame = i;
    updateCurrentFrame();
    playCurrentSound();
   }
   break;
  }
  ++i;
  durationSum += duration;
 }
 if (previousTime < mTotalTime && mCurrentTime == mTotalTime && hasEventListener(Event.MOVIE_COMPLETED))
 {
  dispatchEvent(new Event(Event.MOVIE_COMPLETED));
 }
 advanceTime(carryOverTime);
}


Интерфейс объекта
• add : добавляет объект в Juggler.
• advanceTime : метод для ручного управления Juggle.
• delayCall : устанавливает промежуток времени, через который будет вызвать определенный метод.
• elapsedTime : общее время существования Juggler.
• isComplete : статус Juggler.
• purge : удаляет все объекты.
• remove : удаляет один объект.
• removeTweens : удаляет все объекты типа Tween, примененные к определенному объекту.

В некоторых случаях вам может понадобиться создать другой Juggler для анимации пока основной контент вашей игры стоит на паузе. Для анимации таких элементов как меню вы можете использовать другой экземпляр Juggler, все, что нужно - это создать его и использовать его метод advanceTime.

Другая интересная возможность жонглера – возможность отложенных вызовов методов. В следующем коде мы используем жонглера для удаления потомка из родителя через определенный момент времени.


juggler.delayCall(object.removeFromParent, 1.0);


Теперь посмотрим на важную часть Starling в плане интерактивности – Button


Button



В Starling есть нативный компонент Button. Вот сигнатура конструктора класса Button:


public function Button(upState:Texture, text:String="", downState:Texture=null)


По-умолчанию, класс Button создает TextField для названия кнопки и это текстовое поле центрируется внутри кнопки. В коде ниже мы создаем простую кнопку из растрового изображения, которое используем как скин:


package
{
 import flash.display.Bitmap;
 import starling.display.Button;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.textures.Texture;
 
 public class Game4 extends Sprite
 {
  [Embed(source="../media/textures/button_normal.png")]
  private static const ButtonTexture:Class;
  
  public function Game4()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create a Bitmap object out of the embedded image
   var buttonSkin:Bitmap = new ButtonTexture();
// create a Texture object to feed the Button object
   var texture:Texture = Texture.fromBitmap(buttonSkin);
// create a button using this skin as up state
   var myButton:Button = new Button(texture, "Play");
// createa container for the menu (buttons)
   var menuContainer:Sprite = new Sprite();
// add the button to our container
   menuContainer.addChild(myButton);
// centers the menu
   menuContainer.x = stage.stageWidth - menuContainer.width >> 1;
   menuContainer.y = stage.stageHeight - menuContainer.height >> 1;
// show the button
   addChild(menuContainer);
  }
 }
}


Обратите внимание, мы используем метод fromBitmap для создания текстуры, которую будем использовать в качестве скина для кнопки:


// create a Texture object to feed the Button object
var texture:Texture = Texture.fromBitmap(buttonSkin);


Давайте создадим простое меню из каких-нибудь данных, которые будут храниться в Vector, с помощью простого цикла:


package
{
 import flash.display.Bitmap;
 import starling.display.Button;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.textures.Texture;
 
 public class Game4 extends Sprite
 {
  [Embed(source="../media/textures/button_normal.png")]
  private static const ButtonTexture:Class;
// sections
  private var _sections:Vector. = Vector.(["Play", "Options", "Rules", "Sign in"]);
  
  public function Game4()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create a Bitmap object out of the embedded image
   var buttonSkin:Bitmap = new ButtonTexture();
// create a Texture object to feed the Button object
   var texture:Texture = Texture.fromBitmap(buttonSkin);
// createa container for the menu (buttons)
   var menuContainer:Sprite = new Sprite();
   var numSections:uint = _sections.length
   for (var i:uint = 0; i < 4; i++)
   {
// create a button using this skin as up state
    var myButton:Button = new Button(texture, _sections[i]);
// bold labels
    myButton.fontBold = true;
// position the buttons
    myButton.y = myButton.height * i;
// add the button to our container
    menuContainer.addChild(myButton);
   }
// centers the menu
   menuContainer.x = stage.stageWidth - menuContainer.width >> 1;
   menuContainer.y = stage.stageHeight - menuContainer.height >> 1;
// show the button
   addChild(menuContainer);
  }
 }
}


В результате мы получим следующее:

Рисунок 1.30 – Простое меню из кнопок


Мы не использовали здесь лист спрайтов из всех скинов для кнопки. Мы просто внедрили один скин и загрузили его в GPU с помощью статического метода fromBitmap() класса Texture. Это здорово, если нужен только один скин для всех кнопок. Но лучше определять все ваши скины внутри одной текстурной карты, также как мы ранее делали с клипами мальчика и мясника.

Давайте посмотрим на список свойств, доступных в классе Button:
• alphaWhenDisabled : значение прозрачности, когда кнопка отключена.
• downState : текстура для кнопки, если она находится в нажатом состоянии.
• enabled : включает/выключает кнопку для воздействия пользователя.
• fontBold : устанавливает вес шрифта лейбла.
• fontColor : цвет шрифта лейбла.
• fontName : шрифт лейбла, может быть системным или встроенным растровым.
• fontSize : размер шрифта лейбла.
• scaleWhenDown : коэффициент масштабирования для кнопки в нажатом состоянии. Если используется для нажатого состояния своя текстура, то масштабирования не будет.
• text : текст лейбла.
• textBounds : позиция лейбла.
• upState : текстура для кнопки по-умолчанию.

В отличие от нативной кнопки, объект Button это подкласс класса DisplayObjectContainer, а это значит, что вы не ограничены состояними вашей кнопки. Вы можете сделать ее такой, какой пожелаете.

Обратите внимание, что Button диспатчит особенное событие Event.TRIGGERED для обработки клика:


// listen to the Event.TRIGGERED event
myButton.addEventListener(Event.TRIGGERED, onTriggered);
private function onTriggered(e:Event):void
{
trace ("I got clicked!");
}


Событие Event.TRIGGERED всплывает, если вы отследить событие, то вам можете регистрировать приемник события в контейнере:


package
{
 import flash.display.Bitmap;
 import starling.display.Button;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.textures.Texture;
 
 public class Game4 extends Sprite
 {
  [Embed(source="../media/textures/button_normal.png")]
  private static const ButtonTexture:Class;
// sections
  private var _sections:Vector. = Vector.(["Play", "Options", "Rules", "Sign in"]);
  
  public function Game4()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create a Bitmap object out of the embedded image
   var buttonSkin:Bitmap = new ButtonTexture();
// create a Texture object to feed the Button object
   var texture:Texture = Texture.fromBitmap(buttonSkin);
// createa container for the menu (buttons)
   var menuContainer:Sprite = new Sprite();
   var numSections:uint = _sections.length
   for (var i:uint = 0; i < 4; i++)
   {
// create a button using this skin as up state
    var myButton:Button = new Button(texture, _sections[i]);
// bold labels
    myButton.fontBold = true;
// position the buttons
    myButton.y = myButton.height * i;
// add the button to our container
    menuContainer.addChild(myButton);
   }
// catch the Event.TRIGGERED event
   menuContainer.addEventListener(Event.TRIGGERED, onTriggered);
// centers the menu
   menuContainer.x = stage.stageWidth - menuContainer.width >> 1;
   menuContainer.y = stage.stageHeight - menuContainer.height >> 1;
// show the button
   addChild(menuContainer);
  }
  
  private function onTriggered(e:Event):void
  {
// outputs : [object Sprite] [object Button]
   trace(e.currentTarget, e.target);
// outputs : triggered!
   trace("triggered!");
  }
 }
}


Добавим к нашему пользовательскому интерфейсу фон, например простую текстуру, которая будет двигаться на заднем плане:


package
{
 import flash.display.Bitmap;
 import starling.display.Button;
 import starling.display.Image;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.textures.Texture;
 
 public class Game4 extends Sprite
 {
  [Embed(source="../media/textures/button_normal.png")]
  private static const ButtonTexture:Class;
  [Embed(source="../media/textures/background.jpg")]
  private static const BackgroundImage:Class;
  private var backgroundContainer:Sprite;
  private var background1:Image;
  private var background2:Image;
// sections
  private var sections:Vector. = Vector.(["Play", "Options", "Rules", "Sign in"]);
  
  public function Game4()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create a Bitmap object out of the embedded image
   var buttonSkin:Bitmap = new ButtonTexture();
// create a Texture object to feed the Button object
   var texture:Texture = Texture.fromBitmap(buttonSkin);
// create a Bitmap object out of the embedded image
   var background:Bitmap = new BackgroundImage();
// create a Texture object to feed the Image object
   var textureBackground:Texture = Texture.fromBitmap(background);
// container for the background textures
   backgroundContainer = new Sprite();
// create the images for the background
   background1 = new Image(textureBackground);
   background2 = new Image(textureBackground);
// positions the second part
   background2.x = background1.width;
// nest them
   backgroundContainer.addChild(background1);
   backgroundContainer.addChild(background2);
// show the background
   addChild(backgroundContainer);
// create container for the menu (buttons)
   var menuContainer:Sprite = new Sprite();
   var numSections:uint = sections.length
   for (var i:uint = 0; i < 4; i++)
   {
// create a button using this skin as up state
    var myButton:Button = new Button(texture, sections[i]);
// bold labels
    myButton.fontBold = true;
// position the buttons
    myButton.y = myButton.height * i;
// add the button to our container
    menuContainer.addChild(myButton);
   }
// catch the Event.TRIGGERED event
// catch the Event.TRIGGERED event
   menuContainer.addEventListener(Event.TRIGGERED, onTriggered);
// on each frame
   stage.addEventListener(Event.ENTER_FRAME, onFrame);
// centers the menu
   menuContainer.x = stage.stageWidth - menuContainer.width >> 1;
   menuContainer.y = stage.stageHeight - menuContainer.height >> 1;
// show the button
   addChild(menuContainer);
  }
  
  private function onTriggered(e:Event):void
  {
// outputs : [object Sprite] [object Button]
   trace(e.currentTarget, e.target);
// outputs : triggered!
   trace("triggered!");
  }
  
  private function onFrame(e:Event):void
  {
// scroll it
   backgroundContainer.x -= 10;
// reset
   if (backgroundContainer.x <= -background1.width)
    backgroundContainer.x = 0;
  }
 }
}


Теперь у нас есть меню с кнопками и задним фоном:

Рисунок 1.31


Обратите внимание, что мы добавили небольшой эффект размытия в Photoshop, для создания ощущения движения, когда картинка на заднем фоне перемещается.


TextField



Мы использовали класс starling.text.TextField раньше, когда создавали квадрат с текстом. Давайте рассмотрим как работает текст в Starling получше. Starling создает нативное текстовое поле и растрирует его и полученную текстуру загружает в GPU.

Заметьте, что объект flash.text.TextField не создается для каждого нового starling.text.TextField. Он создается один раз, сохраняется и используется для рендеринга всего текста.

В следующем коде мы создаем объект TextField и присваиваем ему шрифт Verdana:


package
{
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.text.TextField;
 
 public class Game5 extends Sprite
 {
  public function Game5()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
   // create the TextField object
   var legend:TextField = new TextField(300, 300, "Here is some text, using an embedded font!", "Verdana", 38, 0xFFFFFF);
   // centers the text on stage
   legend.x = stage.stageWidth - legend.width >> 1;
   legend.y = stage.stageHeight - legend.height >> 1;
   // show it
   addChild(legend);
  }
 }
}


Результат:

Рисунок 1.32


Свойства объекта TextField:
- alpha : прозрачность текста.
- autoScale : автомасштабирование текста до заданных размеров.
- bold : вес шрифта.
- border : показывает границу текстового поля. Полезно при отладке.
- bounds : границы символа в текстовом поле.
- color : цвет текста.
- fontName : имя шрифта.
- fontSize : размер шрифта.
- hAlign : горизонтальное выравнивание текста.
- italic : курсив.
- kerning : позволяет использовать кернинг (если доступен).
- text : текст.
- textBounds : границы символа в текстовом поле.
- underline : подчеркивает текст.
- vAlign : горизонтальное выравнивание текста.

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


// create the TextField object
var legend:TextField = new TextField(300, 300, "Here is some text, using an embedded font!", "Verdana", 38,
0xFFFFFF);
// change the color, set bold and enable a border
legend.color = 0x990000;
legend.bold = true;
legend.border = true;


Результат:

Рисунок 1.33 – Простой цветной текст


TextField не поддерживает HTML текст. Однако в будущем такая возможность может быть реализована. Не стесняйтесь выражать свое мнение на форуме Starling, если хотите, чтобы в фреймворк была добавлена та или иная функциональность. Starling это опенсорсный проект, вы можете реализовать возможности самостоятельно и поделиться ими с сообществом.
В предыдущем примере мы использовали системный шрифт, но вам может понадобиться внедренный. Сначала вам нужно внедрить или загрузить шрифт, а затем просто передать его в конструктор TextField.

В коде ниже мы внедряем шрифт, создаем его экземпляр и передаем его fontName в конструктор:


package
{
 import flash.text.Font;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.text.TextField;
 
 public class Game5 extends Sprite
 {
  [Embed(source='/../media/fonts/Abduction.ttf',embedAsCFF='false',fontName='Abduction')]
  public static var Abduction:Class;
  
  public function Game5()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create the font
   var font:Font = new Abduction();
// create the TextField object
   var legend:TextField = new TextField(300, 300, "Here is some text, using an embedded font!", font.fontName, 38, 0xFFFFFF);
// centers the text on stage
   legend.x = stage.stageWidth - legend.width >> 1;
   legend.y = stage.stageHeight - legend.height >> 1;
// show it
   addChild(legend);
  }
 }
}


На рисунке ниже результат использования внедренного шрифта Abducion:

Рисунок 1.34 – Простой текст с использованием внедренного шрифта


Все предельно просто. В ваших приложения вам может понадобиться текстовое поле для ввода, например, имени пользователя или другой информации. Как вы можете уже представить, редактирование текста это непростая вещь для реализации на GPU. Для этого мы будем использовать нативный список отображения.

Т.к. список отображения распологается выше содержимого Stage3D, мы можем поместить текстовое поле над Stage3D.

Раньше мы видели, как Starling показывает сообщение об ошибке, если wmode присвоено неверное значение. Вызывается метод showFatalError, который показывает это самое сообщение над Stage3D в нативный спрайт (свойство nativeOverlay) который добавлен в нативный stage:


private function showFatalError(message:String):void
{
var textField:TextField = new TextField();
var textFormat:TextFormat = new TextFormat("Verdana", 12, 0xFFFFFF);
textFormat.align = TextFormatAlign.CENTER;
textField.defaultTextFormat = textFormat;
textField.wordWrap = true;
textField.width = mStage.stageWidth * 0.75;
textField.autoSize = TextFieldAutoSize.CENTER;
textField.text = message;
textField.x = (mStage.stageWidth - textField.width) / 2;
textField.y = (mStage.stageHeight - textField.height) / 2;
textField.background = true;
textField.backgroundColor = 0x440000;
nativeOverlay.addChild(textField);
}


В коде ниже мы создаем текстовое поле для ввода, добавляем его над нашим содержимым Starling в нативный список отображения:


var textInput:flash.text.TextField = new flash.text.TextField();
textInput.type = TextFieldType.INPUT;
Starling.current.nativeOverlay.addChild(textInput);



Растровые шрифты



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

Ниже скриншот инструмента GliphDesigner, который готовит лист спрайтов для шрифта Britannic Bold:

Рисунок 1.35 – GlyphDesigner на MacOs


На Windows есть похожая программа, называемая Bitmap Font Generator:

Рисунок 1.36 - Bitmap Font Generator


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

При экспорте ваша текстура будет сохранена в изображение, а также будет сгенерирован файл описания, в котором указаны позиции каждого глифа в изображении следующим образом:





 






 



Когда у нас все готово, мы можем внедрять шрифт как обычно:


[Embed(source = "../media/fonts/britannic-bold.png")]
private static const BitmapChars:Class;
[Embed(source="../media/fonts/britannic-bold.fnt", mimeType="application/octet-stream")]
private static const BritannicXML:Class;


Теперь, чтобы использовать шрифт, мы должны его зарегистрировать с помощью метода registerBitmapFont (если он не нужен, то можно отменить регистрацию unregisterBitmapFont):


package
{
 import flash.display.Bitmap;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.text.BitmapFont;
 import starling.text.TextField;
 import starling.textures.Texture;
 import starling.utils.Color;
 
 public class Game5 extends Sprite
 {
  [Embed(source="../media/fonts/britannic-bold.png")]
  private static const BitmapChars:Class;
  [Embed(source="../media/fonts/britannic-bold.fnt",mimeType="application/octet-stream")]
  private static const BritannicXML:Class;
  
  public function Game5()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// creates the embedded bitmap (spritesheet file)
   var bitmap:Bitmap = new BitmapChars();
// creates a texture out of it
   var texture:Texture = Texture.fromBitmap(bitmap);
// create the XML file describing the glyphes position on the spritesheet
   var xml:XML = XML(new BritannicXML());
// register the bitmap font to make it available to TextField
   TextField.registerBitmapFont(new BitmapFont(texture, xml));
// create the TextField object
   var bmpFontTF:TextField = new TextField(400, 400, "Here is some text, using an embedded font!", "BritannicBold", 10);
// the native bitmap font size, no scaling
   bmpFontTF.fontSize = BitmapFont.NATIVE_SIZE;
// use white to use the texture as it is (no tinting)
   bmpFontTF.color = Color.WHITE;
// centers the text on stage
   bmpFontTF.x = stage.stageWidth - bmpFontTF.width >> 1;
   bmpFontTF.y = stage.stageHeight - bmpFontTF.height >> 1;
// show it
   addChild(bmpFontTF);
  }
 }
}


Зарегистрировав шрифт, вы можете использовать его имя при создании TextField.

Результат:

Рисунок 1.37


Теперь давайте сделаем текст чуть длиннее и посмотрим, что случится:


var bmpFontTF:TextField = new TextField(400, 400, "Here is some longer text that is very likely to be cut, usingan embedded font!", "BritannicBold", 10);


Границы текстового поля слишком малы для того, чтобы вместить весь текст, в результате текст не поместился целиком:

Рисунок 1.38


К счастью в Starling есть свойство autoScale:


// make the text fit into the box
bmpFontTF.autoScale = true;


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

Ниже результат работы свойства autoScaled:

Рисунок 1.39


Мы можем улучшить наш предыдущий пример с меню:

package
{
 import flash.display.Bitmap;
 import flash.geom.Rectangle;
 import starling.display.Button;
 import starling.display.Image;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.text.BitmapFont;
 import starling.text.TextField;
 import starling.textures.Texture;
 
 public class Game4 extends Sprite
 {
  [Embed(source="../media/textures/sausage-skin.png")]
  private static const ButtonTexture:Class;
  [Embed(source="../media/textures/background.jpg")]
  private static const BackgroundImage:Class;
  [Embed(source="../media/fonts/hobo-std.png")]
  private static const BitmapChars:Class;
  [Embed(source="../media/fonts/hobo-std.fnt",mimeType="application/octet-stream")]
  private static const Hobo:Class;
  private static const FONT_NAME:String = "HoboStd";
  private var backgroundContainer:Sprite;
  private var background1:Image;
  private var background2:Image;
// sections
  private var sections:Vector. = Vector.(["Play", "Options", "Rules", "Sign in"]);
  
  public function Game4()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// creates the embedded bitmap (spritesheet file)
   var bitmap:Bitmap = new BitmapChars();
// creates a texture out of it
   var texture:Texture = Texture.fromBitmap(bitmap);
// create the XML file describing the glyphes position on the spritesheet
   var xml:XML = XML(new Hobo());
// register the bitmap font to make it available to TextField
   TextField.registerBitmapFont(new BitmapFont(texture, xml));
// create a Bitmap object out of the embedded image
   var buttonSkin:Bitmap = new ButtonTexture();
// create a Texture object to feed the Button object
   var textureSkin:Texture = Texture.fromBitmap(buttonSkin);
// create a Bitmap object out of the embedded image
   var background:Bitmap = new BackgroundImage();
// create a Texture object to feed the Image object
   var textureBackground:Texture = Texture.fromBitmap(background);
// container for the background textures
   backgroundContainer = new Sprite();
// create the images for the background
   background1 = new Image(textureBackground);
   background2 = new Image(textureBackground);
// positions the second part
   background2.x = background1.width;
// nest them
   backgroundContainer.addChild(background1);
   backgroundContainer.addChild(background2);
// show the background
   addChild(backgroundContainer);
// create container for the menu (buttons)
   var menuContainer:Sprite = new Sprite();
   var numSections:uint = sections.length
   for (var i:uint = 0; i < numSections; i++)
   {
// create a button using this skin as up state
    var myButton:Button = new Button(textureSkin, sections[i]);
// font name
    myButton.fontName = FONT_NAME;
    myButton.fontColor = 0xFFFFFF;
// positions the text
    myButton.textBounds = new Rectangle(10, 38, 160, 30);
// font size
    myButton.fontSize = 26;
// position the buttons
    myButton.y = (myButton.height - 10) * i;
// add the button to our container
    menuContainer.addChild(myButton);
   }
// catch the Event.TRIGGERED event
   menuContainer.addEventListener(Event.TRIGGERED, onTriggered);
// on each frame
   stage.addEventListener(Event.ENTER_FRAME, onFrame);
// centers the menu
   menuContainer.x = stage.stageWidth - menuContainer.width >> 1;
   menuContainer.y = stage.stageHeight - menuContainer.height >> 1;
// show the button
   addChild(menuContainer);
  }
  
  private function onTriggered(e:Event):void
  {
// outputs : [object Sprite] [object Button]
   trace(e.currentTarget, e.target);
// outputs : triggered!
   trace("triggered!");
  }
  
  private function onFrame(e:Event):void
  {
// scroll it
   backgroundContainer.x -= 10;
// reset
   if (backgroundContainer.x <= -background1.width)
    backgroundContainer.x = 0;
  }
 }
}



Рисунок 1.40 – В меню используется растровый шрифт



RenderTexture



Класс starling.textures.RenderTexture позволяет создавать неразрывное рисование. Думайте об этом как о BitmapData. Этот класс может быть очень полезен при создании приложений для рисования.

В коде ниже мы повторяем функциональность метода draw объекта BitmapData, на GPU с использованием Starling:


package
{
 import flash.display.Bitmap;
 import flash.geom.Point;
 import starling.display.Image;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.events.Touch;
 import starling.events.TouchEvent;
 import starling.events.TouchPhase;
 import starling.textures.RenderTexture;
 import starling.textures.Texture;
 
 public class Game10 extends Sprite
 {
  private var mRenderTexture:RenderTexture;
  private var mBrush:Image;
  [Embed(source="/../media/textures/egg_closed.png")]
  private static const Egg:Class;
  
  public function Game10()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create a Bitmap object out of the embedded image
   var brush:Bitmap = new Egg();
// create a Texture object to feed the Image object
   var texture:Texture = Texture.fromBitmap(brush);
// create the texture to draw into the texture
   mBrush = new Image(texture);
// set the registration point
   mBrush.pivotX = mBrush.width >> 1;
   mBrush.pivotY = mBrush.height >> 1;
// scale it
   mBrush.scaleX = mBrush.scaleY = 0.5;
// creates the canvas to draw into
   mRenderTexture = new RenderTexture(stage.stageWidth, stage.stageHeight);
// we encapsulate it into an Image object
   var canvas:Image = new Image(mRenderTexture);
// show it
   addChild(canvas);
// listen to mouse interactions on the stage
   stage.addEventListener(TouchEvent.TOUCH, onTouch);
  }
  
  private function onTouch(event:TouchEvent):void
  {
// retrieves the entire set of touch points (in case of multiple fingers on a touch screen)
   var touches:Vector. = event.getTouches(this);
   for each (var touch:Touch in touches)
   {
// if only hovering or click states, let's skip
    if (touch.phase == TouchPhase.HOVER || touch.phase == TouchPhase.ENDED)
     continue;
// grab the location of the mouse or each finger
    var location:Point = touch.getLocation(this);
// positions the brush to draw
    mBrush.x = location.x;
    mBrush.y = location.y;
// draw into the canvas
    mRenderTexture.draw(mBrush);
   }
  }
 }
}


Если зажать левую кнопку мыши и перемещать мышь, то мы получим подобный результат:

Рисунок 1.41 – Неразрывное рисование


Теперь обсудим очень известную Flash-разработчикам тему – твининг.


Твининг



Starling поддерживает твининг и имеет собственный движок для этого, реализующий большинство функций твинов:

Рисунок 1.42 – Уравнения замедления доступные в Starling


В коде ниже мы применяем твин к координатам х и у текстового поля:


// create a Tween object
var t:Tween = new Tween(bmpFontTF, 4, Transitions.EASE_IN_OUT_BOUNCE);
// move the object position
t.moveTo(bmpFontTF.x+300, bmpFontTF.y);
// add it to the Juggler
Starling.juggler.add(t);


Ниже интерфейс объекта Tween:

• animate : анимирует свойство до указанного значения. Вы можете вызывать этот метод множество раз для одного твина.
• complete : End the tween when called.
• currentTime : The current timing for the tween.
• delay : задержка перед началом твина.
• fadeTo : изменяет значение прозрачности до указанного значения. Вы можете вызывать этот метод множество раз для одного твина.
• isComplete : показывает завершился ли твин.
• moveTo : изменяте свойства х и у объекта.
• onComplete : ссылка на функцию, которая будет вызвана при завершении твина.
• onCompleteArgs : параметры, которые будут переданы в функцию, которая будет вызвана при завершении твина.
• onStart : ссылка на функцию, которая будет вызвана в начале твина.
• onStartArgs : параметры, которые будут переданы в функцию, которая будет вызвана в начале твина.
• onUpdate : ссылка на функцию, которая будет вызвана в процессе твина.
• onUpdateArgs : параметры, передаваемые в функцию, которая будет вызываться в процессе твина.
• roundToInt :
• scaleTo : анимация масштабирования.
• target : целевой объект для анимации.
• totalTime : время продолжительности твина.
• transition : функция твина.

В качестве примера, в коде ниже мы слушаем окончания твина, затем мы удаляем объект из потомка, установив в качестве ссылки на функцию по окончанию твина метод removeFromParent:


// create a Tween object
var t:Tween = new Tween(bmpFontTF, 4, Transitions.EASE_IN_OUT_BOUNCE);
// move the object position
t.moveTo(bmpFontTF.x+300, bmpFontTF.y);
t.animate("alpha", 0);
// add it to the Juggler
Starling.juggler.add(t);
// on complete, remove the textfield from the stage
t.onComplete = bmpFontTF.removeFromParent;
// pass the dispose argument to the removeFromParent call
t.onCompleteArgs = [true];


В коде ниже мы слушаем прогресс твина и используем каллбаки для onStart, onUpdate и onComplete:


package
{
 import flash.text.Font;
 import starling.animation.Transitions;
 import starling.animation.Tween;
 import starling.core.Starling;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.text.TextField;
 
 public class Game5 extends Sprite
 {
  [Embed(source='/../media/fonts/Abduction.ttf',embedAsCFF='false',fontName='Abduction')]
  public static var Abduction:Class;
  
  public function Game5()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// create the font
   var font:Font = new Abduction();
// create the TextField object
   var legend:TextField = new TextField(300, 300, "Here is some text, using an embedded font!", font.fontName, 38, 0xFFFFFF);
// centers the text on stage
   legend.x = stage.stageWidth - legend.width >> 1;
   legend.y = stage.stageHeight - legend.height >> 1;
// create a Tween object
   var t:Tween = new Tween(legend, 4, Transitions.EASE_IN_OUT_BOUNCE);
// move the object position
   t.moveTo(legend.x + 300, legend.y);
// add it to the Juggler
   Starling.juggler.add(t);
// listen to the start
   t.onStart = onStart;
// listen to the progress
   t.onUpdate = onProgress;
// listen to the end
   t.onComplete = onComplete;
// show it
   addChild(legend);
  }
  
  private function onStart():void
  {
   trace("tweening complete");
  }
  
  private function onProgress():void
  {
   trace("tweening in progress");
  }
  
  private function onComplete():void
  {
   trace("tweening complete")
  }
 }
}


Теперь давайте посмотрим, как мы можем управлять нашими внедренными ресурсами. Прежде мы просто использовали тег embed когда нам это было нужно. В больших проектах, вы возможно захотите хранить все свои ресурсы в одном месте. В следующем разделе мы рассмотрим простые техники для группировки ресурсов в одном месте и повторного их использования.


Управление ресурсами



Прежде мы использовали ресурсы очень просто. Чтобы оптимизировать работу с ресурсами очень рекомендуется использовать объект Assets в качестве центрального хранилища для получения ресурсов. Объект Assets служит для хранения всех ресурсов и дает возможность их повторного использования без ошибок и нагрузки на GC.

В следующем коде мы спроектируем метод getTexture для объекта Assets, который возвращает внедренную текстуру


public static function getTexture(name:String):Texture
{
if (Assets[name] != undefined)
{
if (sTextures[name] == undefined)
{
var bitmap:Bitmap = new Assets[name]();
sTextures[name] = Texture.fromBitmap(bitmap);
}
return sTextures[name];
} else throw new Error("Resource not defined.");
}


Обратите внимание, мы используем простой Dictionary для хранения ресурсов, в следующий раз, когда нам понадобиться ресурс, мы его возьмем из пула, а не будем создавать заново.
Как обычно, текстуры внедряются с помощью тега Embed:


[Embed(source = "../media/textures/background.png")]
private static const Background:Class;


Текстуры можно не только внедрять, но и загружать динамически с помощью объекта Loader:


// create the Loader
var loader:Loader = new Loader();
// listen to the Event.COMPLETE event
loader.contentLoaderInfo.addEventListener ( Event.COMPLETE, onComplete );
// load the image
loader.load ( new URLRequest ("texture.png" ) );
function onComplete ( e:Event ):void
{
// create the Bitmap
var bitmap:Bitmap = e.currentTarget.data;
// creates a texture out of it
var texture:Texture = Texture.fromBitmap(bitmap);
// create the Image object
var image:Image = new Image (texture);
// show the image
addChild (image);
}


Текстуры можно генерировать динамически из объекта BitmapData, прежде мы использовали подготовленные заранее текстуры, но сейчас попробуем создать их в рантайме.
Для этого используем статический метод fromBitmapData класса Texture:


// creates a dynamic bitmap
var dynamicBitmap:BitmapData = new BitmapData (512, 512);
// we draw a custom vector shape coming from the library or drawn at runtime too
dynamicBitmap.draw ( myCustomShape );
// creates a texture out of it
var texture:Texture = Texture.fromBitmapData(bitmap);
// create the Image object
var image:Image = new Image (texture);
// show the image
addChild (image);


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


Обработка изменения размера экрана



Как Flash-разработчик для обработки изменения размера экрана вы в основном полагались на событие Event.RESIZE. Используя свойства stage.stageWidth и stage.stageHeight вы могли разместить ваше содержимое соответствующим образом, не выходя за границы экрана.

В Starling используется похожий механизм – объект starling.display.Stage диспатчит событие ResizeEvent.RESIZE, позволяющее нам обрабатывать изменение размера экрана.
В следующем коде мы обновляем позицию квадрата на сцене:


package
{
 import flash.geom.Rectangle;
 import starling.core.Starling;
 import starling.display.Quad;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.events.ResizeEvent;
 
 public class Game extends Sprite
 {
  private var q:Quad;
  private var rect:Rectangle = new Rectangle(0, 0, 0, 0);
  
  public function Game()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// listen to the event
   stage.addEventListener(ResizeEvent.RESIZE, onResize);
   q = new Quad(200, 200);
   q.color = 0x00FF00;
   q.x = stage.stageWidth - q.width >> 1;
   q.y = stage.stageHeight - q.height >> 1;
   addChild(q);
  }
  
  private function onResize(event:ResizeEvent):void
  {
// set rect dimmensions
   rect.width = event.width, rect.height = event.height;
// resize the viewport
   Starling.current.viewPort = rect;
// assign the new stage width and height
   stage.stageWidth = event.width;
   stage.stageHeight = event.height;
// repositions our quad
   q.x = stage.stageWidth - q.width >> 1;
   q.y = stage.stageHeight - q.height >> 1;
  }
 }
}


Всегда, когда размер SWF меняется диспатчится событие ResizeEvent.RESIZE, новые размеры хранятся в объекте ResizeEvent, мы вручную присваиваем их значения свойствам stageWidth и stageHeight. Затем мы обновляем позицию нашего контента.


Starling и Box2D



Starling очень просто заставить работать с другими фреймворками. Например мы хотим использовать Box2D для добавления какой-нибудь физики в игру. Больше информации о Box2D вы можете получить по адресу http://box2dflash.sourceforge.net/

В следующем коде мы сделаем падающие на землю ящики:


package
{
 import Box2D.Collision.Shapes.b2CircleShape;
 import Box2D.Collision.Shapes.b2PolygonShape;
 import Box2D.Common.Math.b2Vec2;
 import Box2D.Dynamics.b2Body;
 import Box2D.Dynamics.b2BodyDef;
 import Box2D.Dynamics.b2FixtureDef;
 import Box2D.Dynamics.b2World;
 import starling.display.DisplayObject;
 import starling.display.Quad;
 import starling.display.Sprite;
 import starling.events.Event;
 
 public class PhysicsTest extends Sprite
 {
  private var mMainMenu:Sprite;
  private var bodyDef:b2BodyDef;
  private var inc:int;
  public var m_world:b2World;
  public var m_velocityIterations:int = 10;
  public var m_positionIterations:int = 10;
  public var m_timeStep:Number = 1.0 / 30.0;
  
  public function PhysicsTest()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// Define the gravity vector
   var gravity:b2Vec2 = new b2Vec2(0.0, 10.0);
// Allow bodies to sleep
   var doSleep:Boolean = true;
// Construct a world object
   m_world = new b2World(gravity, doSleep);
// Vars used to create bodies
   var body:b2Body;
   var boxShape:b2PolygonShape;
   var circleShape:b2CircleShape;
// Add ground body
   bodyDef = new b2BodyDef();
//bodyDef.position.Set(15, 19);
   bodyDef.position.Set(10, 28);
//bodyDef.angle = 0.1;
   boxShape = new b2PolygonShape();
   boxShape.SetAsBox(30, 3);
   var fixtureDef:b2FixtureDef = new b2FixtureDef();
   fixtureDef.shape = boxShape;
   fixtureDef.friction = 0.3;
// static bodies require zero density
   fixtureDef.density = 0;
// Add sprite to body userData
   var box:Quad = new Quad(2000, 200, 0xCCCCCC);
   box.pivotX = box.width / 2.0;
   box.pivotY = box.height / 2.0;
   bodyDef.userData = box;
   bodyDef.userData.width = 34 * 2 * 30;
   bodyDef.userData.height = 30 * 2 * 3;
   addChild(bodyDef.userData);
   body = m_world.CreateBody(bodyDef);
   body.CreateFixture(fixtureDef);
   var quad:Quad;
// Add some objects
   for (var i:int = 1; i < 100; i++)
   {
    bodyDef = new b2BodyDef();
    bodyDef.type = b2Body.b2_dynamicBody;
    bodyDef.position.x = Math.random() * 15 + 5;
    bodyDef.position.y = Math.random() * 10;
    var rX:Number = Math.random() + 0.5;
    var rY:Number = Math.random() + 0.5;
// Box
    boxShape = new b2PolygonShape();
    boxShape.SetAsBox(rX, rY);
    fixtureDef.shape = boxShape;
    fixtureDef.density = 1.0;
    fixtureDef.friction = 0.5;
    fixtureDef.restitution = 0.2;
// create the quads
    quad = new Quad(100, 100, Math.random() * 0xFFFFFF);
    quad.pivotX = quad.width / 2.0;
    quad.pivotY = quad.height / 2.0;
// this is the key line, we pass as a userData the starling.display.Quad
    bodyDef.userData = quad;
    bodyDef.userData.width = rX * 2 * 30;
    bodyDef.userData.height = rY * 2 * 30;
    body = m_world.CreateBody(bodyDef);
    body.CreateFixture(fixtureDef);
// show each quad (acting as a skin of each body)
    addChild(bodyDef.userData);
   }
// on each frame
   addEventListener(Event.ENTER_FRAME, Update);
  }
  
  public function Update(e:Event):void
  {
// we make the world run
   m_world.Step(m_timeStep, m_velocityIterations, m_positionIterations);
   m_world.ClearForces();
// Go through body list and update sprite positions/rotations
   for (var bb:b2Body = m_world.GetBodyList(); bb; bb = bb.GetNext())
   {
// key line here, we test if we find any starling.display.DisplayObject objects and apply thephysics to them
    if (bb.GetUserData() is DisplayObject)
    {
// we cast as a Starling DisplayObject, not the native one !
     var sprite:DisplayObject = bb.GetUserData() as DisplayObject;
     sprite.x = bb.GetPosition().x * 30;
     sprite.y = bb.GetPosition().y * 30;
     sprite.rotation = bb.GetAngle();
    }
   }
   bodyDef.position.Set(10, 28);
  }
 }
}


Тестируя код, мы получим следующий результат:

Рисунок 1.43 – Box2D и Starling


А теперь запустим тот же самый контент на AIR с поддержкой Stage3D:

Рисунок 1.44 – Тот же контент запущенный на планшете с 60fps



Профилирование Starling



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

В этом руководстве мы уже использовали классический класс Stats от mr.doob, который доступен по ссылке https://github.com/mrdoob/Hi-ReS-Stats

Мы могли бы использовать класс Stats как и раньше, но есть определенные ограничения для мобильных платформ. Использование в приложении одновременно нативного списка отображения и Stage3D может потенциально привести к большому падению производительности. В результате даже для отладки желательно использовать только Stage3D. Мы уже рассмотрели понятие растровых шрифтов, поэтому мы могли бы разработать небольшой класс FPS, который бы отображал частоту кадров с помощью Stage3D.
На рисунке ниже показан спрайт с глифами, обратите внимание, что мы взяли только те глифы, которые нам понадобятся для счетчика, чтобы сделать спрайт очень легким:

Рисунок 1.45 – Текстура для счетчика FPS


После создания спрайта и файла описания, мы используем TextField и BitmapFont для отображения FPS:


package
{
 import flash.display.Bitmap;
 import flash.utils.getTimer;
 import starling.display.Sprite;
 import starling.events.Event;
 import starling.text.BitmapFont;
 import starling.text.TextField;
 import starling.textures.Texture;
 import starling.utils.Color;
 
 public class FPS extends Sprite
 {
  private var container:Sprite = new Sprite();
  private var bmpFontTF:TextField;
  private var frameCount:uint = 0;
  private var totalTime:Number = 0;
  private static var last:uint = getTimer();
  private static var ticks:uint = 0;
  private static var text:String = "--.- FPS";
  [Embed(source="../media/fonts/futura-fps.png")]
  private static const BitmapChars:Class;
  [Embed(source="../media/fonts/futura-fps.fnt",mimeType="application/octet-stream")]
  private static const BritannicXML:Class;
  
  public function FPS()
  {
   addEventListener(Event.ADDED_TO_STAGE, onAdded);
  }
  
  private function onAdded(e:Event):void
  {
// creates the embedded bitmap (spritesheet file)
   var bitmap:Bitmap = new BitmapChars();
// creates a texture out of it
   var texture:Texture = Texture.fromBitmap(bitmap);
// create the XML file describing the glyphes position on the spritesheet
   var xml:XML = XML(new BritannicXML());
// register the bitmap font to make it available to TextField
   TextField.registerBitmapFont(new BitmapFont(texture, xml));
// create the TextField object
   bmpFontTF = new TextField(70, 70, "... FPS", "Futura-Medium", 12);
// border
   bmpFontTF.border = true;
// use white to use the texture as it is (no tinting)
   bmpFontTF.color = Color.WHITE;
// show it
   addChild(bmpFontTF);
// on each frame
   stage.addEventListener(Event.ENTER_FRAME, onFrame);
  }
  
  public function onFrame(e:Event):void
  {
   ticks++;
   var now:uint = getTimer();
   var delta:uint = now - last;
   if (delta >= 1000)
   {
    var fps:Number = ticks / delta * 1000;
    text = fps.toFixed(1) + " FPS";
    ticks = 0;
    last = now;
   }
   bmpFontTF.text = text;
  }
 }
}


Использовать этот счетчик очень просто, просто добавьте следующие строчки:


// show the fps counter
addChild ( new FPS() );


На рисунке ниже сам счетчик:

Рисунок 1.46 – Счетчик, отображаемый с помощью GPU


Теперь мы можем обновить нашу демонстрацию физики и вставить туда счетчик:

Рисунок 1.47 – Счетчик, интегрированный в приложение


Теперь мы можем профилировать наш контент и на мобильных устройствах.


Частицы



Здесь мы поговорим о моих любимых эффектах и частицах. Верьте или не, но в частицах нет ничего по-настоящему сложного. С технической точки зрения, частицы это просто перемещающиеся квадратные текстуры, которые используют определенные режимы смешивания, которые дают красивые комбинации.

Чтобы создавать частицы в Starling, вы можете использовать очень удобный инструмент ParticleDesigner, разработанный той же компанией, что и GlyphDesigner, которую мы использовали для растровых шрифтов. Ниже скриншот ParticleDesigner, справа есть окошко для режима эмуляции, где вы можете увидеть свои частицы:

Рисунок 1.48 – ParticleDesigner на MacOS


Главное окно это просто набор параметров для изменения. Вы можете часами играться с этими опциями, чтобы создать такие частицы, какие вам нужны. Есть также кнопка Randomize, которая сгенерирует частицы с случайными параметрами.

На рисунке ниже, окошко для сохранения эффекта, в файл ParticleEmitter (.pex) и текстуру в формате PNG. Эти два файла будут использованы в нашем объекте ParticleDesignerPS.

Рисунок 1.49


Ниже рисунок с примером частиц, созданных в ParticleDesigner и запущенных с помощью Starling:

Рисунок 1.50


Красиво, не правда ли? Как видите, Starling не содержит собственного генератора частиц. Текущая реализация расширения Starling для работы с ParticleDesigner доступна по адресу https://github.com/PrimaryFeather/Starling-Extension-Particle-System
Ниже еще один красивый пример частиц:

Рисунок 1.51


Частицы – это обычный анимированный объект и поэтому он должен быть добавлен в Juggler:


// load the XML config file
var psConfig:XML = XML(new StarConfig());
// create the particle texture
var psTexture:Texture = Texture.fromBitmap(new StarParticle());
// create the particle system out of the texture and XML description
mParticleSystem = new ParticleDesignerPS(psConfig, psTexture);
// positions the particles starting point
mParticleSystem.emitterX = 800;
mParticleSystem.emitterY = 240;
// start the particles
mParticleSystem.start();
// show them
addChild(mParticleSystem);
// animate them
Starling.juggler.add(mParticleSystem);


Не стесняйтесь менять позицию частиц как вам угодно, объект ParticleDesignerPS это обычный DisplayObject. Когда вам нужно удалить частицы, то сначала нужно удалить их из Juggler, а потом вызвать метод dispose() объекта ParticleDesignerPS.
Lee Brimelow (leebrimelow.com) создал пример использования частиц для симуляции пламени космического корабля, см рисунок:

Рисунок 1.52


Мы можем добавить частицы и к ракете, которыми стреляет корабль:

Рисунок 1.53


Когда ракеты вылетают за сцену, нам нужно удалить их со сцены и из Juggler, а также очистить частицы перед удалением:


Starling.juggler.remove(this.particle);
this.particle.dispose();
this.removeFromParent(true);


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

5 комментариев:

  1. Чертовски интересная статья, большое спасибо!
    А так же благодарю за весь сайт, от которого невозможно оторваться. Пожалуй один из лучших ресурсов про флэш с зашкаливающим отношением полезной информации ко всей информации :)
    Жду новых статей, а пока буду разбираться с теми, что есть.
     
    P.S. Не хватает живых примеров.

    ОтветитьУдалить
  2. Большой труд, спасибо.

    ОтветитьУдалить
  3. Ответы
    1. с картинками есть тут http://demiart.ru/forum/index.php?showtopic=183472

      только там регистрация нужна вроде как

      Удалить
  4. Если кому нужны картинки по этой документации, они есть в этой книге http://www.bytearray.org/wp-content/projects/starling/introducing-starling-latest.zip

    ОтветитьУдалить