前面章节介绍的都是 ActionScript 交互动画的基础,也可以说是一些高级“基础”。
现在开始,我们进入另一条有趣的技术之路,运动学。
高级 3D 动画编程的技术。上网搜索一下,会发现其涉及到的方程中到处都是些陌生符号,
这也成为了我们学习的最大障碍,似乎前面所学的内容都像是很基础的算法。
运动学并没有那么可怕。前面章节中只介绍了我们所需的一些基本知识,
以组合。
所以,无非就是速度,方向,速度向量等等。听起来不难哈?虽然这是个非常简单的定义,
但是蕴藏在里面的内容是比较复杂的,但是要完成我们的目标已经足够了。
两个特殊的分支:正向运动学和反向运动学。让我们就从这里开始吧。
正向和反向运动学介绍
关节相连的手臂。它们要负责整个系统的运动,以及某个零件相对于其它零件和整个系统的
运动。
臂通常有一端被固定住,而另一端则用于伸出去抓东西。一串链条也许有一两个末端都连在
某个东西上,或者什么都不连。
自由端进行运动。反向运动学(Inverse kinematics,缩写:IK)则是向反的:运动以自由
端为起始,回退到固定端,如果有的话。
移动带动脚的运动,最终让脚产生运动。脚的运动不会决定其它部分的运动,它所带动的就
是它本身,其位置根据下肢的位置来确定。
前臂,大臂,以致整个身体的位置和运动。
也可以说,大臂和前臂也是运动的,它们控制了手的位置。没错,不过最直接的目的是把手
放在某个特定的位置。这就是传动力。它不是一种实际的力,而是一种意图。前臂和大臂只
是根据结构的需要通过排列它们的位置来设置手的位置。
般的反向运动学,而一个重复的循环运动,如行走,则是最常见的正向运动学,也就是本章
的课题。
正向运动学编程准备
■ 系统部件。我们称为关节(Segment)。
■ 各关节的位置。
■ 各关节的旋转。
的关节可以是任意的形状,比如手,脚,钳子,刺或是入侵者的绿色激光炮。
关节的话,子关节还会以它们的另一末端作为旋转的枢轴。就像大臂绕着肩膀旋转,小臂绕
着肘部旋转,手绕着手腕旋转一样。
可以让手绕着手腕旋转。到本书的最后,大家也许会在 Flash 中亲自进行一下尝试。但是,
现在,我们这个系统完全是二维的。
单关节运动
是一个继承自 Sprite 的自定义类。因为 sprite 影片可以进行绘图,设定位置,旋转,加
入显示列表。以下是这个类 Segment.as:
package {
}
public function getPin():Point {
}
在关节的注册点(0,0)位置上,另一个则放在相对的另一个末端,这两个“枢轴”用于关
节之间的相互连接。(大家也许注意到了两个公共变量 vx 和 vy。稍后在本章的“处理反
作用力”一节会做更多的介绍)下面一段代码使用不同的宽度和高度创建了若干个关节
(segment),给大家一些创建 Segment 类实例的启发:
var segment:Segment = new Segment(100, 20);
addChild(segment);
segment.x = 100;
segment.y = 50;
var segment1:Segment = new Segment(200, 10);
addChild(segment1);
segment1.x = 100;
segment1.y = 80;
var segment2:Segment = new Segment(80, 40);
addChild(segment2);
segment2.x = 100;
segment2.y =120;
这段代码的执行结果如图 13-1 所示。
图 13-1 一些关节示例
二者的范围。我们可以看到每个关节都放在了 x 轴为 100 的位置上。虽然它们的左侧的边
没有排列整齐,但所有左侧的枢轴却排列得很整齐。当我们旋转关节时,则会绕着左侧的枢
轴进行旋转。
flash.geom.Point 的实例。它将返回右侧枢轴的 x,y 坐标。显然,它会随着关节的旋转而
改变,所以我们使用一些基本的三角学来计算这个位置。这也就是下一个关节将连接上的位
置——我们会在本章看到下一个关节。
同时,我还创建一个滑块类 SimpleSlider.as 加入到了这个项目中。大家可以从本书的下
载页面 www.friendsofed.com 中进行下载,这样我们就可以在任何时候自由地使用这个类
了,它用于在运行时对数值进行调整。对于这个滑块我们可以在构造函数中设置最小值,最
大值,以及当前取值。下面例子中,将最小值设为 -90,最大值设为 90,取值设为 0。这
个类文件结合了上述所有内容:
package {
}
图 13-2 动起来了!
segment 的 rotation 为当前滑块的值。测试一下,运行结果如图 13-2 所示。如果运行没
有问题,我们就完成了正向运动学的第一阶段。
双关节的运动
个关节的实例,名为 segment1,还要创建一个滑块实例名为 slider1。新滑块将控制新关
节的运动,新关节的位置将由 segment0 的 getPin() 方法来确定。以下是代码,见
TwoSegments.as:
package {
private var slider1:SimpleSlider;
private var segment0:Segment;
private var segment1:Segment;
public function TwoSegments() {
}
private function init():void {
}
private function onChange(event:Event):void {
}
图 13-3 双关节正向运动
segment1 的位置。与首次创建 segment1 时设置位置的代码相同。
rotation 要根据 slider1 来确定。
所示。要知道两者间没有实际的物理链接,而都是用数学的方法计算出来的。我们同样可以
使用 segment1 的滑块独立地对它进行旋转。为了好玩一点,改变每个关节的宽度和高度后,
程序仍可以完美地工作。不过,看起来有些奇怪,因为当 segment0 带动 segment1 运动时;
segment0 并没有带动 segment1 旋转。这就像安装了回旋稳定器,将它的方向稳定住了一
样。我不知道您是怎么样的,反正我的前臂没有装回旋稳定器(尽管可能会很酷),因此,
这样的运动看起来不太自然。真正的情况应该是,segment1 的旋转应等于 segment0 的
rotation 加上 slider1 的值。那么 TwoSegment2.as 文档类的函数应该是这样:
private function onChange(event:Event):void {
}
一样向两个方向弯曲。我们只需要改变 slider1 的范围,让最小值等于 -160 让最大值为
0,那么看上去就更加正常了,代码如下:
点,自由端是 segment1 的自由端。这时我们可以想象一下手臂。固定端的旋转和位置决定
了 segment1 的位置。 segment1 的旋转和位置决定了其自由端的位置。
谓,它只是来凑凑热闹而已。因此,控制就是从固定端向前运动到自由端。
自动化过程
让零件产生运动。如果想实现真正的行走,就需要加入一些自动控制。
乘以 90,让数值的变化从 90 到 -90。循环变量是实时累加的,这样就可以实现摆动。接
下来就可以使用计算出的角度变量来控制两个关节了。加入 enterFrame 函数来控制动作,
这样运动就是持续的了。
package {
}
创建自然行走循环
1.将 segment0 的旋转增加 90 度,将范围减小到从 90 度开始到每个方向 45 度。
2.每个关节都要有不同的角度,因此要声明两个角度 angle0 和 angle1。
3.将 angle1 的范围减少到 45 度, 然后再增加 45 度。这样就使最终的范围变为 0 到 90
度,因此只能向一个方向弯曲,像是真正的膝关节。光这样说不能完全解释清楚,请大家试
着观察加或不加 45 度有何区别,或试着加入其它的数值,直到感觉都合适为止。
private function onEnterFrame(event:Event):void {
}
图 13-4 循环行走
不像在走路。这是因为两个关节在同一时刻内向同一方向运动。它们是完全同步的,而真正
的行走过程,并不是这样的。
步,我们应采用 cycle0 和 cycle1 变量,但是可以不做这么大的改变。只需要加入循环的
偏移量求出 angle1 即可,如下:
到您认为满意为止吧。给大家一些提示:这个数应该在 Math.PI 到 –Math.PI (3.14 到
-3.14)之间。任何大于或小于这个范围的数,只是将这个数进行了重复而已。例如,我用 –
Math.PI / 2,将 angle0 向后推四分之一个周期。当然,-Math.PI / 2 大约为 -1.57,因
此我们可以试试其周边的一些数如 -1.7 或 -1.3,看看效果是好是坏。稍后,我们将放入
一个滑块来动态地进行调整。带有偏移的循环行走代码见 Walking3.as。
segment2 和 segment3。segment2 对象应与 segment0 位置相同,因为它也将是一个顶级
端或固定端,而 segment3 应该用 segment2 的 getPin() 方法来设置位置。
和 segment1 运动要好得多:
private function walk(segA:Segment, segB:Segment, cyc:Number):void {
}
代码都是我们用过的。现在让 segment0 和 segment1 行走,只需要这样调用:
来吧。onEnterFrame 方法如下:
private function onEnterFrame(event:Event):void {
}
所以看上去像是只有一条腿。因此,要让它们不能同步运动。我们要将第二条腿与第一条腿
的运动周期进行偏移。 这就要改变 cycle 的值。只需要在 cycle 上面加上或减去一些值就
可以了,这样做比加入两个不同的变量要好得多。因此 onEnterFrame 就变成了这样:
private function onEnterFrame(event:Event):void {
}
度,因此在第一条腿向前走时,第二条腿正在向后走,反之亦然。简略的回答是,因为它奏
效了!大家可以试试其它的值,如 Math.PI / 2,观察一下这个运动,就像疾速飞跑,而不
是在行走或跑步。不过需要加以留心——说不定某一天会需要它呢!
比末端关节("calves"[小腿])要稍大一些。请记住,由于我们使用的方法是动态的,所以
影片的运行与零件的大小无关。在下一个版本中,我们会用滑块使更多的部件变成动态的,
不过我强烈建议大家现在开始手动改变一下代码中变量的值,
的不同效果。
图 13-5 看!走起来了!