2012年7月26日 星期四

as3 carousel effect

flash 旋轉木馬的效果有很多方式可以實做出來,
從早期用  2D 的方式去模擬,
到後來用 pv3d or away3d 很多 3D framework 都可以辦到。

本篇文章用 flash 內建的 3D 功能來實做,

大概說明一下,
flash 3D 座標系,Z軸愈往螢幕愈大

弧度,圓一圈為 Math.PI * 2
anglePer = (Math.PI * 2) / NUM_ITEMS;

從弧度推算 x/z,將物件依序排列
angle = i * anglePer;
mc.x = Math.cos(angle) * RADIU_X;
mc.z = Math.sin(angle) * RADIU_Z;

設定排在最前面的物件 z=0
container.z = RADIU_Z;

沿 Y 軸旋轉 conainer
container.rotationY += SPEED;

物件也要跟著反轉(這樣物件才能面向使用者)
arrItems[i].rotationY -= SPEED;

因為 flash 3D 不支援 ZSort,只好自己處理
SimpleZSort3D.simpleZSort3DChildren(container, false);

透視變形,取得 root 顯示物件的透視投影設定以及變更 perspectiveProjection 屬性的
視野和投影中心屬性
projection = root.transform.perspectiveProjection
預設為 stage 中心
projection.projectionCenter = new Point(stage.stageWidth >> 1, stage.stageHeight >> 1 - 100);

因為我很懶,
接下來改用 TweenLite 幫忙處理轉動的部份
先計算轉動要花的時間
var dif:Number = clickid - currid;
if (dif != dif % (NUM_ITEMS >> 1)) {
  dif = (dif < 0) ? dif + NUM_ITEMS : dif - NUM_ITEMS;
}
dif = (dif < 0 ? -dif : dif);
var time:Number = dif * SPEED;
計算旋轉角度
rotY = degPer * (clickid - frontid);

接下來就交給 TweenLite 處理
TweenMax.to(container, time, { shortRotation: { rotationY: rotY }} );
TweenMax.to(arrItems[i], time, { shortRotation:{rotationY: -rotY }} );

最後附上結果圖,
完整 code 如下。
package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
import iqcat.utility.TextUtils;
/**
* draw circle
* @author jacky
*/
public class Item extends Sprite
{
public var onClick:Function = null;
private var _id:uint;
public function Item(__id:uint, __r:Number = 50.0)
{
_id = __id;
graphics.beginFill(0xFFFFFF * Math.random());
graphics.drawCircle(0, 0, __r);
graphics.endFill();
addChild(TextUtils.textToBitmap(String(_id)));
cacheAsBitmap = true;
addEventListener(MouseEvent.CLICK, handleClick);
}
private function handleClick(e:MouseEvent):void
{
trace("id:" + _id);
if (onClick != null) {
onClick(_id, e);
}
}
}
}
view raw Item.as hosted with ❤ by GitHub
package
{
import com.greensock.TweenMax;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.PerspectiveProjection;
import flash.geom.Point;
/**
* carousel effect
* @author jacky
*/
public class Main extends Sprite
{
// 四的倍數
static public const NUM_ITEMS:int = 12;
public const RADIU_X:Number = 400;
public const RADIU_Z:Number = 400;
public const SPEED:Number = .5;
public const ACTIVE_BLUR:Boolean = true;
private var projection:PerspectiveProjection;
private var anglePer:Number;
private var degPer:Number;
private var container:Sprite;
private var arrItems:Vector.<Item> = new Vector.<Item>();
private var isMoving:Boolean;
private var rotY:Number;
private var clickid:int;
private var frontid:int;
private var currid:int;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
isMoving = false;
// 最前面的物件的 id
currid = frontid = NUM_ITEMS - NUM_ITEMS / 4;
// 透視變形,取得 root 顯示物件的透視投影設定以及變更 perspectiveProjection 屬性的視野和投影中心屬性
projection = root.transform.perspectiveProjection
// 預設為 stage 中心
projection.projectionCenter = new Point(stage.stageWidth >> 1, stage.stageHeight >> 1 - 100);
container = new Sprite();
container.x = stage.stageWidth >> 1;
container.y = stage.stageHeight >> 1;
// 設定排在最前面的物件 z=0
container.z = RADIU_Z;
addChild(container);
anglePer = (Math.PI * 2) / NUM_ITEMS;
// 弧度,圓一圈為 Math.PI * 2
degPer = 360 / NUM_ITEMS;
var mc:Item;
var angle:Number;
for (var i:int = 0; i < NUM_ITEMS; i++) {
mc = new Item(i, 50);
angle = i * anglePer;
mc.x = Math.cos(angle) * RADIU_X;
mc.z = Math.sin(angle) * RADIU_Z;
//mc.rotationY = ( -360 / NUM_ITEMS) * i - 90;
mc.onClick = move;
arrItems.push(mc);
container.addChild(mc);
}
updateObj();
//addEventListener(Event.ENTER_FRAME, render);
TweenMax.delayedCall(.25, function():void { move(2)} );
}
private function move(__id:int = 0, e:MouseEvent = null):void
{
if (!isMoving) {
isMoving = true;
clickid = __id;
// 計算轉動要花的時間
var dif:Number = clickid - currid;
if (dif != dif % (NUM_ITEMS >> 1)) {
dif = (dif < 0) ? dif + NUM_ITEMS : dif - NUM_ITEMS;
}
dif = (dif < 0 ? -dif : dif);
var time:Number = dif * SPEED;
currid = __id;
rotY = degPer * (clickid - frontid);
TweenMax.to(container, time, { shortRotation: { rotationY: rotY }, onUpdate:updateObj, onComplete:onComplete } );
var len:int = arrItems.length;
for (var i:int = 0; i < len; i++) {
TweenMax.to(arrItems[i], time, { shortRotation:{rotationY: -rotY }} );
}
}
}
private function onComplete():void
{
isMoving = false;
}
private function updateObj():void
{
SimpleZSort3D.simpleZSort3DChildren(container, false);
if(ACTIVE_BLUR) {
var len:int = arrItems.length;
var b:Number;
for (var i:int = 0; i < len; i++) {
b = len - container.getChildIndex(arrItems[i]);
// 最前面三個不用blur
arrItems[i].filters = (b > 3) ? [new BlurFilter(b >> 1, b >> 1, 3)] : [];
}
}
}
private function render(e:Event):void
{
SimpleZSort3D.simpleZSort3DChildren(container, false);
container.rotationY += SPEED;
for (var i:int = 0; i < arrItems.length; i++) {
arrItems[i].rotationY -= SPEED;
}
}
}
}
view raw main.as hosted with ❤ by GitHub
package
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.geom.Matrix3D;
/**
* simple zsort
* @author jacky
*/
public class SimpleZSort3D
{
public static function simpleZSort3DChildren( doc:DisplayObjectContainer, recurse:Boolean = true ) : void
{
//transforms from local to world oordinate frame
doc.z = doc.z;
var transform:Matrix3D = doc.transform.getRelativeMatrix3D( doc.stage );
var numChildren:int = doc.numChildren;
//v = ( n * 3 )- (x,y,z) set for each child
var vLength:int = numChildren * 3;
var vLocal:Vector.<Number> = new Vector.<Number>( vLength, true );
var vWorld:Vector.<Number> = new Vector.<Number>( vLength, true );
var vIndex:int = 0;
for( var i:int = 0; i <numChildren; i++ )
{
var child:DisplayObject = doc.getChildAt( i );
if (!(child == null && i == 0)) {
if( recurse && child is DisplayObjectContainer ) simpleZSort3DChildren( DisplayObjectContainer( child ), true );
vLocal[ vIndex ] = child.x;
vLocal[ vIndex + 1 ] = child.y;
vLocal[ vIndex + 2 ] = child.z;
vIndex += 3;
}
}
transform.transformVectors( vLocal, vWorld );
//bubble sorts children along world z-axis
for( i = numChildren - 1; i > 0; i-- )
{
var hasSwapped:Boolean = false;
vIndex = 2;
for( var j:int = 0; j < i; j++ )
{
//z value at that index for each child
var z1:Number = vWorld[ vIndex ];
vIndex += 3;
var z2:Number = vWorld[ vIndex ];
if( z2> z1 )
{
//swap
doc.swapChildrenAt( j, j + 1 );
vWorld[ vIndex - 3 ] = z2;
vWorld[ vIndex ] = z1;
//mark as swapped
hasSwapped = true;
}
}
if( !hasSwapped ) return;
}
}
}
}

沒有留言:

張貼留言