5.3.2 计算机动画

顾名思义,动画就是运动的画面,计算机动画就是通过计算机编程来实现运动的画面。计算机动画在很多领域中都有应用,例如游戏开发、电影电视制作、教学演示等。计算机动 画并不神秘,只要掌握了静止图形的绘制方法,就很容易学会活动画面的制作。

现实世界中运动是连续的,而数字计算机只能处理离散量,因此计算机动画本质上只能 是对连续运动的近似和模拟。具体来说,动画是通过在屏幕上快速地交替显示一组静止图形(图像),或者让一幅图形(图像)快速地移动而实现的。每一幅静止图形(图像)称为一 帧,帧与帧之间在画面上只有小部分的不同,于是人眼的视觉暂留现象会使我们产生运动的 感觉。实验表明,每秒显示 24 帧画面能使人眼感觉到最佳的连续运动效果,所以在连续两帧画面之间应该停顿 0.04 秒左右。 动画制作有很多现成软件工具可用,例如在网页和多媒体教学中常用的 Flash。而我们在此介绍的是直接编程实现动画。

下面我们利用 Tkinter 来实现一个简单的动画程序。程序的功能是演示太阳、地球和月 球三个天体之间的运动情况,即月球绕地球运动,并且和地球一起绕太阳运动。

程序规格

输入:没有输入。

输出:演示太阳、地球和月球之间相对运动的动画。

算法设计

首先当然是建立窗口和画布,然后画出太阳、地球和月球三个天体,具体做法与 5.2.3 节中绘制椭圆的例子相似(图 5.9),当然现在需要多画一个月球,并且需要移动地球和月球。 本程序最关键的部分是解决地球和月球沿椭圆轨道移动的计算(假设太阳位置固定不动),下面先解决地球的运动问题。中学数学告诉我们,椭圆可以用如下方程来刻划:

x = a cos t
y = b sin t

因此,地球在轨道上自西向东旋转时的每一个位置(x,y)都可利用此方程算出,其中椭圆轨 道的 a、b 值是固定不变的,位置只由旋转角度 t 决定(参见图 5.20)。假设地球每次旋转 0.01p 弧度(这就是连续运动的离散化!),则地球的下一位置就是

x' = a cos(t + 0.01 pi)
y' = b sin(t + 0.01 pi)

由此可算出

dx = x' - x
dy = y' - y

于是可利用画布对象的 move()方法来移动地球到新的位置。

再看图 5.20,由于画布的坐标系原点不是椭圆轨道的中心,椭圆中心在画布坐标系中是(150,100),故地球在 t 角度时的位置应该做个变换:

x = 150 + a cos t
y = 100 - b sin t

注意画布坐标系中 y 轴是向下的,因此上式计算 x 和 y 坐标时有加减的不同。

图 5.20 地球沿椭圆轨道旋转位置的计算

接下来解决月球的运动问题。首先月球是与地球一起沿椭圆轨道绕太阳运动的,因此月球相对于太阳的位置变化与地球一样由上述 dx 和 dy 决定,即程序 5.3 中的 edx 和 edy。 此外,月球又在绕地球旋转,利用同样方法可算出月球沿绕地球椭圆轨道(设 a、b 的值分 别为 20 和 15)运动时相对于地球的位置变化,即程序 5.3 中的 mdx 和 mdy。最终月球的位 置变化为 edx+mdx 和 edy+mdy。注意,月球绕地球的旋转速度大约是地球绕太阳的旋转 速度的 12 倍(一年有十二个月)。

解决了关键的地球月球的位置计算问题,本程序的算法就明确了,伪代码如下:

算法: 
创建窗口和画布;
在画布上绘制太阳、地球和月球,以及地球的绕日椭圆轨道; 
设置地球和月球的当前位置;
进入动画循环: 
    旋转 0.01p;
    计算地球和月球的新位置; 
    移动地球和月球到新位置 
    更新地球和月球的当前位置; 
    停顿一会

代码实现

上面的算法很容易翻译成如程序 5.3 所示的 Python 代码。代码中有两处需要说明一下: 第一,每次循环中修改图形位置后都必须执行一个更新画布显示的方法 c.update(),以 使新画面显示出来;第二,两个画面之间的停顿可以用 time 模块中的 sleep()函数来实 现,该函数的作用就是让程序休眠一会(参数以秒为单位)①。

【程序 5.3】animation.py

from Tkinter import *
from math import sin,cos,pi from time import sleep
def main():
    root = Tk()
    c = Canvas(root,width=300,height=200,bg='white') 
    c.pack()
    orbit = c.create_oval(50,50,250,150)
    sun = c.create_oval(110,85,140,115,fill='red') 
    earth = c.create_oval(245,95,255,105,fill='blue') 
    moon = c.create_oval(265,98,270,103)
    eX = 250 # earth's X
    eY = 100 # earth's Y
    m2eX = 20 # moon's X relative to earth 
    m2eY = 0 # moon's Y relative to earth 
    t = 0
    while True:
        t = t + 0.01*pi
        new_eX = 150 + 100 * cos(t) 
        new_eY = 100 - 50 * sin(t) 
        new_m2eX = 20 * cos(12*t) 
        new_m2eY = -15 * sin(12*t)
        edx = new_eX - eX 
        edy = new_eY - eY
        mdx = new_m2eX - m2eX 
        mdy = new_m2eY - m2eY
        c.move(earth,edx,edy) 
        c.move(moon,mdx+edx,mdy+edy) 
        c.update()
        eX = new_eX 
        eY = new_eY
        m2eX = new_m2eX 
        m2eY = new_m2eY
        sleep(0.04)
main()

① 如果不知道这个 sleep 函数,也可以自己写一个纯粹消磨时间的循环语句,例如循环 1 百万次,每次 都执行无用语句。同样能起到让画面停顿的效果。

图 5.21 是程序 5.3 执行过程中的一个屏幕截图。

图 5.21 程序 5.3 的屏幕截图