Flex有一个Move效果,播放该效果时,目标对象会沿直线运动。有时候我们需要更丰富一些的运动效果,比如抛物线运动,Move就显得无能为力了。基于这种需求,我创建了一个MoveEx类,可以播放大多数的运动效果。
通过查看Flex源代码可以看出,Move以及MoveInstance分别继承自TweenEffect与TweenEffectInstance,Move效果实际就是创建Tween对象,并通过它来改变目标的x与y属性。因此可以考虑只用Tween来改变目标的x属性,而y属性则根据一个函数得出结果,那样一来,如果在函数里指定为直线方程,那么效果就是直线运动,指定为抛物线方程,效果就是抛物线运动…… 这样会灵活许多。
有人可能会问了,如果实现垂直运动效果,x属性是不变的,所以也就没办法播放垂直运动效果了。其实针对这种情况,可以设计让Tween来改变目标的y属性,然后根据一个函数得出x属性的值。甚至我们需要加一个额外属性,指定Tween改变目标的哪个属性:x或y。因为我只想要一个简单的开口向下的抛物线效果,所以就不考虑这种情况了。
实现代码如下所示:
MoveEx.as
package com.thornbird.effects
{
import com.swoondazzle.plantsVSAnimals.effects.effectClasses.MoveExInstance;
import mx.effects.IEffectInstance;
import mx.effects.TweenEffect;
/**
* 移动扩展效果
*/
public class MoveEx extends TweenEffect
{
//--------------------------------------------------------------------------
//
// Class constants
//
//--------------------------------------------------------------------------
/**
* @private
* 影响的属性
*/
private static var AFFECTED_PROPERTIES:Array = ["x", "y"];
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor
*/
public function MoveEx(target:Object = null)
{
super(target);
instanceClass = MoveExInstance;
}
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var beelineParameters:Array = null;
/**
* @private
*/
private var parabolaParameters:Array = null;
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// xFrom
//----------------------------------
/**
* 起点x坐标
*/
public var xFrom:Number = NaN;
//----------------------------------
// xTo
//----------------------------------
/**
* 终点x坐标
*/
public var xTo:Number = NaN;
//----------------------------------
// track
//----------------------------------
/**
* 运动轨迹函数
*/
public var track:Function = null;
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
override public function getAffectedProperties():Array
{
return AFFECTED_PROPERTIES;
}
/**
* @private
*/
override protected function initInstance(instance:IEffectInstance):void
{
super.initInstance(instance);
var moveExInstance:MoveExInstance = instance as MoveExInstance;
moveExInstance.xFrom = xFrom;
moveExInstance.xTo = xTo;
moveExInstance.track = track;
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* 创建直线运动轨迹函数
* @param startX 起点x坐标
* @param startY 起点y坐标
* @param endX 终点x坐标
* @param endY 终点y坐标
*/
public function createBeelineTrack(startX:Number, startY:Number,
endX:Number, endY:Number):void
{
// 此时起点与终点已经确定
xFrom = startX;
xTo = endX;
var x1:Number = startX;
var y1:Number = startY;
var x2:Number = endX;
var y2:Number = endY;
// 将k, b分别代入 y = k * x + b 得出k, b的值:
var k:Number = (y1 - y2) / (x1 - x2);
var b:Number = b = y1 - x1 * k;
beelineParameters = [k, b];
track = beeline;
}
/**
* 创建抛物线轨迹函数
* @param startX 起点x坐标
* @param startY 起点y坐标
* @param distance 距离
* @param height高度
*/
public function createParabolaTrack(startX:Number, startY:Number,
distance:Number, height:Number):void
{
// 此时起点与终点已经确定
xFrom = startX;
xTo = startX + distance;
var x1:Number = xFrom;
var y1:Number = 0;
var x2:Number = xTo;
var y2:Number = 0;
var x0:Number = x1 + distance / 2;
var y0:Number = height;
// 将x1, y1, x2, y2, x0, y0分别代入 y = a * Math.pow(x, 2) + b * x + c
// 得出a, b, c的值:
var a:Number = ((x1 - x0) * (y1 - y2) - (x1 - x2) * (y1 - y0))
/ ((x1 - x0) * (Math.pow(x1, 2) - Math.pow(x2, 2))
- (x1 - x2) * (Math.pow(x1, 2) - Math.pow(x0, 2)));
var b:Number = ((y1 - y2) - (Math.pow(x1, 2) - Math.pow(x2, 2)) * a)
/ (x1 - x2);
var c:Number = y1 - Math.pow(x1, 2) * a + x1 * b;
parabolaParameters = [a, b, c, startY];
track = parabola;
}
/**
* @private
*/
public function beeline(x:Number):Number
{
var k:Number = beelineParameters[0];
var b:Number = beelineParameters[1];
return (x * k + b);
}
/**
* @private
*/
public function parabola(x:Number):Number
{
var a:Number = parabolaParameters[0];
var b:Number = parabolaParameters[1];
var c:Number = parabolaParameters[2];
var y:Number = parabolaParameters[3];
var h:Number = parabolaParameters[4];
return (y - ((Math.pow(x, 2) * a + x * b + c)
- (Math.pow(xFrom, 2) * a + xFrom * b + c)));
}
}
}
MoveExInstance.as
package com.thornbird.effects.effectClasses
{
import flash.events.Event;
import mx.core.Container;
import mx.core.EdgeMetrics;
import mx.core.IUIComponent;
import mx.core.mx_internal;
import mx.effects.EffectManager;
import mx.effects.effectClasses.TweenEffectInstance;
import mx.events.MoveEvent;
use namespace mx_internal;
/**
* 移动扩展效果实例
*/
public class MoveExInstance extends TweenEffectInstance
{
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* @private
*/
private var forceClipping:Boolean = false;
/**
* @private
*/
private var checkClipping:Boolean = true;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor
*/
public function MoveExInstance(target:Object)
{
super(target);
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// xFrom
//----------------------------------
/**
* 起点x坐标
*/
public var xFrom:Number = NaN;
//----------------------------------
// xTo
//----------------------------------
/**
* 终点x坐标
*/
public var xTo:Number = NaN;
//----------------------------------
// track
//----------------------------------
/**
* 运动轨迹函数
*/
public var track:Function = null;
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
override public function initEffect(event:Event):void
{
super.initEffect(event);
if (event is MoveEvent && event.type == MoveEvent.MOVE)
{
if (isNaN(xFrom) && isNaN(xTo))
{
xFrom = MoveEvent(event).oldX;
xTo = target.x;
}
}
}
/**
* @private
*/
override public function play():void
{
super.play();
EffectManager.startBitmapEffect(IUIComponent(target));
tween = createTween(this, [xFrom], [xTo], duration);
var targetParent:Container = target.parent as Container;
if (targetParent != null)
{
var yFrom:Number = calculateYCoord(xFrom);
var yTo:Number = calculateYCoord(xTo);
var vm:EdgeMetrics = targetParent.viewMetrics;
var l:Number = vm.left;
var r:Number = targetParent.width - vm.right;
var t:Number = vm.top;
var b:Number = targetParent.height - vm.bottom;
if (xFrom < l || xTo < l
|| xFrom + target.width > r
|| xTo + target.width > r
|| yFrom < t || yTo < t
|| yFrom + target.height > b
|| yTo + target.height > b)
{
forceClipping = true;
targetParent.forceClipping = true;
}
}
applyTweenStartValues();
}
/**
* @private
*/
override public function onTweenUpdate(value:Object):void
{
EffectManager.suspendEventHandling();
var x:Number = value[0];
var y:Number = calculateYCoord(x);
if (!forceClipping && checkClipping)
{
var targetParent:Container = target.parent as Container;
if (targetParent != null)
{
var vm:EdgeMetrics = targetParent.viewMetrics;
var l:Number = vm.left;
var r:Number = targetParent.width - vm.right;
var t:Number = vm.top;
var b:Number = targetParent.height - vm.bottom;
if (x < l || x + target.width > r
|| y < t || y + target.height > b)
{
forceClipping = true;
targetParent.forceClipping = true;
}
}
}
target.move(x, y);
EffectManager.resumeEventHandling();
}
/**
* @private
*/
override public function onTweenEnd(value:Object):void
{
EffectManager.endBitmapEffect(IUIComponent(target));
if (forceClipping)
{
var targetParent:Container = target.parent as Container;
if (targetParent != null)
{
forceClipping = false;
targetParent.forceClipping = false;
}
}
checkClipping = false;
super.onTweenEnd(value);
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* @private
* 根据x坐标计算y坐标
* @param x x坐标
*/
private function calculateYCoord(x:Number):Number
{
var y:Number = 0;
if (track != null)
return track(x);
return target.y;
}
}
}
示例代码
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:effect="com.thornbird.effects.*"
layout="absolute">
<mx:Script>
<![CDATA[
private function beginMove():void
{
moveEx.createParabolaTrack(0, 100, 800, 100);
moveEx.play([button]);
}
]]>
</mx:Script>
<effect:MoveEx id="moveEx" xFrom="0" xTo="700" duration="1000" />
<mx:Button id="button" x="0" y="100" label="Button" click="beginMove()" />
</mx:Application>
这样一个简单的抛物线效果就完成了。这是我山寨植物大战僵尸游戏时用到的,代码很简单,主要是初中的几何知识几乎都忘光了,解方程还算了半天。不胜唏嘘。
另外,MoveInstance类的onTweenUpdate()方法中,有这么一行代码:EffectManager.suspendEventHandling(); 这行代码屏蔽了move,需要注意一下。