2009年9月28日 星期一

Embedding JavaScript into a SWF

Flash 可以使用 ExternalInterface 類別來呼叫外部的.js函式
但前提是該html需要事件把js寫好
AS3之後, 可以把js的function整個包成.as裡, 一樣透過ExternalInterface 呼叫即可

在這個範例, 我使用了bit101的Button組件, 你可以換成自已的按鈕。
透過 XML 的 CDATA, 把整個js包進.as檔裡。

SourceCodeDownload
進階的用法, 可以使用Flash裡的Tweener類別, 來更改html裡DIV的大小

SourceCodeDownload

2009年9月25日 星期五

Progression3 tutorial part2

上一篇簡單分享過Progression全組件式的寫法
雖然方便, 但擴充性並不強, 要寫一個專案, 還是需要乖乖來寫程式
玩Progression要先搞懂三個要點:
1. 場景 ScendObject
他是Progression的整個核心架構
你可以想成html, 一個scene就是一頁html
該scene要有什麼元素, 都由這個SceneObject來管理
SceneObject有四個重要的事件:
_onLoad:當該場景被載入時。
_onInit:當該場景被初始化。
_onGoto:當該場景切換到別的場景時。
_onUnload:當該場景被移除時。
SceneObject的生命週期就是走這四個事件。
只有最上一層的主場景_onLoad只會發生一次
_onUnload永遠不會發生, 這個後面會再補充。

2. 可視物件 CastSprite, CastMovieClip
跟一般的Sprite, MovieClip使用起來差不多
但有二個重要的事件。
_onCastAdded:當透過new AddChild 或是 new AddChildAt 加入該物件時, 會發生的事件
_onCastRemoved:當透過new RemoveChild 移除該物件時, 會發生的事件。

3. Command
Progression提供了許多的 Command可以使用
Command Design pattern的好處就是可以依序的執行Command
跑完A, 再跑B , 再跑C,
所以可以很方便的製作單元切換的進退場效果。
像第二點的new AddChild就是 Command的其中一種

一個專案一般只會有一個Progression實體物件。
var prog:Progression= new Progression( 'index', stage, IndexScene );
'index':為該專案的Progression唯一的 Key 值, 只要key值不同, 彼此寫的Progression就不會打架
stage:把stage實體傳入
IndexScene:則是最上一層的 SceneObject, 其他的場景就由這裡開始加入

在這兒範例裡, 做了一個很簡單的架構, 好讓快速上手
首頁場景:
場景上有二個Button,
About:
About的內容可視物件
Contact:
Contact的內容可視物件

其中首頁的場景就是對映剛剛的IndexScene。

先建立Progression
Index.as
package milkmidi.progression {
import flash.display.*;  
import jp.nium.events.DocumentEvent;
import jp.progression.casts.*;
import jp.progression.commands.*;
import jp.progression.core.debug.Verbose;
import jp.progression.events.*;
import jp.progression.loader.*;
import jp.progression.*;
import jp.progression.scenes.*; 
import milkmidi.progression.scenes.IndexScene;
public class Index extends CastDocument {
public var prog:Progression;   
public function Index() {}  
protected override function _onInit():void {   
align = StageAlign.TOP_LEFT;
quality = StageQuality.HIGH;
scaleMode = StageScaleMode.NO_SCALE;    
//Verbose.enabled = true;
//是否啟用 debug 功能。   
//Verbose.filteringCommand();   
//debug 功能是否過濾 Command 資訊   
prog = new Progression( 'index', stage, IndexScene );    
prog.sync = true;      
//是否啟用swfAddress功能。   
prog.goto( prog.firstSceneId );
//播放第一個場景, 也就是 IndexScene
}
}
}
建立IndexScene, 主場景物件
同時在IndexScene裡有一個 IndexPage的主畫面。
package milkmidi.progression.scenes {
import jp.progression.casts.*;
import jp.progression.commands.*;
import jp.progression.events.*;
import jp.progression.loader.*;
import jp.progression.*;
import jp.progression.scenes.*;
import milkmidi.progression.btn.*;  
import milkmidi.progression.page.IndexPage;
public class IndexScene extends SceneObject {  
public var page:IndexPage = new IndexPage(); 
public function IndexScene() {   
title = "IndexSceneTitle"; 
//網址的 Title      
addScene( new AboutScene('about') );           
//加入子場景 
}    
protected override function _onLoad():void { 
//當場景被載入時
//如果是IndexScene(主場景), 就只會被載入一次, 
//所以可以把要常駐的物件在這兒加入。
//在這個範例是加入二個按鈕。    
var indexButton:IndexButton = new IndexButton();
indexButton.x = 0;
indexButton.y = 400;
progression.container.addChild( indexButton );

var _aboutBtn:AboutButton = new AboutButton();
_aboutBtn.x = 100;
_aboutBtn.y = 400;
progression.container.addChild(_aboutBtn); 

addCommand( 
new Trace(this + "._onLoad()")    
);
}
protected override function _onInit():void {   
//當場景被初始化時
//該函式會接在_onLoad後發生
//除非是直接呼叫該場景的子場景
//ex:  #/about/aboutchild/
//那_onInit就不會發生。   
addCommand(
new Trace(this + "._onInit()"),
new AddChild( progression.container, page ) 
);
}  
protected override function _onGoto():void {    
//當場景離開時
addCommand(
new Trace(this + "._onGoto()"),
new RemoveChild( progression.container, page )
);
}
protected override function _onUnload():void {  
//當場景移除時
addCommand(
new Trace(this + "._onUnload()")
);
}
override public function toString():String {
return "[" + name + "Scene]";   
}
}
}

package milkmidi.progression.page {
import flash.text.TextField;
import flash.text.TextFormat;
import jp.progression.casts.*;
import jp.progression.commands.*;
import jp.progression.events.*;
import jp.progression.loader.*;
import jp.progression.*;
import jp.progression.scenes.*;
public class IndexPage extends CastSprite { 
public var txt:TextField; 
public function IndexPage( initObject:Object = null ) {
super( initObject ); 
//這兒只是做一個文字。
txt = new TextField();
txt.width = 400;
txt.text = "IndexPage";      
txt.setTextFormat( new TextFormat("Arial", 60));
txt.mouseEnabled = false;
addChild( txt );     
}
protected override function _onCastAdded():void {   
//當透過new AddChild 或是 new AddChildAt 加入該物件時, 會發生的事件。
//直接使用addChild的話是不會發生該事件的喔。
txt.x = -400;
//透過 Command, 可以在這兒加入進場的效果。
addCommand(    
new Trace(this + "_onCastAdded()"),
new DoTweener( txt, {
x:0,
time:1
} ) 
);
}  
protected override function _onCastRemoved():void {   
//當透過new RemoveChild 移除該物件時, 會發生的事件。
//直接使用DisplayObjectContent的removeChild的話是不會發生該事件的喔。
//在這兒就可以加入退場的效果。
addCommand(
new Trace(this + "_onCastRemoved()"),    
new DoTweener( txt, {
x:-400,
time:1
} )  
);
}
override public function toString():String {
return "IndexPage";
}
}
}

到這兒先來分析一下
Progression實體被建立, 並goto到第一個場景也就是 IndexScene
當來到 IndexScene 後
就依序發生了_onLoad , _onInit事件
然後我們在_onInit時, 透過new AddChild加入 IndexPage時
接著就會發生 IndexPage裡的 _onCastAdded 事件,
在這兒我們做一個Tweener的進場

當按下about的按鈕時, 要切換到 about場景
就會發生 IndexScene的 _onGogo事件
接著new RemoveChild把 IndexPage移掉
就會發生 IndexPage裡的 _onCastRemoved事件,
在這兒我們也是做一個Tweener的退場

退完場後, 才會進到 AboutScene裡的
_onLoad, _onInit事件。

Progression都是走這樣的模式。
因為是Command模式, 所以一定是一個Command跑完才跑下一個, 不會同時發生
要同時執行多個Command的話也是有這樣的類別。


有任何錯誤的地方, 歡迎請教討論。
SourceCodeDownload

補充:Progression內建的Tween類別是使用Tweener, 我將TweenMax包裝成Command類別,方便在Progression裡使用
請注意TweenMax的類別包喔,
舊版是:gs的package
新版是:com.greensock
package milkmidi.progression.commands.display {
import jp.progression.core.commands.Command;
import gs.TweenMax;
import gs.easing.*;
import gs.events.TweenEvent;
public class DoTweenMax extends Command {
private var _target  :Object;
private var _parameters :Object;   
private var _tweenMax :TweenMax;
private var _duration :Number;
public function DoTweenMax( pTarget:Object, pDuration:Number, pParameters:Object, pInitObject:Object = null ) {      
_target = pTarget;
_duration = pDuration;
_parameters = pParameters;     
super( _execute, _interrupt, pInitObject );
}
private function _execute():void { 
timeOut = Math.max( timeOut, _duration + 5000 );   
_tweenMax = new TweenMax( _target, _duration, _parameters);
_tweenMax.addEventListener(TweenEvent.COMPLETE , _complete, false, int.MAX_VALUE, true);         
if ( _tweenMax ) { return; }
executeComplete();
}   
private function _complete( e:TweenEvent ):void {     
_tweenMax.removeEventListener(TweenEvent.COMPLETE , _complete, false); 
executeComplete();
}
override public function executeComplete():void {
super.executeComplete();
_tweenMax = null;
}  
private function _interrupt():void { 
//trace("_interrupt");
_tweenMax.clear();      
interruptComplete();
}
public override function clone():Command {
return new DoTweenMax( _target, _duration , _parameters );   
}
}
}

Hack手法強制執行垃圾回收

一起來說 Flash 垃圾回收機制是自動發生, 無法透過手動呼叫控制, 而我們若要強制啟用垃圾回收機, 則需要用一些非正規手段。
在這兒我們產生一萬個 Sprite ,並繪製色塊, 一秒鐘後, 將一萬個Sprite移除。
此時可以看到記憶體還是這麼的高。

透過使用
try {
new LocalConnection().connect("milkmidi");
new LocalConnection().connect("milkmidi");
}catch (err:Error){}

記憶體就整個降了下來。
其原理就是故意使 swf 在運行時出錯, 然後throw出錯誤,
而同時通過catch error來繼續運行swf文件
而垃圾回收機則會在swf拋出錯誤的時候, 被強制執行一次
以清除 flash 內無效的數值, 減少資源的消耗。
但不是所有的throw錯誤都會發生垃圾回收。
至少這個方法是可行的。
要檢測你的swf用了多少的記憶體量, 可以使用Mr.doob所所的Stats類別來做監控。
http://code.google.com/p/mrdoob/wiki/stats

package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.net.LocalConnection;
import flash.utils.setTimeout;
import net.hires.debug.Stats;
public class HackDemo extends Sprite {
private const AMOUNT :int = 10000;  
private var _arr  :Array;
public function HackDemo(){   


createTenThousandObj();

addChild(new Stats());


setTimeout( removeTenThousandObj , 1000);
//雖然物件已經被移除, 但memory還是佔了約 33。

setTimeout( doHack , 1100);
//透過 Hack的手法, memory 就整個降下來了, 可以試著把上一行注解掉來做比較
}

private function createTenThousandObj():void {
_arr = [];
for (var i:int = 0; i < AMOUNT; i++) {
var _mc:Sprite = addChild( new Sprite() ) as Sprite;
_mc.graphics.beginFill(0x00ff00);
_mc.graphics.drawRect(0, 0, 100, 100);
_mc.graphics.endFill();
_arr.push(_mc);
}
}
private function removeTenThousandObj():void {
for (var i:int = 0; i < AMOUNT; i++) {
removeChild( _arr[i] );
delete _arr[i];
}
_arr = null;
}

private function doHack():void {
trace("doHack()");
try {
new LocalConnection().connect("foo");
new LocalConnection().connect("foo");
}catch (err:Error){

}
}
}
}

2009年9月24日 星期四

PV3D Pivot

away3d能直接修改物件的中心點
但PV3D並沒有該屬性, 但能透過以下三種方法達到一樣的效果。
1.用DisplayObject3D多包一層。
將物件加入到該DisplayObject3D, 然後對他做旋轉。
http://wonderfl.net/code/7fbfda83de4c282f304b3f11ef59575a1a10d484
2.直接去修改物件的Vertex3D座標。
http://wonderfl.net/code/b7e4f5b3650731820256cf7fbb3461662e2ec0d8
3.使用as3dmod提供的Pivot來更改。
要先下載as3dmod類別喔。

package  {  
import com.as3dmod.modifiers.Pivot;
import com.as3dmod.util.ModConstant; 
import com.as3dmod.ModifierStack; 
import com.as3dmod.plugins.pv3d.LibraryPv3d;  
import org.papervision3d.materials.ColorMaterial; 
import org.papervision3d.materials.utils.MaterialsList;
import org.papervision3d.objects.primitives.Cube;
import org.papervision3d.cameras.CameraType;
import org.papervision3d.view.BasicView; 
import flash.events.Event; 
public class PivotExampleas3dmod extends BasicView {   
private var do3d:Cube;   
public function PivotExampleas3dmod() {
super(800, 600, true, false, CameraType.TARGET);   
init3DObject();
startRendering();   
}
private function init3DObject():void {
var _ml:MaterialsList = new MaterialsList( { all:new ColorMaterial() } );
do3d = new Cube(_ml, 200, 200, 200);
var stack:ModifierStack = new ModifierStack(new LibraryPv3d(), do3d);
var pivot:Pivot = new Pivot( -200, -200, -200);
stack.addModifier(pivot);
stack.collapse();
scene.addChild(do3d);
scene.addChild( new Axes3D());
}  
protected override function onRenderTick(event:Event = null):void {
do3d.rotationY += 2;
super.onRenderTick(event);
}  
}
}

import org.papervision3d.core.geom.*;
import org.papervision3d.core.geom.renderables.*;
import org.papervision3d.materials.special.*;
import org.papervision3d.objects.*;
class Axes3D extends DisplayObject3D {
public function Axes3D() {
var _lines3D:Lines3D = new Lines3D();
var _red :LineMaterial = new LineMaterial(0xff0000);
var _green :LineMaterial = new LineMaterial(0x00ff00);
var _blue :LineMaterial = new LineMaterial(0x0000ff);
var _length :uint = 120;
var _lineX :Line3D = new Line3D(_lines3D, _red, 2, new Vertex3D(-_length, 0, 0), new Vertex3D(_length, 0, 0));
var _lineY :Line3D = new Line3D(_lines3D, _green, 2, new Vertex3D(0, -_length, 0), new Vertex3D(0, _length, 0));
var _lineZ :Line3D = new Line3D(_lines3D, _blue, 2, new Vertex3D(0, 0, -_length), new Vertex3D(0, 0, _length));
_lines3D.addLine(_lineX);
_lines3D.addLine(_lineY);
_lines3D.addLine(_lineZ);
addChild(_lines3D);            
}
}

2009年9月20日 星期日

[新案上線]TOYOTA NEW YARIS-注目!我的代表色!

[新案上線]TOYOTA NEW YARIS-注目!我的代表色!
http://yaris.toyota.com.tw/




這次依然使用超好用的 Progression Flash Framework來製作
也嘗試了把所有flash的設計元件製作成swc, 透過FlashDevelop來發怖整個專案
這樣的製作方法超好用的啦,
發怖速度快, 終於可以不用開超慢的CS4, 又可以有Flash10的功能, 推。
身為一位專業的AS程式設計師, 一定要加個密技的啦
只要在輸入名字的欄位上,輸入大寫的RAINBOW
就會有隱藏版的畫面喔
而開場的吸入效果, 則是參考clockmaker製作的GinnyEffect
http://wonderfl.net/code/b8ec2e7155357ddc65d21eb8b1fa2e94c8363cfc



2009年9月14日 星期一

PV3D CubeInteractive

如何使用PV3D, 針對單一Cube偵聽點擊時的面呢
不需要去貼六個Plane,
只需要對Cube做一般的InteractiveScene3DEvent
透過e.face3d,即可得到點擊時的面物件。
再透過e.face3d.material.name來做判斷, 就可以完成

2009年9月7日 星期一

Dictionary And Proxy

今天來介紹二個 AS3 新增的二個類別(AS2沒有這東西喔)
Dictionary
簡單來說跟 Object 和 Array 是做一樣的事。
是用來索引物件用的類別。
Array是用數字來當做 Key 值。
_array[0] = "字串1";
_array[1] = "字串2";

_array[0] = "字串3";
時, 本來的值就會被取代掉
trace(_array[0]) //得到 字串3

Object是使用String來當作 Key 值
_obj['key'] = "String1";
_obj['key2'] = "String2";
(或寫成_obj.key2 = "String2";)

_obj['key2'] = "String3";
本來的值也會被取
trace(_obj['key2']) //得到 String3

Dictionary
是把'物件', 當作索引值,
每個物件都是獨立單一的, 這樣就可以確保索引值是唯一的。
var _dic :Dictionary = new Dictionary();
var _mc:MovieClip = new MovieClip();
var _mc2:MovieClip = new MovieClip();
//建立二個MovieClip
_dic[_mc] = "Dictionary Value1";
_dic[_mc2] = "Dictionary Value2";
用for in 掃一下內容。
for (var d:* in _dic) {
trace(d, _dic[d])
}
// [object MovieClip] Dictionary Value1
// [object MovieClip] Dictionary Value2
就可以得到當初寫入的值了
, 記得, 不要時一定要delete掉, 否則物件的指派還在
就不會被記憶體回被器回收喔。
完整程式碼:

package  {  
import flash.display.MovieClip;
import flash.display.Sprite;
import flash.utils.Dictionary;

public class DictionaryDemo extends Sprite {    
private var _obj :Object = { };
private var _dic :Dictionary = new Dictionary();
public function DictionaryDemo()  {   

//_obj[Key] = Value
//但 Object 的 Key 必需是字串
//所以有可能會發生 Key 值已經用掉的問題

var _mc:MovieClip = new MovieClip();
_obj[_mc] = "數值1";
//這樣寫就等於 _obj[_mc.toString()] = 值。

var _mc2:MovieClip = new MovieClip();
_obj[_mc2] = "數值2";

//使用 for in 來掃obj裡的變數
for (var a:String in _obj) {
trace(a, _obj[a]);
// [object MovieClip] 數值2     
}

_dic[_mc] = "Dictionary Value1";
_dic[_mc2] = "Dictionary Value2";
for (var d:* in _dic) {        
trace(d, _dic[d])  
// [object MovieClip] Dictionary Value1
// [object MovieClip] Dictionary Value2    
}
}  
} 
}

Proxy , 代理人
好比我們要載入一個xml
要先透過URLLoader載入
然後再用一個XML物件把載入後的結果存起來
有沒有辦法整合成一個呢, 這時 Proxy 就很好用了
理想
var _xxx:類別 = new 類別
_xxx.載入外部("xml");
_xxx.偵聽事件。
載入成功後。
直接用_xxx.xml裡的tag名稱, 直接就當成xml使用。
先準備一份要載入用的XML文件





 



 


撰寫XMLProxy.as類別,
因為 Flash 不充許多重繼成, 但可以透過 Interface 來做到類似的效果,
所以XMLProxy要先extneds Proxy類別, 同時實作IEventDispatcher
package {
import flash.utils.Proxy; 
import flash.events.Event; 
import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;
import flash.net.URLLoader;
import flash.net.URLRequest; 
import flash.utils.Proxy;
import flash.utils.flash_proxy; 
[Event(name = "complete", type = "flash.events.Event")]
//加入這組 tag , 是要給 FlashDevelop 或是 Flex 在編程式碼時能有事件提示。
dynamic public class XMLProxy extends Proxy implements IEventDispatcher {  
//extends Proxy 時, 一定要設定成 dunamic 類別
private var _eventDispatcher:EventDispatcher;  
private var _urlLoader  :URLLoader;
private var _xml   :XML;  
private var _isLoaded  :Boolean;

public function XMLProxy(){ 
_eventDispatcher = new EventDispatcher();   
_urlLoader = new URLLoader();
_urlLoader.addEventListener(Event.COMPLETE, xmlLoadedHandler);
}
private function xmlLoadedHandler(e:Event):void {
_xml = XML(_urlLoader.data); 
_isLoaded = true;
dispatchEvent(new Event(Event.COMPLETE, true, true));   
}
public function loadURL(url:String):void {
var urlRequest:URLRequest = new URLRequest(url);
_urlLoader.load(urlRequest);
}
flash_proxy override function getProperty(name:*):*{
var _name:String = String(name);
return _xml[_name];
}
//以下是因為 interface IEventDispatcher, 所以要實作一樣的 function 名稱。
public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, weakRef:Boolean = false):void {
_eventDispatcher.addEventListener(type, listener, useCapture, priority, weakRef);
}
public function dispatchEvent(event:Event):Boolean {
return _eventDispatcher.dispatchEvent(event);
}
public function hasEventListener(type:String):Boolean {
return _eventDispatcher.hasEventListener(type);
}
public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void {
_eventDispatcher.removeEventListener(type, listener, useCapture);
}
public function willTrigger(type:String):Boolean {
return _eventDispatcher.willTrigger(type)
}  

public function get isLoaded():Boolean { return _isLoaded; }
}
}


完後成, 測試吧。
package  {  
import flash.display.Sprite;
import flash.events.Event;

public class XMLProxyDemo extends Sprite{  


private var _xmlProxy:XMLProxy = new XMLProxy();
//建構 XMLProxy 類別
public function XMLProxyDemo()  {   
_xmlProxy.addEventListener(Event.COMPLETE , _xmlCompleteHandler);
//偵聽事件, 因為實作了 IEventDispatcher , 所以也可以發送和偵聽。
_xmlProxy.loadURL("xml_data.xml");
//載入 xml。
}  

private function _xmlCompleteHandler(e:Event):void {   
//載入完成後, 當去使用_xmlProxy物件的屬性時,
//如果是 _xmlProxy 本身有定義的屬性, 就會去讀該屬性。
/* 而在 XMLProxy 裡定義的這行, 就是當屬性沒有定義時, 就去找指定的物件裡的屬性
* 方法也是可以這樣做。
* flash_proxy override function getProperty(name:*):*{
var _name:String = String(name);
return _xml[_name];
}*/

trace(_xmlProxy.isLoaded);
//.isLoaded 是定義好的屬性。

trace(_xmlProxy.item[0].img.@src);
//.item不是 _xmlProxy 裡一開始就定義好的屬性, 透過 Proxy 代理, 去找指定物件的屬性
//就好像真的是_xmlProxy物件的屬性一樣。
trace(_xmlProxy.item[1].label);   

}
} 
}