返回“Flash基礎(chǔ)理論課 - 目錄”
彈簧鏈
下面我們將幾個(gè)彈性小球串聯(lián)起來(lái)。在介紹緩動(dòng)一節(jié)時(shí),我們簡(jiǎn)單地討論了鼠標(biāo)跟隨的概念,意思是說一個(gè)物體跟隨鼠標(biāo),另一個(gè)物體再跟隨這個(gè)物體,依此類推。當(dāng)時(shí)沒有給大家舉例子,是因?yàn)檫@個(gè)效果現(xiàn)在看來(lái)有些遜色。但是,當(dāng)我們?cè)趶椥赃\(yùn)動(dòng)中使用這個(gè)概念時(shí),效果就截然不同了。
本程序的設(shè)計(jì)思想:創(chuàng)建三個(gè)小球,名為ball0,ball1,ball2。第一個(gè)小球,ball0的動(dòng)作與上面例子中的效果是相同的。ball1向ball0 運(yùn)動(dòng),ball2向ball1 運(yùn)動(dòng)。每個(gè)小球都受到重力的影響,所以它們都會(huì)向下墜。代碼稍有些復(fù)雜,文檔類 Chain.as:
package {
import flash.display.Sprite;
import flash.events.Event;
public class Chain extends Sprite {
private var ball0:Ball;
private var ball1:Ball;
private var ball2:Ball;
private var spring:Number = 0.1;
private var friction:Number = 0.8;
private var gravity:Number = 5;
public function Chain() {
init();
}
private function init():void {
ball0 = new Ball(20);
addChild(ball0);
ball1 = new Ball(20);
addChild(ball1);
ball2 = new Ball(20);
addChild(ball2);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(event:Event):void {
moveBall(ball0,mouseX,mouseY);
moveBall(ball1,ball0.x,ball0.y);
moveBall(ball2,ball1.x,ball1.y);
graphics.clear();
graphics.lineStyle(1);
graphics.moveTo(mouseX,mouseY);
graphics.lineTo(ball0.x,ball0.y);
graphics.lineTo(ball1.x,ball1.y);
graphics.lineTo(ball2.x,ball2.y);
}
private function moveBall(ball:Ball,targetX:Number,targetY:Number):void {
ball.vx += (targetX - ball.x) * spring;
ball.vy += (targetY - ball.y) * spring;
ball.vy += gravity;
ball.vx *= friction;
ball.vy *= friction;
ball.x += ball.vx;
ball.y += ball.vy;
}
}
}
看一下 Ball 這個(gè)類,我們發(fā)現(xiàn)每個(gè)對(duì)象實(shí)例都有自己 vx 和 vy 屬性,并且它們的初始值均為0。所以在init方法中,我們只需要?jiǎng)?chuàng)建小球并把它們加入顯示列表。
然后在onEnterFrame函數(shù)中,實(shí)現(xiàn)彈性運(yùn)動(dòng)。這里我們調(diào)用了 moveBall方法,比復(fù)制三次運(yùn)動(dòng)代碼要好用得多。該函數(shù)的參數(shù)分別為一個(gè) ball 對(duì)象以及目標(biāo)點(diǎn)的 x,y 坐標(biāo)。每個(gè)小球都調(diào)用這個(gè)函數(shù),第一個(gè)小球以鼠標(biāo)的 x,y作為目標(biāo)位置,第二第三個(gè)小球以第一第二個(gè)小球作為目標(biāo)位置。
最后,在確定了所有小球的位置后,開始畫線,畫線的起點(diǎn)是鼠標(biāo)位置,然后依次畫到每個(gè)小球上,這樣橡皮圈就連接上了所有的小球。注意,程序中的 friction 降為0.8 為了使小球能夠很快穩(wěn)定下來(lái)。
創(chuàng)建一個(gè)數(shù)組保存鏈中所有對(duì)象的引用,然后通過循環(huán)遍歷數(shù)組中的每個(gè)小球并執(zhí)行運(yùn)動(dòng),使這個(gè)程序更加靈活。這里只需要做一些小小的改變。首先,需要兩個(gè)新的變量代表數(shù)組和對(duì)象數(shù)目:
private var balls:Array;
private var numBalls:Number = 5;
在函數(shù) init 中,使用for循環(huán)創(chuàng)建所有對(duì)象,并將對(duì)象引用加入數(shù)組:
private function init():void {
balls = new Array();
for(var i:uint = 0; i < numBalls; i++) {
var ball:Ball = new Ball(20);
addChild(ball);
balls.push(ball);
}
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
最后,onEnterFrame方法的變化最大。首先設(shè)置線條,將繪圖起點(diǎn)移動(dòng)到鼠標(biāo)位置,再到第一個(gè)小球,然后循環(huán)為剩下的小球設(shè)置位置并連線。通過改變 numBalls 變量,我們可以加入任意多個(gè)小球。
private function onEnterFrame(event:Event):void {
graphics.clear();
graphics.lineStyle(1);
graphics.moveTo(mouseX,mouseY);
moveBall(balls[0],mouseX,mouseY);
graphics.lineTo(balls[0].x,balls[0].y);
for(var i:uint = 1; i < numBalls; i++) {
var ballA:Ball = balls[i-1];
var ballB:Ball = balls[i];
moveBall(ballB,ballA.x,ballA.y);
graphics.lineTo(ballB.x,ballB.y);
}
}
運(yùn)行結(jié)果見圖 8-3,大家可以在ChainArray.as找到這個(gè)類。
圖8-3 彈簧鏈
多目標(biāo)彈性運(yùn)動(dòng)
我們?cè)诘谖逭掠懻撍俣扰c加速度時(shí),曾說過如何使一個(gè)物體受到多種外力。如果每種力都是加速度,我們只需要把它們一個(gè)個(gè)都加到速度向量中去。因?yàn)閺椓Σ贿^就是施加在物體上的一種加速度,因此在一個(gè)物體上添加多種彈力也是非常容易的。
下面是創(chuàng)建多目標(biāo)彈簧的方法:我們需要三個(gè)控制點(diǎn),這些點(diǎn)都是 Ball 類的實(shí)例,并且具有簡(jiǎn)單的拖拽功能,用它們作為小球彈性運(yùn)動(dòng)的控制點(diǎn)。小球會(huì)立即運(yùn)動(dòng)到點(diǎn),并在兩點(diǎn)間尋找平衡。換句話講,每個(gè)目標(biāo)都會(huì)對(duì)小球施加一定的外力,小球的運(yùn)動(dòng)速度就是這些外力相加的結(jié)果。
例子程序相當(dāng)復(fù)雜,使用多個(gè)方法處理不同的事件。以下是代碼(文檔類 MultiSpring.as),看過后再進(jìn)行分段講解:
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class MultiSpring extends Sprite {
private var ball:Ball;
private var handles:Array;
private var spring:Number = 0.1;
private var friction:Number = 0.8;
private var numHandles:Number = 3;
public function MultiSpring() {
init();
}
private function init():void {
ball = new Ball(20);
addChild(ball);
handles = new Array();
for (var i:uint = 0; i < numHandles; i++) {
var handle:Ball = new Ball(10,0x0000ff);
handle.x = Math.random() * stage.stageWidth;
handle.y = Math.random() * stage.stageHeight;
handle.addEventListener(MouseEvent.MOUSE_DOWN,onPress);
addChild(handle);
handles.push(handle);
}
addEventListener(Event.ENTER_FRAME,onEnterFrame);
addEventListener(MouseEvent.MOUSE_UP,onRelease);
}
private function onEnterFrame(event:Event):void {
for (var i:uint = 0; i < numHandles; i++) {
var handle:Ball = handles[i] as Ball;
var dx:Number = handle.x - ball.x;
var dy:Number = handle.y - ball.y;
ball.vx += dx * spring;
ball.vy += dy * spring;
}
ball.vx *= friction;
ball.vy *= friction;
ball.x += ball.vx;
ball.y += ball.vy;
graphics.clear();
graphics.lineStyle(1);
for (i = 0; i < numHandles; i++) {
graphics.moveTo(ball.x,ball.y);
graphics.lineTo(handles[i].x,handles[i].y);
}
}
private function onPress(event:MouseEvent):void {
event.target.startDrag();
}
private function onRelease(event:MouseEvent):void {
stopDrag();
}
}
}
在init方法中,創(chuàng)建小球并用for循環(huán)創(chuàng)建三個(gè)控制點(diǎn),隨機(jī)安排位置,并為它們?cè)O(shè)置拖拽行為。
onEnterFrame方法循環(huán)取出每個(gè)控制點(diǎn),使小球向該點(diǎn)方向運(yùn)動(dòng)。然后,用控制點(diǎn)的坐標(biāo)設(shè)置小球的速度,反復(fù)循環(huán),從小球開始向各個(gè)控制點(diǎn)畫線。onPress方法的內(nèi)容非常簡(jiǎn)單,但是請(qǐng)注意 onRelease函數(shù),我們無(wú)法知道當(dāng)前拖拽的是哪個(gè)小球。幸運(yùn)的是,使用任何一個(gè)顯示調(diào)用stopDrag方法,都可以停止所有的拖拽,所以只需要在文檔類中直接調(diào)用該方法。
我們只要改變 numHandles 變量的值,就可以輕松地設(shè)置控制點(diǎn)的數(shù)量。運(yùn)行結(jié)果如圖 8-4所示。
圖8-4 多目標(biāo)彈性
到目前為止,我相信大家已經(jīng)有了很多的心得與體會(huì),并且開始嘗試解決一些書中沒有提到的問題。如果真是這樣的話,那就太好了!這也正是我寫這本書的目的。
目標(biāo)偏移
我們拿到一個(gè)真正的彈簧——有彈性的金屬圈——然后將它的一頭固定起來(lái),另一頭放上小球或其它物體,那么物體運(yùn)動(dòng)的目標(biāo)點(diǎn)是哪里?難道說目標(biāo)點(diǎn)是固定彈簧的那頭兒?不,這并不實(shí)際。小球永遠(yuǎn)也到不了這個(gè)點(diǎn),因?yàn)樗鼤?huì)受到彈簧自身的阻礙。一旦彈簧變回了正常的長(zhǎng)度,它會(huì)對(duì)小球施加更大的力。因此,目標(biāo)點(diǎn)就應(yīng)該是彈簧展開后的末端。
要尋找目標(biāo)點(diǎn),首先要找到物體與固定點(diǎn)之間的夾角,然后沿這個(gè)角度從固定點(diǎn)向外展開一段長(zhǎng)度——彈簧的長(zhǎng)度。換句話講,如果彈簧長(zhǎng)度是 50,小球與固定點(diǎn)的夾角是 45 度的話,那么就要以 45 度的夾角向外運(yùn)動(dòng) 50 個(gè)像素,而這個(gè)點(diǎn)就是小球的目標(biāo)點(diǎn)。圖8-5 解釋了這一過程。
圖8-5 彈簧復(fù)位
尋找目標(biāo)點(diǎn)的代碼如下:
var dx:Number = ball.x - fixedX;
var dy:Number = ball.y - fixedY;
var angle:Number = Math.atan2(dy,dx);
var targetX:Number = fixedX + Math.cos(angle) * springLength;
var targetY:Number = fixedY + Math.sin(angle) * springLength;
運(yùn)行結(jié)果是,物體向著固定點(diǎn)運(yùn)動(dòng),但會(huì)在與目標(biāo)點(diǎn)相差一段距離時(shí)停止移動(dòng)。大家還要注意,雖然我們叫它“固定點(diǎn)”,只是代表彈簧固定到的某個(gè)點(diǎn)。而不是指這個(gè)點(diǎn)不能移動(dòng)。也許最好的方法就是看代碼。
我們繼續(xù)使用鼠標(biāo)位置作為固定點(diǎn),彈簧的長(zhǎng)度為100 像素。以下是文檔類(OffsetSpring.as):
package {
import flash.display.Sprite;
import flash.events.Event;
public class OffsetSpring extends Sprite {
private var ball:Ball;
private var spring:Number = 0.1;
private var vx:Number = 0;
private var vy:Number = 0;
private var friction:Number = 0.95;
private var springLength:Number = 100;
public function OffsetSpring() {
init();
}
private function init():void {
ball = new Ball(20);
addChild(ball);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(event:Event):void {
var dx:Number = ball.x - mouseX;
var dy:Number = ball.y - mouseY;
var angle:Number = Math.atan2(dy,dx);
var targetX:Number = mouseX + Math.cos(angle) * springLength;
var targetY:Number = mouseY + Math.sin(angle) * springLength;
vx += (targetX - ball.x) * spring;
vy += (targetY - ball.y) * spring;
vx *= friction;
vy *= friction;
ball.x += vx;
ball.y += vy;
graphics.clear();
graphics.lineStyle(1);
graphics.moveTo(ball.x,ball.y);
graphics.lineTo(mouseX,mouseY);
}
}
}
雖然我們能夠看到運(yùn)行結(jié)果,但卻不能真正發(fā)現(xiàn)這項(xiàng)技術(shù)的特殊用處。沒關(guān)系,下一節(jié)會(huì)給大家一個(gè)特別的例子。
彈簧連接多個(gè)物體
我們知道如何用彈簧連接兩個(gè)物體,還知道這個(gè)點(diǎn)不是固定的。但是,如果另一個(gè)物體上還有一個(gè)彈簧反作用在第一個(gè)物體上,又是怎樣的呢?這里有兩個(gè)物體之間由一根彈簧連接。其中一個(gè)運(yùn)動(dòng)了,另一個(gè)物體就要向該物體移動(dòng)過來(lái)。
我開始認(rèn)為制作這種效果會(huì)導(dǎo)致死循環(huán)從而無(wú)法實(shí)現(xiàn),或者至少會(huì)引起錯(cuò)誤。但我也沒管那么多,勇敢地進(jìn)行嘗試。結(jié)果非常完美!
雖然前面已經(jīng)描述了一些策略,但這里還要細(xì)致得說一下:物體A以物體B作為目標(biāo),并向它移動(dòng)。物體B 反過來(lái)又以物體A作為目標(biāo)。事實(shí)上,本例中目標(biāo)偏移起了重要的作用。如果一個(gè)物體以其它物體直接作為目標(biāo),那么它們之就會(huì)相互吸引,最終聚集在一個(gè)點(diǎn)上。通過使用偏移目標(biāo),我們就可以使它們之間保持距離,如圖 8-6所示。
圖8-6 彈簧連接的兩個(gè)物體
下面舉一個(gè)例子,我們需要兩個(gè) Ball 類的實(shí)例。分別為ball0 和 ball1。ball0向ball1 偏移運(yùn)動(dòng)。ball1向ball0 偏移運(yùn)動(dòng)。為了不去反復(fù)寫偏移彈性運(yùn)動(dòng)的代碼,我們將這些功能寫到函數(shù) springTo 中,直接調(diào)用函數(shù)即可。如果想讓 ball0向ball1 運(yùn)動(dòng),只要寫 springTo(ball0,ball1),然后再讓 ball1向ball0 運(yùn)動(dòng),就寫 springTo(ball1,ball0)。還要設(shè)置兩個(gè)變量,ball0Dragging 和 ball1Dragging,作為每個(gè)小球運(yùn)動(dòng)的開關(guān)。以下是文檔類(DoubleSpring.as):
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class DoubleSpring extends Sprite {
private var ball0:Ball;
private var ball1:Ball;
private var ball0Dragging:Boolean = false;
private var ball1Dragging:Boolean = false;
private var spring:Number = 0.1;
private var friction:Number = 0.95;
private var springLength:Number = 100;
public function DoubleSpring() {
init();
}
private function init():void {
ball0 = new Ball(20);
ball0.x = Math.random() * stage.stageWidth;
ball0.y = Math.random() * stage.stageHeight;
ball0.addEventListener(MouseEvent.MOUSE_DOWN,onPress);
addChild(ball0);
ball1 = new Ball(20);
ball1.x = Math.random() * stage.stageWidth;
ball1.y = Math.random() * stage.stageHeight;
ball1.addEventListener(MouseEvent.MOUSE_DOWN,onPress);
addChild(ball1);
addEventListener(Event.ENTER_FRAME,onEnterFrame);
stage.addEventListener(MouseEvent.MOUSE_UP,onRelease);
}
private function onEnterFrame(event:Event):void {
if (!ball0Dragging) {
springTo(ball0,ball1);
}
if (!ball1Dragging) {
springTo(ball1,ball0);
}
graphics.clear();
graphics.lineStyle(1);
graphics.moveTo(ball0.x,ball0.y);
graphics.lineTo(ball1.x,ball1.y);
}
private function springTo(ballA:Ball,ballB:Ball):void {
var dx:Number = ballB.x - ballA.x;
var dy:Number = ballB.y - ballA.y;
var angle:Number = Math.atan2(dy,dx);
var targetX:Number = ballB.x - Math.cos(angle) * springLength;
var targetY:Number = ballB.y - Math.sin(angle) * springLength;
ballA.vx += (targetX - ballA.x) * spring;
ballA.vy += (targetY - ballA.y) * spring;
ballA.vx *= friction;
ballA.vy *= friction;
ballA.x += ballA.vx;
ballA.y += ballA.vy;
}
private function onPress(event:MouseEvent):void {
event.target.startDrag();
if (event.target == ball0) {
ball0Dragging = true;
}
if (event.target == ball1) {
ball1Dragging = true;
}
}
private function onRelease(event:MouseEvent):void {
ball0.stopDrag();
ball1.stopDrag();
ball0Dragging = false;
ball1Dragging = false;
}
}
}
本例中,每個(gè)小球都是可以拖拽的。enterFrame函數(shù)負(fù)責(zé)為小球調(diào)用springTo函數(shù)。請(qǐng)注意,這兩條語(yǔ)句都是由兩條判斷語(yǔ)句包圍起來(lái)的,目的是要確認(rèn)小球目前沒被拖拽:
springTo(ball0,ball1);
springTo(ball1,ball0);
springTo函數(shù)用于產(chǎn)生運(yùn)動(dòng),函數(shù)中的所有語(yǔ)句大家應(yīng)該都很熟悉。首先,求出距離和角度,再計(jì)算目標(biāo)點(diǎn),然后向目標(biāo)點(diǎn)運(yùn)動(dòng)。第二次調(diào)用函數(shù)時(shí),參數(shù)相反,兩個(gè)小球交換位置,開始的小球向另一個(gè)小球運(yùn)動(dòng)。這也許不是效率最高的代碼,但是它可以最好地表現(xiàn)出運(yùn)動(dòng)的過程。
我們看到,小球不會(huì)依附在任何固定點(diǎn)上,它們都是自由飄浮的。小球之間唯一的約束就是彼此保持一定的距離。這種寫法最好的地方是可以很容易地加入新的物體。例如,再創(chuàng)建第三個(gè)小球(ball2),同時(shí)為它設(shè)置一個(gè)變量(ball2Dragging),就可以這么添加:
if(!ball0Dragging) {
springTo(ball0,ball1);
springTo(ball0,ball2);
}
if(!ball1Dragging) {
springTo(ball1,ball0);
springTo(ball1,ball2);
}
if(!ball2Dragging) {
springTo(ball2,ball0);
springTo(ball2,ball1);
}
這樣就建立了一個(gè)三角形結(jié)構(gòu),如圖 8-7所示。大家熟練掌握后,很快就能做出四邊形結(jié)構(gòu),直到一切復(fù)雜的彈簧結(jié)構(gòu)。
圖8-7 一根彈簧連接三個(gè)物體
本章重要公式總結(jié)
現(xiàn)在來(lái)回顧一下本章的重要公式
簡(jiǎn)單緩動(dòng),長(zhǎng)形:
var dx:Number = targetX - sprite.x;
var dy:Number = targetY - sprite.y;
vx = dx * easing;
vy = dy * easing;
sprite.x += vx;
sprite.y += vy;
簡(jiǎn)單緩動(dòng),中形:
vx = (targetX - sprite.x) * easing;
vy = (targetY - sprite.y) * easing;
sprite.x += vx;
sprite.y += vy;
簡(jiǎn)單緩動(dòng),短形:
sprite.x += (targetX - sprite.x) * easing;
sprite.y += (targetY - sprite.y) * easing;
簡(jiǎn)單彈性,長(zhǎng)形:
var ax:Number = (targetX - sprite.x) * spring;
var ay:Number = (targetY - sprite.y) * spring;
vx += ax;
vy += ay;
vx *= friction;
vy *= friction;
sprite.x += vx;
sprite.y += vy;
簡(jiǎn)單彈性,中形:
vx += (targetX - sprite.x) * spring;
vy += (targetY - sprite.y) * spring;
vx *= friction;
vy *= friction;
sprite.x += vx;
sprite.y += vy;
簡(jiǎn)單彈性,短形:
vx += (targetX - sprite.x) * spring;
vy += (targetY - sprite.y) * spring;
sprite.x += (vx *= friction);
sprite.y += (vy *= friction);
偏移彈性運(yùn)動(dòng):
var dx:Number = sprite.x - fixedX;
var dy:Number = sprite.y - fixedY;
var angle:Number = Math.atan2(dy,dx);
var targetX:Number = fixedX + Math.cos(angle) * springLength;
var targetY:Number = fixedX + Math.sin(angle) * springLength;
聯(lián)系客服