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分散到各個元件影格上,因為對維護者而言是一件很辛苦的事。

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

[Flash] Constructor Overloading

  • Constructor Overloading
  • 在Actionscript 3.0 裡是沒有支援overloading的功能,但有時候我們需要傳遞不同的參數到建構子裡,解決方案除了設定預設值在建構子裡以外,我們還可以使用Variable-Length Argument Lists的技術來傳遞不同類型的值到建構子裡,如下:

    package{
    import flash.display.Sprite;
    public class ArgsDemo extends Sprite{
    public function ArgsDemo(...str){
    for each(var object:* in str){
    trace(object);
    }
    }
    }
    }

Thursday, February 05, 2009

[Flash] Local Connection

  • Local Connection
  • Download: BallMotionSenderReceiver.zip

    兩個swf要互相通訊可以藉由LocalConnection來完成,不需要透過第三方的Communication Server來溝通。範例畫面如下:

    Sender:

    只有兩個按鈕,負責傳送控制訊息到Reciver端。

    Receiver:

    接收端上只是一個停滯的球體,等待傳送端發送啟動訊息。

    主要程式片段如下:

    /* in LocalConnSend.as */
    public function LocalConnSend(){
    initUI();
    conn = new LocalConnection();
    conn.addEventListener(StatusEvent.STATUS, onStatus);
    }

    private function sendMessage(event:MouseEvent):void {
    conn.send("myConnection", "lcHandler", event.target.parent.getLabel());
    trace(event.target.parent.getLabel());
    }

    在Constructor裡只是簡單的建立LocalConnection物件,而訊息的傳送則是由按鈕來觸發,getLabel()回傳的是按鈕的文字。

    /* in LocalConnRecv.as */
    public function lcHandler(msg:String):void {
    switch(msg){
    case "Run":
    mBall.run();
    break;
    case "Stop":
    mBall.stop();
    break;
    default:
    trace(msg + "\n");
    break;
    }
    }

    對應到sender裡的lcHandler,依造所接收的字串給予對應的動作。

    其於詳細部分請參考原始碼。

[Flash] Pass Variables into SWF in AS3

  • Pass Variables to SWF in AS3
  • Download: PassVars.zip

    範例輸出畫面請點選下面連結:

    http://huaning.myweb.hinet.net/Flash/PassVars/PassVars.html

    在AS3裡要傳遞變數到SWF裡需要用到loaderInfo,程式碼如下:

    root.loaderInfo.addEventListener(Event.COMPLETE, onLoaded);
    function onLoaded(event:Event):void {
    var paramObj:Object=LoaderInfo(this.root.loaderInfo).parameters;
    var1_txt.text="var1:"+paramObj["var1"];
    var2_txt.text="var2:"+paramObj["var2"];
    }

    明明只是個很單純的問題,沒想到將檔案放置到網頁上參數卻傳不過去,最後才發現原來這一切都是AC_RunActiveContent.js造成的,為了能正常展示運作,這個範例裡我將所有的AC_RunActiveContent資料刪除。

    除了上面的做法之外,你也可以使用Adobe官方推薦的swfobject來完成,頁面如下:

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

    他們還有提供code generator,所以使用上會更方便。

    下面是有關Flash OBJECT and EMBED tag attributes的資料:

    http://kb.adobe.com/selfservice/viewContent.do?externalId=tn_12701&sliceId=2

[Blog] Google Code Prettify

  • Google Code Prettify
  • 之前就有考慮要syntax highlighter來輔助程式碼的顯示,但是總感覺loading太重,所以還是選擇用抓圖的形式來顯示程式碼。最近看到了Google Code Prettify感覺還算可以,有興趣的人可以用用看。

    官方頁面如下:

    http://code.google.com/p/google-code-prettify/

    安裝方式請參考下面的網頁:

    http://google-code-prettify.googlecode.com/svn/trunk/README.html

    當一切設定好之後,你會發現code的顏色的確有改變,但是總覺得有點不完善,這時你可以修改你的html範本,增加CSS Code如下:

    code {
    display: block;
    font-family: 'Verdana';
    font-size: 8pt;
    font-weight: bold;
    overflow: auto;
    white-space: nowrap;
    border: 1px solid #7F7F7F;
    padding: 10px 10px 10px 21px;
    max-height: 600px;
    line-height: 1.5em;
    letter-spacing: 0px;
    color: #000000;
    background: #fbfaf7 url(http://sites.google.com/site/xinyu0123/Home/BG_CODE.gif) left top repeat-y;
    }

    最後那個background設定目的是要在程式碼左邊顯示一條code的標籤,你可以不用加上它。儲存完範本之後,每當你使用code標籤貼上程式碼就會有如上的效果。

    PS. 每當我切換blog的撰寫模式之後,整個code的排版又被歸零,看來只能再尋找更好的方法,或者是哪天想不開自己用Flash寫好了。

    Web Color Code的部份可以參考下面的網頁:

    http://www.computerhope.com/htmcolor.htm

    http://html-color-codes.com/

    關於CSS語法部分可以參考下面的網站:

    http://www.w3schools.com/Css/

[Flash] Flash Socket & C++/Java Socket

  • Flash Socket & C++/Java Socket
  • Download: SocketComm.zip Java_C_Comm.zip

    根據Google Analytics資料顯示,我的部落格點閱率最高的文章竟然是[C/C++] Socket Connection ,為此我決定再增加Flash Socket的部份。

    這一篇是最簡單的範例,只是實做讓Flash Socket與C++/Java Socket互通,因為Flash Socket僅有Client的連線功能,Server的部份我們必須仰賴C++/C#/Perl/Java/PHP等語言來完成。預覽畫面如下(Flash Client Side):

    Server-Side (Server的部份則是直接沿用[C/C++/JAVA] Socket Communication Between JAVA and...這篇的程式碼)

    Waiting for Connection......
    Connected from 192.168.2.49/192.168.2.49:1899
    Hello! I'm Flash Client

    程式碼的部份其實很單純,唯一要講解的只有傳送的部份:

    private function sendData():void{
    var buff:ByteArray = new ByteArray();
    buff.writeMultiByte(input.text+"\n","unicode");
    socket.writeBytes(buff);
    socket.flush();
    output.appendText(input.text);
    input.text = "";
    }

    可以看到我們在傳送數據時用的是writeBytes而不是writeUTFBytes,這麼做的原因是要解決中文會亂碼的問題。傳送完之後記的要呼叫flush(),如此才能正常傳送出去。

    關於編碼的資料可以參考下面的網頁:

    http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/charset-codes.html

    Policy File的資訊請參考下面的網頁:

    http://www.adobe.com/devnet/flashplayer/articles/fplayer9_security_print.html

    有了上面的簡單概念之後,你可以將Flash的介面稍加修改,再配合之前的聊天程式的片段,就可以組合成聊天室了。

Wednesday, February 04, 2009

[Flash] Screen Wall

  • Screen Wall
  • Download: ScreenWall.zip

    要在Flash裡面實現Screen Wall是一件很簡單的事,最常見的手法就是將影片轉成嵌入式的MovieClip來擺放。這裡我使用Flash CS4的3D視角旋轉功能,再加上簡單的VideoPlayer應用,參考畫面如下:(你可以點選這個連結來實際預覽畫面)

    左邊畫面的翻轉是直接使用Flash CS4的編輯工具達成,而右邊畫面翻轉則是以ActionScript來完成,中間則為主要畫面來源,使用VideoPlayer Class,兩旁的畫面則是透過BitmapData的形式複製過來,為了不處裡Security Sandbox Violation的問題,我把所有檔案都放在同個Domain下。

    影片來源: 電影Once 的預告片,更多Once的資訊可以參考下面的網頁:

    http://www.foxsearchlight.com/once/

    http://www.imdb.com/title/tt0907657/

    程式碼很單純,如下:

    詳細資訊請參考原始碼。

[Flash] Bitmap Selection

    雖然只是小小的框起複製的效果,但裡面仍然有許多細節要注意,之後再進一步一一說明。有興趣者可以再將此程式改寫,讓複製後的圖片做一些處理等等。詳細資訊還是請參照原始碼。

Tuesday, February 03, 2009

[Flash] Custom Button

  • Custom Button
  • Download: SymbolButton.zip

    在Flash裡面要製作按鈕除了使用fl component和手動繪製之外,還可以完全使用actionscript來完成。這裡為了方便我直接沿用SimpleButton這個類別來設計,參考畫面如下:

    SymbolButton.as 片段程式碼:
    這裡我們使用了fl.motion.Color的類別,在Flex裡將無法支援。

    為了節省程式碼的撰寫,將每一種功能的圖案結合在下面的函式裡。

    在Flash裡面測試SymbolButton:

    詳細部分請參考原始碼

[UTips. 66] HardInfo

  • HardInfo
  • 雖然在ubuntu裡面很少有機會要去看硬體資訊,但你還是可以透過這個小工具來偵測。

    sudo apt-get install hardinfo


Monday, February 02, 2009

[Flash] pass by reference

  • pass by reference
  • 在過去的Actionscript設計過程中幾乎都沒有需要用到像是c語言的pointer的特性,直到最近才針對此問題做了一個小小的檢測,由於測試環境在Linux底下的Flex SDK 3,所以程式碼顯得有點囉唆。

    下面是一個典型的swap範例:

    輸出結果:

    a:1 b:2

    由此可知在actionscript裡的參數傳遞是pass by value(javascript也是如此),為了達到兩數交換的目的,在actionscript裡就不行使用原生型態,要改以Object型態來傳遞,如下:

    輸出結果:

    a:2 b:1

    雖然成功將數值給交換,但是這種撰寫方法反而不方便,只好期待actionscript 4.0能夠支援像python的數值交換的寫法:

    [a,b] = [b,a]

    從ECMAScript 4.0的規範裡來看似乎很有希望。