### 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 坐标时有加减的不同。
![](https://box.kancloud.cn/2016-02-22_56cafce1ac4c2.png)
图 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 执行过程中的一个屏幕截图。
![](https://box.kancloud.cn/2016-02-22_56cafce1bddb3.png)
图 5.21 程序 5.3 的屏幕截图
- 前言
- 第 1 章 计算与计算思维
- 1.1 什么是计算?
- 1.1.1 计算机与计算
- 1.1.2 计算机语言
- 1.1.3 算法
- 1.1.4 实现
- 1.2 什么是计算思维?
- 1.2.1 计算思维的基本原则
- 1.2.2 计算思维的具体例子
- 1.2.3 日常生活中的计算思维
- 1.2.4 计算思维对其他学科的影响
- 1.3 初识 Python
- 1.3.1 Python 简介
- 1.3.2 第一个程序
- 1.3.3 程序的执行方式
- 1.3.4 Python 语言的基本成分
- 1.4 程序排错
- 1.5 练习
- 第 2 章 用数据表示现实世界
- 2.1 数据和数据类型
- 2.1.1 数据是对现实的抽象
- 2.1.1 常量与变量
- 2.1.2 数据类型
- 2.1.3 Python 的动态类型*
- 2.2 数值类型
- 2.2.1 整数类型 int
- 2.2.2 长整数类型 long
- 2.2.3 浮点数类型 float
- 2.2.4 数学库模块 math
- 2.2.5 复数类型 complex*
- 2.3 字符串类型 str
- 2.3.1 字符串类型的字面值形式
- 2.3.2 字符串类型的操作
- 2.3.3 字符的机内表示
- 2.3.4 字符串类型与其他类型的转换
- 2.3.5 字符串库 string
- 2.4 布尔类型 bool
- 2.4.1 关系运算
- 2.4.2 逻辑运算
- 2.4.3 布尔代数运算定律*
- 2.4.4 Python 中真假的表示与计算*
- 2.5 列表和元组类型
- 2.5.1 列表类型 list
- 2.5.2 元组类型 tuple
- 2.6 数据的输入和输出
- 2.6.1 数据的输入
- 2.6.2 数据的输出
- 2.6.3 格式化输出
- 2.7 编程案例:查找问题
- 2.8 练习
- 第 3 章 数据处理的流程控制
- 3.1 顺序控制结构
- 3.2 分支控制结构
- 3.2.1 单分支结构
- 3.2.2 两路分支结构
- 3.2.3 多路分支结构
- 3.3 异常处理
- 3.3.1 传统的错误检测方法
- 3.3.2 传统错误检测方法的缺点
- 3.3.3 异常处理机制
- 3.4 循环控制结构
- 3.4.1 for 循环
- 3.4.2 while 循环
- 3.4.3 循环的非正常中断
- 3.4.4 嵌套循环
- 3.5 结构化程序设计
- 3.5.1 程序开发过程
- 3.5.2 结构化程序设计的基本内容
- 3.6 编程案例:如何求 n 个数据的最大值?
- 3.6.1 几种解题策略
- 3.6.2 经验总结
- 3.7 Python 布尔表达式用作控制结构*
- 3.8 练习
- 第 4 章 模块化编程
- 4.1 模块化编程基本概念
- 4.1.1 模块化设计概述
- 4.1.2 模块化编程
- 4.1.3 编程语言对模块化编程的支持
- 4.2 Python 语言中的函数
- 4.2.1 用函数减少重复代码 首先看一个简单的用字符画一棵树的程序:
- 4.2.2 用函数改善程序结构
- 4.2.3 用函数增强程序的通用性
- 4.2.4 小结:函数的定义与调用
- 4.2.5 变量的作用域
- 4.2.6 函数的返回值
- 4.3 自顶向下设计
- 4.3.1 顶层设计
- 4.3.2 第二层设计
- 4.3.3 第三层设计
- 4.3.4 第四层设计
- 4.3.5 自底向上实现与单元测试
- 4.3.6 开发过程小结
- 4.4 Python 模块*
- 4.4.1 模块的创建和使用
- 4.4.2 Python 程序架构
- 4.4.3 标准库模块
- 4.4.4 模块的有条件执行
- 4.5 练习
- 第 5 章 图形编程
- 5.1 概述
- 5.1.1 计算可视化
- 5.1.2 图形是复杂数据
- 5.1.3 用对象表示复杂数据
- 5.2 Tkinter 图形编程
- 5.2.1 导入模块及创建根窗口
- 5.2.2 创建画布
- 5.2.3 在画布上绘图
- 5.2.4 图形的事件处理
- 5.3 编程案例
- 5.3.1 统计图表
- 5.3.2 计算机动画
- 5.4 软件的层次化设计:一个案例
- 5.4.1 层次化体系结构
- 5.4.2 案例:图形库 graphics
- 5.4.3 graphics 与面向对象
- 5.5 练习
- 第 6 章 大量数据的表示和处理
- 6.1 概述
- 6.2 有序的数据集合体
- 6.2.1 字符串
- 6.2.2 列表
- 6.2.3 元组
- 6.3 无序的数据集合体
- 6.3.1 集合
- 6.3.2 字典
- 6.4 文件
- 6.4.1 文件的基本概念
- 6.4.2 文件操作
- 6.4.3 编程案例:文本文件分析
- 6.4.4 缓冲
- 6.4.5 二进制文件与随机存取*
- 6.5 几种高级数据结构*
- 6.5.1 链表
- 6.5.2 堆栈
- 6.5.3 队列
- 6.6 练习
- 第 7 章 面向对象思想与编程
- 7.1 数据与操作:两种观点
- 7.1.1 面向过程观点
- 7.1.2 面向对象观点
- 7.1.3 类是类型概念的发展
- 7.2 面向对象编程
- 7.2.1 类的定义
- 7.2.2 对象的创建
- 7.2.3 对象方法的调用
- 7.2.4 编程实例:模拟炮弹飞行
- 7.2.5 类与模块化
- 7.2.6 对象的集合体
- 7.3 超类与子类*
- 7.3.1 继承
- 7.3.2 覆写
- 7.3.3 多态性
- 7.4 面向对象设计*
- 7.5 练习
- 第 8 章 图形用户界面
- 8.1 图形用户界面概述
- 8.1.1 程序的用户界面
- 8.1.2 图形界面的组成
- 8.1.3 事件驱动
- 8.2 GUI 编程
- 8.2.1 UI 编程概述
- 8.2.2 初识 Tkinter
- 8.2.3 常见 GUI 构件的用法
- 8.2.4 布局
- 8.2.5 对话框*
- 8.3 Tkinter 事件驱动编程
- 8.3.1 事件和事件对象
- 8.3.2 事件处理
- 8.4 模型-视图设计方法
- 8.4.1 将 GUI 应用程序封装成对象
- 8.4.2 模型与视图
- 8.4.3 编程案例:汇率换算器
- 8.5 练习
- 第 9 章 模拟与并发
- 9.1 模拟
- 9.1.1 计算机建模
- 9.1.2 随机问题的建模与模拟
- 9.1.3 编程案例:乒乓球比赛模拟
- 9.2 原型法
- 9.3 并行计算*
- 9.3.1 串行、并发与并行
- 9.3.2 进程与线程
- 9.3.3 多线程编程的应用
- 9.3.4 Python 多线程编程
- 9.3.5 小结
- 9.4 练习
- 第 10 章 算法设计和分析
- 10.1 枚举法
- 10.2 递归
- 10.3 分治法
- 10.4 贪心法
- 10.5 算法分析
- 10.5.1 算法复杂度
- 10.5.2 算法分析实例
- 10.6 不可计算的问题
- 10.7 练习
- 第 11 章 计算+X
- 11.1 计算数学
- 11.2 生物信息学
- 11.3 计算物理学
- 11.4 计算化学
- 11.5 计算经济学
- 11.6 练习
- 附录
- 1 Python 异常处理参考
- 2 Tkinter 画布方法
- 3 Tkinter 编程参考
- 3.1 构件属性值的设置
- 3.2 构件的标准属性
- 3.3 各种构件的属性
- 3.4 对话框
- 3.5 事件
- 参考文献