Diese GPU Oberfläche liegt unter der Display List. Das bedeutet in der Praxis das alles, was mit der Display List gemacht wird, nicht über den Stage3D Content gelegt werden kann. Oder salopp ausgedrückt: Es ist also nicht möglich Dinge mit Starling zu erstellen und Dinge, die händisch oder auch programmatisch auf herkömmliche Art erzeugt wurden, zusätzlich anzuzeigen.
Die Starling Api ist der AS3 Api sehr ähnlich. Es gibt jedoch einige Unterschiede, die es zu erlernen gilt.
Nähere Infos auf der offiziellen Starling Seite.
Dort finden Sie auch einen Link zum Download des Frameworks.
Ein besonders guter Einstieg ist das kostenlose Buch "Introducing Starling" geschrieben von Thibault Imbert im O'Reilly Shop
Die Starling Api Reference
Download des Starling Frameworks
Viele dieser Beispiele habe ich aus dem oben genannten Buch "Introducing Starling" übernommen und etwas abgeändert.
Ich erkläre die ersten Beispiele mit der Flash IDE. Doch wenn es darum geht Starling MovieClips zu erstellen, gehe ich langsam dazu über das mit Flash Builder zu machen. Sofern man Flash Builder hat, ist das sowieso die bessere Wahl, da man die Funktionen der Flash IDE mit Starling Projekten eigentlich nicht nutzen kann. Außerdem gibt es noch die kostenlose Alternative "FlashDevelop". Spenden Sie etwas an die FD Community, wenn Sie Flash Develop benutzen.
Sollten Sie Flash Builder 4.6 besitzen ist alles im grünen Bereich. Sollten Sie Flash Builder 4.5 haben, müssen Sie dieses folgendermaßen einrichten.
Video Tutorial -flash-builder-4-5 für Starling bzw. Stage 3D einrichten
Im folgenden fasse ich die Schritte zusammen, die in dem Videotutorial erläutert werden:
Laden Sie sich die neueste Version der Flex SDK bei Adobe runter und entpacken Sie diese. Vorzugsweise in einen dafür vorgesehen Ordner. Programme Adobe / Adobe Flash Builder / sdks / und erstellen Sie sich dort einen passenden Unterordner.
Laden Sie sich das Starling Framework runter. hier die offizielle Starling Seite.
Flash Builder öffnen, Fenster / Voreinstellungen
Geben sie oben unter Filter "sdk" ein, dann erscheint, "Installierte Flex SDKs"
Füge eine neue hinzu und verweise auf den Ordner, welcher die aktuelle entpackte flex sdk beinhaltet.
Neu, Neues Actionscript Projekt
Name vergeben und unter "spezielle SDK Version" die neue hinzufügen bzw. auswählen. Weiter
Quellpfad, Starling Framework auswählen
Die Actionscriptdateien entnehmen Sie bitte dem nächsten Beispiel.
Sollte es nicht funktionieren schauen Sie nochmal in den "Einstellungen des Projekts, ob unter Compiler auch die richtige Flex SDK gewählt ist und auch ob das Starling Framework richtig eingebunden ist.
Desweiteren muss in der HTML Datei der wmode den Wert direct bekommen. Öffnen Sie im Projektunterordner HTML Template, die html Datei und fügen Sie an 3 Stellen den parameter wmode value = direct ein. Achten Sie auf die genaue Schreibweise der anderen Parameter, am besten Sie kopieren sich jeweils eine Parameterzeile und ersetzen die entsprechenden Stellen.
Flash Player Debugger Version
Hat man nicht die aktuelle Flashplayer Version installiert, kann man diese im folgenden Ordner ersetzen:
C:/Programme/ Adobe / Adobe Flash Builder 4.5 / player / win
Speichere das Framework und binde es ein. Siehe dazu meinen Tipp Klassenbibliotheken.
In fast allen Starling Tipps wird mit Flex und FlashBuilder gearbeitet. Ich erkläre alles mit der Flash IDE. Die Unterschiede sind nicht der Rede wert.
Erzeuge eine Klasse als Dokumentenklasse genannt Startup:
package { import flash.display.Sprite; import starling.core.Starling; public class Startup extends Sprite { private var _starling:Starling; /*public function Starling(rootClass:Class, stage:flash.display.Stage, viewPort:Rectangle=null, stage3D:Stage3D=null, renderMode:String="auto")*/ public function Startup() { _starling = new Starling(Game,stage); _starling.start(); } } }
Der Klassenname muss im Eigenschaftenfenster der Fla Datei, welche im gleichen Ordner abgespeichert wird, unter Klasse eingegeben werden.
Erzeugen wir nun eine Klasse namens Game, welche in der Starling Klasse als erstes Argument übergeben wurde:
package { import starling.display.Sprite; import starling.text.TextField; import starling.textures.Texture; import starling.display.Image; import flash.display.BitmapData; import flash.display.Bitmap; import starling.utils.deg2rad; public class Game extends Sprite { public function Game() { var textField:TextField = new TextField(400,100,"Welcome to Starling!"); textField.text = "Halleluja"; addChild(textField); // create a Texture object out of an embedded bitmap var texture:Texture = Texture.fromBitmapData ( new Jesus()); // create an Image object our of the Texture var image:Image = new Image(texture); // set the properties image.pivotX = 0; image.pivotY = 0; image.x = 320; image.y = 80; image.rotation = starling.utils.deg2rad(10); // display it addChild(image); } } }
Hier wird ein Textfeld und Bild auf die Bühne gebracht. Vorraussetzung für das Bild ist ein in die Bibliothek importiertes Bild mit Export für Actionscript Klassenname "Jesus" erbende Klasse: flash.display.BitmapData. Siehe dazu meinen Tipp Bitmap
Unter Einstellungen für Veröffentlichungen HTML muss als Fenstermodus die Option "direct" eingegeben werden.
Mittels pivotX und pivotY lässt sich der Registrierpunkt neu bestimmen
Die Eigenschaft rotation erwartet einen Wert im Bogenmaß. Für die Umrechnung stehen in Starling 2 praktische Methoden zur Verfügung deg2rad() und rad2deg() Degree zu Radians und umgekehrt.
In Starling gibt es keine Mouse Events stattdessen benutzt man Touch Events. Ein Touch Event tritt ein, wenn die Maus oder der Finger (Mobile) das Objekt berührt. Da es verschiedene EventPhasen gibt, gibt es hier viele Möglichkeiten
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, true); } }
Der Touch Event wird auf eine Phase hin abgefragt und entsprechend darauf reagiert. Es gibt weitere Phasen wie
hover (MouseOver)
began (MouseDown)
moved (mit gedrückter Maustaste ziehen
stationary (bleibt über dem Objekt keine Mausaktivität)
ended (Clicked)
Im folgenden Beispiel sieht man einige Möglichkeiten. Man bekommt die Mausposition auf der Bühne und speichert diese in einer Point Instanz. Außerdem wird die touch.phase ermittelt.
private function onTouch(e:TouchEvent):void { // Mouse Position in Bezug auf die stage var touch:Touch = e.getTouch(stage); var pos:Point = touch.getLocation(stage); trace( touch.phase ); }
Über den Event Parameter eines Touch Events kann man auch andere Dinge abfragen, zum Beispiel:
trace(e.ctrlKey);
liefert true, sobald die Strg Taste gedrückt wird. Weitere Parameter sind:
getTouch
das erste TouchObjekt über einem bestimmten Ziel in einer bestimmten Phase
getTouches
Eine Anzahl von Touch Objekten über einem bestimmten Ziel in einer bestimmten Phase
ctrlKey
Liefert einen Boolschen Wert ob Strg Taste gedrückt wurde oder nicht.
shiftkey
Liefert einen Boolschen Wert ob Strg Taste gedrückt wurde oder nicht.
timestamp
Die Zeit in Sekunden als das Ereignis eintrat seit die Anwendung began.
touches
Alle touches, die zur Zeit stattfinden. Im folgenden Codeschnipsel wird ein Vector erzeugt, in dem alle touches enthalten sind.
var touches:Vector.<Touch > = e.touches;
Im vorigen Code Beispiel wird ein Touch Objekt erzeugt.
var touch:Touch = e.getTouch(stage);
Folgende Methoden und Eigenschaften gibt es:
clone
Ein Objekt wird gecloned.
getLocation
Die Position eines Touch wird in das lokale Koordinatensystem eines Display Objekts übertragen.
getPreviousLocation
Die Position des vorigen Touch wird in das lokale Koordinatensystem eines Display Objekts übertragen.
globalX
Die xPositon des Touch in Screen Koordinaten
globalY
Die yPositon des Touch in Screen Koordinaten
id
Eine eindeutige id für das Objekt
phase
Die aktuelle Phase, in der sich Objekt befindet
previousGlobalX
Die vorige xPositon des Touch in Screen Koordinaten
previousGlobalY
Die vorige yPositon des Touch in Screen Koordinaten
tapCount
The number of taps the finger made in a short amount of time. Use this to detect double-taps, for example.
target
The display object at which the touch occurred.
timestamp
Die Zeit in Sekunden als das Ereignis eintrat seit die Anwendung began.
Multi touch Events kann auf dem Desktop schlecht ausprobieren. Starling bietet jedoch eine Möglichkeit auch auf dem Desktop Multitouch Events zu simulieren.
mStarling.simulateMultitouch = true;
Dazu muss die simulateMultitouch Eigenschaft der Starling Instanz auf true gesetzt werden. Wenn man dann auf dem Desktop die Strg-Taste drückt erscheinen 2 Punkte, stellvertretend für die beiden Touchpunkte.
Im folgenden Beispiel wird der Abstand zwischen 2 Touches berechnet:
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); } } }
Beachte im vorigen Code Beispiel auch den zweiten Parameter bei removeChild(child, dispose) , der hier auf true gesetzt wurde, er bewirkt das alle EventListener des Objekts gelöscht wurden.
Man kann auch alle Listener entfernen, indem man dispose explicit auf einem Display Object aufruft:
myObj.dispose();
Außerdem gibt es in Starling neben dem removeEventListener auch removeEventListeners, welches alle EventListener eines bestimmten Typs entfernt oder ohne Argument alle EventListener entfernt..
button.removeEventListeners(Event.TRIGGERED);
button.removeEventListeners();
Man kann abfragen ob, ein bestimmter Event registriert wurde
myObj.hasEventListener(e.type)
Ein Texutre Object muss erstellt werden um ein Image Objekt zu füttern. Man kann es sich ähnlich vorstellen, wie die Beziehung zwischen BitmapData und Bitmap.
Wenn man ein Texture Object erzeugt, braucht man die Texture API. Ich zitiere aus dem Buch "Introducing Starling" by Thibault Imbert, Verlag O`Reilly
base
The Stage3D texture object the texture is based on.
dispose
Disposes the underlying texture data.
empty
Returns a Texture object out of dimensions (width and height).
frame
The texture frame (see class description).
fromBitmap
Returns a Texture object out of a Bitmap object. This Bitmap object can be embedded or loaded dynamically.
fromBitmapData
Returns a Texture object out of a BitmapData object.
fromAtfData
Allows the use of a compressed texture using the ATF (Adobe Texture Format). Compressed textures allows you to save a lot of memory especially on constrained environments like mobile devices.
fromTexture
Allows the use of a texture and returns a new texture.
height
The height of the texture in pixels.
mipmapping
Indicates if the texture contains mip maps.
premultipliedAlpha
Indicates if the alpha values are premultiplied into the RGB values.
repeat
Indicates if the texture should repeat like a wallpaper or stretch the outermost pixels.
width
The width of the texture in pixels. Different image formats can be used for your textures. The following list summarizes the various formats that can be used for your textures:
PNG
As alpha channel is often required, PNG is one of the most common file format used for textures.
JPEG
The classic JPEG format can also be used. Remember that on the GPU the image will be decompressed, so using JPEG will not limit the memory usage and you will not be able to use transparency in your textures.
ATF
Adobe Texture Format. This is the best file format for the best compression. ATF files are primarily a file container to store lossy texture data. It achieves its lossy
compression through to the use of two common techniques: JPEG-XR1 compression
and block based compression. JPEG-XR compression provide a competitive
method to save storage space and network bandwidth. Block based compression
provides a way to reduce texture memory usage on the client, at a fixed ratio of 1:8
compared to RGBA textures. ATF supports three types of block based compression:
DXT1/5, ETC1/3 and PVRTC4.
In Starling, a starling.display.Image object is the equivalent of a native flash.display.Bitmap object.
Im folgenden Beispiel werden Bilder zur Anzeige gebracht. In meinem Beispiel für die Flash IDE habe ich in die Bibliothek der fla Datei ein Bild importiert und diesem mittels "Export für Actionscipt" den Klassennamen "SausageData" gegeben. Näheres dazu in meinem Tipp Bitmap.
Weitere Hinweise siehe Kommentare im Quelltext.
Achtung, diese Klasse für sich alleine funktioniert nicht, sie muss, wie im ersten Beispiel in der Starling Klasse aufgerufen werden.
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 Game extends Sprite { private var sausagesVector:Vector.= new Vector. (NUM_SAUSAGES,true); private const NUM_SAUSAGES:uint = 400; public function Game() { addEventListener(Event.ADDED_TO_STAGE,onAdded); } private function onAdded(e:Event):void { // erzeuge ein Texture object um das Image object zu füttern //SausageData ist der Klassenname eines BitmapData Objektes. Es ist ein importiertes Bild //in der Bibliothek der fla mitExport für Actionscript und Klassenename SausageData var texture:Texture = Texture.fromBitmapData(new SausageData()); for (var i:int = 0; i < NUM_SAUSAGES; i++) { // erstelle ein Image object mit dieser einen texture var image:Image = new Image(texture); // Zufallswerte für alpha, position, rotation image.alpha = Math.random(); // Startposition und Drehung zuweisen image.x = Math.random() * stage.stageWidth; image.y = Math.random() * stage.stageHeight; image.rotation = deg2rad(Math.random() * 360); // show it addChild(image); // Referencen auf die einzelnen images werden im Vector sausageVector gespeichert. sausagesVector[i] = image; } } } }
Die Image Klasse erbt die Methoden und Eigenschaften der DisplayObject Klasse. Außerdem gibt es eine spezielle smoothing Eigenschaft:
image.smoothing = TextureSmoothing.NONE;
Folgende Werte gibt es:
NONE
BILINEAR
TRILINEAR
Wenn man ein Image Objekt erstellen will, mit einem von außen geladenem Bild, so kann man das mit den üblichen Loader, URLRequest sowie den dazugehörigen Events machen. Hierbei muss man jedoch darauf achten, dass die Events richtig zugewiesen werden, denn man braucht 2 Import Anweisungen
import starling.events.Event;
import flash.events.Event;
In der Funktion wird dem Argument die Eventklasse genau zugewiesen:
protected function onComplete(evt:flash.events.Event):void.....etc.
Auch im EventListener wird die genaue Zuweisung benötigt:
loader.contentLoaderInfo.addEventListener(flash.events.Event.COMPLETE, onComplete );
Gleiches gilt dann auch für alle Events, die auf die starling.events verweisen.
myfunction (evt: starling.events.Event)
und im Listener
addEventListener(starling.events.ADDED_TO_STAGE, onAdded)
Hier nun das komplette Beispiel:
Achtung, diese Klasse für sich alleine funktioniert nicht, sie muss, wie im ersten Beispiel in der Starling Klasse aufgerufen werden.
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; import flash.display.Loader; import flash.events.Event; import flash.net.URLRequest; public class Game extends Sprite { private var loader:Loader; private var texture:Texture; public function Game() { //achte auf den direkten Verweise starling.events.Event..... //denn auch die flash.events. wurden importiert. addEventListener(starling.events.Event.ADDED_TO_STAGE,onAdded); } //achte auf den Verweise e:starling.events.Event private function onAdded(e:starling.events.Event):void { loader = new Loader(); loader.load( new URLRequest ("sausage.png") ); // when texture is loaded, achte auf den direkten Verweise von flash.events.Event.COMPLETE loader.contentLoaderInfo.addEventListener(flash.events.Event.COMPLETE, onComplete ); function onComplete( evt:flash.events.Event ):void { // grab the loaded bitmap var loadedBitmap:Bitmap = evt.currentTarget.loader.content as Bitmap; // create a texture from the loaded bitmap texture = Texture.fromBitmap(loadedBitmap); // erstelle ein Image object mit dieser einen texture var image:Image = new Image(texture); image.x = 20; image.y=50; addChild(image); } } } }
Startup Dokumentklasse siehe oben erstes Code beispiel
Game Klasse
CustomImage
Game
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 Game extends Sprite { private var sausagesVector:Vector.= new Vector. (NUM_SAUSAGES,true); private const NUM_SAUSAGES:uint = 400; public function Game() { addEventListener(Event.ADDED_TO_STAGE,onAdded); } private function onAdded(e:Event):void { // erzeuge ein Texture object um das Image object zu füttern //SausageData ist der Klassenname eines BitmapData Objektes. Es ist ein importiertes Bild //in der Bibliothek der fla mitExport für Actionscript und Klassenename SausageData var texture:Texture = Texture.fromBitmapData(new SausageData()); for (var i:int = 0; i < NUM_SAUSAGES; i++) { // erstelle ein Image object mit dieser einen texture var image:CustomImage = new CustomImage(texture); // Zufallswerte für alpha, position, rotation image.alpha = Math.random(); // Startposition und Drehung zuweisen image.x = Math.random() * stage.stageWidth; image.y = Math.random() * stage.stageHeight; image.rotation = deg2rad(Math.random() * 360); image.destX = Math.random() * stage.stageWidth; image.destY = Math.random() * stage.stageHeight; // show it addChild(image); // Referencen auf die einzelnen images werden im Vector sausageVector gespeichert. sausagesVector[i] = image; } 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 ) * .01; sausage.y -= ( sausage.y - sausage.destY ) * .01; // 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.stageHeight; sausage.rotation = deg2rad(Math.random() * 360); } } } } }
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); } } }
dgd
Die Startling Hittest Funktion ist nicht identisch mit den AS3 Hittest Methoden.
public function hitTest(firstPoint: Point, firstAlphaThreshold: uint,
secondObject: Object, secondBitmapDataPoint: Point = null,
secondAlphaThreshold: uint = 1): Boolean
Der SecondObject Parameter kann ein Point, ein Rectangle oder ein BitmapData Object sein. Wenn es ein BitmapData Object ist, darf das Image Object nicht scaliert oder gedreht werden.
if ( sausageBitmapData1.hitTest(new Point(sausageImage2.x, sausageImage2.y), 255,
sausageBitmapData1, new Point(sausageImage1.x, sausageImage1.y), 255))
{
trace("Treffer");
}
Es folgt ein Beispiel. In dem Beispiel geht es darum image2 zu berühren, welche dann an die Position von image1 wechselt, woraufhin hittest true wird und image1 eine neue Position einnimmt.
Achtung, diese Klasse für sich alleine funktioniert nicht, sie muss, wie im ersten Beispiel in der Starling Klasse aufgerufen werden. Außerdem muss sich in der Bibliothek der fla ein Bild befinden mit "Export für Actionscript" und Klassenename SausageData
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; import starling.events.Touch; import starling.events.TouchEvent; import starling.events.TouchPhase; import flash.geom.Point; import flash.display.BitmapData; public class Game extends Sprite { private var image1:Image; private var image2:Image; private var wurstData:BitmapData; var point1:Point = new Point(0,0); var point2:Point = new Point(0,0); public function Game() { addEventListener(Event.ADDED_TO_STAGE,onAdded); } private function onAdded(e:Event):void { //in der Bibliothek der fla liegt ein importiertes Bild, //export für Actionscript, Name SausageData wurstData = new SausageData(); var texture:Texture = Texture.fromBitmapData(wurstData); image1 = new Image(texture); newPosition(); image2 = new Image(texture); image2.x = Math.random() * stage.stageWidth; image2.y = Math.random() * stage.stageHeight; addChild(image1); addChild(image2); image2.addEventListener(TouchEvent.TOUCH, onTouch); } private function onTouch(evt:TouchEvent):void { image2.x = image1.x; image2.y = image1.y; stage.addEventListener(Event.ENTER_FRAME, onFrame); } private function onFrame(event:Event):void { point1.x = image1.x; point1.y = image1.y; point2.x = image2.x; point2.y = image2.y; if (wurstData.hitTest(point2,255,wurstData,point1,255)) { newPosition(); stage.removeEventListener(Event.ENTER_FRAME, onFrame); } } private function newPosition():void { image1.x = Math.random() * stage.stageWidth; image1.y = Math.random() * stage.stageHeight; } } }
Im folgenden Beispiel bewegt sich ein Engel auf den anderen zu, sobald man ihn berührt. Wenn beide Engel kollidieren, fliegen sie in unterschiedliche Richtungen.
Beispiel Einer der beiden Engel muss berührt werden.
package { import flash.display.Bitmap; import flash.display.BitmapData; import starling.display.Sprite; import starling.events.Event; import starling.textures.Texture; import starling.utils.deg2rad; import starling.events.TouchEvent; import starling.events.TouchPhase; import flash.geom.Point; public class Game extends Sprite { private var image1:CustomImage; private var image2:CustomImage; private var engelData:BitmapData; var point1:Point = new Point(0,0); var point2:Point = new Point(0,0); public function Game() { addEventListener(Event.ADDED_TO_STAGE,onAdded); } private function onAdded(e:Event):void { // in der Bibliothek der fla liegt ein Bild export für actionscript klassenname EngelData engelData = new EngelData(); var texture:Texture = Texture.fromBitmapData(engelData); image1 = new CustomImage(texture); image1.x = Math.random() * stage.stageWidth; image1.y = Math.random() * stage.stageHeight; newDest(image1); image2 = new CustomImage(texture); image2.x = Math.random() * stage.stageWidth; image2.y = Math.random() * stage.stageHeight; addChild(image1); addChild(image2); //berührt man die große Wurst, bewegt diese sich auf die kleine zu image2.addEventListener(TouchEvent.TOUCH, onTouch); } private function onFrame(evt:Event):void { collisionTest(); //engel Bewegung var engel:CustomImage = (evt.currentTarget as CustomImage); engel.x -= (engel.x - engel.destX) * .1; engel.y -= (engel.y - engel.destY) * .1; // Wenn Zielpunkt erreicht, EventListener entfernen if (Math.abs(engel.x - engel.destX) < 1 && Math.abs(engel.y - engel.destY) < 1) { engel.removeEventListener(Event.ENTER_FRAME, onFrame); } } //wenn beide Engel sich berühren, bewegen sich sich auf 2 zufällige Positionen private function collisionTest():void { point1.x = image1.x; point1.y = image1.y; point2.x = image2.x; point2.y = image2.y; if (engelData.hitTest(point2,255,engelData,point1,255)) { newDest(image1); image1.addEventListener(Event.ENTER_FRAME, onFrame); newDest(image2); image2.addEventListener(Event.ENTER_FRAME, onFrame); } } private function newDest(engel:CustomImage):void { engel.destX = Math.random() * stage.stageWidth; engel.destY = Math.random() * stage.stageHeight; } private function onTouch(evt:TouchEvent):void { image2.destX = image1.x; image2.destY = image1.y; image2.addEventListener(Event.ENTER_FRAME, onFrame); } } }
Custom Image Klasse
Hierzu gehört auch noch eine Klasse CustomImage, in welcher die destX und destY Parameter definiert sind. Außerdem noch die Startup Klasse siehe erstes Codebeispiel.
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); } } }
Es gibt in Starling keine Drawing API. Stattdessen kann man die Drawing Api von Flash nutzen und dann daraus ein BitmapData Objekt erstellen. Siehe meinen Tipp Bitmap. Dort wird die BitmapData.draw Methode erklärt.
var meinBitmapData:BitmapData = new BitmapData(110,100);
meinBitmapData.draw(farben_mc);
Es folgt ein Beispiel:
Achte im folgenden Beispiel auf die Kleinigkeiten in Punkto Maße, ohne Rand wäre hier einiges einfacher. Jedoch die 4 Pixel Rand müssen bei der Positionierung und Größe berücksichtigt werden.
package { import flash.display.BitmapData; import starling.display.Image; import starling.display.Sprite; import starling.events.Event; import starling.textures.Texture; import flash.display.Bitmap; public class Game extends Sprite { private const NUM_CIRCLES:uint = 400; public function Game() { addEventListener(Event.ADDED_TO_STAGE, onAdded); } private function onAdded(e:Event):void { // Achte hier auf die genaue Zuweisung flash.display.Sprite() //denn es gibt auch starling Sprite var shape:flash.display.Sprite = new flash.display.Sprite(); // radius var radius:uint = 100; shape.graphics.beginFill(0xFFFF00); shape.graphics.lineStyle(4, 0x000000, .75); // circle Argumente drawCircle(x, y, radius), Mittelpunkt in der Mitte shape.graphics.drawCircle(radius+2,radius+2,radius); shape.graphics.endFill(); //radius plus Linienbreite radius = 104; var bmd:BitmapData = new BitmapData(radius * 2, radius * 2, true, 0); //draw Methode zeichnet die gerade erstellte Circle Grafik bmd.draw(shape); // Texture von Bitmap Data var texture:Texture = Texture.fromBitmapData(bmd); // bild aus texture erstellen var image:Image = new Image(texture); //Das Bild mit Farben versehen //im Uhrzeigersinn kann man in jeder Ecke eine Farbe definieren image.setVertexColor(0, 0xA00000); image.setVertexColor(1, 0xA00000); //image.setVertexColor(2, 0xFFFF00); //image.setVertexColor(3, 0xFFFF00); image.x = image.y = 20; addChild(image); } } }
Im folgenden Beispiel werden viele bunte Kreise erzeugt. Beachte, dass alle image Objekte auf ein Texture Objekt zugreifen. Würden die BitmapData und Texture Objekte alle in der For-Schleife neu instanziiert, würde die Performance darunter leiden.
package { import flash.display.BitmapData; import starling.display.Image; import starling.display.Sprite; import starling.events.Event; import starling.textures.Texture; public class Game extends Sprite { private const NUM_CIRCLES:uint = 400; public function Game() { addEventListener(Event.ADDED_TO_STAGE, onAdded); } private function onAdded(e:Event):void { // Achte hier auf die genaue Zuweisung flash.display.Sprite() //denn es gibt auch starling Sprite var shape:flash.display.Sprite = new flash.display.Sprite(); // radius var radius:uint = 20; // color fill shape.graphics.beginFill(0xFFFFFF); // circle Argumente drawCircle(x, y, radius), Mittelpunkt in der Mitte; shape.graphics.drawCircle(radius,radius,radius); shape.graphics.endFill(); // erzeuge ein BitmapData buffer; var bmd:BitmapData = new BitmapData(radius * 2, radius * 2, true, 0); // draw Methode zeichnet die gerade erstellte Circle Grafik bmd.draw(shape); // create a Texture out of the BitmapData var texture:Texture = Texture.fromBitmapData(bmd); for (var i:int = 0; i < NUM_CIRCLES; i++) { // create an Image out of the texture var image:Image = new Image(texture); image.x = Math.random() * stage.stageWidth; image.y = Math.random() * stage.stageHeight; image.color = Math.random() * 0xFFFFFF; image.scaleX = image.scaleY = Math.random(); // show it! addChild(image); } } } }
Um einen Starling Movieclip zu erstellen benötigen wir ein Sprite Sheet. Das ist ein Bild, welches aufgeteilt ist und aus lauter Einzelbildern besteht. Die Anordnung der Einzelbilder wird in einer XML oder JSON Datei gespeichert. Mit einem Programm wie Texture Packer lassen sich derlei Sprite Sheets erstellen. Aber auch in Flash CS6 ist es möglich einen Movieclip mit einer Zeitleistenanimation als SpriteSheet für Starling zu erzeugen. Auch dieses Videotutorial von Hemanth Sharma erklärt die SpriteSheet Exportfunktion in Flash CS6.
Wähle einen oder mehrere Movieclips in der Bibliothek aus und wähle aus dem Kontextmenü: Spritesheet erstellen. Wähle unter Datenformat "Starling". Voila es entstehen eine Xml Datei und ein png.
Nun geht es weiter mit Flash Builder. Wenn Sie Flash Builder 4.5 haben, müssen Sie dieses erst einrichten siehe oben.
Erzeugen Sie ein neues Actionscript Projekt. (in Flash Builder 4.5, wählen Sie die eingefügte neue Flex SDK Version).
Quellpfad, Starling Framework auswählen.
Es folgt die Dokumentenklasse: StarlingSetup
package { import flash.display.Sprite; import starling.core.Starling; [SWF(width="1280", height="752", frameRate="60", backgroundColor="#002143")] public class StarlingSetup extends Sprite { public function StarlingSetup() { var _st:Starling = new Starling(Game, stage); _st.start(); } } }
Es folgt die Klasse für das Spritesheet: Game
package { import flash.display.Bitmap; import starling.display.MovieClip; import starling.display.Sprite; import starling.events.Event; import starling.textures.Texture; import starling.textures.TextureAtlas; import starling.core.Starling; import starling.animation.Juggler; public class Game extends Sprite { private var mMovie:MovieClip; [Embed(source="mySpritesheet.xml",mimeType="application/octet-stream")] public static const SpriteSheetXML:Class; [Embed(source="mySpritesheet.png")] private static const SpriteSheet:Class; public function Game() { addEventListener(Event.ADDED_TO_STAGE, onAdded); } private function onAdded (e:Event):void { // das eingebettete bitmap (die spritesheet png Datei) wird instanziiert var bitmap:Bitmap = new SpriteSheet(); // ein Texure Objekt wird daraus erzeugt var texture:Texture = Texture.fromBitmap(bitmap); //die XML Datei, welche die Detailinfos der Frames im Spritesheet enthält, wird erzeugt var xml:XML = XML(new SpriteSheetXML()); // creates a texture atlas mit den Argumenten( texture/spritesheet , XML Beschreibung) var sTextureAtlas:TextureAtlas = new TextureAtlas(texture, xml); // ein Vector welches die Frames bekommt, in meinem Beispiel beginngen alle Bilder mit mySpritesheet var frames:Vector.= sTextureAtlas.getTextures("mySpritesheet"); // erzeugt MovieClip mit 40fps mMovie = new MovieClip(frames, 40); // Movieclip wird positoniert mMovie.x = stage.stageWidth - mMovie.width >> 1; mMovie.y = stage.stageHeight - mMovie.height >> 1; // zur Displayliste hinzufügen addChild ( mMovie ); //mMovie.currentFrame = 20; Starling.juggler.add(mMovie); //mMovie.pause(); mMovie.play(); mMovie.currentFrame = 20; } } }
Die XML Datei und die png Datei müssen in den "src" Unterordner des Projekts geschoben werden. Sie heißen in meinem Beispiel
mySpritesheet.png
mySpritesheet.xml
In der XML Datei sind Verweise auf die einzelnen Bilder. Sie beginnen in meinem Beispiel alle mit mySpritesheet, gefolgt von einer Zahl. Dieses Präfix wird auch in Actionscript benötigt, um auf die Bilder des Movieclips zu verweisen.
var frames:Vector.
Es ist auch möglich und von Vorteil mehrere verschiedenen Animationen mit unterschiedlichen Namen in einem Spritesheet zu hinterlegen. Man findet dazu zahlreiche Tipps im Netz.
Der MovieClip wird abgespielt, wenn man ihn mit Juggler Klasse hinzugefügt hat.
Siehe auch in den Referenzen die Möglichkeiten, die die MovieClip Klasse von Starling bietet. Man kann Frames hinzufügen oder entfernen. Man kann einzelnen Frames eine individuelle Dauer zuweisen und vieles mehr.
Es ist auch möglich das gleiche in der Flash IDE zu machen. Dazu solltet ihr die png Datei in die Biblitothek importieren. Export für Actionscript, Klassenname SpriteData
Um die Ladeprozedur der XML Datei zu umgehen, habe ich einfach die XML Struktur direkt in die Actionscript Klasse eingefügt.Die Dokumentklasse entspricht dem vorigen Beispiel. Es folgt das Beispiel der Game Klasse. Anstelle der XML Daten habe ich einen Hinweis eingefügt der muss durch die XML Daten ersetzt werden siehe meinen XML Tipp. Das beginnt folgendermaßen:
var xml:XML = <TextureAtlas imagePath="mySpritesheet.png">
package { import flash.display.Bitmap; import starling.display.MovieClip; import starling.display.Sprite; import starling.events.Event; import starling.textures.Texture; import starling.textures.TextureAtlas; import flash.display.BitmapData; import starling.animation.Juggler; import starling.core.Starling; public class SpriteSheetExample extends Sprite { private var mMovie:MovieClip; public function SpriteSheetExample() { addEventListener(Event.ADDED_TO_STAGE, onAdded); } private function onAdded (e:Event):void { // das eingebettete bitmap (die spritesheet png Datei) wird instanziiert var bitmap:Bitmap = new Bitmap(new SpriteData()); // ein Texure Objekt wird daraus erzeugt var texture:Texture = Texture.fromBitmap(bitmap); //die XML Datei, welche die Detailinfos der Frames im Spritesheet enthält, wird erzeugt var xml:XML = !!!! hier kommen die XML Daten rein !!!!!! // creates a texture atlas mit den Argumenten( texture/spritesheet , XML Beschreibung) var sTextureAtlas:TextureAtlas = new TextureAtlas(texture, xml); // ein Vector welches die Frames bekommt, in meinem Beispiel beginngen alle Bilder mit mySpritesheet var frames:Vector.= sTextureAtlas.getTextures("mySpritesheet"); // erzeugt MovieClip mit 40fps mMovie = new MovieClip(frames, 40); // Movieclip wird positoniert mMovie.x = stage.stageWidth - mMovie.width >> 1; mMovie.y = stage.stageHeight - mMovie.height >> 1; // zur Displayliste hinzufügen addChild ( mMovie ); Starling.juggler.add(mMovie); } } }
Es macht Sinn alle Animationen in einer Textur Datei abzulegen. Die Anzahl der uploads sinkt. Auch das Wechseln von einer zu anderen Textur braucht Zeit. Außerdem ist es praktisch.
Graphic Processor Unit und Central Processor Unit. Wenn es um Optimierung der Performance geht, gibt es 2 Gründe, die das verlangsame. Die CPU wird überlastet und die GPU muss auf die CPU warten oder umgekehrt.
CPU macht eine Menge in Sachen (physic, komplexe Spiel Logik etc) Es ist also wichtig das GPU und CPU schön aufeinander abgestimmt arbeiten.
Um herauszubekommen, wo die meiste Zeit im Code verbraucht wird, hat Adobe einen legen(warte es kommt gleich)deren Profiler entwickelt, genannt AdobeScout
Der Garbage Collector (GC) wirkt sich stark auf die Performance aus. Jedesmal wenn er ausgelöst wird, gibt es eine Unterbrechung, die eine flüssige Performance verhindert. Also sollte man nicht jede Menge Objekte zerstören und wieder neu erschaffen, sondern stattdessen sowenig wie möglich Objekte im Speicher haben, die bei Bedarf eingesetzt werden können. Auf der anderen Seite darf natürlich nicht alles im Speicher bleiben. Eine ausgewogene Balance ist hier wichtig. So sollte man beispielsweise in Schleifen (for-Schleifen) bei jedem Schleifendurchlauf neue Objekte erzeugen, wenn es auch möglich wäre immer auf der gleiche Objekt (mit eventuell geänderten Eigenschaften) zuzugreifen.
FALSCH
const MAX_NUM:int = 18; const COLOR:uint = 0xCCCCCC; var area:Rectangle; for (var:int = 0; i < MAX_NUM; i++) { //Do not use the following code area = new Rectangle(i,0,1,10); myBitmapData.fillRect(area,COLOR); }
RICHTIG
const MAX_NUM:int = 18; const COLOR:uint = 0xCCCCCC; // Create the rectangle outside the loop var area:Rectangle = new Rectangle(0,0,1,10); for (var:int = 0; i < MAX_NUM; i++) { area.x = i; myBitmapData.fillRect(area,COLOR); }
Pooling
Ein anderes Szenario. Man erstellt eine Anzahl von Objekten und hinterlegt sie beispielsweise in einem Vektor oder Array. Wenn man mit dem Objekt fertig ist, deaktiviert man es, so dass es keine CPU Ressourcen beansprucht. Man entfernt alle Referenzen, aber man setzt die Referenzen nicht auf null, denn erst dann könnte der GC sie entfernen. Wenn man das Objekt wieder braucht, bringt man es zurück in den Pool.
Schleifen
For Schleifen, sollte man bevorzugt einsetzen. Es macht Sinn die Länge abzuspeichern, als sie in jedem Schleifendurchlauf erneut zu evaluieren.
// langsam: for each (var item:Object in vector){ ... } // besser: for (var i:int=0; i < vector.length; ++i) { ... } // am schnellsten: var length:int = vector.length; for (var i:int=0; i < length; ++i) { ... }
Wenn man einen Index im Array oder Vector benutzt, sollte man diesen als int casten.
// bad: var element:Sprite = vector[10*x]; // better: var element:Sprite = vector[int(10*x)];
Vector ist schneller als Array
Starling besitzt eine Button Klasse, die sich von der Flash SimpleButton Klasse unterscheidet. Man kann 2 Bilder für 2 Zustände definieren und man kann einen Text hinzufügen.
public function Button(upState:Texture, text:String="", downState:Texture=null)
Es folgt ein Beispiel für die Flash IDE. Man kann dieses Beispiel aber auch mit Flashbuilder benutzen, die dafür vorgesehenen Codestellen sind auskommentiert.
package { import flash.display.Bitmap; import starling.display.Button; import starling.display.Sprite; import starling.events.Event; import starling.textures.Texture; public class Game extends Sprite { //eingebettet wenn mit Flashbuilder erstellt /*[Embed(source = "../media/textures/button_normal.png")] private static const ButtonTexture:Class;*/ public function Game() { addEventListener(Event.ADDED_TO_STAGE, onAdded); } private function onAdded(e:Event):void { // folgender Code wenn Flashbuilder //var buttonSkin:Bitmap = new ButtonTexture(new But1()); //Flash IDE Bild in Bibliothek BitmapData, Name: But1 var buttonSkin:Bitmap = new Bitmap(new But1()); // 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); } } }