Posted on

Starling的Display Objects介紹(五)

上一篇:Starling的Display Objects介紹(四) 下一篇:產生TextureAtlas素材的方式 這篇會介紹starling.display.MovieClip以及starling.animation.Juggler

MovieClip物件介紹

官方手冊在此:http://doc.starling-framework.org/core/starling/display/MovieClip.html 在Starling裡面的MovieClip和原生的MovieClip差異蠻大的,列舉說明如下:

  1. 每一個MovieClip都可設定不同的fps(需在new MovieClip時指定):這是因為一般來說在Starling裡的frameRate都會設定的很高,但動畫可能不會有這麼多圖片可跑,因此每一個MovieClip
  2. 由一連串的連續圖檔組成:MovieClip是由一連串的動畫圖片(TextureAtlas)輪流播放。
  3. MovieClip裡面無法有任何物件:在Starling裡的MovieClip並不是繼承DisplayObjectContainer,而是繼承於Image,因此無法addChild()。我們可以想像MovieClip在Starling裡是一個Image下面放著一張很大的png,然後有一個方型遮罩,不停的變換顯示的區塊。
  4. 動畫效果必需由Juggler驅動:在Starling裡所有的動畫物件都必需實作IAnimatable這個介面。而所有的動畫效果則統一由Juggler驅動(另外還包括Tween、DelayedCall)。
  5. 沒有frameLabel的概念(這部份有些Starling extension有加上去此功能)
  6. 當isComplete等於true時會停止動畫的播放

順便介紹兩個好用的method。第一個為setFrameDuration(),可以另外再設定某個影格的停留時間,影格數從0開始。而setFrameSound()可以設定播放到某影格時播放一個聲音檔。 連續圖檔的產生方式請見此文章:產生TextureAtlas的方式

Juggler物件介紹

官方手冊在此:http://doc.starling-framework.org/core/starling/animation/Juggler.html 介紹在此:http://grayliao.blogspot.tw/2011/11/starling-framework6jugglertweendelaycal.html

Juggle是個簡單的Class,用來控制動畫的進行。他負責管理經由add()加進來的實現IAnimatable介面的物件,然後當Juggler的advanceTime()被呼叫時,它會負責去呼叫這些IAnimatable的advanceTime(),讓動畫進行下去;而當某個IAnimatable到達complete狀態時,則會被Juggler踢出去。我們就只要負責每個frame去呼叫Juggler的advanceTime()就好。而Starling class裡有個預設的juggler,當Starling.current正在運行時,Starling.juggler在每個frame時會自動被呼叫advanceTime()。通常我們把遊戲裡的動畫加到Starling.juggler裡,而一些特殊的動畫,例如遊戲暫停時要撥放的動畫,再加到另一個我們新增的Juggler,然後每個frame去呼叫它的advanceTime()。這裡講的每個frame,我們是使用EnterFrameEvent.ENTER_FRAME,而不是Event.ENTER_FRAME,因為EnterFrameEvent可以取得passedTime,這個passTime是指跟上一次事件發生經過的時間,可以傳給Juggle的advanceTime()的第一個參數。用經過時間來播放動畫,這樣就不會受frame數不穩定而影響動畫撥放的時間。 而要實現IAnimatable介面,只要實作advanceTime()這個方法,並且要設定一個complete條件,當達到這個條件時將自己的isComplete設為true,這樣就可以自動被Juggle移除。

簡單的MovieClip範例

因為在Starling裡面,不論是MovieClip或是Button,都是正方形的。若要去判別透明,將透明的Touch事件設為無效,都需要去覆寫原有的類別裡的hitTest函數,以最原始的BitmapData的hitTest去判別是否為透明圖層。 這邊有一個已寫好的類別:AlphaMovieClip,已經寫好將透明圖層的所有hitTest事件設為無效。若有這個需求的人可以使用此類別來應用。下面是一個我寫的簡單應用範例,可點此下載原始檔:StarlingTest StarlingTest.as

package
{
	import flash.display.Sprite;

	import starling.core.Starling;

	[SWF(frameRate="60",Width="800",Height="600")]
	public class StarlingTest extends Sprite
	{
		public function StarlingTest()
		{
			var star:Starling = new Starling(Main, stage);
			star.start();
		}
	}
}

Main.as

package {
	import flash.display.Bitmap;

	import starling.core.Starling;
	import starling.display.MovieClip;
	import starling.display.Sprite;
	import starling.events.Touch;
	import starling.events.TouchEvent;
	import starling.events.TouchPhase;
	import starling.textures.Texture;
	import starling.textures.TextureAtlas;

	public class Main extends Sprite {
		[Embed(source = 'test.xml', mimeType = 'application/octet-stream')]
		private var AtlasXML:Class;

		[Embed(source = 'test.png')]
		private var AtlasTexture:Class;

		private var mc:AlphaMovieClip ;
		public function Main() {
			var bitmap:Bitmap = new AtlasTexture();
			var texture:Texture = Texture.fromBitmap(bitmap);
			var xml:XML = XML(new AtlasXML());
			var atlas:TextureAtlas = new TextureAtlas(texture, xml);

			mc = new AlphaMovieClip("run",atlas, bitmap.bitmapData,30);
			var m2:MovieClip = new MovieClip(atlas.getTextures("run"),30);
			m2.loop = false;
			m2.x = 200;
			mc.addEventListener(TouchEvent.TOUCH, touchEventHandler);
			addChild(mc);
			addChild(m2);

			Starling.juggler.add(mc);
			Starling.juggler.add(m2);
		}

		private function touchEventHandler(event:TouchEvent):void {
			var touch:Touch = event.getTouch(this);
			if(!touch ) return;
			if (touch.phase == TouchPhase.BEGAN){
				mc.pause();
			}else if(touch.phase == TouchPhase.ENDED){
				mc.play();
			}
		}
	}
}

AlphaMovieClip.as

package {
	import flash.display.BitmapData;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;
	import starling.display.DisplayObject;
	import starling.display.MovieClip;
	import starling.textures.SubTexture;
	import starling.textures.Texture;
	import starling.textures.TextureAtlas;

	public class AlphaMovieClip extends MovieClip
	{
		private var m_TexturePrefix	:String				= "";
		private var m_TextureAtlas	:TextureAtlas		= null;
		private var m_Textures		:Vector.	= null;
		private var m_TextureInfo	:Dictionary			= null;
		private var m_BitmapData	:BitmapData			= null;

		public function AlphaMovieClip(texturePrefix:String, textureAtlas:TextureAtlas, bitmapData:BitmapData, fps:Number = 12)
		{
			m_TextureInfo = new Dictionary();

			var names:Vector. = textureAtlas.getNames(texturePrefix);
			for each (var name:String in names)
			{
				var textureInfo:Object = { };
				textureInfo.name = name;
				textureInfo.texture = textureAtlas.getTexture(name);
				m_TextureInfo[name] = textureInfo;
			}

			m_TexturePrefix = texturePrefix;
			m_TextureAtlas = textureAtlas;
			m_Textures = textureAtlas.getTextures(texturePrefix);
			m_BitmapData = bitmapData;

			super(m_Textures, fps);
		}

		override public function hitTest(localPoint:Point, forTouch:Boolean = false):DisplayObject
		{
			if (forTouch && visible && touchable)
			{
				if (getBounds(this).containsPoint(localPoint))
				{
					var texture:Texture = getFrameTexture(currentFrame);
					var subtexture:SubTexture = texture as SubTexture;
					var textureFrame:Rectangle = subtexture.frame;
					var clipping:Rectangle = subtexture.clipping;
					var frameBound:Rectangle = new Rectangle(
						Math.abs(textureFrame.x),
						Math.abs(textureFrame.y),
						clipping.width * m_TextureAtlas.texture.width,
						clipping.height * m_TextureAtlas.texture.height
					);
					clipping.x *= m_TextureAtlas.texture.width;
					clipping.y *= m_TextureAtlas.texture.height;

					var final_x:uint = (frameBound.containsPoint(localPoint) ? localPoint.x - frameBound.x : uint.MAX_VALUE);
					var final_y:uint = (frameBound.containsPoint(localPoint) ? localPoint.y - frameBound.y : uint.MAX_VALUE);

					if (final_x != uint.MAX_VALUE && final_y != uint.MAX_VALUE)
					{
						var pixel:uint = m_BitmapData.getPixel32(clipping.x + final_x, clipping.y + final_y);
						if (uint((pixel >> 24) & 0xFF) == 0)
						{
							return null;
						}
					}
					else
					{
						return null;
					}
				}
			}
			return super.hitTest(localPoint, forTouch);
		}
	}
}

test.png
test
test.xml

<?xml version="1.0" encoding="UTF-8"?>
<textureAtlas imagePath="test.png">
    <subTexture name="run0001" x="2" y="2" width="94" height="156" frameX="0" frameY="-4" frameWidth="100" frameHeight="160"/>
    <subTexture name="run0002" x="98" y="2" width="94" height="150" frameX="0" frameY="-10" frameWidth="100" frameHeight="160"/>
    <subTexture name="run0003" x="194" y="2" width="98" height="148" frameX="-1" frameY="-12" frameWidth="100" frameHeight="160"/>
    <subTexture name="run0004" x="294" y="2" width="100" height="154" frameX="0" frameY="-6" frameWidth="100" frameHeight="160"/>
    <subTexture name="run0005" x="396" y="2" width="98" height="160" frameX="0" frameY="0" frameWidth="100" frameHeight="160"/>
    <subTexture name="run0006" x="2" y="164" width="96" height="160" frameX="0" frameY="0" frameWidth="100" frameHeight="160"/>
    <subTexture name="run0007" x="100" y="164" width="96" height="158" frameX="-1" frameY="-2" frameWidth="100" frameHeight="160"/>
    <subTexture name="run0008" x="198" y="164" width="96" height="152" frameX="-1" frameY="-8" frameWidth="100" frameHeight="160"/>
    <subTexture name="run0009" x="296" y="164" width="98" height="150" frameX="-1" frameY="-10" frameWidth="100" frameHeight="160"/>
    <subTexture name="run0010" x="396" y="164" width="100" height="156" frameX="0" frameY="-4" frameWidth="100" frameHeight="160"/>
    <subTexture name="run0011" x="2" y="326" width="98" height="160" frameX="0" frameY="0" frameWidth="100" frameHeight="160"/>
    <subTexture name="run0012" x="102" y="326" width="96" height="160" frameX="-1" frameY="0" frameWidth="100" frameHeight="160"/>
</textureAtlas>