Sunday, May 17, 2009

[C/C++] QRencode

  • QRencode
  • 隨著QRCode逐漸的流行,我也花點時間去找一下相關的encoder/decoder,QRencode是一個授權於LGPL的QRcode encoder library,你可以從下面的網站下載到:

    http://megaui.net/fukuchi/works/qrencode/index.en.html

    下載完編譯好之後,你就可以在terminal底下輸入如下的指令:

    qrencode http://nxforce.blogspot.com -o blog

    第一個參數就是要編碼的字串,這裡我用blog網址,而-o代表輸出指令,blog則為輸出的檔名,產生出來的圖樣如下:

    除此之外你也可以使用QRencode來撰寫一些QRcode相關的應用程式,說明文件如下:

    http://megaui.net/fukuchi/works/qrencode/manual/index.html

    如果你只是單純想要產生QRcode的話,其實你可以直接使用下面網的的QRCode Generator來完成即可。

    http://qrcode.kaywa.com/

Tuesday, April 28, 2009

[Flash] as3crypto

  • as3crypto
  • as3crypto是由henrit等人合力完成的Actionscript 3.0 Cryptography Library,提供了RSA、Data Encryption、Message-Digest等功能,讓Flash使用者可以輕鬆完成加密的工作,你可以從下面的網頁下載到這個API

    http://code.google.com/p/as3crypto/

    官方提供了一個很好的範例如下:

    http://crypto.hurlant.com/demo/

    我則使用as3crypto簡單做了一個Data Encryption/Decryption的demo(理論上來說,如果要為了效能應該是要用C+OpenSSL),程式碼裡直接指定使用Triple-DES+ECB Mode,而Key的長度預設為128bits,原始碼如下:

    package{
    import flash.display.Sprite;
    import flash.net.FileReference;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.utils.ByteArray;

    import fl.controls.Button;

    import com.hurlant.crypto.symmetric.ICipher;
    import com.hurlant.crypto.symmetric.IVMode;
    import com.hurlant.crypto.symmetric.IMode;
    import com.hurlant.crypto.symmetric.NullPad;
    import com.hurlant.crypto.symmetric.PKCS5;
    import com.hurlant.crypto.symmetric.IPad;
    import com.hurlant.crypto.prng.Random;
    import com.hurlant.crypto.hash.HMAC;
    import com.hurlant.util.Base64;
    import com.hurlant.util.Hex;
    import com.hurlant.crypto.Crypto;
    import com.hurlant.crypto.hash.IHash;

    public class CryptoDemo extends Sprite{
    private var loadFile:FileReference;
    private var saveFile:FileReference;

    private var plainData:ByteArray;
    private var cipherData:ByteArray;

    private var option:String;

    public function CryptoDemo(){
    encryBtn.label= "Encrypt";
    encryBtn.addEventListener(MouseEvent.CLICK, onClicked);

    decryBtn.label = "Decrypt";
    decryBtn.addEventListener(MouseEvent.CLICK, onClicked);

    keyLabel.text = "Key:";
    infoLabel.text = "Info:";

    genKeyBtn.label = "Generate 128 Bits";
    genKeyBtn.addEventListener(MouseEvent.CLICK, onClicked);

    loadFile = new FileReference();
    loadFile.addEventListener(Event.SELECT, onFileSelected);
    loadFile.addEventListener(Event.COMPLETE, onFileLoaded);

    saveFile = new FileReference();
    }

    private function onClicked(event:MouseEvent):Boolean{
    if(event.target.label == "Generate 128 Bits"){
    genKey(128);
    }else{
    if(keyIn.text == ""){
    infoTxt.text = "Please give a key!";
    return false;
    }
    option = event.target.label;
    loadFile.browse();
    }

    return true;
    }

    private function onFileSelected(event:Event):void{
    event.target.load();
    }

    private function onFileLoaded(event:Event):void{
    infoTxt.text = "Filename: "+event.target.name+"\nSize: "+event.target.size+" Bytes";

    switch(option){
    case "Encrypt":
    plainData = event.target.data;
    encrypt();
    saveFile.save(plainData, "encrypt.dat");
    break;
    case "Decrypt":
    cipherData = event.target.data;
    decrypt();
    saveFile.save(cipherData, "decrypt.dat");
    break;
    }

    plainData = cipherData = null;
    }

    private function genKey(v:int):void {
    var r:Random = new Random;
    var b:ByteArray = new ByteArray
    r.nextBytes(b, v/8);
    keyIn.text = Hex.fromArray(b);
    }

    private function encrypt():void {
    // key
    var k:String = keyIn.text;
    var kdata:ByteArray;
    kdata = Hex.toArray(Hex.fromString(k));

    // algorithm..
    var name:String = "simple-des3-ecb";

    // encryption
    var pad:IPad = new PKCS5;
    var mode:ICipher = Crypto.getCipher(name, kdata, pad);
    pad.setBlockSize(mode.getBlockSize());
    mode.encrypt(plainData);
    }

    private function decrypt():void {
    // key
    var k:String = keyIn.text;
    var kdata:ByteArray;
    kdata = Hex.toArray(Hex.fromString(k));

    // algorithm..
    var name:String = "simple-des3-ecb";

    // decryption
    var pad:IPad = new PKCS5;
    var mode:ICipher = Crypto.getCipher(name, kdata, pad);
    pad.setBlockSize(mode.getBlockSize());
    mode.decrypt(cipherData);
    }
    }
    }

    預覽畫面如下:

    由於Flash Player基於安全性的考量,FileReference物件的save()函式必須由mouse click等event來觸發,所以上面的範例將無法讓你正常儲存。

Monday, April 27, 2009

[C/C++] Bitmap Grayscale Conversion

  • Bitmap Grayscale Conversion
  • Download:bmpGrayConvertor.zip

    繼上次的[C/C++] Bitmap Bit Conversion之後,心血來潮再做一個Grayscale的。Grayscale比bit conversion容易實做多了,一個簡易的Grayscale的演算如下:

    ----------------------------------------------

    newRGB = (R*2 + G*5 + B) / 8

    ----------------------------------------------

    一個24bits全彩的pixel裡的紅色乘上2+綠色乘上5+藍色的總和再除以8,存到一個為8bits的pixel裡,所以技巧跟bitConversion很像,只是調色盤換了而已。

    這個範例裡所使用的調色盤:

    輸出結果如下:










    [左圖]是24bits全彩原始圖,[右圖]則是轉換成8bits grayscale的圖。雖然與完美的grayscale相比仍有些差距,但整體來說已經不錯了。

    這次的程式還有稍微處理一下width padding的問題,理論上應該可以正常轉換非4倍數大小的圖片。

Thursday, April 23, 2009

[Flash] TweenLite/Max

Wednesday, April 22, 2009

[Flash] HashMap

  • HashMap
  • Download:HashMap.zip

    原先遇到HashMap結構時我都直接使用polygonal網站上所提供的,但因為設計上的需求,需要更改HashMap裡的key/value pair的次序性,所以只好自己重新實做一個。下載包裡面包含了以下五個原始檔:

    -------------------------------------------------------

    • HashMapNode.as:A double linkedlist node for hash structure
    • Map.as:Interface for Map structure
    • HashMap.as:HashMap Class
    • Iterator.as:Interface for Iterator
    • HashIterator.as:HashIterator Class

    -------------------------------------------------------

    使用Double LinkedList Node結構比較方便之後的功能擴充

    /* HashMapNode.as */
    package xinyu.collection{
    public class HashMapNode{
    public var key:*;
    public var obj:*;

    public var prev:HashMapNode;
    public var next:HashMapNode;

    public function HashMapNode(key:* = null, obj:* = null, prev:HashMapNode = null, next:HashMapNode = null){
    this.key = key;
    this.obj = obj;
    this.prev = prev;
    this.next = next;
    }
    }
    }

    Map結構的介面,定義了一些Map結構該有的基本功能。

    /* Map.as */
    package xinyu.collection{
    public interface Map{
    function insert(key:*, obj:*):Boolean;
    function find(key:*):*;
    function remove(key:*):Boolean;
    function keySet():Array;
    function entrySet():Array;
    function clear():void;
    function isEmpty():Boolean;
    function iterator():Iterator;
    function containsValue(obj:*):Boolean;
    function containsKey(key:*):Boolean;
    }
    }

    有別於ploygonal的HashMap,我將Dictionary給移除,僅只有在entrySet()裡有使用到(回傳不重複的entrySet),其餘的操作皆以Double LinkedList結構來運作,目的是要控制key/value 的次序性,所以我還多了moveUp()跟moveDown()這兩個方法來移動entry的位置。

    /* HashMap.as */
    package xinyu.collection{
    import flash.utils.Dictionary;

    import xinyu.collection.Map;
    import xinyu.collection.HashMapNode;
    import xinyu.collection.Iterator;
    import xinyu.collection.HashIterator;

    public class HashMap implements Map {
    private var head:HashMapNode = new HashMapNode();
    private var tail:HashMapNode = new HashMapNode();

    public function HashMap() {
    head.next = tail;
    tail.prev = head;
    }

    public function insert(key:*, obj:*):Boolean {
    if (key == null || obj == null) return false;
    if (searchByKey(key) != null) return false;

    var node:HashMapNode = new HashMapNode(key, obj, tail.prev, tail);
    node.prev.next = node;
    tail.prev = node;

    return true;
    }

    public function find(key:*):*{
    var node:HashMapNode = searchByKey(key);
    if(node) return node.obj;
    return null;
    }

    public function remove(key:*):Boolean{
    var node:HashMapNode = searchByKey(key);
    if(node == null) return false;

    node.prev.next = node.next;
    node.next.prev = node.prev;
    return true;
    }

    public function keySet():Array{
    var array:Array = new Array();
    var curr:HashMapNode = head.next;

    while(curr!=tail){
    array.push(curr.key);
    curr = curr.next;
    }

    return array;
    }

    public function entrySet():Array{
    var array:Array = new Array();
    var node:HashMapNode = head.next;
    var dict:Dictionary = new Dictionary(true);

    while(node != tail){
    if(dict[node.obj] == undefined){
    dict[node.obj] = 1;
    array.push(node.obj);
    }
    node = node.next;
    }
    return array;
    }

    public function clear():void{
    var curr:HashMapNode = head.next;
    var nextPtr:*;
    while(curr!=tail){
    curr.key = null;
    curr.obj = null;
    nextPtr = curr.next;
    curr.next = curr.prev = null;
    curr = nextPtr;
    }

    head.next = tail;
    tail.prev = head;
    }

    public function swap(key1:*, key2:*):Boolean{
    if (key1 == null||key2 == null) return false;

    var node1:HashMapNode = searchByKey(key1);
    var node2:HashMapNode = searchByKey(key2);
    if(node1 == null || node2 == null) return false;

    var temp:HashMapNode = new HashMapNode();
    temp.key = node1.key;
    temp.obj = node1.obj;

    node1.key = node2.key;
    node1.obj = node2.obj;

    node2.key = temp.key;
    node2.obj = temp.obj;

    return true;
    }

    public function isEmpty():Boolean {
    if (head.next == tail) return true;
    return false;
    }

    public function moveUp(key:*):Boolean{
    if(key == null) return false;

    var node:HashMapNode = searchByKey(key);
    if(node == null) return false;
    if(node.prev == head) return false;

    return swap(key, node.prev.key);
    }

    public function moveDown(key:*):Boolean{
    if(key == null) return false;

    var node:HashMapNode = searchByKey(key);
    if(node == null) return false;
    if(node.next == tail) return false;

    return swap(key, node.next.key);
    }

    public function get size():int{
    var count:int = 0;
    var curr:HashMapNode = head.next;

    while(curr!=tail){
    count++;
    curr=curr.next;
    }

    return count;
    }

    public function containsKey(key:*):Boolean{
    if(searchByKey(key)) return true;
    return false;
    }

    public function containsValue(obj:*):Boolean{
    var curr:HashMapNode = new HashMapNode();
    curr = head.next;

    while(curr!=tail){
    if(curr.obj === obj) return true;
    curr = curr.next;
    }

    return false;
    }

    private function searchByKey(key:*):HashMapNode{
    var curr:HashMapNode = new HashMapNode();
    curr = head.next;

    while(curr!=tail){
    if(curr.key === key) return curr;
    curr = curr.next;
    }

    return null;
    }

    public function iterator():Iterator{
    return new HashIterator(head,tail);
    }
    }
    }

    Iterator介面的部份只有簡單定義幾個基本的功能。

    /* Iterator */
    package xinyu.collection{
    public interface Iterator{
    function hasNext():Boolean;
    function next():*;
    function remove():void;
    function rewind():void;
    }
    }

    HashIterator就依照著Iterator介面去實做,並沒有增加額外的功能,有別於Java的Iterator,我多了rewind()。因為我在其他設計專案中需要將iterator重新指向一開始,所以多了這個功能。

    /* HashIterator */
    package xinyu.collection{
    import xinyu.collection.Iterator;
    import xinyu.collection.HashMapNode;

    public class HashIterator implements Iterator{
    private var head:HashMapNode;
    private var tail:HashMapNode;
    private var curr:HashMapNode;

    public function HashIterator(head:HashMapNode, tail:HashMapNode){
    this.head = head;
    this.tail = tail;
    curr = head.next;
    }

    public function hasNext():Boolean{
    return (curr != tail);
    }

    public function next():*{
    if(curr != tail){
    var obj:* = curr.obj;
    curr = curr.next;
    return obj;
    }
    return null;
    }

    public function rewind():void{
    curr = head.next;
    }

    public function remove():void{
    tail.prev = tail.prev.prev;
    tail.prev.next = tail;
    }
    }
    }

    底下是一個很簡單的範例用來展示上面的HashMap的用法

    package{
    import flash.display.Sprite;

    import xinyu.collection.HashMap;
    import xinyu.collection.Iterator;
    import xinyu.collection.HashIterator;

    public class HashMapDemo extends Sprite{
    public function HashMapDemo(){
    var hashMap:HashMap = new HashMap();

    var obj1:Object = new Object();
    obj1.name = "object_1";
    var obj2:Object = new Object();
    obj2.name = "object_2";

    hashMap.insert("1", obj1);
    hashMap.insert("2", obj2);
    trace("isEmpty (after insertion): "+hashMap.isEmpty());
    trace("Size of hashmap: "+hashMap.size);

    trace("\nKeySet (before swap): "+hashMap.keySet());
    trace("Key 1's Object: "+hashMap.find("1").name);
    trace("Key 2's Object: "+hashMap.find("2").name);
    trace("EntrySet"+hashMap.entrySet());

    hashMap.swap("1","2");
    trace("\nKeySet (after swap): "+hashMap.keySet());
    trace("Key 1's Object: "+hashMap.find("1").name);
    trace("Key 2's Object: "+hashMap.find("2").name);


    trace("\nIteration result before move up the key 1:");
    var iter:Iterator = hashMap.iterator();
    while(iter.hasNext()){
    trace(iter.next().name);
    }

    hashMap.moveUp("1");

    trace("\nIteration result after move up the key 1:");
    iter.rewind();
    while(iter.hasNext()){
    trace(iter.next().name);
    }

    iter.remove();
    trace("\nSize of hashmap(after remove a node): "+hashMap.size);

    hashMap.clear();
    trace("\nisEmpty (after clear): "+hashMap.isEmpty());
    trace("Size of hashmap: "+hashMap.size);
    }
    }
    }

    輸出畫面如下:

    isEmpty (after insertion): false
    Size of hashmap: 2

    KeySet (before swap): 1,2
    Key 1's Object: object_1
    Key 2's Object: object_2
    EntrySet[object Object],[object Object]

    KeySet (after swap): 2,1
    Key 1's Object: object_1
    Key 2's Object: object_2

    Iteration result before move up the key 1:
    object_2
    object_1

    Iteration result after move up the key 1:
    object_1
    object_2

    Size of hashmap(after remove a node): 1

    isEmpty (after clear): true
    Size of hashmap: 0

Tuesday, April 21, 2009

[UTips. 67] BitDefender Antivirus

  • BitDefender Antivirus
  • 一套來自於羅馬尼亞的防毒軟體 - BitDefender,個人使用者可以在 EULA license的授權限制下免費使用BitDefender Antivirus Scanner for Unices,你可以從下面的網站與BitDefender官方取得授權序號和軟體。

    BitDefender Antivirus Scanner for Unices

    系統需求:

    Linux Kernel: 2.4.x or 2.6.x (recommended)
    FreeBSD: 5.4 (or newer with compat5x)
    glibc: version 2.3.1 or newer, and libstdc++5 from gcc 3.2.2 or newer
    Processor: x86 compatible 300 MHz; i686 500MHz; amd64(x86_64)
    Minimum Memory: 64MB (128MB recommended)
    Minimum Free Disk Space: 100MB

    系統環境:

    RedHat Enterprise Linux 3 or newer
    SuSE Linux Enterprise Server 9 or newer
    Fedora Core 1 or newer
    Debian GNU/Linux 3.1 or newer
    Slackware 9.x or newer
    Mandrake/Mandriva 9.1 or newer
    FreeBSD 5.4 or newer

    對於多系統的使用者不訪可以使用BitDefender來降低病毒散播。

Sunday, April 12, 2009

[Qt] Qt 4.5 QGroupBox with QGTkStyle

Wednesday, April 08, 2009

[Flash] Unduplicated Random Numbers

  • Unduplicated Random Numbers
  • 最近看到有人使用陣列元素移除的方式來產生不重複亂數,下面就是一個簡單的不重複亂數產生的範例,亂數範圍0~99,產生10個不重複亂數。

    Method One (Array Splice):

    var numSet:Array = new Array();
    var numArr:Array = new Array();
    var randNum:int;
    var n:int = 0;

    for (var i:int = 0; i < 100; i++) {
    numSet[n++] = i;
    }


    for (i = 0; i < 10; i++) {
    randNum = Math.floor(Math.random()* 99);
    numArr[i] = numSet[randNum];
    numSet.splice(randNum, 1);
    }

    上面的程式碼裡,只要有出現過的數字就在集合裡把他給移除,這樣的作法很直觀易懂,但是所付出的運算時間代價也比較高。下面是另一個我比較常用的手法:

    Method Two:

    var numSet:Array = new Array();
    var numArr:Array = new Array();
    var randNum:int;

    for (var i:int = 0; i < 100; i++) {
    numSet[i] = 0;
    }

    for (i = 0; i < 10; i++) {
    do {
    randNum = Math.floor(Math.random()* 99);
    } while (numSet[randNum]);
    numSet[randNum] = 1;
    numArr[i] = randNum;
    }

    numSet裡面所存的並不是亂數數字的集合,而是對應到每個數字的flag,用來標示此數字是否已經產生,這樣的作法乍看之下好像比耗空間(例如數字集合是在10000~20000時),但其實你可以透過一些簡單的運算技巧讓空間的消耗跟數字集合數成比例。

    使用第二個方法的效能會比第一個快許多,尤其當集合數增大時,差距會更明顯。

Saturday, April 04, 2009

[C/C++] Timer

  • Timer
  • 最近在做一些效能的測試需要用到比較精準的Timer,分別查了一下給Windows和Linux平台的寫法。

    For Linux:

    在Linux裡,則是使用timeval來計算時間,理論上精準到micro second

    #include <stdio.h>
    #include <sys/time.h>

    int main()
    {
    int i;
    struct timeval t1, t2;
    double elapsedTime;

    gettimeofday(&t1, NULL);
    gettimeofday(&t2, NULL);

    elapsedTime = (t2.tv_sec - t1.tv_sec) * 1000.0;
    elapsedTime += (t2.tv_usec - t1.tv_usec) / 1000.0;
    printf("%.3f ms\n",elapsedTime);
    return 0;
    }

    For Windows:

    Windows部份使用QueryPerformanceFrequency()這個指令,理論上是精準到跟時脈值一樣

    #include <stdio.h>
    #include <windows.h>
    int main()
    {
    LARGE_INTEGER frequency;
    LARGE_INTEGER t1, t2;
    double elapsedTime;

    QueryPerformanceFrequency(&frequency);

    QueryPerformanceCounter(&t1);
    QueryPerformanceCounter(&t2);

    elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000.0 / frequency.QuadPart;
    printf("%.3f ms\n", elapsedTime);
    return 0;
    }

Friday, April 03, 2009

[Flash] FIDbg10.ocx crashed while loading file

  • FIDbg10.ocx crashed while loading file
  • 原以為FileReference的物件可以像在使用save()函式時宣告在method裡,沒想到這樣子會造成FIDbg10.ocx掛掉然後連帶著瀏覽器也被關閉。下面是一個會當掉的寫法

    package{
    import flash.display.Sprite;
    import flash.net.FileReference;
    import flash.events.Event;
    import flash.events.MouseEvent;

    import com.xinyu.button.TextButton;

    public class LoadFileDemo extends Sprite{
    private var loadBtn:TextButton;

    public function LoadFileDemo(){
    loadBtn = new TextButton(80,20,10,1,0xCCCCCC,"Load",0x000000);
    addChild(loadBtn);
    loadBtn.addEventListener(MouseEvent.CLICK, onClicked);
    }

    private function onClicked(event:MouseEvent):void{
    //declare the FileReference object inside the method that would lead to browser crash
    var file:FileReference = new FileReference();
    file.addEventListener(Event.SELECT, onFileSelected);
    file.addEventListener(Event.COMPLETE, onFileLoaded);

    file.browse();
    }

    private function onFileSelected(event:Event):void{
    event.target.load();
    }

    private function onFileLoaded(event:Event):void{
    trace(event.target.size);
    }
    }
    }

    可以看到上面的範例程式碼裡,我將FileReference的物件宣告在onClicked()這個Event Handler裡,拿去編譯執行後程式就會當掉。為了要解決這個問題,寫法要改成這樣:

    package{
    import flash.display.Sprite;
    import flash.net.FileReference;
    import flash.events.Event;
    import flash.events.MouseEvent;

    import com.xinyu.button.TextButton;

    public class LoadFileDemo extends Sprite{
    private var loadBtn:TextButton;
    private var file:FileReference;

    public function LoadFileDemo(){
    loadBtn = new TextButton(80,20,10,1,0xCCCCCC,"Load",0x000000);
    addChild(loadBtn);
    loadBtn.addEventListener(MouseEvent.CLICK, onClicked);
    }

    private function onClicked(event:MouseEvent):void{
    file = new FileReference();
    file.addEventListener(Event.SELECT, onFileSelected);
    file.addEventListener(Event.COMPLETE, onFileLoaded);

    file.browse();
    }

    private function onFileSelected(event:Event):void{
    event.target.load();
    }

    private function onFileLoaded(event:Event):void{
    trace(event.target.size);
    }
    }
    }

Sunday, March 29, 2009

[Flash] Zinc 3

[Blog] Comment Section

  • Comment Section
  • 以往的經驗是幾乎沒有人會在我的blog上回文,多半都是直接mail給我,所以久而久之就忘了檢查這類的訊息。直到最近在整理文章時才赫然發現居然有人回文,之前未能及時回文,這裡跟您們說聲抱歉。

    小小紀錄一下blogspot新增comment section的方式。

    版面配置->新增小工具->資訊提供,輸入你要顯示的網頁,以我的部落格為例如下:

    http://nxforce.blogspot.com/feeds/comments/summary

    將紅色字替換成你的blog id即可。

[Flash] Performance Tips for CPU Usage

  • Performance Tips for CPU Usage
  • 提供一些基本的Actionscript 3.0 最佳化的寫法。

    做整數運算使用 int 會比 Number 快許多,int 又比 uint還要快一點。

    不良的寫法如下:

    var n:Number;
    n++;

    for(var i:Number=0; i<100;i++){}

    較好的寫法如下:

    var n:int;
    n++;

    for(var i:int=0; i<100;i++){}

    宣告變數時給予明確的型態會比宣告成Object快許多。

    不良的寫法如下:

    var i;
    var j:Object;
    var k:*;

    較好的寫法如下:

    var i:int;
    var j:Boolean;
    var k:Number;

    型態轉型使用 Type(variable) 會比使用 as 或者其他轉換函式快許多

    不良的寫法如下:

    var n:int = 100;
    var m:String = n.toString();

    var o:Object;
    o as int;

    較好的寫法如下:

    var n:int = 100;
    var m:String = String(n)();

    var o:Object;
    int(o);

    直接讀取變數值會比透過物件來取值快許多

    不良的寫法如下:

    var obj:Object = {"n":1000} ;
    for(var i:int=0; i<obj.n;i++){}

    較好的寫法如下:

    var obj:Object = {"n":1000} ;
    var n:int = obj.n;
    for(var i:int=0; i<n;i++){}

    陣列複製使用concat()會比loop複製快

    不良的寫法如下:

    var arr1:Array = new Array(100000);
    var arr2:Array = new Array(100000);
    for(var i:int = 0; i<arr1.length ; i++){
    arr1[i] = arr2[i];
    }

    較好的寫法如下:

    var arr1:Array = new Array(100000);
    var arr2:Array = new Array(100000);
    arr2 = arr1.concat();

    基本數學運算使用自行客制化的會比使用Math class快

    不良的寫法如下:

    Math.floor(0.9);
    Math.abs(-1);
    Math.abs(n,2);

    較好的寫法如下:

    int(0.9);
    n>0?n:-n;
    n*n;

    想要了解更多關於Actionscript的最佳化可以參考下面的網頁

    http://www.rozengain.com/blog/2007/05/01/some-actionscript-30-optimizations/

Tuesday, March 24, 2009

[Flash] Copy to Clipboard

  • Copy to Clipboard
  • 在一些比較人性化的設計裡,你可能會需要將Flash上的文字輸出複製到剪貼簿裡,這裡提供適用於Flash CS3/4的方法。

    假設你有一個TextField的物件名稱為txtField。

    For Flash CS3 (Flash Player 9)

    import flash.system.System;

    System.setClipboard(txtField.text);

    For Flash CS4 (Flash Player 10)

    import flash.desktop.Clipboard;
    import flash.desktop.ClipboardFormats;
    import flash.desktop.ClipboardTransferMode;

    Clipboard.generalClipboard.setData(ClipboardFormats.TEXT_FORMAT, txtField.text);

    實際使用的結果是CS4的方法比較能將文字格式完整複製到剪貼簿上。

    http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/system/System.html

    http://help.adobe.com/en_US/AS3LCR/Flash_10.0/flash/desktop/Clipboard.html

Monday, March 23, 2009

[Flash] Export to Image

  • Export to Image
  • 使用PNGEncoder/JPEGEncoder配上bitmapdata可以將Sprite上的顯示物件轉成Image。

    For Flash:

    編碼可以從這一篇取得[Flash] AS3 Core Library

    package{
    import flash.display.Sprite;
    import flash.events.MouseEvent;
    import flash.utils.ByteArray;
    import flash.display.BitmapData;
    import flash.net.FileReference;

    import com.xinyu.button.TextButton;
    import com.adobe.images.PNGEncoder;

    public class EncodeDemo extends Sprite{
    private var mainUI:Sprite;
    private var printBtn:TextButton;

    public function EncodeDemo(){
    mainUI = new Sprite();
    addChild(mainUI);
    mainUI.graphics.lineStyle(5,0x000000,1);
    mainUI.graphics.drawCircle(stage.stageWidth/2-25,stage.stageHeight/2-25,50);
    mainUI.graphics.endFill();

    printBtn = new TextButton(100,20,10,2,0xCCCCCC,"Export to PNG",0x000000);
    addChild(printBtn);
    printBtn.x = stage.stageWidth/2 - printBtn.width/2;
    printBtn.y = stage.stageHeight - 2*printBtn.height;
    printBtn.addEventListener(MouseEvent.CLICK, onClicked);
    }

    private function onClicked(event:MouseEvent):void{
    var bmpData:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight);
    bmpData.draw(mainUI);

    var pngEncoder:PNGEncoder = new PNGEncoder();
    var byteArr:ByteArray = PNGEncoder.encode(bmpData);
    var file:FileReference = new FileReference();
    file.save(byteArr, "image.png");
    }
    }
    }

    For Flex:

    <?xml version="1.0" encoding="utf-8"?>
    <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="400" height="300" backgroundGradientAlphas="[1.0, 1.0]" backgroundGradientColors="[#495D7B, #303F4A]" creationComplete="initApp()">
    <mx:Script>
    <![CDATA[
    import mx.graphics.codec.PNGEncoder;
    import mx.graphics.codec.JPEGEncoder;
    import flash.filesystem.File;

    private function initApp():void{
    mainUI.graphics.lineStyle(5,0x000000,1);
    mainUI.graphics.drawCircle(this.width/2-25,this.height/2-25,50);
    mainUI.graphics.endFill();
    }

    private function onClicked():void{
    var bmpData:BitmapData = new BitmapData(mainUI.width, mainUI.height);
    bmpData.draw(mainUI);

    var pngEncoder:PNGEncoder = new PNGEncoder();
    var byteArr:ByteArray = pngEncoder.encode(bmpData);
    var file:File = File.desktopDirectory.resolvePath("");
    file.save(byteArr,"image.png");
    file.clone();
    }
    ]]>
    </mx:Script>
    <mx:VBox left="10" top="10" right="10" bottom="10" verticalGap="2">
    <mx:Panel width="100%" height="220" layout="absolute" title="PNG Encoder">
    <mx:UIComponent id="mainUI" width="100%" height="100%" />
    </mx:Panel>
    <mx:ApplicationControlBar width="100%" height="100%" cornerRadius="0">
    <mx:HBox width="100%" horizontalAlign="center" verticalAlign="middle" horizontalGap="0">
    <mx:Button label="Export to PNG" click="onClicked()"/>
    </mx:HBox>
    </mx:ApplicationControlBar>
    </mx:VBox>
    </mx:WindowedApplication>


[Flash] Printing - Orientation and Resize

  • Printing - Orientation and Resize
  • 從AS3的PrintJob說明文件看來,功能似乎很簡潔,這裡提供自己處理orientation和border的方法,範例程式碼如下:

    package {
    import flash.display.Sprite;
    import flash.printing.PrintJob;
    import flash.printing.PrintJobOrientation;
    import flash.events.MouseEvent;

    import com.xinyu.button.TextButton;

    public class PrintDemo extends Sprite {
    var sp1:Sprite ;

    public function PrintDemo() {
    sp1 = new Sprite();
    addChild(sp1);

    var printBtn:TextButton = new TextButton(80,20,10,2,0xCCCCCC,"Print",0x000000);
    sp1.addChild(printBtn);
    printBtn.x = 10;
    printBtn.y = 10;
    printBtn.addEventListener(MouseEvent.CLICK, onPrintClicked);
    }

    private function onPrintClicked(event:MouseEvent):void {
    var myPrintJob:PrintJob = new PrintJob();

    var sp2:Sprite = new Sprite();
    sp2.graphics.beginFill(0x000000);
    sp2.graphics.drawRect(0,0,400,300);
    sp2.graphics.endFill();

    this.addChild(sp2);
    sp2.addChild(sp1);

    sp1.x += 50;
    sp1.y += 50;

    if (myPrintJob.start()) {
    if(myPrintJob.orientation == PrintJobOrientation.PORTRAIT) {
    sp2.rotation = 90;
    }

    try {
    myPrintJob.addPage(sp2);
    } catch (error:Error) {

    }
    myPrintJob.send();
    }

    this.removeChild(sp2);
    this.addChild(sp1);

    sp1.x -= 50;
    sp1.y -= 50;
    }
    }
    }

    其中的TextButton是我自己的物件,你可以換成其他任何形式的按鈕物件。主要的技巧是我使用了兩個Sprite,分別為sp1:作為主要的顯示物件,sp2作為列印時的顯示物件。你可以看到當按鈕按下進入onPrintClicked()函式,我建立了sp2這個Sprite,並且繪製了400x300pixel大小的黑色底色(這裡使用黑色只是為了除錯時方便檢驗,實際列印過程中請使用白色),這樣做是避免Flash本身會最佳剪裁大小而讓我的sp2的寬長不能固定。

    接著將sp1加入到sp2裡,並且設定sp1的x,y值,來達到有邊界的效果(當然你在設計過程中要處理好sp1的寬長,免得sp1在移動之後超出sp2的底色),開使printJob時由印表機的列印方向來決定是否旋轉sp2。

    傳送完printJob之後,再將sp2從主場景移除,並將sp1加入回來。大致上多了這兩項的處理可以讓你的列印完善許多。

    PS. 如果你要列印的物件有包含到文字,經旋轉後會無法正常列出,請使用上一篇的技巧,將字體轉成embedded,[Flash] Embedded Font

[Flash] Embedded Font

  • Embedded Font
  • 在使用AS3所產生的文字物件常常會面臨到無法做放大縮小或者漸變等特效,這時你可以試著先將要使用的字體載入到Library裡,並設定embedded 模式即可。

    Step 1. 在Library Panel上的空白處按右鍵選擇"New Font",選擇你要的字體並給予一個名稱。

    Step 2. 接著滑鼠右鍵點選你剛匯入的字體,選擇linkage,給予這個字體一個class name。

    Step 3. 撰寫程式碼,如下:

    import fl.transitions.Tween;
    import fl.transitions.easing.Strong;

    var container:Sprite = new Sprite();
    addChild(container);

    var txtFmt:TextFormat = new TextFormat();
    txtFmt.font = "Verdana";
    txtFmt.size = 16;
    txtFmt.bold = true;

    var txtField:TextField = new TextField();
    container.addChild(txtField);
    txtField.autoSize = TextFieldAutoSize.LEFT;
    txtField.embedFonts = true;
    txtField.defaultTextFormat = txtFmt;
    txtField.text = "Embedded Font Effect!";

    var tween:Tween = new Tween(txtField, "alpha", Strong.easeOut, 1, 0, 5, true);

    上面的方法不適用於TextInput 模式的物件。


[Flash] setTextFormat() v.s. defaultTextFormat

  • setTextFormat() v.s. defaultTextFormat
  • 之前在使用TextField時常常被這兩個東西搞混,這裡做個小小紀錄。

    setTextFormat() :用在文字已經寫在text這個變數上時,範例如下:

    var txtFmt:TextFormat = new TextFormat();
    txtFmt.size = 16;
    txtFmt.bold = true;
    txtFmt.color = 0x0000ff;

    var txtField:TextField = new TextField();
    addChild(txtField);
    txtField.x = 100;
    txtField.y = 10;
    txtField.text = "setTextFormat method.";
    txtField.autoSize = TextFieldAutoSize.CENTER;
    txtField.setTextFormat(txtFmt);

    承接著上面的使用方式,如果要做後續的性質修改,必須使用下面的方法:

    txtFmt = txtField.getTextFormat();
    txtFmt.size = 28;
    txtField.setTextFormat(txtFmt);

    defaultTextFormat:用在文字是由執行時期所輸入的,範例如下:

    var txtFmt:TextFormat = new TextFormat();
    txtFmt.size = 16;
    txtFmt.bold = true;
    txtFmt.color = 0x0000ff;

    var txtField:TextField = new TextField();
    addChild(txtField);
    txtField.type = TextFieldType.INPUT;
    txtField.x = 100;
    txtField.y = 10;
    txtField.autoSize = TextFieldAutoSize.CENTER;
    txtField.defaultTextFormat = txtFmt;

    承接著上面的使用方式,如果要做後續的性質修改,必須使用下面的方法:

    txtFmt = txtField.defaultTextFormat;
    txtFmt.size = 28;
    txtField.defaultTextFormat = txtFmt;

    這時問題又來了,如果我又想要先預設一段文字,且又希望此TextField可以作為輸入,那你的程式要這樣寫:

    var txtFmt:TextFormat = new TextFormat();
    txtFmt.size = 16;
    txtFmt.bold = true;
    txtFmt.color = 0x0000ff;

    var txtField:TextField = new TextField();
    addChild(txtField);
    txtField.type = TextFieldType.INPUT;
    txtField.x = 100;
    txtField.y = 10;
    txtField.text = "editable TextField with setTextFormat.";
    txtField.autoSize = TextFieldAutoSize.CENTER;
    txtField.setTextFormat(txtFmt);

    ...@@

[Flash] AS3 Data Structures For Game Developers

  • AS3 Data Structures For Game Developers
  • 如同之前所提,AS3 並沒有像Java一樣提供許多方便的API使用,所以往往都是由程式設計師自行完成。這裡提供由Michael Baczynski所實做的Library,裡面包含了Linkedlist, Tree, Hash and Sorting等常見的資料結構程式碼,下載位置如下:

    http://code.google.com/p/as3ds/

    此Library在Flash CS3底下運作正常,但如果你要將他移植到Flash CS4底下撰寫時,有部份的原始碼並沒有實做interface裡的function,所以必須自行稍微修正一下。(Flash CS4的AS3語法檢查比較嚴格)

[Flash] AS3 Core Library

  • AS3 Core Library
  • 由於Flash本身所提供的library沒有像Flex多,所以往往我們必須自行實做或者下載已經包好的,下面就是由mikechambers等人主導的as3corelib,下載位置如下:

    http://code.google.com/p/as3corelib/

    裡面有包含AS3的image encoder,如此要做runtime image exporting就方便多了

Wednesday, March 18, 2009

[Flash] Type Checking

  • Type Checking
  • 在ActionScript 3.0 裡如果要做型態檢驗,可以透過 isas 這兩個operator來達成,下面是一個簡單的範例:

    import com.xinyu.geom.Ball;
    import com.xinyu.geom.Triangle;

    var ball:Ball = new Ball();
    addChild(ball);

    trace(ball is Ball);
    trace(ball is Triangle);
    trace(ball is Sprite);
    trace(ball as Ball);
    trace(ball as Triangle);
    trace(ball as Sprite);

    輸出畫面如下:

    true
    false
    true
    [object Ball]
    null
    [object Ball]

Wednesday, March 11, 2009

[Flash] Maze

Download: MazeDemo.zip

  • 按下Run可執行迷宮出口搜尋
  • 按下Stop可停止迷宮出口搜尋
  • 按下Reset可將狀態歸零
  • 按下Enable可以自訂迷宮,透過滑鼠的點擊,以及托移紅色(終點)/綠色(起點)方塊來修改。
  • 按下Disable可以關閉客制化,一旦開啟修改模式,修改完請手動關閉,否則無法執行迷宮搜尋。
  • 程式防呆處理有限,請勿做白目舉動。

迷宮算是在程式設計裡一個很經典的設計問題,絕大數的人在用程式來解迷宮時多半都使用Recursive的形式來撰寫,但是在Flash底下我則是選擇以Stack/Array的形式來設計,一方面是受限於Flash Stack Size,而另一方面則是為了方便圖形化。

下面的迷宮演算是使用一種類似stack的形式來撰寫,這種演算是建立在已知全體地圖下才適用,如果要達到在未知地圖的自由空間裡搜尋,那個演算就會稍微複雜點。

/* SimpleMazeDemo */
package {
import flash.display.Sprite;
import flash.utils.Timer;
import flash.events.TimerEvent;

import com.xinyu.geom.Maze;
import com.xinyu.geom.Ball;
import com.xinyu.geom.Position2D;

public class MazeDemo extends Sprite {
private var pos:Position2D;
private var maze:Maze;
private var obj:Ball;
private var path:Array;
private var iPath:uint = 0;
private var timer:Timer;

private const STEP:Number = 40;
private const DIRECTION:Array = [new Position2D(0,-1), new Position2D(0,1), new Position2D(-1,0), new Position2D(1,0)];

public function MazeDemo() {
maze = new Maze(10, 10, STEP);
addChild(maze);

path = new Array(50);
for(var i:uint = 0; i<path.length;i++){
path[i] = new Position2D();
}

obj = new Ball(0xCCCCCC, 0x000000, STEP/2, 90);
addChild(obj);
obj.x = maze.start.x * STEP + STEP/2;
obj.y = maze.start.y * STEP + STEP/2;
pos = new Position2D(maze.start.x, maze.start.y);
maze.setPath(pos);
path[iPath].x = pos.x;
path[iPath].y = pos.y;

timer = new Timer(50);
timer.addEventListener(TimerEvent.TIMER, onTimer);
timer.start();
}

private function onTimer(event:TimerEvent):void {
if (pos.x == maze.end.x && pos.y == maze.end.y) {
timer.stop();
}

if(path[iPath].dir){
if (!maze.isValid(pos, DIRECTION[path[iPath].dir-1])){
moveForward(DIRECTION[path[iPath].dir-1]);
}else{
path[iPath].dir--;
}
}else{
backTrack();
}
}

private function moveForward(dir:Position2D):void {
path[iPath].dir--;
updatePos(dir);
maze.setPath(pos);
iPath++;
path[iPath].x = pos.x;
path[iPath].y = pos.y;
}

private function backTrack():void{
var dir:Position2D = new Position2D(path[iPath-1].x - pos.x, path[iPath-1].y - pos.y);
path[iPath] = new Position2D();
maze.cleanPath(pos);
--iPath;
updatePos(dir);
}

private function updatePos(dir:Position2D):void{
pos.x += dir.x;
pos.y += dir.y;
obj.x += dir.x * STEP;
obj.y += dir.y * STEP;
}
}
}

整個核心函式其實只有moveForward()跟backTrack()這兩個而已,可以說是非常簡單。而最上面的展示範例則是依造SimpleMazeDemo的基礎擴展而成。由於要實做UI的關係,所以完整程式碼篇幅就不小,所使用的class也不少,但是發佈後的swf很小(因為所有畫面上的物件都是由AS 3.0所撰寫出來的),詳細資訊請參考下載檔。

[Flash] Stack & Queue

  • Stack & Queue
  • 雖然Flash本身並沒有提供Stack這類的Class,但是事實上你可以直接使用Array Class的pop(), push(), shift()來達到Stack or Queue的效果。這裡我則是不使用Array Class來實做。上面的範例只是小小的展示Stack(FILO)跟Queue(FIFO)的性質。

    下面程式碼是依造Stack/Queue Implemented Linked Structures這一篇所修改而來的。

    /* Node.as */
    package com.xinyu.collection{
    public class Node {
    public var next:Node = null;
    public var data:Object = null;

    public function Node(data:Object = null, next:Node = null){
    this.data = data;
    this.next = next;
    }
    }
    }

    /* Stack.as */
    package com.xinyu.collection{
    import com.xinyu.collection.Node;
    public class Stack {
    private var _top:Node;
    private var _length:Number;

    public function Stack(){
    _top = null;
    _length = 0;
    }

    public function isEmpty():Boolean {
    return _top == null;
    }

    public function push(data : Object):void {
    var newTop:Node = new Node(data, _top);
    _top = newTop;
    _length += 1;
    }

    public function pop():Object {
    if (isEmpty ()) {
    return "empty";
    }

    _length -= 1;
    var data:Object = _top.data;
    _top = _top.next;
    return data;
    }

    public function peek():Object {
    if (isEmpty ()) {
    return "empty";
    }
    return _top.data;
    }

    public function get length():Number{
    return _length;
    }
    }
    }

    /* Queue */
    package com.xinyu.collection{
    import com.xinyu.collection.Node;

    public class Queue {
    private var _bottom:Node;
    private var _top:Node;
    private var _length:Number;

    public function Queue(){
    _bottom = _top = null;
    _length = 0;
    }

    public function isEmpty():Boolean {
    return (_bottom == null);
    }

    public function enqueue(data:Object):void {
    var node:Node = new Node(data, null);
    if (isEmpty()) {
    _bottom = node;
    _top = node;
    } else {
    _top.next = node;
    _top = node;
    }
    _length += 1;
    }

    public function dequeue():Object{
    if (isEmpty()) {
    return "empty";
    }

    _length -= 1;
    var data:Object = _bottom.data;
    _bottom = _bottom.next;
    return data;
    }

    public function peek():Object {
    if (isEmpty()) {
    return "empty";
    }

    return _bottom.data;
    }

    public function get length():Number{
    return _length;
    }
    }
    }

    正常來說Stack跟Queue是不需要在意length的問題(前提是不考慮記憶體大小以及程式本身的Stack Size 等限制),而這裡我卻加了一個length變數,目的只是想要知道目前存了多少個元素(有點像ArrayCollection),當然上面的寫法是一種很簡易的實做,並沒有去限定Stack/Queue本身儲存的Data type一致的問題(也就是說Number, String, Boolean...等各種型態皆可放置在上面的 Stack/Queue裡),所以在設計時要格外注意一下型態的問題。(由於StackQueueDemo.mxml只是單純展示Stack/Queue用的,並沒有實質的意義,所以就不貼出了)

Friday, February 20, 2009

Crayon Physics

  • Crayon Physics
  • 記得兩三年前才在youtube上看到MIT的Assist Sketch Understanding System and Operation,結果現在這個設計已經商品化了,你可以參觀下面的官方網站並且下載demo來玩玩看。

    http://www.crayonphysics.com/

Sunday, February 15, 2009

[Flash] Performance V.S. Reusability

  • Performance V.S. Reusability
  • 我想這是在OOP裡一個很大的tradeoff,我們必須在效能與框架設計之間取得平衡,考慮下面這一個簡單的效果:

    我們手邊已經有一個CircleMotion的Class,如下:

    /* CircleMotion.as */
    package com.xinyu.motion{
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.display.DisplayObject;

    import com.xinyu.science.Science;

    public class CircleMotion2D extends Motion{
    private var _timer:Timer;
    private var _radian:Number = 0;
    private var _radius:Number;
    private var _speed:Number;
    private var _dir:int;
    private var _xOffset:Number;
    private var _yOffset:Number;
    private var _obj:DisplayObject;

    public static const CW:int = 1;
    public static const CCW:int = -1;

    public function CircleMotion2D(obj:DisplayObject, xOffset:Number = 0 ,yOffset:Number = 0, radius:Number = 50, speed:Number = 0.1, fps:Number = 30, dir:Number = CircleMotion2D.CW, radOffset:int = 0){
    _obj = obj;
    _radius = radius;
    _speed = Math.min(1, Math.max(speed, 0.001));
    _dir = dir;
    _xOffset = xOffset;
    _yOffset = yOffset;
    _radian = Science.degToRad(radOffset%360);

    _timer = new Timer(1000/fps);
    _timer.addEventListener(TimerEvent.TIMER, onTimer);
    }

    private function onTimer(event:TimerEvent):void{
    _obj.x = _radius*Math.cos(_radian) + _xOffset - _radius;
    _obj.y = _radius*Math.sin(_radian) + _yOffset;

    _radian += _dir * _speed;

    if(_radian > 2*Math.PI){
    _radian = 0;
    }
    }

    override public function start():void{
    _timer.start();
    }

    override public function stop():void{
    _timer.stop();
    }

    override public function get running():Boolean{
    return _timer.running;
    }
    }
    }

    所以以直覺來講,RingMotion2D應該要直接沿用CircleMotion2D的定義,簡單的改變每個Object的initial radian就可以達到環繞的效果,所以RingMotion2D理論上是會這樣撰寫:

    /* RingMotion2D.as*/
    package com.xinyu.motion{
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.display.DisplayObject;

    import com.xinyu.motion.CircleMotion2D;
    import com.xinyu.science.Science;

    public class RingMotion2D extends Motion{
    private var _obj:Array;
    private var circleM2D:Array;
    public static const CW:int = 1;
    public static const CCW:int = -1;

    public function RingMotion2D(obj:Array, xOffset:Number = 0 ,yOffset:Number = 0, radius:Number = 100, speed:Number = 0.1, fps:Number = 30, dir:Number = RingMotion2D2.CW){
    _obj = obj;

    circleM2D = new Array(_obj.length);
    for(var i:uint = 0; i < _obj.length; i++){
    circleM2D[i] = new CircleMotion2D(_obj[i], xOffset, yOffset, radius, speed, fps, dir, 360 / _obj.length * i);
    }
    }

    override public function start():void{
    for(var i:uint = 0; i < _obj.length; i++){
    circleM2D[i].start();
    }
    }

    override public function stop():void{
    for(var i:uint = 0; i < _obj.length; i++){
    circleM2D[i].stop();
    }
    }

    override public function get running():Boolean{
    return circleM2D[0].running;
    }
    }
    }

    乍看之下,這樣的寫法很簡潔且沒有問題,但是實際去做測試之後問題就會浮現了,那就是效能,一旦物件增多,fps加快,這個Class所用到的timer也會是成比例成長,縱使是再快的CPU也會有些微的影響 (當然你可能會質疑說,誰沒事會在畫面上產生幾百顆球? 但別忘了這個效果可能只是你的project的功能之一,且並不是所有的瀏覽者的電腦都是那麼快的)。

    為了降低程式的效能成本,我必須重新定義一個RingMotion,如下:

    /* RingMotion2D.as */
    package com.xinyu.motion{
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.display.DisplayObject;

    import com.xinyu.science.Science;

    public class RingMotion2D extends Motion{
    private var _timer:Timer;
    private var _radian:Array;
    private var _radius:Number;
    private var _speed:Number;
    private var _dir:int;
    private var _xOffset:Number;
    private var _yOffset:Number;
    private var _obj:Array;

    public static const CW:int = 1;
    public static const CCW:int = -1;

    public function RingMotion2D(obj:Array, xOffset:Number = 0 ,yOffset:Number = 0, radius:Number = 100, speed:Number = 0.1, fps:Number = 30, dir:Number = RingMotion2D.CW){
    _obj = obj;
    _radian = new Array(_obj.length);
    for(var i:uint = 0; i<_obj.length; i++){
    _radian[i] = Science.degToRad(360/_obj.length * i);
    }

    _radius = radius;
    _speed = Math.min(1, Math.max(speed, 0.001));
    _dir = dir;
    _xOffset = xOffset;
    _yOffset = yOffset;

    _timer = new Timer(1000/fps);
    _timer.addEventListener(TimerEvent.TIMER, onTimer);
    }

    private function onTimer(event:TimerEvent):void{
    for(var i:uint = 0; i < _obj.length; i++){
    _obj[i].x = _radius * Math.cos(_radian[i]) + _xOffset - _radius;
    _obj[i].y = _radius * Math.sin(_radian[i]) + _yOffset;

    _radian[i] += _dir * _speed;
    if(_radian[i] > 2*Math.PI){
    _radian[i] = 0;
    }
    }
    }

    override public function start():void{
    _timer.start();
    }

    override public function stop():void{
    _timer.stop();
    }

    override public function get running():Boolean{
    return _timer.running;
    }
    }
    }

    無可否認這樣的寫法的確違反了OOP的精神,但是為了效能不得如此,因為這樣寫不管有多少個DisplayObject,我們都只需要一個Timer。

    看到這裡也許你會發現如果新的RingMotion2D裡只控制一個物件,那不就等於CircleMotion2D,所以理當CircleMotion2D應該要被RingMotion2D給取代掉才對? 未必,畢竟CircleMotion2D在處裡單顆物件上的效能會比RingMotion2D快一點點(也只有那麼微渺的一點點)。

[Flash] abstract class & virtual function

  • abstract class & virtual function
  • 在c++裡我們有virtual function可以用,在Java裡則可以宣告abstract class ,而在actionscript 3.0雖然有undocumented的virtual specifier可以使用,但卻無法達到pure virtual function的效果,所幸還有override可以發揮,我們一樣也可以在actionscript 3.0達到abstract class的效果,一個簡易的範例如下:

    /* Motion.as */
    package com.xinyu.motion{
    public class Motion{
    public virtual function get running():Boolean{
    return false;
    }

    public virtual function start():void{
    }

    public virtual function stop():void{
    }
    }
    }

    借用之前motion那一篇文章裡的程式碼,只是這裡我們不使用interface的方式來設定,並且將整個motion獨立出來。可以看到我在Motion裡所宣告的函式都加上virtual關鍵字,實際上有沒有加上都不會有任何差別,只是這麼做是為了一個良好的設計習慣。

    底下則是一個SpaceMotion2D繼承於Motion的撰寫方式。

    /* SpaceMotion2D */
    package com.xinyu.motion{
    import flash.display.Sprite;
    import flash.utils.Timer;
    import flash.events.TimerEvent
    import flash.geom.Rectangle;
    import flash.display.DisplayObject;

    import com.xinyu.motion.Motion;
    import com.xinyu.science.Science;

    public class SpaceMotion2D extends Motion{
    private var _timer:Timer;
    protected var _xDir:int;
    protected var _yDir:int;
    protected var _speed:Number;
    protected var _range:Rectangle;
    protected var _obj:DisplayObject;

    public function SpaceMotion2D(obj:DisplayObject, range:Rectangle, speed:Number = 0.1, fps:Number = 25){
    _obj = obj;
    _range = range;
    _speed = Math.min(20, Math.max(speed, 0.001));

    do{
    _xDir = Science.randInt(-1,1);
    }while(_xDir == 0);

    do{
    _yDir = Science.randInt(-1,1);
    }while(_yDir == 0);

    _timer = new Timer(1000/fps);
    _timer.addEventListener(TimerEvent.TIMER, onTimer);
    }

    protected function onTimer(event:TimerEvent):void{
    if(_obj.x + _obj.width/2 + _speed > _range.width){
    _xDir = -1;
    }

    if(_obj.x - _obj.width/2 - _speed < _range.x){
    _xDir = 1;
    }

    if(_obj.y + _obj.height/2 + _speed > _range.height){
    _yDir = -1;
    }

    if(_obj.y - _obj.height/2 - _speed < _range.y){
    _yDir = 1;
    }

    _obj.x += _xDir * _speed;
    _obj.y += _yDir * _speed;
    }

    override public function start():void{
    _timer.start();
    }

    override public function stop():void{
    _timer.stop();
    }

    override public function get running():Boolean{
    return _timer.running;
    }
    }
    }

    如同剛剛所說的,為了培養良好的設計模式,請在start(), stop(), running()前面都加上override以表示這些functions是從Motion繼承而來重新實做的。

    所以大致上AS3裡的abstract class就是以這種方式來實現,至於AS4裡會怎麼去定義這個性質就請拭目以待啦!

    另外如果你有興趣的話也可以將SpaceMotion2D改成3D,下面就是一個SpaceMotion3D繼承於SpaceMotion2D的寫法:

    /* SpaceMotion3D */
    package com.xinyu.motion{
    import flash.display.Sprite;
    import flash.utils.Timer;
    import flash.events.TimerEvent;
    import flash.geom.Rectangle;
    import flash.display.DisplayObject;

    import com.xinyu.motion.SpaceMotion2D;
    import com.xinyu.science.Science;

    public class SpaceMotion3D extends SpaceMotion2D {
    private var _zDir:int;
    private var _z:Number;
    private var _depth:Number;
    private var xyScaleRatio:Number;
    private var objScaleRatio:Number;

    public function SpaceMotion3D(obj:DisplayObject, range:Rectangle, depth:Number, speed:Number = 1, fps:Number=25) {
    super(obj, range, speed, fps);

    _z = depth;
    _depth = depth;

    calXYScaleRatio();
    calObjScaleRatio();

    do {
    _zDir = Science.randInt(-1, 1);
    } while (_zDir == 0);
    }

    override protected function onTimer(event:TimerEvent):void {
    var zRatio:Number = _z / _depth;

    if (_obj.x + _obj.width / 2 + _speed > _range.width - _range.width * xyScaleRatio * zRatio ) {
    _xDir=-1;
    }
    if (_obj.x - _obj.width / 2 - _speed < _range.x + _range.width * xyScaleRatio * zRatio) {
    _xDir=1;
    }
    if (_obj.y + _obj.height / 2 + _speed > _range.height - _range.height * xyScaleRatio * zRatio ) {
    _yDir=-1;
    }
    if (_obj.y - _obj.height / 2 - _speed < _range.y + _range.height * xyScaleRatio * zRatio) {
    _yDir=1;
    }

    if (_z - _speed < 0) {
    _zDir = 1;
    }else if (_z + _speed > _depth) {
    _zDir = -1;
    }

    _obj.x += _xDir * _speed;
    _obj.y += _yDir * _speed;
    _z += _zDir * _speed;
    _obj.scaleX = 1 - objScaleRatio * zRatio;
    _obj.scaleY = 1 - objScaleRatio * zRatio;
    }

    private function calXYScaleRatio():void{
    var max: Number = Science.findMax(_range.width, _range.height);
    xyScaleRatio = _depth / ( 4*max);
    xyScaleRatio = Math.min(0.9, Math.max(xyScaleRatio, 0.1));
    }

    private function calObjScaleRatio():void{
    var min: Number = Science.findMin(_range.width, _range.height);
    objScaleRatio = min / (2 * _depth);
    objScaleRatio = Math.min(0.9, Math.max(objScaleRatio, 0.1));
    }
    }
    }

    這裡的3D效果是我簡單以視覺的判斷產生出來的,並沒有透過嚴謹的mathematical計算來達成,也就是說SpaceMotion3D.as並不是仰賴Flash CS4的z-axes的功能,想要更完善的功能請使用PaperVision3D吧!

Friday, February 13, 2009

[Flash] ActionScript 3.0 Class Access Specifiers

[Flex] Actionscript Project Migration

  • Actionscript Project Migration
  • Download: MotionUIComponent.zip

    平常在Flash底下辛苦寫的actionscript project如果要移植到Flex Project上可以透過UIComponent來承接Sprite,只是有一個限制就是如果你有使用 fl.* package在Flex裡是不支援的。這裡我直接以[Flash] Motion為範例,在這一篇裡我們寫了不小的程式碼來達到多種Motion的特效,如果要拿到Flex Project上當然是不希望要重新撰寫,以下就是一個移植的範例程式碼:

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" applicationComplete="initApp()" backgroundGradientAlphas="[1.0, 1.0]" backgroundGradientColors="[#7E9ED7, #CFE6EF]" width="400" height="300" viewSourceURL="srcview/index.html">
    <mx:Script>
    <![CDATA[
    import flash.display.Sprite;

    import xinyu.geom.Ball;
    import xinyu.science.Science;
    import xinyu.motions.SpaceMotion2D;
    import xinyu.motions.CircleMotion2D;

    private var ball:Ball;

    private var ballMask:Sprite;

    private function initApp(): void {
    ball = new Ball(0xCCCCCC, 0x000055, 10, -45);
    rootStage.addChild(ball);
    ball.setMotion(new SpaceMotion2D(new Rectangle(0,0,rootStage.width, rootStage.height), 5, 30));
    ball.x = Science.randInt(0, rootStage.width);
    ball.y = Science.randInt(0, rootStage.height);
    motionPanel.title = "2D Space Motion";

    initMask();
    }

    private function initMask():void{
    ballMask = new Sprite();
    ballMask.graphics.beginFill(0x000000);
    ballMask.graphics.drawRect(0,0,rootStage.width, rootStage.height);
    ballMask.graphics.endFill();
    rootStage.addChild(ballMask);
    rootStage.mask = ballMask;
    }

    private function run():void{
    ball.motion.run();
    }

    private function stop():void{
    ball.motion.stop();
    }

    private function onMotionClicked(event:MouseEvent):void{
    var running:Boolean = ball.motion.running;

    switch(event.target.label){
    case "2D Space Motion":
    ball.setMotion(new SpaceMotion2D(new Rectangle(0,0,rootStage.width, rootStage.height), 5, 30));
    break;
    case "2D Circle Motion":
    ball.setMotion(new CircleMotion2D(ball.x, ball.y, 50, 0.1, 30, CircleMotion2D.CW));
    break;
    }

    motionPanel.title = event.target.label;

    if(running){
    run();
    }
    }
    ]]>
    </mx:Script>

    <mx:VBox verticalGap="4" left="10" top="10" right="10" bottom="10">
    <mx:Panel id="motionPanel" width="100%" height="240" layout="absolute" title="2D Space Motion" horizontalAlign="center" verticalAlign="middle" cornerRadius="5">
    <mx:UIComponent id="rootStage" width="100%" height="100%" x="0"/>
    </mx:Panel>
    <mx:ApplicationControlBar width="100%" height="100%">
    <mx:Button label="Run" click="run()" width="100%" height="100%"/>
    <mx:Button label="Stop" click="stop()" width="100%" height="100%"/>
    <mx:Button label="2D Space Motion" click="onMotionClicked(event)" width="100%" height="100%"/>
    <mx:Button label="2D Circle Motion" click="onMotionClicked(event)" width="100%" height="100%"/>
    </mx:ApplicationControlBar>
    </mx:VBox>
    </mx:Application>

    從Flex Reference裡可以得知UIComponent是FlexSprite的子類別,而FlexSprite繼承於Sprite,所以當我們要加入當初在Flash底下設計的DisplayObject時,要在Flex主場景裡新增一個UIComponent來包住Ball物件。

    Flex Reference - UIComponent

    從這樣的特性可以得知,以後如果要直接在Flex撰寫ActionScript可以直接繼承於UIComponent即可。

Thursday, February 12, 2009

[Flex] Cross Domain Policy

  • Cross Domain Policy
  • 在Flex SDK裡編寫的Socket程式在連線的過程中出現下面的訊息:

    <policy-file-request/>

    這是Flash Player 安全原則設定,在做網站連線或者Socket連結時需要先取得cross domain policy才能繼續連線。

    以Socket Policy File為例,crossdomain.xml寫法如下:

    <policy-file-request/>

    <?xml version="1.0"?>
    <!DOCTYPE cross-domain-policy SYSTEM
    "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

    <cross-domain-policy>
    <allow-access-from domain="yoursockethosts.com" to-ports="2115"/>
    </cross-domain-policy>

    下面這個則為URL Policy File的寫法:

    <?xml version="1.0"?>
    <!DOCTYPE cross-domain-policy SYSTEM
    "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">

    <cross-domain-policy>
    <site-control permitted-cross-domain-policies="master-only"/>
    <allow-access-from domain="www.yourwebpage.com"/>
    </cross-domain-policy>

    在flash裡要要載入crossdomain最簡易的方法就是使用loadPolicyFile(),如下:

    package{
    import flash.net.Socket;
    import flash.events.Event;
    import flash.events.ProgressEvent;
    import flash.text.TextField;
    import flash.display.Sprite;
    import flash.system.Security;

    public class SocketDemo extends Sprite{
    private var socket:Socket;
    private var textField:TextField;

    Security.loadPolicyFile("http://localhost/crossdomain.xml");

    public function SocketDemo(){
    socket = new Socket("localhost", 2115);
    socket.addEventListener(Event.CONNECT, onConnected);

    textField = new TextField();
    addChild(textField);
    }

    private function onConnected(event:Event):void{
    textField.text = "connected!";
    }
    }
    }

    除了透過從 flash裡面主動去載入crossdomain.xml之外,你也可以把policy file寫入到server然後由server傳到Flash Socket裡。下面的寫法是承襲[Flash] Flash Socket & C++/Java Socket
    這一篇裡的C++ Socket的部份。

    string xml = "<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"1025-9999\"/></cross-domain-policy>";
    client.send(xml);

    詳細的cross domain policy file的資料可以參考下面的網頁:

    Cross-domain policy file specification

    如果你要了解更多關於Flash Security的問題請參考下面的網頁:

    Flash Player Developer Center - Security

[Flash] Event Dispatching

  • Event Dispatching
  • Download: Reminder.zip

    在比較複雜一點的開發專案裡就比較常用到Event Dispatching,因為並不是所有的物件都可以加到Timeline上(未必繼承於Sprite),這時dispatchEvent就派上用場。

    舉個例子來說,假設我們今天要設計一個Reminder,當Reminder時間一到就顯示當初設定的訊息。比較笨的做法就是不斷的去測試Reminder的物件裡的時間是否已經Timeout,然後再顯示設定的訊息,但我想應該沒有人會這樣做。因為在Reminder裡面是使用Timer來計算時間,所以就直接使用TimerEvent.COMPLETE,當Timer結束時在主場景上顯示設定的訊息,要達到這個條件 ,Reminder必須跟主要顯示物件是繼承或者是parent/child的關係,如此才能控制parent的functions以及properties。或者你也可以在Reminder裡面新增TextField之類的物件,只是這樣的話顯示訊息的UI就被鎖死在Reminder裡,所以這種方式還是不夠好,為了讓Reminder就是專心做計時並且回傳當初設定的訊息,下面就是使用dispatchEvent來實做的方法。

    /* Reminder */
    package{
    import flash.events.TimerEvent;
    import flash.events.Event;
    import flash.utils.Timer;
    import flash.events.EventDispatcher;

    public class Reminder extends EventDispatcher{
    private var timer:Timer;
    private var msg:String;
    public function Reminder(minsec:uint, msg:String){
    this.msg = msg;
    timer = new Timer(minsec,1);
    timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete);
    timer.start();
    }

    public function get message():String{
    return msg;
    }

    private function onTimerComplete(event:TimerEvent):void{
    dispatchEvent(new Event("onTimeOut"));
    }
    }
    }

    這個範例裡設定的dispatchEvent名稱為onTimeOut,在ReminderDemo裡面要用到。(如果你想讓Reminder裡顯示一些資訊,可以將原本繼承於EventDispatcher改為Sprite,如此在ReminderDemo裡就可以將reminder加入為child。(因為ReminderDemo是繼承於Sprite),但是如前面所提,我們不希望Reminder本身做那麼多事情。)

    /* ReminderDemo */
    package{
    import flash.display.Sprite;
    import flash.events.Event;
    import Reminder;

    public class ReminderDemo extends Sprite{
    private var reminder:Reminder;
    public function ReminderDemo(){
    reminder = new Reminder(1000," event dispatched by remainder! ");
    reminder.addEventListener("onTimeOut", onTimeOut);
    }

    private function onTimeOut(event:Event):void{
    trace(event.target.message);
    }
    }
    }

    Reminder物件經過dispatchEvent之後就多了一個onTimeOut可以設定,我刻意在Reminder裡面將msg設定為read-only,如此我們就可以接收到當初設定的訊息(假使你要一次開啟很多的Reminder時,這樣的寫法應該會比較便捷)。

    有了Event Dispatching的功能之後,在OOP裡會更方便許多。

    P.S. 如果你熟悉flash.utils這個package,你應該會發現有一個叫做setTimeout可以用,但是在AS3裡,Adobe建議我們使用Timer來完成TimeOut的功能(我想他們可能很想把setInterval/setTimeout給拿掉。)

[Flash] ActionScript 3 for Java Programmers

Wednesday, February 11, 2009

[Flash] Motion

  • Motion
  • Download: BallMotion.zip

    原先只是要簡單介紹get/set function以及interface的運用,結果卻寫了一個不小的範例,畫面如下:


    球體的產生程式碼如下:

    /* Ball.as */
    package xinyu.geom{
    import flash.display.Sprite;
    import flash.geom.Matrix;
    import flash.display.GradientType;
    import flash.display.SpreadMethod;
    import flash.display.DisplayObject;
    import flash.display.Shape;

    import xinyu.science.Science;
    import xinyu.motions.Motions;
    import xinyu.motions.CircleMotion2D;

    public class Ball extends Sprite{
    private var shape:Shape;
    public var motion:Motions;

    public function Ball(innCol:int, outCol:int, radius:Number = 20, degree:Number = 0){
    shape = new Shape();

    var fillType:String = GradientType.RADIAL;
    var colors:Array = [innCol, outCol];
    var alphas:Array = [1, 1];
    var ratios:Array = [0x00, 0xAA];
    var matr:Matrix = new Matrix();
    var spreadMethod:String = SpreadMethod.PAD;

    var radian:Number = Science.degToRad(degree);

    matr.createGradientBox(4*radius, 4*radius, 0, 0.5*radius*Math.cos(radian), 0.5*radius*Math.sin(radian));
    shape.graphics.beginGradientFill(fillType, colors, alphas, ratios, matr, spreadMethod);
    shape.graphics.drawCircle(2*radius,2*radius,radius);
    shape.graphics.endFill();

    shape.x = -2*radius;
    shape.y = -2*radius;
    addChild(shape);
    }

    public function setMotion(m:Motions){
    if(motion != null){
    motion.stop();
    removeChild(motion as DisplayObject);
    }
    motion = m;
    addChild(motion as DisplayObject);
    }
    }
    }

    主要的重點是將球體繪製在shape裡,setMotion()由設計者自己決定是否要讓motion特效重疊,這裡我設定為單一DisplayObject僅能有一個Motions Object,Motions為一個Interface,為了不要跟fl.motion相衝,所以將名稱宣告為Motions如下:

    /* Motions.as */
    package xinyu.motions{
    public interface Motions {
    function run():void;
    function stop():void;
    function get running():Boolean;
    }
    }

    run()跟stop()是控制motion內的timer的啟動與關閉,而get running()則是回傳timer內的running的值,我設定這三個function為時做Motion必要的函式。

    實做Motion的二維球體運動程式碼:

    /* CircleMotion2D.as */
    package xinyu.motions{
    import flash.display.Sprite;
    import flash.utils.Timer;
    import flash.events.TimerEvent

    import xinyu.motions.Motions;

    public class CircleMotion2D extends Sprite implements Motions{
    private var timer:Timer;
    private var rad:Number = 0;
    private var radius:Number;
    private var speed:Number;
    private var dir:int;
    private var xOffset:Number;
    private var yOffset:Number;

    public static const CW:int = 1;
    public static const CCW:int = -1;

    public function CircleMotion2D(xOffset:Number = 0 ,yOffset:Number = 0, radius:Number = 50, speed:Number = 0.1, fps:Number = 25, dir:Number = CircleMotion2D.CW){
    this.radius = radius;
    this.speed = Math.min(1, Math.max(speed, 0.001));
    this.dir = dir;
    this.xOffset = xOffset;
    this.yOffset = yOffset;

    timer = new Timer(1000/fps);
    timer.addEventListener(TimerEvent.TIMER, onTimer);
    }

    private function onTimer(event:TimerEvent):void{
    this.parent.x = 100+radius*Math.cos(rad) + xOffset;
    this.parent.y = 100+radius*Math.sin(rad) + yOffset;

    rad += dir*speed;
    if(rad > 2*Math.PI){
    rad = 0;
    }
    }

    public function run():void{
    timer.start();
    }

    public function stop():void{
    timer.stop();
    }

    public function get running():Boolean{
    return timer.running;
    }
    }
    }

    實做Motion的二維自由運動程式碼:

    /* SpaceMotion2D */
    package xinyu.motions{
    import flash.display.Sprite;
    import flash.utils.Timer;
    import flash.events.TimerEvent
    import flash.geom.Rectangle;

    import xinyu.motions.Motions;
    import xinyu.science.Science;

    public class SpaceMotion2D extends Sprite implements Motions{
    private var timer:Timer;
    private var x_dir:int;
    private var y_dir:int;
    private var speed:Number;
    private var range:Rectangle;

    public function SpaceMotion2D(range:Rectangle, speed:Number = 0.1, fps:Number = 25){
    this.range = range;
    this.speed = Math.min(20, Math.max(speed, 0.001));

    do{
    x_dir = Science.randInt(-1,1);
    }while(x_dir == 0);

    do{
    y_dir = Science.randInt(-1,1);
    }while(y_dir == 0);

    timer = new Timer(1000/fps);
    timer.addEventListener(TimerEvent.TIMER, onTimer);
    }

    private function onTimer(event:TimerEvent):void{
    if(this.parent.x + this.parent.width/2 > range.width){
    x_dir = -1;
    }

    if(this.parent.x - this.parent.width/2 < range.x){
    x_dir = 1;
    }

    if(this.parent.y + this.parent.height/2 > range.height){
    y_dir = -1;
    }

    if(this.parent.y - this.parent.height/2 < range.y){
    y_dir = 1;
    }

    this.parent.x += x_dir*speed;
    this.parent.y += y_dir*speed;
    }

    public function run():void{
    timer.start();
    }

    public function stop():void{
    timer.stop();
    }

    public function get running():Boolean{
    return timer.running;
    }
    }
    }

    這些運動函式多半會需要用到基本的數學物理的轉換,我把他寫在一個Class底下:

    /* Science */
    package xinyu.science{
    public class Science{
    public function Science(){
    }

    public static function radToDeg(rad:Number):Number{
    return rad/Math.PI*180;
    }

    public static function degToRad(deg:Number):Number{
    return deg/180*Math.PI;
    }

    public static function randInt(from:int = 0, end:int = 100):int{
    if(from > end){
    var temp:int = end;
    end = from;
    from = temp;
    }
    return Math.round(Math.random()*(end-from))-Math.abs(from);
    }
    }
    }

    最後在fla裡的第一個影格加上如下程式碼即可編譯執行:

    import xinyu.geom.Ball;
    import xinyu.motions.CircleMotion2D;
    import xinyu.motions.SpaceMotion2D;

    var ball1:Ball = new Ball(0xff0000, 0x0000ff, 20, -45);
    addChild(ball1);

    ball1.x = 100;
    ball1.y = 70;

    ball1.setMotion(new CircleMotion2D(ball1.x, ball1.y, 100, 0.1, 30, CircleMotion2D.CCW));
    ball1.motion.run();

    var ball2:Ball = new Ball(0x00ff00, 0x0000ff, 20, -45);
    addChild(ball2);

    ball2.x = 100;
    ball2.y = 100;

    ball2.setMotion(new SpaceMotion2D(new Rectangle(0,0,stage.stageWidth, stage.stageHeight), 5, 30));
    ball2.motion.run();

    這樣的寫法目的是要讓每個物件都可以套用已經設計好的Motion函式,而不必對每一個顯示物件重新設計過動作,如果要增加新的Motion也比較容易點。

Sunday, February 08, 2009

[Blog] Recommended Browser

  • Recommended Browser
  • 長期以來一直都在用Firefox,直到最近才知道原來我的部落格在Internet Explorer裡的排版如此難看,有鑒於我實在很懶,只能跟Internet Exploer的瀏覽者說聲抱歉,推薦您使用Firefox啦!

    Get Firefox Now!

    由Google Analytics分析的資料,瀏覽者所使用的瀏覽器統計:

Saturday, February 07, 2009

[Flash] setInterval v.s Timer

  • setInterval v.s Timer
  • Download: TimerInterval.zip

    原以為Actionscript 3.0 會把setInterval給拿掉,沒想到在flash.utils裡仍然有這個method,我用一個很簡單的範例來展示他們的不同:

    畫面上有三個按鈕,前兩個按鈕分別為啟動Interval和Timer,第三個按紐則是停止所有的運動,為了呈現他們原始的性質,我並沒有對程式做一些例外處理。

    所有的 Event Handler:

    function onInterClick(event:MouseEvent):void{
    interval = setInterval(run, 25);
    }

    function onTimerClick(event:MouseEvent):void{
    timer.start();
    }

    function onTimer(event:TimerEvent):void{
    run();
    }

    function onStopClick(event:MouseEvent):void{
    timer.stop();
    clearInterval(interval);
    }

    其中最有爭議的是onIntervalClick內的程式碼,每當Run With Interval按鈕被按下時,就會產生一個Interval物件來跑run()這個函式,所以如果你多按幾次Run With Interval你就會多產生很多Interval,球也就的更快,但是當你按下Stop All時那些球卻依然在跑,原因是因為在onStopClick()我們只能停止一個Interval的運作(除非你把所產生過的Interval都給記錄起來),所以以往在actionscript 2.0裡使用setInterval我們都會需要判斷這個interval是否在執行中,如果是的話就不要再產生一個interval。

    但是反觀Timer卻不會有這個問題,原因是他被封裝成Class且內部也做了一些偵測處裡,所以如果你是使用Actionscript3.0來開發的話建議使用Timer啦!

[Flash] Excpetion Handling

  • Exception Handling
  • Download: ExceptionHandling.zip

    以往在C/C++底下寫程式時,很不習慣使用Exception Handling,總覺得使用那種寫法程式碼會顯得很冗長,且在C++底下的Exception Handling是完全自行定義的,面對一些普通的錯誤處裡我會直接選擇if...else來完成會比較快(當然部份的人認為使用try... catch來寫程式碼會比較好讀易懂)。直到最近我又跳回Actionscript環境時才又重新開始使用try...catch,一個簡單的範例如下:

    只是簡單的在畫面上每一秒產生一顆球,直到產生五顆為止。而下方的按鈕則是啟動第五顆球的運動,如果你在產生出第五顆球前之前按下run這個按鈕,這個時候就會出現錯誤訊息,因為第五顆球尚未產生,處裡這個錯誤的主要原始碼如下:

    private function onBtnClick(event:MouseEvent):void{
    try{
    ballArr[4].run();
    errMsg.text = "ballArr[4] is running!";
    }catch (e:Error){
    errMsg.text = e.message;
    }
    }

    其他詳細原始碼請參考下載檔案啦!

[Flash] Timeline Control

  • Timeline Control
  • Download: MovieClipDemo1.zip MovieClipDemo2.zip

    Demo: MovieClipDemo1 MovieClipDemo2

    從接觸Flash 4 到現在Timeline Control一直都是讓許多入門者感到疑惑的地方,大部分的人直覺只要我有建立元件在場景上,在任何影格上就可以呼叫這個元件,這是一個錯誤的認知,範例狀況如下:

    在MovieClipDemo1.fla裡有三個圖層,主場景共兩個影格。在圖層MoveiClips裡影格一為MC1 這個 MovieClip,而影格二則為MC2 這個 MovieClip。MC1 和MC2皆包含著三個按鈕來跳躍時間軸,如下:

    MC1 MovieClip:

    MC2 MovieClip:

    分別將Actionscript碼寫到個別的MovieClip裡,以MC1為例如下:

    stop();

    main_btn.addEventListener(MouseEvent.CLICK, onMainClick);
    mc1_btn.addEventListener(MouseEvent.CLICK, onMC1Click);
    mc2_btn.addEventListener(MouseEvent.CLICK, onMC2Click);

    function onMainClick(event:MouseEvent):void{
    MovieClip(root).gotoAndStop(2);
    }

    function onMC1Click(event:MouseEvent):void{
    this.gotoAndStop(2);
    }

    function onMC2Click(event:MouseEvent):void{
    //MovieClip(root).mc2_mc.gotoAndStop(3);
    //unable to call a undefined movieclip
    }

    Main Timeline跟MC1 Timeline的按鈕都不會有問題,但是如果你將對應到MC2 Timeline按鈕的程式語法註解拿掉的話,當你編譯完後在MC1裡點擊MC2 Timeline按鈕會出現錯誤訊息,回顧前面兩張圖,在主場景影格一裡我們沒有建立MC2影片元件,所以MC1自然就看不到他,也就無法操控(也就是所謂scope的問題)。

    要解決這個問題你可以把MC2跟MC1建立在同個影格上,且長度一致,如下:

    然後在主場景影格一加上下面的程式碼:

    mc1_mc.visible = true;
    mc2_mc.visible = false;

    目的是要讓MC2一開始先隱藏起來,如法炮製,在影格二你就要把這個性質給對調。

    元件建立完後我們還要對個別的MovieClip做些微的語法控制修正,以MC1為例:

    stop();

    main_btn.addEventListener(MouseEvent.CLICK, onMainClick);
    mc1_btn.addEventListener(MouseEvent.CLICK, onMC1Click);
    mc2_btn.addEventListener(MouseEvent.CLICK, onMC2Click);

    function onMainClick(event:MouseEvent):void{
    MovieClip(root).gotoAndStop(2);
    }

    function onMC1Click(event:MouseEvent):void{
    this.gotoAndStop(2);
    }

    function onMC2Click(event:MouseEvent):void{
    MovieClip(root).mc2_mc.gotoAndStop(3);
    MovieClip(root).gotoAndStop(2);
    }

    在onMC2Click裡的程式碼做了小小的手腳,先將mc2跳到frame3在跳到maine timeline frame 2。

    由於這是個演示範例,所以模式寫成這樣,實際設計運作情況應該盡量讓同質性的功能寫在一起,非必要盡量避免將actionscript分散到各個元件影格上,因為對維護者而言是一件很辛苦的事。

    這裡只有簡單提到單一場景的時間軸控制,如果再加上場景的切換又有一些基礎概念需要了解,有機會要講解時我再做一個解說吧!