返回“Flash基础理论课 - 目录”
缓动与弹性运动
在3D中的缓动与弹性运动不会比2D中的难多少(第八章的课题),
Flash基础理论课 第十五章 3D基础 Ⅲ
。我们只需要为z轴再加入一至两个变量。缓动
对于缓动的介绍不算很多。在2D中,我们用tx和ty最为目标点。现在只需要再在z轴上加入tz。每帧计算物体每个轴到目标点的距离,并移动一段距离。
让我们来看一个简单的例子,让物体缓动运动到随机的目标点,到达该点后,再选出另一个目标并让物体移动过去。注意后面两个例子,我们又回到了 Ball3D 这个类上。以下是代码(可以在Easing3D.as中找到):
package {
import flash.display.Sprite;
import flash.events.Event;
public class Easing3D extends Sprite {
private var ball:Ball3D;
private var tx:Number;
private var ty:Number;
private var tz:Number;
private var easing:Number = .1;
private var fl:Number = 250;
private var vpX:Number = stage.stageWidth / 2;
private var vpY:Number = stage.stageHeight / 2;
public function Easing3D() {
init();
}
private function init():void {
ball = new Ball3D();
addChild(ball);
tx = Math.random() * 500 - 250;
ty = Math.random() * 500 - 250;
tz = Math.random() * 500;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
var dx:Number = tx - ball.xpos;
var dy:Number = ty - ball.ypos;
var dz:Number = tz - ball.zpos;
ball.xpos += dx * easing;
ball.ypos += dy * easing;
ball.zpos += dz * easing;
var dist:Number = Math.sqrt(dx*dx + dy*dy + dz*dz);
if (dist < 1) {
tx = Math.random() * 500 - 250;
ty = Math.random() * 500 - 250;
tz = Math.random() * 500;
}
if (ball.zpos > -fl) {
var scale:Number = fl / (fl + ball.zpos);
ball.scaleX = ball.scaleY = scale;
ball.x = vpX + ball.xpos * scale;
ball.y = vpY + ball.ypos * scale;
ball.visible = true;
} else {
ball.visible = false;
}
}
}
}
代码中最有趣的地方是下面这行:
var dist:Number = Math.sqrt(dx * dx + dy * dy + dz * dz);
我们知道,在2D中计算两点间距离的方程是:
var dist:Number = Math.sqrt(dx * dx + dy * dy);
在3D 距离中,只需要将第三个轴距离的平方加入进去。由于这个公式过于简单所以我常常会受到质疑。在加入了一个条件后,似乎应该使用立方根。但是它并不是用在这里的。
弹性运动
弹性运动是缓动的兄弟,需用相似的方法将其调整为3D 的。我们只使用物体到目标的距离改变速度,而不是改变位置。给大家一个快速的示例。本例中(Spring3D.as),点击鼠标将创建出一个随机的目标点。
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
public class Spring3D extends Sprite {
private var ball:Ball3D;
private var tx:Number;
private var ty:Number;
private var tz:Number;
private var spring:Number = .1;
private var friction:Number = .94;
private var fl:Number = 250;
private var vpX:Number = stage.stageWidth / 2;
private var vpY:Number = stage.stageHeight / 2;
public function Spring3D() {
init();
}
private function init():void {
ball = new Ball3D();
addChild(ball);
tx = Math.random() * 500 - 250;
ty = Math.random() * 500 - 250;
tz = Math.random() * 500;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
private function onEnterFrame(event:Event):void {
var dx:Number = tx - ball.xpos;
var dy:Number = ty - ball.ypos;
var dz:Number = tz - ball.zpos;
ball.vx += dx * spring;
ball.vy += dy * spring;
ball.vz += dz * spring;
ball.xpos += ball.vx;
ball.ypos += ball.vy;
ball.zpos += ball.vz;
ball.vx *= friction;
ball.vy *= friction;
ball.vz *= friction;
if (ball.zpos > -fl) {
var scale:Number = fl / (fl + ball.zpos);
ball.scaleX = ball.scaleY = scale;
ball.x = vpX + ball.xpos * scale;
ball.y = vpY + ball.ypos * scale;
ball.visible = true;
} else {
ball.visible = false;
}
}
private function onMouseDown(event:MouseEvent):void {
tx = Math.random() * 500 - 250;
ty = Math.random() * 500 - 250;
tz = Math.random() * 500;
}
}
}
我们看到,在第三个轴上使用了基本的弹性运动公式(来自第八章)。
坐标旋转
接下来是3D坐标旋转。这里要比第十和十一章中的2D坐标旋转要稍微复杂一些。我们不仅可以在三个不同的轴上旋转,还可以同时在两个以上的轴上进行旋转。
在2D坐标旋转中物体是绕着 z轴旋转的,如图 15-9 所示。想一想命运之轮(Wheel of Fortune)这类游戏——轮轴从轮盘的中心穿过。轮轴就是z轴。只改变x和y坐标。
图15-9 z轴旋转
在3D中,我们也可以在y 或 z轴上旋转。x轴的旋转如同车轮向前滚动,如图 15-10 所示。这时轮轴就是x轴。只改变y和z 上的点。
图15-10 x轴旋转
y轴上的旋转,请想象一下老的唱片机,如图 15-11 所示。轴心是y轴。只改变x和z 上的点。
图15-11 y轴旋转
因此,对于3D 而言,当我们在某个轴上旋转一个物体时,将改变两个轴上的位置。回顾一下第十章,我们找到 2D 旋转的公式如下:
x1 = cos(angle) * x - sin(angle) * y;
y1 = cos(angle) * y + sin(angle) * x;
在3D中,方法几乎相同,但是需要指定操作的是哪个轴:x, y, z。因此得到下面三个公式:
x1 = cos(angleZ) * x - sin(angleZ) * y;
y1 = cos(angleZ) * y + sin(angleZ) * x;
x1 = cos(angleY) * x - sin(angleY) * z;
z1 = cos(angleY) * z + sin(angleY) * x;
y1 = cos(angleX) * y - sin(angleX) * z;
z1 = cos(angleX) * z + sin(angleX) * y;
下面,试一下 y轴的旋转。以下代码可在RotateY.as中找到。创建 50 个 Ball3D 的实例,随机放置舞台上。根据鼠标 x轴上的位置计算出 y轴角度。鼠标越向右,角度值越高。物体就像跟着鼠标旋转一样。
package {
import flash.display.Sprite;
import flash.events.Event;
public class RotateY extends Sprite {
private var balls:Array;
private var numBalls:uint = 50;
private var fl:Number = 250;
private var vpX:Number = stage.stageWidth / 2;
private var vpY:Number = stage.stageHeight / 2;
public function RotateY() {
init();
}
private function init():void {
balls = new Array();
for (var i:uint = 0; i < numBalls; i++) {
var ball:Ball3D = new Ball3D(15);
balls.push(ball);
ball.xpos = Math.random() * 200 - 100;
ball.ypos = Math.random() * 200 - 100;
ball.zpos = Math.random() * 200 - 100;
addChild(ball);
}
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
var angleY:Number = (mouseX - vpX) * .001;
for (var i:uint = 0; i < numBalls; i++) {
var ball:Ball3D = balls[i];
rotateY(ball, angleY);
}
sortZ();
}
private function rotateY(ball:Ball3D, angleY:Number):void {
var cosY:Number = Math.cos(angleY);
var sinY:Number = Math.sin(angleY);
var x1:Number = ball.xpos * cosY - ball.zpos * sinY;
var z1:Number = ball.zpos * cosY + ball.xpos * sinY;
ball.xpos = x1;
ball.zpos = z1;
if (ball.zpos > -fl) {
var scale:Number = fl / (fl + ball.zpos);
ball.scaleX = ball.scaleY = scale;
ball.x = vpX + ball.xpos * scale;
ball.y = vpY + ball.ypos * scale;
ball.visible = true;
} else {
ball.visible = false;
}
}
private function sortZ():void {
balls.sortOn("zpos", Array.DESCENDING | Array.NUMERIC);
for (var i:uint = 0; i < numBalls; i++) {
var ball:Ball3D = balls[i];
setChildIndex(ball, i);
}
}
}
}
重要的部分加粗表示。获得一个角度,然后使用该角度调用rotateY 的方法。该方法中,获得角度的正弦和余弦值,执行旋转,将 x1和z1 再赋值给 ball.xpos和ball.zpos。接下来,就是标准透视以及 z 排序。运行结果如图 15-12 所示。
图15-12 y轴旋转
如果这个程序没问题,大家一定也可以将其转换为x轴的旋转。只需要改变onEnterFrame. 方法并加入rotateX 方法:
private function onEnterFrame(event:Event):void {
var angleX:Number = (mouseY - vpY) * .001;
for (var i:uint = 0; i < numBalls; i++) {
var ball:Ball3D = balls[i];
rotateX(ball, angleX);
}
sortZ();
}
private function rotateX(ball:Ball3D, angleX:Number):void {
var cosX:Number = Math.cos(angleX);
var sinX:Number = Math.sin(angleX);
var y1:Number = ball.ypos * cosX - ball.zpos * sinX;
var z1:Number = ball.zpos * cosX + ball.ypos * sinX;
ball.ypos = y1;
ball.zpos = z1;
if (ball.zpos > -fl) {
var scale:Number = fl / (fl + ball.zpos);
ball.scaleX = ball.scaleY = scale;
ball.x = vpX + ball.xpos * scale;
ball.y = vpY + ball.ypos * scale;
ball.visible = true;
} else {
ball.visible = false;
}
}
现在,angleX 是根据鼠标的y坐标确定的,
电脑资料
《Flash基础理论课 第十五章 3D基础 Ⅲ》(https://www.unjs.com)。然后计算出正余弦的值,并使用它们算出 y1和z1,再赋值给 ypos和zpos 属性。接下来,我们将两种旋转组合起来。以下是代码(可以在RotateXY.as中找到):
package {
import flash.display.Sprite;
import flash.events.Event;
public class RotateXY extends Sprite {
private var balls:Array;
private var numBalls:uint = 50;
private var fl:Number = 250;
private var vpX:Number = stage.stageWidth / 2;
private var vpY:Number = stage.stageHeight / 2;
public function RotateXY() {
init();
}
private function init():void {
balls = new Array();
for (var i:uint = 0; i < numBalls; i++) {
var ball:Ball3D = new Ball3D(15);
balls.push(ball);
ball.xpos = Math.random() * 200 - 100;
ball.ypos = Math.random() * 200 - 100;
ball.zpos = Math.random() * 200 - 100;
addChild(ball);
}
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
var angleX:Number = (mouseY - vpY) * .001;
var angleY:Number = (mouseX - vpX) * .001;
for (var i:uint = 0; i < numBalls; i++) {
var ball:Ball3D = balls[i];
rotateX(ball, angleX);
rotateY(ball, angleY);
doPerspective(ball);
}
sortZ();
}
private function rotateX(ball:Ball3D, angleX:Number):void {
var cosX:Number = Math.cos(angleX);
var sinX:Number = Math.sin(angleX);
var y1:Number = ball.ypos * cosX - ball.zpos * sinX;
var z1:Number = ball.zpos * cosX + ball.ypos * sinX;
ball.ypos = y1;
ball.zpos = z1;
}
private function rotateY(ball:Ball3D, angleY:Number):void {
var cosY:Number = Math.cos(angleY);
var sinY:Number = Math.sin(angleY);
var x1:Number = ball.xpos * cosY - ball.zpos * sinY;
var z1:Number = ball.zpos * cosY + ball.xpos * sinY;
ball.xpos = x1;
ball.zpos = z1;
}
private function doPerspective(ball:Ball3D):void {
if (ball.zpos > -fl) {
var scale:Number = fl / (fl + ball.zpos);
ball.scaleX = ball.scaleY = scale;
ball.x = vpX + ball.xpos * scale;
ball.y = vpY + ball.ypos * scale;
ball.visible = true;
} else {
ball.visible = false;
}
}
private function sortZ():void {
balls.sortOn("zpos", Array.DESCENDING | Array.NUMERIC);
for (var i:uint = 0; i < numBalls; i++) {
var ball:Ball3D = balls[i];
setChildIndex(ball, i);
}
}
}
}
与前一个例子相比主要的变化用粗体表示。现在,我们计算出 angleY, angleX ,并调用rotateX, rotateY。注意,我将透视的代码从 rotate 方法中分离到一个单独的方法中,因为它不需要被调用两次。我相信根据前面的公式,您一定可以自行加入rotateZ 方法。
测试一下这个影片。通过将 3D坐标旋转与前一节赛车游戏的“屏幕环绕”的概念相结合,大家一定还可以创建出丰富的,交互性的3D 环境。
碰撞检测
本章的最后我要给大家介绍一下 3D 的碰撞检测。在Flash 的三维空间中唯一可行的方法就是距离碰撞检测。找出两个物体的距离(使用3D 距离公式),如果小于两个物体的半径之和,就产生了碰撞。
我将前面 3D 反弹的例子改成了 3D 碰撞检测的例子,加入了少量的物体并扩大了一些空间。代码中首先执行普通的3D 运动及透视,然后做一个双重循环比较所有小球的位置。如果有两个物体的距离小于它们的半径之和,就使用颜色转换代码将它们都设置为蓝色。非常简单。以下是代码(Collision3D.as):
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.ColorTransform;
public class Collision3D extends Sprite {
private var balls:Array;
private var numBalls:uint = 20;
private var fl:Number = 250;
private var vpX:Number = stage.stageWidth / 2;
private var vpY:Number = stage.stageHeight / 2;
private var top:Number = -200;
private var bottom:Number = 200;
private var left:Number = -200;
private var right:Number = 200;
private var front:Number = 200;
private var back:Number = -200;
public function Collision3D() {
init();
}
private function init():void {
balls = new Array();
for (var i:uint = 0; i < numBalls; i++) {
var ball:Ball3D = new Ball3D(15);
balls.push(ball);
ball.xpos = Math.random() * 400 - 200;
ball.ypos = Math.random() * 400 - 200;
ball.zpos = Math.random() * 400 - 200;
ball.vx = Math.random() * 10 - 5;
ball.vy = Math.random() * 10 - 5;
ball.vz = Math.random() * 10 - 5;
addChild(ball);
}
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
for (var i:uint = 0; i < numBalls; i++) {
var ball:Ball3D = balls[i];
move(ball);
}
for (i = 0; i < numBalls - 1; i++) {
var ballA:Ball3D = balls[i];
for (var j:uint = i + 1; j < numBalls; j++) {
var ballB:Ball3D = balls[j];
var dx:Number = ballA.xpos - ballB.xpos;
var dy:Number = ballA.ypos - ballB.ypos;
var dz:Number = ballA.zpos - ballB.zpos;
var dist:Number = Math.sqrt(dx*dx + dy*dy + dz*dz);
if (dist < ballA.radius + ballB.radius) {
var blueTransform.:ColorTransform. =
new ColorTransform(0, 1, 1, 1, 0, 0, 255, 0);
ballA.transform.colorTransform. = blueTransform;
ballB.transform.colorTransform. = blueTransform;
}
}
}
sortZ();
}
private function move(ball:Ball3D):void {
var radius:Number = ball.radius;
ball.xpos += ball.vx;
ball.ypos += ball.vy;
ball.zpos += ball.vz;
if (ball.xpos + radius > right) {
ball.xpos = right - radius;
ball.vx *= -1;
} else if (ball.xpos - radius < left) {
ball.xpos = left + radius;
ball.vx *= -1;
}
if (ball.ypos + radius > bottom) {
ball.ypos = bottom - radius;
ball.vy *= -1;
} else if (ball.ypos - radius < top) {
ball.ypos = top + radius;
ball.vy *= -1;
}
if (ball.zpos + radius > front) {
ball.zpos = front - radius;
ball.vz *= -1;
} else if (ball.zpos - radius < back) {
ball.zpos = back + radius;
ball.vz *= -1;
}
if (ball.zpos > -fl) {
var scale:Number = fl / (fl + ball.zpos);
ball.scaleX = ball.scaleY = scale;
ball.x = vpX + ball.xpos * scale;
ball.y = vpY + ball.ypos * scale;
ball.visible = true;
} else {
ball.visible = false;
}
}
private function sortZ():void {
balls.sortOn("zpos", Array.DESCENDING | Array.NUMERIC);
for (var i:uint = 0; i < numBalls; i++) {
var ball:Ball3D = balls[i];
setChildIndex(ball, i);
}
}
}
}
重要的部分用粗体表示。小球起初都是红色的,当它们碰撞后,颜色发生改变。最后,全部变为蓝色。
本章重要公式
本章的重要公式是3D 透视,坐标旋转以及距离的计算。
基本透视法:
scale = fl / (fl + zpos);
sprite.scaleX = sprite.scaleY = scale;
sprite.alpha = scale; // 可选
sprite.x = vanishingPointX + xpos * scale;
sprite.y = vanishingPointY + ypos * scale;
Z 排序:
// 假设有一个带有 zpos 属性的3D 物体的数组
objectArray.sortOn("zpos", Array.DESCENDING | Array.NUMERIC);
for(var i:uint = 0; i < numObjects; i++) {
setChildIndex(objectArray[i], i);
}
坐标旋转:
x1 = cos(angleZ) * xpos - sin(angleZ) * ypos;
y1 = cos(angleZ) * ypos + sin(angleZ) * xpos;
x1 = cos(angleY) * xpos - sin(angleY) * zpos;
z1 = cos(angleY) * zpos + sin(angleY) * xpos;
y1 = cos(angleX) * ypos - sin(angleX) * zpos;
z1 = cos(angleX) * zpos + sin(angleX) * ypos;
3D 距离:
dist = Math.sqrt(dx * dx + dy * dy + dz * dz);