Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
TLFSprite is a Starling extension that provides an alternative to starling.text.TextField to render text from OpenType or TrueType fonts (both device and embedded CFF) using the Text Layout Framework instead of flash.text.TextField
// =================================================================================================
//
// based on starling.text.TextField
// modified to use text layout framework engine for rendering text
//
// =================================================================================================
package starling.extensions
{
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flashx.textLayout.conversion.TextConverter;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.factory.TextFlowTextLineFactory;
import flashx.textLayout.factory.TruncationOptions;
import flashx.textLayout.formats.ITextLayoutFormat;
import flashx.textLayout.formats.TextLayoutFormat;
import starling.core.RenderSupport;
import starling.core.Starling;
import starling.display.DisplayObject;
import starling.display.DisplayObjectContainer;
import starling.display.Image;
import starling.display.Quad;
import starling.display.Sprite;
import starling.events.Event;
import starling.textures.Texture;
import starling.textures.TextureSmoothing;
/** A TLFSprite displays text, using standard open type or true type fonts.
*
* Rendering is done with a backing of the text layout framework engine as opposed
* to the classic flash.text.TextField as the standard starling.text.TextField employs.
*
* If relying on embedded font use ensure TextLayoutFormat.fontLookup is set to FontLookup.EMBEDDED_CFF,
* this defaults to FontLookup.DEVICE, expecting device fonts.
*
* Additionally, note that TLF expects embedded fonts with CFF, embedAsCFF="true" unlike
* classic TextField which uses embedded fonts with CFF disabled, embedAsCFF="false"
*
* Download and find out more about the latest Text Layout Framework at
* <a href="http://sourceforge.net/adobe/tlf/home/Home/">Text Layout Framework</a>
*/
public class TLFSprite extends Sprite
{
private var mTextFlow:TextFlow;
private var mFormat:TextLayoutFormat;
private var mRequiresRedraw:Boolean;
private var mBorder:DisplayObjectContainer;
private var mImage:Image;
private var mSmoothing:String;
private var mTruncationOptions:TruncationOptions;
private var mCompositionBounds:Rectangle;
// TLF rendering objects
private static var sTextLineFactory:TextFlowTextLineFactory;
private static var sTextLinesOrShapes:Vector.<flash.display.DisplayObject>;
private static var sHelperMatrix:Matrix = new Matrix();
/** Creates a TLFSprite from plain text.
* Optionally providing default formatting with TextLayoutFormat and composition
* width and height to limit active drawing area for rendering text
* */
public static function fromPlainText(text:String, format:TextLayoutFormat = null,
compositionWidth:Number = 2048, compositionHeight:Number = 2048):TLFSprite
{
return fromFormat(text, TextConverter.PLAIN_TEXT_FORMAT, format, compositionWidth, compositionHeight);
}
/** Creates a TLFSprite from a string of HTML text, limited by the HTML tags the TLF engine supports.
* See the Text Layout Framework documentation for supported tags.
*
* Optionally providing default formatting with TextLayoutFormat and composition
* width and height to limit active drawing area for rendering text
* */
public static function fromHTML(htmlString:String, format:TextLayoutFormat = null,
compositionWidth:Number = 2048, compositionHeight:Number = 2048):TLFSprite
{
return fromFormat(htmlString, TextConverter.TEXT_FIELD_HTML_FORMAT, format, compositionWidth, compositionHeight);
}
/** Creates a TLFSprite from a string of text layout XML text, limited by the XML tags the TLF engine supports.
* See the Text Layout Framework documentation for supported tags.
*
* Optionally providing default formatting with TextLayoutFormat and composition
* width and height to limit active drawing area for rendering text
* */
public static function fromTextLayout(layoutXMLString:String, format:TextLayoutFormat = null,
compositionWidth:Number = 2048, compositionHeight:Number = 2048):TLFSprite
{
return fromFormat(layoutXMLString, TextConverter.TEXT_LAYOUT_FORMAT, format, compositionWidth, compositionHeight);
}
private static function fromFormat(text:String, type:String, format:TextLayoutFormat = null,
compositionWidth:Number = 2048, compositionHeight:Number = 2048):TLFSprite
{
var tlfSprite:TLFSprite = null;
var textFlow:TextFlow = TextConverter.importToFlow(text ? text : "", type);
if (textFlow)
tlfSprite = new TLFSprite(textFlow, format, compositionWidth, compositionHeight);
return tlfSprite;
}
/**
* Basic constructor that takes an already constructed TLF TextFlow with optional
* default format and composition limits
*
* See the static helper methods for quickly instantiating a TLFSprite from
* a simple plain text unformatted string, HTML or TLF text layout markup.
* */
public function TLFSprite(textFlow:TextFlow, format:TextLayoutFormat = null,
compositionWidth:Number = 2048, compositionHeight:Number = 2048)
{
super();
initTLF();
mTextFlow = textFlow;
mTruncationOptions = new TruncationOptions();
mCompositionBounds = new Rectangle( 0, 0, compositionWidth, compositionHeight);
if (format) mFormat = format;
else {
mFormat = new TextLayoutFormat();
}
mSmoothing = TextureSmoothing.BILINEAR;
addEventListener(Event.FLATTEN, onFlatten);
mRequiresRedraw = true;
}
/** Disposes the underlying texture data. */
public override function dispose():void
{
removeEventListener(Event.FLATTEN, onFlatten);
if (mImage) mImage.texture.dispose();
super.dispose();
}
private function onFlatten(event:Event):void
{
if (mRequiresRedraw) redrawContents();
}
/** @inheritDoc */
public override function render(support:RenderSupport, parentAlpha:Number):void
{
if (mRequiresRedraw) redrawContents();
super.render(support, parentAlpha);
}
private function redrawContents():void
{
createRenderedContents();
mRequiresRedraw = false;
}
private function createRenderedContents():void
{
var scale:Number = Starling.contentScaleFactor;
var bitmapData:BitmapData = createRenderedBitmap();
if (!bitmapData) return;
var texture:Texture = Texture.fromBitmapData(bitmapData, false, false, scale);
if (mImage == null)
{
mImage = new Image(texture);
mImage.touchable = false;
mImage.smoothing = mSmoothing;
addChild(mImage);
}
else
{
if (mImage.texture) mImage.texture.dispose();
mImage.texture = texture;
mImage.readjustSize();
}
updateBorder();
}
/** public in case one wants to use this class as a pipeline for
* creating bitmap data outside the typical end use of a sprite's texture
* */
public function createRenderedBitmap():BitmapData
{
if (!mTextFlow) return null;
var scale:Number = Starling.contentScaleFactor;
// clear out any existing text lines or shapes
sTextLinesOrShapes.length = 0;
sTextLineFactory.compositionBounds = mCompositionBounds;
sTextLineFactory.truncationOptions = mTruncationOptions;
// NOTE: so that we function similar to Starling's TextField that hides
// the fontSize scaling of Starling.contentScaleFactor internally,
// we temporarily also scale up the format's fontSize setting only
// to then reset it when finished
if (scale != 1.0) {
var origFontSize:* = mFormat.fontSize;
mFormat.fontSize = Math.max(1, Math.min(720,
(origFontSize == undefined ? 12 : origFontSize as Number)*scale));
}
mTextFlow.hostFormat = mFormat;
sTextLineFactory.createTextLines( generatedTextLineOrShape, mTextFlow);
// after lines are generated we can ask the factory for the content
// bounds that encompasses the current line renderings
var contentBounds:Rectangle = sTextLineFactory.getContentBounds();
// Reset modified fontSize value
if (scale != 1.0) mFormat.fontSize = origFontSize;
var textWidth:Number = Math.min(2048, contentBounds.width*scale);
var textHeight:Number = Math.min(2048, contentBounds.height*scale);
var bitmapData:BitmapData = new BitmapData(textWidth, textHeight, true, 0x0);
// draw each text line or shape into bitmap
var lineOrShape:flash.display.DisplayObject;
for (var i:int = 0; i < sTextLinesOrShapes.length; ++i) {
lineOrShape = sTextLinesOrShapes[i];
sHelperMatrix.setTo(scale, 0, 0, scale,
(lineOrShape.x - contentBounds.x)*scale, (lineOrShape.y - contentBounds.y)*scale);
bitmapData.draw(lineOrShape, sHelperMatrix);
}
// finished need for generated lines or shapes
sTextLinesOrShapes.length = 0;
return bitmapData;
}
private static function initTLF():void
{
if (sTextLineFactory == null) {
sTextLineFactory = new TextFlowTextLineFactory();
sTextLinesOrShapes = new <flash.display.DisplayObject>[];
}
}
/** generated TextLines or Shapes (from background colors etc..)
* get added to a collected vector of results
* */
private function generatedTextLineOrShape( lineOrShape:flash.display.DisplayObject ):void
{
sTextLinesOrShapes.push(lineOrShape);
}
private function updateBorder():void
{
if (mBorder == null || mImage == null) return;
var width:Number = mImage.width;
var height:Number = mImage.height;
var topLine:Quad = mBorder.getChildAt(0) as Quad;
var rightLine:Quad = mBorder.getChildAt(1) as Quad;
var bottomLine:Quad = mBorder.getChildAt(2) as Quad;
var leftLine:Quad = mBorder.getChildAt(3) as Quad;
topLine.width = width; topLine.height = 1;
bottomLine.width = width; bottomLine.height = 1;
leftLine.width = 1; leftLine.height = height;
rightLine.width = 1; rightLine.height = height;
rightLine.x = width - 1;
bottomLine.y = height - 1;
topLine.color = rightLine.color = bottomLine.color = leftLine.color = mFormat.color;
}
/** @inheritDoc */
public override function getBounds(targetSpace:starling.display.DisplayObject, resultRect:Rectangle=null):Rectangle
{
return mImage.getBounds(targetSpace, resultRect);
}
/** Calling set text makes the assumption that you now expect only
* simple content with external formats and completely replace any
* previously set content, plain text, html or otherwise
* */
public function set text(value:String):void
{
mTextFlow = TextConverter.importToFlow(value ? value : "", TextConverter.PLAIN_TEXT_FORMAT);
mRequiresRedraw = true;
}
/** Calling set html makes the assumption that you now expect
* HTML content and completely replace any
* previously set content, plain text, html or otherwise
* */
public function set html(value:String):void
{
mTextFlow = TextConverter.importToFlow(value ? value : "", TextConverter.TEXT_FIELD_HTML_FORMAT);
mRequiresRedraw = true;
}
/** Calling set textLayout makes the assumption that you now expect
* Text layout markup content and completely replace any
* previously set content, plain text, html or otherwise
* */
public function set textLayout(value:String):void
{
mTextFlow = TextConverter.importToFlow(value ? value : "", TextConverter.TEXT_LAYOUT_FORMAT);
mRequiresRedraw = true;
}
public function get compositionWidth():Number {return mCompositionBounds.width;}
public function set compositionWidth(value:Number):void
{
if (value != mCompositionBounds.width) {
mCompositionBounds.width = value;
mRequiresRedraw = true;
}
}
public function get compositionHeight():Number {return mCompositionBounds.height;}
public function set compositionHeight(value:Number):void
{
if (value != mCompositionBounds.height) {
mCompositionBounds.height = value;
mRequiresRedraw = true;
}
}
public function get truncationOptions():TruncationOptions {return mTruncationOptions;}
public function set truncationOptions(value:TruncationOptions):void
{
mTruncationOptions = value;
mRequiresRedraw = true;
}
/** Draws a border around the edges of the text field. Useful for visual debugging.
* @default false */
public function get border():Boolean { return mBorder != null; }
public function set border(value:Boolean):void
{
if (value && mBorder == null)
{
mBorder = new Sprite();
addChild(mBorder);
for (var i:int=0; i<4; ++i)
mBorder.addChild(new Quad(1.0, 1.0));
updateBorder();
}
else if (!value && mBorder != null)
{
mBorder.removeFromParent(true);
mBorder = null;
}
}
/** The smoothing filter that is used for the image texture.
* @default bilinear
* @see starling.textures.TextureSmoothing */
public function get smoothing():String { return mSmoothing; }
public function set smoothing(value:String):void
{
if (TextureSmoothing.isValid(value)) {
mSmoothing = value;
if (mImage) mImage.smoothing = mSmoothing;
}
else
throw new ArgumentError("Invalid smoothing mode: " + value);
}
/** Returns the value of the style specified by the <code>styleProp</code> parameter, which specifies
* the style name from the text's TextLayoutFormat.
*
* @param styleProp The name of the style whose value is to be retrieved.
*
* @return The value of the specified style. The type varies depending on the type of the style being
* accessed. Returns <code>undefined</code> if the style is not set.
*/
public function getStyle(styleProp:String):*
{
return mFormat.getStyle(styleProp);
}
/** Sets the style specified by the <code>styleProp</code> parameter to the value specified by the
* <code>newValue</code> parameter.
*
* Contents are redrawn with the updated property changes on the next render.
*
* @param styleProp The name of the style to set.
* @param newValue The value to which to set the style.
*/
public function setStyle(styleProp:String,newValue:*):void
{
mFormat.setStyle(styleProp, newValue);
mRequiresRedraw = true;
}
/** Returns the styles on this text's TextLayoutFormat. Note that the getter makes a copy of the
* styles dictionary. The coreStyles object encapsulates all styles set in the format property including core and user styles. The
* returned object consists of an array of <em>stylename-value</em> pairs.
*
* @see flashx.textLayout.formats.TextLayoutFormat
*/
public function get styles():Object
{
return mFormat.styles;
}
/**
* Replaces property values in this text's TextLayoutFormat object with the values of properties that are set in
* the <code>incoming</code> ITextLayoutFormat instance. Properties that are <code>undefined</code> in the <code>incoming</code>
* ITextLayoutFormat instance are not changed in this object.
*
* Contents are redrawn with the updated property changes on the next render.
*
* @param incoming instance whose property values are applied to this text's TextLayoutFormat object.
*/
public function apply(incoming:ITextLayoutFormat):void
{
mFormat.apply(incoming);
mRequiresRedraw = true;
}
/**
* Concatenates the values of properties in the <code>incoming</code> ITextLayoutFormat instance
* with the values of this text's TextLayoutFormat object. In this (the receiving) TextLayoutFormat object, properties whose values are <code>FormatValue.INHERIT</code>,
* and inheriting properties whose values are <code>undefined</code> will get new values from the <code>incoming</code> object.
* Non-inheriting properties whose values are <code>undefined</code> will get their default values.
* All other property values will remain unmodified.
*
* Contents are redrawn with the updated property changes on the next render.
*
* @param incoming instance from which values are concatenated.
*/
public function concat(incoming:ITextLayoutFormat):void
{
mFormat.concat(incoming);
mRequiresRedraw = true;
}
/**
* Concatenates the values of properties in the <code>incoming</code> ITextLayoutFormat instance
* with the values of this text's TextLayoutFormat object. In this (the receiving) TextLayoutFormat object, properties whose values are <code>FormatValue.INHERIT</code>,
* and inheriting properties whose values are <code>undefined</code> will get new values from the <code>incoming</code> object.
* All other property values will remain unmodified.
*
* Contents are redrawn with the updated property changes on the next render.
*
* @param incoming instance from which values are concatenated.
*/
public function concatInheritOnly(incoming:ITextLayoutFormat):void
{
mFormat.concatInheritOnly(incoming);
mRequiresRedraw = true;
}
/**
* Copies TextLayoutFormat settings from the <code>values</code> ITextLayoutFormat instance into this text's TextLayoutFormat object.
* If <code>values</code> is <code>null</code>, this TextLayoutFormat object is initialized with undefined values for all properties.
*
* Contents are redrawn with the updated property changes on the next render.
*
* @param values optional instance from which to copy values.
*/
public function copy(incoming:ITextLayoutFormat):void
{
mFormat.copy(incoming);
mRequiresRedraw = true;
}
/**
* Sets properties in this text's TextLayoutFormat object to <code>undefined</code> if they do not match those in the
* <code>incoming</code> ITextLayoutFormat instance.
*
* Contents are redrawn with the updated property changes on the next render.
*
* @param incoming instance against which to compare this TextLayoutFormat object's property values.
*/
public function removeClashing(incoming:ITextLayoutFormat):void
{
mFormat.removeClashing(incoming);
mRequiresRedraw = true;
}
/**
* Sets properties in this text's TextLayoutFormat object to <code>undefined</code> if they match those in the <code>incoming</code>
* ITextLayoutFormat instance.
*
* Contents are redrawn with the updated property changes on the next render.
*
* @param incoming instance against which to compare this TextLayoutFormat object's property values.
*/
public function removeMatching(incoming:ITextLayoutFormat):void
{
mFormat.removeMatching(incoming);
mRequiresRedraw = true;
}
}
}
// Flat list of sample calls to creating TLFSprite instances and adding to a parent DisplayObjectContainer
// default TextLayoutFormat settings using static factory creation
var tlfSprite:TLFSprite = TLFSprite.fromPlainText("Hello World");
tlfSprite.border = true;
tlfSprite.x = 10;
tlfSprite.y = 10;
addChild(tlfSprite);
tlfSprite = TLFSprite.fromPlainText("This is some larger\nline broken text");
tlfSprite.border = true;
// post specify individual TextLayoutFormat style properties individually
tlfSprite.setStyle("fontSize", 32);
tlfSprite.setStyle("color", Color.RED);
tlfSprite.x = 50;
tlfSprite.y = 25;
addChild(tlfSprite);
tlfSprite = TLFSprite.fromPlainText("This is some larger line flow text");
tlfSprite.border = true;
tlfSprite.setStyle("fontSize", 24);
tlfSprite.setStyle("color", Color.RED);
// forces truncation showing ellipses as default truncation indicator
tlfSprite.compositionWidth = 170;
tlfSprite.compositionHeight = 60;
tlfSprite.x = 350;
tlfSprite.y = 25;
addChild(tlfSprite);
// text layout framework supports some very simple HTML style markup
tlfSprite = TLFSprite.fromHTML("<p>Some <b>preformatted</b> <font size=28 color=0x00ff00>text with</font><br> some <i>simple</i> HTML markup.");
tlfSprite.border = true;
tlfSprite.x = 10;
tlfSprite.y = 110;
addChild(tlfSprite);
tlfSprite = TLFSprite.fromPlainText("Here we might want to limit the width this text should display, auto flowing");
tlfSprite.border = true;
// Force a limit on width to trigger line breaking
tlfSprite.compositionWidth = 150;
tlfSprite.x = 10;
tlfSprite.y = 180;
addChild(tlfSprite);
// You can also use an XML markup tagging system from the text layout framework itself
// that allows basic paragraph and span elements using named TextLayoutFormat properties as attributes
tlfSprite = TLFSprite.fromTextLayout("<TextFlow xmlns='http://ns.adobe.com/textLayout/2008'>" +
"<p><span fontSize='16'>Hello, World.</span></p><p><span color='0x0000FF' fontSize='18'>Some simple TLF markup</span></p></TextFlow>");
tlfSprite.border = true;
tlfSprite.x = 300;
tlfSprite.y = 100;
addChild(tlfSprite);
// explicitly set multiple TextLayoutFormat properties at beginning and pass with creation
var textLayoutFormat:TextLayoutFormat = new TextLayoutFormat();
textLayoutFormat.paragraphStartIndent = 15;
textLayoutFormat.paragraphSpaceBefore = 15;
textLayoutFormat.paragraphEndIndent = 15;
textLayoutFormat.paragraphSpaceAfter = 15;
textLayoutFormat.textIndent = 20;
textLayoutFormat.color = 0x336633;
textLayoutFormat.fontFamily = "Arial, Helvetica, _sans";
textLayoutFormat.fontSize = 16;
textLayoutFormat.kerning = Kerning.ON;
textLayoutFormat.lineHeight = "100%";
tlfSprite = TLFSprite.fromPlainText("This example formats a paragraph with 15 pixel margins, a 20 pixel first " +
"line indent. It uses the " +
"Arial font (with alternate device fonts), sets the size to 16 pixels, the color to green, " +
" turns on kerning, \n \t and sets leading (lineHeight) to 100%.", textLayoutFormat);
tlfSprite.border = true;
tlfSprite.compositionWidth = 450;
tlfSprite.x = 230;
tlfSprite.y = 150;
addChild(tlfSprite);
// Define east asian ideographic layout and formatting
// along with enforced composition height limit
textLayoutFormat = new TextLayoutFormat();
// define Japanese text in a string of Unicode characters
var jaText:String = String.fromCharCode(
0x30AF, 0x30ED, 0x30B9, 0x30D7, 0x30E9, 0x30C3, 0x30C8, 0x30D5,
0x30A9, 0x30FC, 0x30E0, 0x4E0A, 0x3067, 0x518D, 0x751F, 0x53EF,
0x80FD, 0x306A) +
"Flash Video" +
String.fromCharCode(
0x3092, 0x914D, 0x4FE1, 0x3001, 0x653F, 0x5E9C, 0x6700, 0x65B0,
0x60C5, 0x5831, 0x3092, 0x3088, 0x308A, 0x591A, 0x304F, 0x306E,
0x56FD, 0x6C11, 0x306B, 0x9AD8, 0x54C1, 0x8CEA, 0x306A, 0x753B,
0x50CF, 0x3067, 0x7C21, 0x5358, 0x304B, 0x3064, 0x30EA, 0x30A2,
0x30EB, 0x30BF, 0x30A4, 0x30E0, 0x306B, 0x63D0, 0x4F9B, 0x3059,
0x308B, 0x3053, 0x3068, 0x304C, 0x53EF, 0x80FD, 0x306B, 0x306A,
0x308A, 0x307e, 0x3057, 0x305F, 0x3002);
textLayoutFormat.locale = "ja";
if (Capabilities.os.search("Mac OS") > -1)
textLayoutFormat.fontFamily = String.fromCharCode(0x5C0F, 0x585A, 0x660E, 0x671D) + " Pro R"; // "Kozuka Mincho Pro R"
else
textLayoutFormat.fontFamily = "Kozuka Mincho Pro R";
// specify right-to-left block progression, east Asian justification, and top vertical alignment
textLayoutFormat.blockProgression = BlockProgression.RL;
textLayoutFormat.justificationRule = JustificationRule.EAST_ASIAN;
textLayoutFormat.verticalAlign = VerticalAlign.TOP;
textLayoutFormat.fontSize = 18;
tlfSprite = TLFSprite.fromPlainText(jaText, textLayoutFormat);
tlfSprite.border = true;
tlfSprite.compositionHeight = 250;
tlfSprite.x = 30;
tlfSprite.y = 250;
addChild(tlfSprite);
// Arabic right-to-left language support
textLayoutFormat = new TextLayoutFormat();
textLayoutFormat.fontFamily = "Arial";
textLayoutFormat.fontSize = 24;
textLayoutFormat.paragraphSpaceBefore = 2;
textLayoutFormat.paragraphSpaceAfter = 2;
textLayoutFormat.paddingBottom = textLayoutFormat.paddingTop =
textLayoutFormat.paddingLeft = textLayoutFormat.paddingRight = 5;
textLayoutFormat.direction = Direction.RTL;
textLayoutFormat.blockProgression = BlockProgression.TB;
tlfSprite = TLFSprite.fromPlainText("وثيقة إثبات صلة القرابة مصدقة من سفارة دولة الإمارات – إذا لم يكن اسم المكفول مدرجاً في جواز سفر", textLayoutFormat);
tlfSprite.border = true;
tlfSprite.compositionWidth = 350;
tlfSprite.x = 220;
tlfSprite.y = 250;
addChild(tlfSprite);
// If you feel like it, you can also just build up your text layout framework
// TextFlow elements by hand
// create TextFlow, ParagraphElement, and SpanElement objects
var textFlow:TextFlow = new TextFlow();
var p:ParagraphElement = new ParagraphElement();
var span1:SpanElement = new SpanElement();
var span2:SpanElement = new SpanElement();
textLayoutFormat = new TextLayoutFormat();
textLayoutFormat.color = 0x333333;
textLayoutFormat.textAlpha = 0.9;
textLayoutFormat.fontSize = 20;
textLayoutFormat.fontFamily = "Times Roman";
// add text to the span, the span to the paragraph, and the paragraph to the text flow.
textFlow.format = textLayoutFormat;
span1.text = "This content was built by combining raw text flow elements together."
p.addChild( span1);
span2.text = "This is a second span in the paragraph."
span2.backgroundColor = 0xFF0000;
span2.backgroundAlpha = 0.6;
p.addChild( span2);
textFlow.addChild(p);
tlfSprite = new TLFSprite(textFlow, textLayoutFormat);
tlfSprite.border = true;
tlfSprite.compositionWidth = 350;
tlfSprite.x = 220;
tlfSprite.y = 360;
addChild(tlfSprite);
@FrancescoMaisto

This comment has been minimized.

Copy link

@FrancescoMaisto FrancescoMaisto commented May 9, 2013

Very useful! thanks for sharing this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment