用BASIC语言实现的动画技术举例 1994-01-28 正如我们在一些电视游戏上看到的那样,如碰撞、跳跃、奔跑、发射、爆炸等一系列迷人的效果,都是动画技术的应用结果。我们虽然无法运用BASIC实现那些错综复杂的大型画面,其中有分辨率的不足,也有速度不够的因素,但通过动画技术的学习,至少可以了解其设计原理,只要运用得当,同样也能制作出栩栩如生的动态游戏。 与电影、电视,尤其是与美术卡通片相似,计算机动画技术利用了计算机能高速显示图形的特点,以适应人的视觉暂停现象。例如,我们想让某一形体移动,所要作的工作只是画出该图形,接着消除它,然后在稍微不同的位置重复这一过程。 这种“显示__消除__(换形或换位)显示”的方法,就是画面活动的基本原理。从另一方面讲,即使在电影中(每秒钟放映24幅胶片)或是在电视中(每秒钟显示25帧图像),也会造成闪烁和跳跃感。实际运用里,电影技术采取了“遮挡放映法”,电视技术则采取了“隔行扫描法”,设法避免闪烁现象。我们也准备采用某种“延时”的技巧尽可能地改善屏幕动画的显示效果。 下面将以移动图形块为例,从不同角度介绍上述动画的形成原理。 [程序1] 图形块水平移动 10 SCREEN 0,1:WIDTH 40:CLS 100 FOR X=1 TO 40 110 LOCATE 10,X:PRINT ""; 120 LOCATE 10,X:PRINT " "; 130 NEXT X 运动后可以看到,由于“显示__消除”过程的不断交替,图块迅速水平穿越屏幕,实现了动画。不过在显示过程中有明显的闪烁现象,这是因为显示图块和消除图块时间相等,显示时间还不足以使人产生视觉暂停效应。为此,我们应当给显示过程以适当的延时,插入下列语句: 115 FOR T=1 TO 1000:NEXT T 插入的空循环,可通过改变循环终值调整延时时间。请读者注意,由于机器的速度不一,循环终值应根据具体情况调整后设定。此时,虽然图块的运动速度减慢,但较好地消除了跳跃感。 [程序2] 图形块垂直移动 10 SCREEN 0,1:WIDTH 40:CLS 100 X=20 110 FOR Y=1 TO 24 120 LOCATE Y,X:PRINT ""; 125 FOR T=1 TO 1000:NEXT T 130 LOCATE Y,X:PRINT " "; 140 NEXT Y 在编程时应注意,无论是X值还是Y值都不允许超界。 上两例循环语句的步长均为1,即速度为单一方向(水平或垂直)的常量1。如果同时考虑速度的两个分量:水平分量H,垂直分量V,且赋予不同的数值,就可产生斜率不同的斜向运动。 [程序3] 斜向移动的图形块 10 SCREEN 0,1:WIDTH 40:CLS 100 X=1:Y=1 110 V=1:H=1 120 LOCATE Y,X:PRINT ""; 125 FOR T=1 TO 1000:NEXT T 130 LOCATE Y,X:PRINT " "; 140 X=X+H:Y=Y+V 150 IF X>40 OR Y>24 THEN 170 160 GOTO 120 170 END 我们很容易通过改变速度增量H或水平增量V值,产生不同的斜度效果。比如令V=1,H=2,加大了水平方向的运动速度。注意150句是必不可少的,它控制了图块的运动范围,使坐标不至于超出界限。 在[程序3]里,我们先画了图块,然后延时,再把它消除。另一种常用的方案是先在原位消除,然后在新的位置显示图块,并启用另外两个变量保存坐标值,以便循环返回时把图块消除,如下列程序所示: [程序4] 另一种斜向运动程序方案 10 SCREEN 0,1:WIDTH 40:CLS 100 X=1:Y=1:M=1:N=1 110 H=1:V=1 120 LOCATE N,M:PRINT " "; 130 LOCATE Y,X:PRINT ""; 140 N=Y:M=X 150 X=X+H:Y=Y+V 160 IF X>40 OR Y>24 THEN END 170 GOTO 120 由于图块显示后要执行若干语句,可以代替空循环的延时作用,这种方法有时显得更灵活。 假如把[程序3]作一点变形,在运动图块即将超界的时刻改变水平或垂直的运动方向,则图块将在屏幕里反弹碰撞,无休无止地运动。只要再添上场地线或桌边线,不就是一场球赛的主要情节吗? [程序5] 反弹碰撞的图块 10 SCREEN 0,1:WIDTH 40:CLS 100 X=1:Y=1:V=1:H=1 110 LOCATE Y,X:PRINT ""; FOR I=1 TO 1000:NEXT I 130 LOCATE Y,X:PRINT " "; 140 IF X<=1 OR X>=40 THEN H=-H 150 IF Y<=1 OR Y>=24 THEN V=-V 160 X=X+H:Y=Y+V 170 GOTO 110 若再次在[程序3]基础上作些修改,不仅改变某方向上的移动位置,而且改变其移动速度,即在一方向或两方向同时引进“加速度”,该图块必然沿着曲线运动,产生变幻无穷的效果。例如,引入垂直加速度,使图块沿抛物线下落,模拟飞机扔炸弹的运动轨迹: [程序6] 曲线运动的图块 10 SCREEN 0,1:WIDTH 40:CLS 100 X=1:Y=1 110 V=0:H=2:A=1 120 LOCATE Y,X:PRINT ""; 130 FOR T=1 TO 1000:NEXT T 140 LOCATE Y,X:PRINT " "; 150 X=X+H:Y=Y+V 160 V=V+A 170 IF X>40 OR Y>24 THEN END 180 GOTO 120 以上这些示例,用图块移动时不同处理的效果展现了动画的基本原理,为进一步讨论设计技法打下了基础。 简单动画设计技巧 一、字符显示模式中的动画设计 图形字符构造的图案,实现动画的方法相对比较简单,从上节对动画原理的讨论中,我们已经看见了对它们的处理方案,即采用“打空格”来消除指定位置的图块。还有没有更有效的技巧呢?请看下列程序示例: [程序7] 数字动画 10 SCREEN 0,1:WIDTH 40:CLS 100 Y=10 110 FOR X=1 TO 30 120 LOCATE Y,X:PRINT 5 130 NEXT X 分析程序似乎应该得出结论:运行后将在屏幕第10行显示30个数字5。然而实际状况却是仅有一个5字快速穿越屏幕。原来,PC BASIC显示数字均带有一个符号位,当数字为正时符号不显示但留有一个空格。上例中的数字5,在自左向右显示过程中,符号位空格自动消除了上一位置的显示字符。 示例启发了我们:与其在程序中设置打空格的语句,不如让图块“自带空格”。例如,把上例中的120句改为: 120 LOCATE Y,X:PRINT " "; 将产生异曲同工之效果。“自带空格”好比图形尾部带有一把扫帚,扫去了它自己留下的脚印。当然,图块向右运动应左随一个空格;向左运动应右随一个空格;若需左右移动,则可把字串写成“ ”。 若要使用两个以上的图块,自带空格的方法也完全可行。这种方法实质上是用新的图块组合去覆盖已显示的图块组合,因此也称为“覆盖法”,只是在覆盖运动中,要特别注意防止图形位置坐标超界。 覆盖法没有消除处理的时间间隔,移动图形不会形成不连续的感觉,因此比较适用于大块图案的动画。在上一章[程序2]里,我们曾在屏幕上画了一匹小马,现在要让它动起来: [程序8] 奔跑的小马 10 SCREEN 0,1:WIDTH 40:COLOR 7,1:CLS 20 DIM A $ (3) 30 A $ (1)="" 40 A $ (2)="" 50 A $ (3)="/ \" 60 Y=10:GOSUB 100 70 END 100 FOR X=1 TO 34 110 FOR I=1 TO 3 120 LOCATE Y+I,X:PRINT A $ (I) 130 NEXT I 140 NEXT X:RETURN 注意在储存小马图案的数组A$中,每一行的左端都有一个空格,从而在运动中一步步覆盖了空格前的图形字符。 对于更复杂的图案,比如用图块拼装的汽车、军舰、飞机等,为了避免形成移动不平稳的视觉效果,每一步的移动不一定需要重新显示全图,可以采取部分修改的办法,即只在图形有变化的部位上修改,其他部位保持不变。 二、中分辨率图形模式中的动画设计 在字符显示模式里,无论是“消除法”还是“覆盖法”,实现动画的主要手段都是用空格字符擦去了原有的字符。为了解决在图形模式里对已显象素的消除问题,PC BASIC提供了专门的擦点语句,给动画设计带来了方便: PRESET (X,Y) 擦点(PRESET语句)可自动选择屏幕底色将X,Y坐标指定位置的点抹去,例如联合使用: 100 PSET (50,50),3:PRESET (50,50) 可以看到屏幕上有一亮点迅速地闪现并立即消失。事实上,如果没有PRESET语句,仅用PSET语句也能达到同样的目的。只需把PSET语句中的颜色代码选择为屏幕底色,它就具备了擦点的能力,如: 100 COLOR 0,0 110 PSET (50,50),3:PSET (50,50),0 利用PSET语句兼任画点和擦点,对编程设计有时更方便。 [程序10] 闪闪的星星 10 SCREEN 1,0:COLOR 0,1:CLS 20 DIM X(50),Y(50) 30 FOR I=1 TO 50 40 X(I)=INT(RND*319):Y(I)=INT(RND*199) 50 NEXT X 100 C=3:GOSUB 200 110 C=0:GOSUB 200 120 GOTO 100 200 FOR I=1 TO 50 210 PSET (X(I),Y(I)),C 220 NEXT I:RETURN 仿照使用屏幕底色擦点的思路,我们当然能够把LINE语句画出的线段或矩形,以及CIRCLE语句画出的圆形等简单图案迅速抹去,从而使它们也动起来,方法仍然是“显示__消除法”。 [程序11] 移动的直线、矩形、圆形 10 SCREEN 1,0:COLOR 0,1:CLS 100 FOR X=O TO 310 110 LINE (X,20)-(X,40),3 120 LINE (X,20)-(X,40),0 130 NEXT X 200 FOR X=0 TO 250 210 LINE (X,80)-(X+50,120),3,BF 220 LINE (X,80)-(X+50,120),0,BF 230 NEXT X 300 FOR X=30 TO 250 310 CIRCLE (X,150),25,3 320 CIRCLE (X,150),25,0 330 NEXT X 在上述程序中,如果想要避免闪烁感,也可以在适当的地方插入延时语句。 对于更复杂的一些图形,若同样采取重新用底色描画一遍的办法来消除它,不仅使程序冗长,而且动画的速度也会慢得令人不能忍受。好在PC BASIC为复杂图形的动画设计设立了快速作图的功能语句。那就是GET和PUT语句。 取图(GET语句)用于从屏幕的指定矩形区域内取出所有的象素点,并储存在一个数组中供今后使用。这就好比我们在字符显示模式里把图形块用程序方法存放在字符型数组的做法一样: GET (X1,Y1)-(X2,Y2),数组其中,(X1,Y1),(X2,Y2)的规定与LINE语句含义一样,是指定的矩形区域左上和右下角顶点坐标;数组是整数或实数型数组,容量大小随矩形面积大小而异。如果该矩形的宽为W个象素点,高为H个象素点,则在中分辨率图形模式里,数组的大小可以用下式计算: 数组容量=4+H*INT((W*2+7)/8) 字节 例如,某矩形的宽为50个象素点,高为20个象素点,则数组容量为:4+20*INT((50*2+7)/8)=264字节。当数组设定为整型数组A%时,由于每个数组元素是两个字节,仅需要264/2=132个元素,用数组说明语句指定容量: 10 DIM A%(132) 100 GET(1,1)-(50,20),A% 此时,已将屏幕左上角小矩形内的图案存放于数组A%之中。 接下来,可以用取图(PUT语句)将数组A%中的图形显示于屏幕的任意地点,常用的格式为: PUT (X,Y),数组,XOR 格式中的(X,Y)指矩形区域左上角坐标,即该图形重新显示于屏幕的位置,数组与GET语句相同,XOR是一个操作符号(异或操作),这种操作的最大特点是:如果把某个图形PUT到屏幕后,再一次于同一位置PUT它,将引起图形的消失,如: 200 PUT (100,100),A%,XOR 210 PUT (100,100),A%,XOR 实现了图形的“显示__消除”过程,这显然十分有利于快速动画的操作。