- Crayon Physics
記得兩三年前才在youtube上看到MIT的Assist Sketch Understanding System and Operation,結果現在這個設計已經商品化了,你可以參觀下面的官方網站並且下載demo來玩玩看。
記得兩三年前才在youtube上看到MIT的Assist Sketch Understanding System and Operation,結果現在這個設計已經商品化了,你可以參觀下面的官方網站並且下載demo來玩玩看。
我想這是在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快一點點(也只有那麼微渺的一點點)。
在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吧!
做個小小的紀錄:
另外還有一篇Tips for learning ActionScript 3.0,值得初學者去讀。
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撰寫ActionScript可以直接繼承於UIComponent即可。
在Flex SDK裡編寫的Socket程式在連線的過程中出現下面的訊息:
這是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的問題請參考下面的網頁:
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給拿掉。)
這一篇文章是無意收尋到的,如果你熟悉Java且想學ActionScript,這篇文章或許有用。
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也比較容易點。
長期以來一直都在用Firefox,直到最近才知道原來我的部落格在Internet Explorer裡的排版如此難看,有鑒於我實在很懶,只能跟Internet Exploer的瀏覽者說聲抱歉,推薦您使用Firefox啦!
由Google Analytics分析的資料,瀏覽者所使用的瀏覽器統計:
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啦!
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;
}
}
其他詳細原始碼請參考下載檔案啦!
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分散到各個元件影格上,因為對維護者而言是一件很辛苦的事。
這裡只有簡單提到單一場景的時間軸控制,如果再加上場景的切換又有一些基礎概念需要了解,有機會要講解時我再做一個解說吧!
在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);
}
}
}
}
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());
}
/* 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;
}
}
其於詳細部分請參考原始碼。
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
之前就有考慮要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
關於CSS語法部分可以參考下面的網站:
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...這篇的程式碼)
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 = "";
}
關於編碼的資料可以參考下面的網頁:
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的介面稍加修改,再配合之前的聊天程式的片段,就可以組合成聊天室了。
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/
程式碼很單純,如下:
詳細資訊請參考原始碼。
Download: BitmapSelection.zip
這是一個簡單的Bitmap Class的應用,結合上一篇的SymbolButton和自製的DupBmpImg類別合成的,這個程式的功能只是簡單的將你所框起的範圍給複製出來,效果如下(你可以點選這個連結來預覽實際效果):
Download: SymbolButton.zip
在Flash裡面要製作按鈕除了使用fl component和手動繪製之外,還可以完全使用actionscript來完成。這裡為了方便我直接沿用SimpleButton這個類別來設計,參考畫面如下:
SymbolButton.as 片段程式碼:在過去的Actionscript設計過程中幾乎都沒有需要用到像是c語言的pointer的特性,直到最近才針對此問題做了一個小小的檢測,由於測試環境在Linux底下的Flex SDK 3,所以程式碼顯得有點囉唆。
下面是一個典型的swap範例:
由此可知在actionscript裡的參數傳遞是pass by value(javascript也是如此),為了達到兩數交換的目的,在actionscript裡就不行使用原生型態,要改以Object型態來傳遞,如下:
雖然成功將數值給交換,但是這種撰寫方法反而不方便,只好期待actionscript 4.0能夠支援像python的數值交換的寫法:
[a,b] = [b,a]
從ECMAScript 4.0的規範裡來看似乎很有希望。
Installation pip install orange3 Run orange python -m Orange.canvas