转 第十三章 正向运动学:行走(2)(as3.0)
2011-10-12 20:16
302 查看
动态调整
下面,让我们来真正体验一下在循环行走中改变各个值所带来的不一样的效果。
Walking5.as 又将 Slider 滑块类加入进来,用于动态改变这些变量。
本例中,在屏幕上创建并放置了五个滑块,如图 13-6 所示。
图 13-6 加入滑块
表 13-1 所示,滑块的名字(从左到右),功能以及设置。这些都是我认为比较合理的
范围和取值。总之,我们可以任意地实验其它的取值。
表 13-1 控制行走的滑块
实例 描述
设置
控制整个系统的运动速度。
最小值: 0,
speedSlider
最大值: 0.3,
[速度滑块
默认值: 0.12
控制顶层关节(大腿)能够向前和向后移动多远。 最小值: 0,
thighRangeSlider
最大值: 90,
[大腿运动范围滑块]
默认值: 45
最小值: 0,
thighBaseSlider 控制顶层关节的基本角度。默认为 90 度,
最大值: 180,
[大腿固定端滑块] 也就是说大腿将垂直向下并从这里向前后运动。 默认值: 90
通过改变这个值可以得到一些有趣的效果。
控制底层关节(小腿)能够运动的范围。 最小值: 0,
calfRangeSlider
最大值: 90,
[小腿运动范围滑块]
默认值: 45
控制偏移量(前面我们用过 –Math.PI / 2)。 最小值: -3.14,
calfOffsetSlider
最大值: 3.14,
[小腿偏移滑块]
默认值: -1.57
下面,为了能够用滑块取代手动设值我们来改变一下代码。
package {
import flash.display.Sprite;
import flash.events.Event;
public class Walking5 extends Sprite {
private var segment0:Segment;
private var segment1:Segment;
private var segment2:Segment;
private var segment3:Segment;
private var speedSlider:SimpleSlider;
private var thighRangeSlider:SimpleSlider;
private var thighBaseSlider:SimpleSlider;
private var calfRangeSlider:SimpleSlider;
private var calfOffsetSlider:SimpleSlider;
private var cycle:Number = 0;
public function Walking5() {
init();
}
private function init():void {
segment0 = new Segment(100, 30);
addChild(segment0);
segment0.x = 200;
segment0.y = 100;
segment1 = new Segment(100, 20);
addChild(segment1);
segment1.x = segment0.getPin().x;
segment1.y = segment0.getPin().y;
segment2 = new Segment(100, 30);
addChild(segment2);
segment2.x = 200;
segment2.y = 100;
segment3 = new Segment(100, 20);
addChild(segment3);
segment3.x = segment2.getPin().x;
segment3.y = segment2.getPin().y;
speedSlider = new SimpleSlider(0, 0.3, 0.12);
addChild(speedSlider);
speedSlider.x = 10;
speedSlider.y = 10;
thighRangeSlider = new SimpleSlider(0, 90, 45);
addChild(thighRangeSlider);
thighRangeSlider.x = 30;
thighRangeSlider.y = 10;
thighBaseSlider = new SimpleSlider(0, 180, 90);
addChild(thighBaseSlider);
thighBaseSlider.x = 50;
thighBaseSlider.y = 10;
calfRangeSlider = new SimpleSlider(0, 90, 45);
addChild(calfRangeSlider);
calfRangeSlider.x = 70;
calfRangeSlider.y = 10;
calfOffsetSlider = new SimpleSlider(-3.14, 3.14, -1.57);
addChild(calfOffsetSlider);
calfOffsetSlider.x = 90;
calfOffsetSlider.y = 10;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
walk(segment0, segment1, cycle);
walk(segment2, segment3, cycle + Math.PI);
cycle += speedSlider.value;
}
private function walk(segA:Segment, segB:Segment,
cyc:Number):void {
var angleA:Number = Math.sin(cyc) *
thighRangeSlider.value +
thighBaseSlider.value;
var angleB:Number = Math.sin(cyc +
calfOffsetSlider.value) *
calfRangeSlider.value +
calfRangeSlider.value;
segA.rotation = angleA;
segB.rotation = segA.rotation + angleB;
segB.x = segA.getPin().x;
segB.y = segA.getPin().y;
}
}
}
这段代码同前面完全一样, 只不过使用滑块的值来控制而非以前的手动控制。我保证从
这个程序中你会得到很多乐趣,探索不同的变化,观察行走效果。
让物体真正走起来
到现在为止,两条腿的运动看起来已经相当真实了,但是它们好像漂浮在太空中一样。
前面章节中, 我们学习了如何让物体以一定的速度或加速度运动,然后会让物体与环境产生
交互。这次也不例外。本章的这一部分显得相当复杂,所以在介绍每段代码时都会给大家相
关的概念。最终的文件会包括所有的这些概念,它就是 RealWalk.as。
给它一些空间
因为物体终归要进行移动, 所以应该让所有的零件变小一点,以便给它们留出更多的运
动空间。创建初始的关节并让它们是原来大小的一半,例如:
segment0 = new Segment(50, 15);
接下来,由于要进行运动并以边界产生交互,所以需要定义 vx 和 vy:
private var vx:Number = 0;
private var vy:Number = 0;
这样一来,运行这个例子后,我们就得到了一个缩小后的版本。
加入重力
接下来,我们需要创建重力。此外,即使有边界的作用力,双腿也会像是漫步在太空一
样。我们需要在运行时加入了重力变量,使用另一个滑块!创建一个新的滑块实例名为
gravitySlider。设置最小值为 0,最大值为 1,取值为 0.2。创建最后一个控制滑块的代码段
如下:
gravitySlider = new SimpleSlider(0, 1, 0.2);
addChild(gravitySlider);
gravitySlider.x = 110;
gravitySlider.y = 10;
下面我们需要连同重力加速度一起来计算速度。创建一个名为 doVelocity 的方法将代
码写在里面,这样比都挤到 onEnterFrame 中要好些:
private function onEnterFrame(event:Event):void {
doVelocity();
walk(segment0, segment1, cycle);
walk(segment2, segment3, cycle + Math.PI);
cycle += speedSlider.value;
}
在这个方法中,只需要将重力加入到 vy,将 vx 和 vy 加入到 segment0 和 segment2
的位置上。记住我们不需要担心 segment1 和 segment3,因为它们的位置是依照顶层关节
的位置计算出来的。
private function doVelocity():void {
vy += gravitySlider.value;
segment0.x += vx;
segment0.y += vy;
segment2.x += vx;
segment2.y += vy;
}
测试后,结果并不令人兴奋。目前还没有 x 速度,而重力也只是将物体向下拉,以至
穿过地面。所以我们需要判断地面是否与双腿产生了碰撞,也就意味着要进行碰撞检测。
处理碰撞
首先,还要让 onEnterFrame 去调用另一个方法,checkFloor。在调用了 walk 方法之
后再调用它,因此它的操作在最后面。总的来说,只需要判断 segment1 和 segment3 ——
底层的关节 ——看看它们是否与地面产生了碰撞。因此两关节都要调用 checkFloor 方法。
private function onEnterFrame(event:Event):void {
doVelocity();
walk(segment0, segment1, cycle);
walk(segment2, segment3, cycle + Math.PI);
cycle += speedSlider.value;
checkFloor(segment1);
checkFloor(segment3);
}
。因
现在进入了第一个有趣的部分:碰撞检测。注意我说的是“有趣”而不是“困难”
为实现起来相当简单。我们需要知道关节的某个部分是否低于底部边界(用 bottom 变量指
定) 。
也许最简单的办法就是让关节调用 getBounds 来判断边界的 bottom 属性是否大于舞
台的高度。下面是 checkFloor 方法的开始部分:
private function checkFloor(seg:Segment):void {
var yMax:Number = seg.getBounds(this).bottom;
if (yMax > stage.stageHeight) {
}
}
对于函数中的第一行我做了一些简化,可能让大家看起来有些怪怪的。
var yMax:Number = seg.getBounds(this).bottom;
一般情况下,大家也许希望写两步,如下:
var bounds:Rectangle = seg.getBounds(this);
var yMax:Number = bounds.bottom;
可能不是每个人都能意识到 seg.getBounds(this) 表达式返回一个 Rectangle 对象,可以
直接访问 Rectangle 的属性,像这样:seg.getBounds(this).bottom。Flash 首先 getBounds 计
算出一个 Rectangle 对象,接着看到了点号和属性,然后就看作是这个对象的一个属性。如
果这种语法让你感到困惑的话,可以继续使用两行代码的方法,别忘了导入
flash.geom.Rectangle 类。如果大家还要对边界对象进行其它操作的话,例如读取附加的属
性,就应该先将这个对象加以保存。但是在本例中,只读取了 bottom 属性,所以可直接进
行调用,而没有使用额外的变量。
OK,现在假设 yMax 确实大于 stage.stageHeight,用现实世界的话说就是,腿与地面
产生了碰撞。 我们应该做什么?就像前面介绍的边界碰撞一样, 首先把物体重置到边界上方。
如果 yMax 是关节的底边,而 stage.stageHeigth 是地面,我们需要将关节运动回它们之间
实际的距离。换句话讲,假设 stage.stageHeight 是 600,而 yMax 是 620,就需要将关节
的 y 坐标 -20。但是,不能只移动这个关节,我们需要让所有的关节都移动这个数值,因
为它们都是同一个身体上的组成部分, 必需当成一个整体进行移动。因此,我们的代码如下:
private function checkFloor(seg:Segment):void {
var yMax:Number = seg.getBounds(this).bottom;
if (yMax > stage.stageHeight) {
var dy:Number = yMax - stage.stageHeight;
segment0.y -= dy;
segment1.y -= dy;
segment2.y -= dy;
segment3.y -= dy;
}
}
试调整滑块的值, 观察不同的行走效果。 这时,我们更加感觉到双腿与环境产生了交互。
当然,这还不是真正的行走。
处理反作用力
现在我们已经成功地让双腿与地面发生了接触,但只重置位置还不够真实。因为,行走
应该使水平方向也产生运动——x 速度。此外,我们的行走应该对 y 速度产生一定的影响
——至少要能与重力暂时地抵消一小会儿。在奔跑中,地面之上停留一会儿。
当脚落地并与地面接触时,就再不能向下了,以便使垂直动量反作用于身体,使身体向
上运动。脚下落的力气越大,那么被抬起来的力量也就越大。同理,如果在与地面接触时,
脚是向后运动的,那么身体就会受到水平的动量,向前进。脚向后运动得越快,水平的推力
就越大。
现在理论已经有了。 如果能知道“脚” x,y 速度,
的 那么在碰撞时,就可以从 vx,vy
OK,
中将 x,y 速度减去。
第一个问题,我们现在还没有找到脚这个物体。事实上,虽然可以自由地加入物理的脚
在上面,并用第二个关节来指定位置,但我不想用真实的脚作例子。而只要计算出虚拟脚的
位置,这个值由底层的关节使用 getPin() 返回。
如果在这个关节运动之前知道它的枢轴的位置, 以及运动后关节的位置, 可以求出两者
的差值来获得脚的 x,y 速度。我们可以在 walk 方法中写入实现,然后将值保存在公有的
vx,vy 属性中。注意,因为使用到了 Point 类,所以需要导入 flash.geom.Point。
function walk(segA:Segment, segB:Segment, cyc:Number):void {
var foot:Point = segB.getPin();
var angleA:Number = Math.sin(cyc) *
thighRangeSlider.value +
thighBaseSlider.value;
var angleB:Number = Math.sin(cyc + calfOffsetSlider.value) *
calfRangeSlider.value +
calfRangeSlider.value;
segA.rotation = angleA;
segB.rotation = segA.rotation + angleB;
segB.x = segA.getPin().x;
segB.y = segA.getPin().y;
segB.vx = segB.getPin().x - foot.x;
segB.vy = segB.getPin().y - foot.y;
}
现在,每个底部的关节都有各自的 vx,vy 属性,表示底部枢轴点的速度,或虚拟脚的
速度,而不是该关节的速度。
那么用这个速度做什么呢?当与地面接触时,将它从总速度中减去。换句话讲,如果脚
在接触地面后,又以每帧 3 像素(vy = 3)运动,我们就要将全部的 vy 将去 3。同样 vx
也是如此。代码实现非常简单:
private function checkFloor(seg:Segment):void {
var yMax:Number = seg.getBounds(this).bottom;
if (yMax > stage.stageHeight) {
var dy:Number = yMax - stage.stageHeight;
segment0.y -= dy;
segment1.y -= dy;
segment2.y -= dy;
segment3.y -= dy;
vx -= seg.vx;
vy -= seg.vy;
}
}
不可否认这是一种极为简单但或许有些不太精确的行走力量的表示。 但是,我还会笑着
说“请测试这个影片” 。看看是否喜欢这个效果。在我看来它确实很酷。我做了很多双腿在
屏幕上跑来跑去。
屏幕环绕,回访
大家都看到了,双腿离开了屏幕后,一去不复返。小小的屏幕环绕很适合用在这里。在
双腿向右离开时,就让它们回到左侧。这里的屏幕环绕比以前稍微复杂一些,因为一个整体
中有四个部分,而原来只是单独的一个物体。我们只需判断两个顶部关节其中的一个,因为
通常它们的位置是相同的,而底部关节的位置都是由顶层关节来决定的。在 onEnterFrame
中加入调用 checkWalls 的方法:
private function onEnterFrame(event:Event):void {
doVelocity();
walk(segment0, segment1, cycle);
walk(segment2, segment3, cycle + Math.PI);
cycle += speedSlider.value;
checkFloor(segment1);
checkFloor(segment3);
checkWalls();
}
让我们留出 100 像素的空隙, 以便让每条腿可以在环绕之前向舞台的右侧运动 100 像
素。如果物体穿越了这个点,则将所有部件都重置到屏幕左侧。那么距离左侧多远呢?舞台
的宽度加上 200,为每边留出 100 像素的空隙。因此 checkWalls 中的 if 语句如下:
private function checkWalls():void {
var w:Number = stage.stageWidth + 200;
if (segment0.x > stage.stageWidth + 100) {
segment0.x -= w;
segment1.x -= w;
segment2.x -= w;
segment3.x -= w;
}
在左侧也加入相同的判断, 因为有些行走动作会使双腿向后运动。最终的 checkWalls
}
方法如下:
private function checkWalls():void {
var w:Number = stage.stageWidth + 200;
if (segment0.x > stage.stageWidth + 100) {
segment0.x -= w;
segment1.x -= w;
segment2.x -= w;
segment3.x -= w;
} else if (segment0.x < -100) {
segment0.x += w;
segment1.x += w;
segment2.x += w;
segment3.x += w;
}
} 为了让大家能看得清楚,不产生混乱,下面给出所有的代码(可见 RealWalk.as) :
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
public class RealWalk extends Sprite {
private var segment0:Segment;
private var segment1:Segment;
private var segment2:Segment;
private var segment3:Segment;
private var speedSlider:SimpleSlider;
private var thighRangeSlider:SimpleSlider;
private var thighBaseSlider:SimpleSlider;
private var calfRangeSlider:SimpleSlider;
private var calfOffsetSlider:SimpleSlider;
private var gravitySlider:SimpleSlider;
private var cycle:Number = 0;
private var vx:Number = 0;
private var vy:Number = 0;
public function RealWalk() {
init();
}
private function init():void {
segment0 = new Segment(50, 15);
addChild(segment0);
segment0.x = 200;
segment0.y = 100;
segment1 = new Segment(50, 10);
addChild(segment1);
segment1.x = segment0.getPin().x;
segment1.y = segment0.getPin().y;
segment2 = new Segment(50, 15);
addChild(segment2);
segment2.x = 200;
segment2.y = 100;
segment3 = new Segment(50, 10);
addChild(segment3);
segment3.x = segment2.getPin().x;
segment3.y = segment2.getPin().y;
speedSlider = new SimpleSlider(0, 0.3, 0.12);
addChild(speedSlider);
speedSlider.x = 10;
speedSlider.y = 10;
thighRangeSlider = new SimpleSlider(0, 90, 45);
addChild(thighRangeSlider);
thighRangeSlider.x = 30;
thighRangeSlider.y = 10;
thighBaseSlider = new SimpleSlider(0, 180, 90);
addChild(thighBaseSlider);
thighBaseSlider.x = 50;
thighBaseSlider.y = 10;
calfRangeSlider = new SimpleSlider(0, 90, 45);
addChild(calfRangeSlider);
calfRangeSlider.x = 70;
calfRangeSlider.y = 10;
calfOffsetSlider = new SimpleSlider(-3.14, 3.14, -1.57);
addChild(calfOffsetSlider);
calfOffsetSlider.x = 90;
calfOffsetSlider.y = 10;
gravitySlider = new SimpleSlider(0, 1, 0.2);
addChild(gravitySlider);
gravitySlider.x = 110;
gravitySlider.y = 10;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
doVelocity();
walk(segment0, segment1, cycle);
walk(segment2, segment3, cycle + Math.PI);
cycle += speedSlider.value;
checkFloor(segment1);
checkFloor(segment3);
checkWalls();
}
private function walk(segA:Segment, segB:Segment,
cyc:Number):void {
var foot:Point = segB.getPin();
var angleA:Number = Math.sin(cyc) *
thighRangeSlider.value +
thighBaseSlider.value;
var angleB:Number = Math.sin(cyc +
calfOffsetSlider.value) *
calfRangeSlider.value +
calfRangeSlider.value;
segA.rotation = angleA;
segB.rotation = segA.rotation + angleB;
segB.x = segA.getPin().x;
segB.y = segA.getPin().y;
segB.vx = segB.getPin().x - foot.x;
segB.vy = segB.getPin().y - foot.y;
}
private function doVelocity():void {
vy += gravitySlider.value;
segment0.x += vx;
segment0.y += vy;
segment2.x += vx;
segment2.y += vy;
}
private function checkFloor(seg:Segment):void {
var yMax:Number = seg.getBounds(this).bottom;
if (yMax > stage.stageHeight) {
var dy:Number = yMax - stage.stageHeight;
segment0.y -= dy;
segment1.y -= dy;
segment2.y -= dy;
segment3.y -= dy;
vx -= seg.vx;
vy -= seg.vy;
}
}
private function checkWalls():void {
var w:Number = stage.stageWidth + 200;
if (segment0.x > stage.stageWidth + 100) {
segment0.x -= w;
segment1.x -= w;
segment2.x -= w;
segment3.x -= w;
} else if (segment0.x < -100) {
segment0.x += w;
segment1.x += w;
segment2.x += w;
segment3.x += w;
}
}
}
}
下面,让我们来真正体验一下在循环行走中改变各个值所带来的不一样的效果。
Walking5.as 又将 Slider 滑块类加入进来,用于动态改变这些变量。
本例中,在屏幕上创建并放置了五个滑块,如图 13-6 所示。
图 13-6 加入滑块
表 13-1 所示,滑块的名字(从左到右),功能以及设置。这些都是我认为比较合理的
范围和取值。总之,我们可以任意地实验其它的取值。
表 13-1 控制行走的滑块
实例 描述
设置
控制整个系统的运动速度。
最小值: 0,
speedSlider
最大值: 0.3,
[速度滑块
默认值: 0.12
控制顶层关节(大腿)能够向前和向后移动多远。 最小值: 0,
thighRangeSlider
最大值: 90,
[大腿运动范围滑块]
默认值: 45
最小值: 0,
thighBaseSlider 控制顶层关节的基本角度。默认为 90 度,
最大值: 180,
[大腿固定端滑块] 也就是说大腿将垂直向下并从这里向前后运动。 默认值: 90
通过改变这个值可以得到一些有趣的效果。
控制底层关节(小腿)能够运动的范围。 最小值: 0,
calfRangeSlider
最大值: 90,
[小腿运动范围滑块]
默认值: 45
控制偏移量(前面我们用过 –Math.PI / 2)。 最小值: -3.14,
calfOffsetSlider
最大值: 3.14,
[小腿偏移滑块]
默认值: -1.57
下面,为了能够用滑块取代手动设值我们来改变一下代码。
package {
import flash.display.Sprite;
import flash.events.Event;
public class Walking5 extends Sprite {
private var segment0:Segment;
private var segment1:Segment;
private var segment2:Segment;
private var segment3:Segment;
private var speedSlider:SimpleSlider;
private var thighRangeSlider:SimpleSlider;
private var thighBaseSlider:SimpleSlider;
private var calfRangeSlider:SimpleSlider;
private var calfOffsetSlider:SimpleSlider;
private var cycle:Number = 0;
public function Walking5() {
init();
}
private function init():void {
segment0 = new Segment(100, 30);
addChild(segment0);
segment0.x = 200;
segment0.y = 100;
segment1 = new Segment(100, 20);
addChild(segment1);
segment1.x = segment0.getPin().x;
segment1.y = segment0.getPin().y;
segment2 = new Segment(100, 30);
addChild(segment2);
segment2.x = 200;
segment2.y = 100;
segment3 = new Segment(100, 20);
addChild(segment3);
segment3.x = segment2.getPin().x;
segment3.y = segment2.getPin().y;
speedSlider = new SimpleSlider(0, 0.3, 0.12);
addChild(speedSlider);
speedSlider.x = 10;
speedSlider.y = 10;
thighRangeSlider = new SimpleSlider(0, 90, 45);
addChild(thighRangeSlider);
thighRangeSlider.x = 30;
thighRangeSlider.y = 10;
thighBaseSlider = new SimpleSlider(0, 180, 90);
addChild(thighBaseSlider);
thighBaseSlider.x = 50;
thighBaseSlider.y = 10;
calfRangeSlider = new SimpleSlider(0, 90, 45);
addChild(calfRangeSlider);
calfRangeSlider.x = 70;
calfRangeSlider.y = 10;
calfOffsetSlider = new SimpleSlider(-3.14, 3.14, -1.57);
addChild(calfOffsetSlider);
calfOffsetSlider.x = 90;
calfOffsetSlider.y = 10;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
walk(segment0, segment1, cycle);
walk(segment2, segment3, cycle + Math.PI);
cycle += speedSlider.value;
}
private function walk(segA:Segment, segB:Segment,
cyc:Number):void {
var angleA:Number = Math.sin(cyc) *
thighRangeSlider.value +
thighBaseSlider.value;
var angleB:Number = Math.sin(cyc +
calfOffsetSlider.value) *
calfRangeSlider.value +
calfRangeSlider.value;
segA.rotation = angleA;
segB.rotation = segA.rotation + angleB;
segB.x = segA.getPin().x;
segB.y = segA.getPin().y;
}
}
}
这段代码同前面完全一样, 只不过使用滑块的值来控制而非以前的手动控制。我保证从
这个程序中你会得到很多乐趣,探索不同的变化,观察行走效果。
让物体真正走起来
到现在为止,两条腿的运动看起来已经相当真实了,但是它们好像漂浮在太空中一样。
前面章节中, 我们学习了如何让物体以一定的速度或加速度运动,然后会让物体与环境产生
交互。这次也不例外。本章的这一部分显得相当复杂,所以在介绍每段代码时都会给大家相
关的概念。最终的文件会包括所有的这些概念,它就是 RealWalk.as。
给它一些空间
因为物体终归要进行移动, 所以应该让所有的零件变小一点,以便给它们留出更多的运
动空间。创建初始的关节并让它们是原来大小的一半,例如:
segment0 = new Segment(50, 15);
接下来,由于要进行运动并以边界产生交互,所以需要定义 vx 和 vy:
private var vx:Number = 0;
private var vy:Number = 0;
这样一来,运行这个例子后,我们就得到了一个缩小后的版本。
加入重力
接下来,我们需要创建重力。此外,即使有边界的作用力,双腿也会像是漫步在太空一
样。我们需要在运行时加入了重力变量,使用另一个滑块!创建一个新的滑块实例名为
gravitySlider。设置最小值为 0,最大值为 1,取值为 0.2。创建最后一个控制滑块的代码段
如下:
gravitySlider = new SimpleSlider(0, 1, 0.2);
addChild(gravitySlider);
gravitySlider.x = 110;
gravitySlider.y = 10;
下面我们需要连同重力加速度一起来计算速度。创建一个名为 doVelocity 的方法将代
码写在里面,这样比都挤到 onEnterFrame 中要好些:
private function onEnterFrame(event:Event):void {
doVelocity();
walk(segment0, segment1, cycle);
walk(segment2, segment3, cycle + Math.PI);
cycle += speedSlider.value;
}
在这个方法中,只需要将重力加入到 vy,将 vx 和 vy 加入到 segment0 和 segment2
的位置上。记住我们不需要担心 segment1 和 segment3,因为它们的位置是依照顶层关节
的位置计算出来的。
private function doVelocity():void {
vy += gravitySlider.value;
segment0.x += vx;
segment0.y += vy;
segment2.x += vx;
segment2.y += vy;
}
测试后,结果并不令人兴奋。目前还没有 x 速度,而重力也只是将物体向下拉,以至
穿过地面。所以我们需要判断地面是否与双腿产生了碰撞,也就意味着要进行碰撞检测。
处理碰撞
首先,还要让 onEnterFrame 去调用另一个方法,checkFloor。在调用了 walk 方法之
后再调用它,因此它的操作在最后面。总的来说,只需要判断 segment1 和 segment3 ——
底层的关节 ——看看它们是否与地面产生了碰撞。因此两关节都要调用 checkFloor 方法。
private function onEnterFrame(event:Event):void {
doVelocity();
walk(segment0, segment1, cycle);
walk(segment2, segment3, cycle + Math.PI);
cycle += speedSlider.value;
checkFloor(segment1);
checkFloor(segment3);
}
。因
现在进入了第一个有趣的部分:碰撞检测。注意我说的是“有趣”而不是“困难”
为实现起来相当简单。我们需要知道关节的某个部分是否低于底部边界(用 bottom 变量指
定) 。
也许最简单的办法就是让关节调用 getBounds 来判断边界的 bottom 属性是否大于舞
台的高度。下面是 checkFloor 方法的开始部分:
private function checkFloor(seg:Segment):void {
var yMax:Number = seg.getBounds(this).bottom;
if (yMax > stage.stageHeight) {
}
}
对于函数中的第一行我做了一些简化,可能让大家看起来有些怪怪的。
var yMax:Number = seg.getBounds(this).bottom;
一般情况下,大家也许希望写两步,如下:
var bounds:Rectangle = seg.getBounds(this);
var yMax:Number = bounds.bottom;
可能不是每个人都能意识到 seg.getBounds(this) 表达式返回一个 Rectangle 对象,可以
直接访问 Rectangle 的属性,像这样:seg.getBounds(this).bottom。Flash 首先 getBounds 计
算出一个 Rectangle 对象,接着看到了点号和属性,然后就看作是这个对象的一个属性。如
果这种语法让你感到困惑的话,可以继续使用两行代码的方法,别忘了导入
flash.geom.Rectangle 类。如果大家还要对边界对象进行其它操作的话,例如读取附加的属
性,就应该先将这个对象加以保存。但是在本例中,只读取了 bottom 属性,所以可直接进
行调用,而没有使用额外的变量。
OK,现在假设 yMax 确实大于 stage.stageHeight,用现实世界的话说就是,腿与地面
产生了碰撞。 我们应该做什么?就像前面介绍的边界碰撞一样, 首先把物体重置到边界上方。
如果 yMax 是关节的底边,而 stage.stageHeigth 是地面,我们需要将关节运动回它们之间
实际的距离。换句话讲,假设 stage.stageHeight 是 600,而 yMax 是 620,就需要将关节
的 y 坐标 -20。但是,不能只移动这个关节,我们需要让所有的关节都移动这个数值,因
为它们都是同一个身体上的组成部分, 必需当成一个整体进行移动。因此,我们的代码如下:
private function checkFloor(seg:Segment):void {
var yMax:Number = seg.getBounds(this).bottom;
if (yMax > stage.stageHeight) {
var dy:Number = yMax - stage.stageHeight;
segment0.y -= dy;
segment1.y -= dy;
segment2.y -= dy;
segment3.y -= dy;
}
}
试调整滑块的值, 观察不同的行走效果。 这时,我们更加感觉到双腿与环境产生了交互。
当然,这还不是真正的行走。
处理反作用力
现在我们已经成功地让双腿与地面发生了接触,但只重置位置还不够真实。因为,行走
应该使水平方向也产生运动——x 速度。此外,我们的行走应该对 y 速度产生一定的影响
——至少要能与重力暂时地抵消一小会儿。在奔跑中,地面之上停留一会儿。
当脚落地并与地面接触时,就再不能向下了,以便使垂直动量反作用于身体,使身体向
上运动。脚下落的力气越大,那么被抬起来的力量也就越大。同理,如果在与地面接触时,
脚是向后运动的,那么身体就会受到水平的动量,向前进。脚向后运动得越快,水平的推力
就越大。
现在理论已经有了。 如果能知道“脚” x,y 速度,
的 那么在碰撞时,就可以从 vx,vy
OK,
中将 x,y 速度减去。
第一个问题,我们现在还没有找到脚这个物体。事实上,虽然可以自由地加入物理的脚
在上面,并用第二个关节来指定位置,但我不想用真实的脚作例子。而只要计算出虚拟脚的
位置,这个值由底层的关节使用 getPin() 返回。
如果在这个关节运动之前知道它的枢轴的位置, 以及运动后关节的位置, 可以求出两者
的差值来获得脚的 x,y 速度。我们可以在 walk 方法中写入实现,然后将值保存在公有的
vx,vy 属性中。注意,因为使用到了 Point 类,所以需要导入 flash.geom.Point。
function walk(segA:Segment, segB:Segment, cyc:Number):void {
var foot:Point = segB.getPin();
var angleA:Number = Math.sin(cyc) *
thighRangeSlider.value +
thighBaseSlider.value;
var angleB:Number = Math.sin(cyc + calfOffsetSlider.value) *
calfRangeSlider.value +
calfRangeSlider.value;
segA.rotation = angleA;
segB.rotation = segA.rotation + angleB;
segB.x = segA.getPin().x;
segB.y = segA.getPin().y;
segB.vx = segB.getPin().x - foot.x;
segB.vy = segB.getPin().y - foot.y;
}
现在,每个底部的关节都有各自的 vx,vy 属性,表示底部枢轴点的速度,或虚拟脚的
速度,而不是该关节的速度。
那么用这个速度做什么呢?当与地面接触时,将它从总速度中减去。换句话讲,如果脚
在接触地面后,又以每帧 3 像素(vy = 3)运动,我们就要将全部的 vy 将去 3。同样 vx
也是如此。代码实现非常简单:
private function checkFloor(seg:Segment):void {
var yMax:Number = seg.getBounds(this).bottom;
if (yMax > stage.stageHeight) {
var dy:Number = yMax - stage.stageHeight;
segment0.y -= dy;
segment1.y -= dy;
segment2.y -= dy;
segment3.y -= dy;
vx -= seg.vx;
vy -= seg.vy;
}
}
不可否认这是一种极为简单但或许有些不太精确的行走力量的表示。 但是,我还会笑着
说“请测试这个影片” 。看看是否喜欢这个效果。在我看来它确实很酷。我做了很多双腿在
屏幕上跑来跑去。
屏幕环绕,回访
大家都看到了,双腿离开了屏幕后,一去不复返。小小的屏幕环绕很适合用在这里。在
双腿向右离开时,就让它们回到左侧。这里的屏幕环绕比以前稍微复杂一些,因为一个整体
中有四个部分,而原来只是单独的一个物体。我们只需判断两个顶部关节其中的一个,因为
通常它们的位置是相同的,而底部关节的位置都是由顶层关节来决定的。在 onEnterFrame
中加入调用 checkWalls 的方法:
private function onEnterFrame(event:Event):void {
doVelocity();
walk(segment0, segment1, cycle);
walk(segment2, segment3, cycle + Math.PI);
cycle += speedSlider.value;
checkFloor(segment1);
checkFloor(segment3);
checkWalls();
}
让我们留出 100 像素的空隙, 以便让每条腿可以在环绕之前向舞台的右侧运动 100 像
素。如果物体穿越了这个点,则将所有部件都重置到屏幕左侧。那么距离左侧多远呢?舞台
的宽度加上 200,为每边留出 100 像素的空隙。因此 checkWalls 中的 if 语句如下:
private function checkWalls():void {
var w:Number = stage.stageWidth + 200;
if (segment0.x > stage.stageWidth + 100) {
segment0.x -= w;
segment1.x -= w;
segment2.x -= w;
segment3.x -= w;
}
在左侧也加入相同的判断, 因为有些行走动作会使双腿向后运动。最终的 checkWalls
}
方法如下:
private function checkWalls():void {
var w:Number = stage.stageWidth + 200;
if (segment0.x > stage.stageWidth + 100) {
segment0.x -= w;
segment1.x -= w;
segment2.x -= w;
segment3.x -= w;
} else if (segment0.x < -100) {
segment0.x += w;
segment1.x += w;
segment2.x += w;
segment3.x += w;
}
} 为了让大家能看得清楚,不产生混乱,下面给出所有的代码(可见 RealWalk.as) :
package {
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Point;
public class RealWalk extends Sprite {
private var segment0:Segment;
private var segment1:Segment;
private var segment2:Segment;
private var segment3:Segment;
private var speedSlider:SimpleSlider;
private var thighRangeSlider:SimpleSlider;
private var thighBaseSlider:SimpleSlider;
private var calfRangeSlider:SimpleSlider;
private var calfOffsetSlider:SimpleSlider;
private var gravitySlider:SimpleSlider;
private var cycle:Number = 0;
private var vx:Number = 0;
private var vy:Number = 0;
public function RealWalk() {
init();
}
private function init():void {
segment0 = new Segment(50, 15);
addChild(segment0);
segment0.x = 200;
segment0.y = 100;
segment1 = new Segment(50, 10);
addChild(segment1);
segment1.x = segment0.getPin().x;
segment1.y = segment0.getPin().y;
segment2 = new Segment(50, 15);
addChild(segment2);
segment2.x = 200;
segment2.y = 100;
segment3 = new Segment(50, 10);
addChild(segment3);
segment3.x = segment2.getPin().x;
segment3.y = segment2.getPin().y;
speedSlider = new SimpleSlider(0, 0.3, 0.12);
addChild(speedSlider);
speedSlider.x = 10;
speedSlider.y = 10;
thighRangeSlider = new SimpleSlider(0, 90, 45);
addChild(thighRangeSlider);
thighRangeSlider.x = 30;
thighRangeSlider.y = 10;
thighBaseSlider = new SimpleSlider(0, 180, 90);
addChild(thighBaseSlider);
thighBaseSlider.x = 50;
thighBaseSlider.y = 10;
calfRangeSlider = new SimpleSlider(0, 90, 45);
addChild(calfRangeSlider);
calfRangeSlider.x = 70;
calfRangeSlider.y = 10;
calfOffsetSlider = new SimpleSlider(-3.14, 3.14, -1.57);
addChild(calfOffsetSlider);
calfOffsetSlider.x = 90;
calfOffsetSlider.y = 10;
gravitySlider = new SimpleSlider(0, 1, 0.2);
addChild(gravitySlider);
gravitySlider.x = 110;
gravitySlider.y = 10;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(event:Event):void {
doVelocity();
walk(segment0, segment1, cycle);
walk(segment2, segment3, cycle + Math.PI);
cycle += speedSlider.value;
checkFloor(segment1);
checkFloor(segment3);
checkWalls();
}
private function walk(segA:Segment, segB:Segment,
cyc:Number):void {
var foot:Point = segB.getPin();
var angleA:Number = Math.sin(cyc) *
thighRangeSlider.value +
thighBaseSlider.value;
var angleB:Number = Math.sin(cyc +
calfOffsetSlider.value) *
calfRangeSlider.value +
calfRangeSlider.value;
segA.rotation = angleA;
segB.rotation = segA.rotation + angleB;
segB.x = segA.getPin().x;
segB.y = segA.getPin().y;
segB.vx = segB.getPin().x - foot.x;
segB.vy = segB.getPin().y - foot.y;
}
private function doVelocity():void {
vy += gravitySlider.value;
segment0.x += vx;
segment0.y += vy;
segment2.x += vx;
segment2.y += vy;
}
private function checkFloor(seg:Segment):void {
var yMax:Number = seg.getBounds(this).bottom;
if (yMax > stage.stageHeight) {
var dy:Number = yMax - stage.stageHeight;
segment0.y -= dy;
segment1.y -= dy;
segment2.y -= dy;
segment3.y -= dy;
vx -= seg.vx;
vy -= seg.vy;
}
}
private function checkWalls():void {
var w:Number = stage.stageWidth + 200;
if (segment0.x > stage.stageWidth + 100) {
segment0.x -= w;
segment1.x -= w;
segment2.x -= w;
segment3.x -= w;
} else if (segment0.x < -100) {
segment0.x += w;
segment1.x += w;
segment2.x += w;
segment3.x += w;
}
}
}
}
相关文章推荐
- 转 第十三章 正向运动学:行走(1)(as3.0)
- AS3.0中人物行走代码剖析。
- 【Ogre编程入门与进阶】第十三章 公告板与粒子系统
- linux程序设计——对FIFO进行读写操作(第十三章)
- apue 第十三章 守护进程
- BZOJ 1602 [Usaco2008 Oct]牧场行走 dfs
- 高性能Tomcat:漫谈行走在sendfile之上的Tomcat
- 华为,15万大军孤独行走在世界上
- 在消逝中行走
- OC学习 第十三章 NSDate、NSData常用方法
- 《Erlang 程序设计》练习答案 -- 第十三章 并发程序中的错误
- 第十三章 红黑树
- Cocos2D实现RPG游戏人物地图行走的跟随效果
- CLRS第十三章思考题
- [ActionScript 3.0] AS3.0 水面波纹效果
- JAVA程序练习---小车行走距离
- [BZOJ1602][Usaco2008 Oct]牧场行走(LCA)
- perl5 第十三章 Perl的面向对象编程
- 敌人AI:行走
- [ActionScript 3.0] AS3.0 生成xml方法之一