发信人: whc()
整理人: fishy(2000-07-09 23:20:46), 站内信件
|
- 信区: PROGRAMR.CHINA [程序人生]------------------------------ --------
信件: 1003 Local 日期: 18 Nov 98 21:15:07
来自: Float Pointer 已读: 是 已回 信 : 否
给: All 标记:
主题: FP的QB教程 第四篇
---------------------------------------------------------------------- --------
过了几个星期,FP的QB教程终于重现江湖. ;)
这部是最精彩的,你将可以编3D动画了!
补充点,上次忘了说那个3D图形的公式中SQR函数是开平方,与x^.5的
作用是相同的,但速度比后者快.
而X*X这种用法也是为了提高速度而不用X^2
上次我们讨论了QB的基本绘图命令,然而用那些命令要做动画是不成的.
我们知道,动画的原理就是一幅幅反复显示出来,在每一时刻都只有一帧
图象. 最简单的动画要属擦图动画了,看下面这段例子:
--- =====================================
SCREEN 12
CONST r = 10
COLOR 14
LINE (0, 200 + r)-(640, 200 + r), 10
FOR i = 0 TO 320 STEP .5
x = i * 2
y = 200 - ABS(SIN(i * .2) * 200) 'ABS取绝对值
CIRCLE (x, y), r
PAINT (x, y)
t1! = TIMER '延时开始
WHILE TIMER < t1! + .01
WEND '延时结束
LINE (x - r, y - r)-(x + r, y + r), 0, BF
IF INKEY$ = CHR$(27) THEN END
NEXT
--- =====================================
不好意思,我用取绝对值的正弦函数代替自由落体的函数.
似乎是动起来了,效果也不错.但因为这是一个简单的图形,画的时间
比较短,现在的CPU时间比较快. 如果你把延时部分放到LINE的后面
或者去掉你就知道问题所在了.
如果是复杂的图形,当画的过程所费时间与延时时间相近时就会发生闪烁.
事实上这个程序也有闪烁,只是速度比较快,眼睛反应不过来.
你可以试试把半径R改大一些.
QB提供了GET和PUT语句来提高绘图的速度.
下面修改一下上面的程序:
--- =====================================
SCREEN 12
CONST r = 20
COLOR 14
bpp = 1
Planes = 4
l = 4 + INT(((r * 2 + 1) * (bpp) + 7) / 8) * Planes * (r * 2 + 1)
l = l / 2 + 1
DIM cir(l) AS INTEGER
CIRCLE (200, 200), r
PAINT (200, 200)
GET (200 - r, 200 - r)-(200 + r, 200 + r), cir(0)
CLS '清屏语句
LINE (0, 300 + r)-(640, 300 + r), 10
FOR i = 20 TO 300 STEP .5
x = i * 2
y = 300 - ABS(SIN(i * .2) * 200)
PUT (x - 20, y - 20), cir(0)
t1! = TIMER
WHILE TIMER < t1! + .01
WEND
LINE (x - r, y - r)-(x + r, y + r), 0, BF
IF INKEY$ = CHR$(27) THEN END
NEXT
--- =====================================
我们把复杂的图形事先画好,用GET语句把点阵数据保存在一个数组变量里,
然后在动画循环中用PUT命令一次画出,效果好一些.
但数组变量的大小应该是多少呢?多了浪费,少了要出错误,QB的HELP里有这样
一个公式:
l = 4 + INT(((X2-X1+ 1) * (bpp) + 7) / 8) * Planes * (Y2-Y1 + 1)
X1,X2,Y1,Y2是图形块的左上和右下角坐标,bpp和planes与屏幕模式有关:
Screen Mode bpP Planes
9 1 4 (if > 64K of EGA memory)
12 1 4
13 8 1
计算出的L是字节长.而在QB里的数字变量没有单字节的,最短也是2字节
的INTEGER.因此我们用L/2+1来表示INT数组长度.
其实也可以用其他类型的数组的.
需要注意的是,与其他语句不同,所有坐标必须都在屏幕的物理范围之内,
否则将会出错.
PUT语句默认是把GET语句记录的图象与要覆盖的背景进行XOR运算.
我们再复习一下各种逻辑运算:
NOT 取非,就是NOT 1=0,NOT 0=1
这里的1是二进制位,不是数字,数字应是-1,因为二进制1111...等于-1
AND 与运算,如果x AND y,只有两个数字都是1才等于1,否则为0
OR 或运算,只要有一个是1,结果就是1
XOR 异或,当两边不相等为1,否则为0.
XOR有一种非常有趣的性质,就是任意交换性,比如A XOR B=C,
则A XOR C=B,C XOR B=A....任意两个数交换位置都可以.
而且只要不是0,通常运算结果跟运算前不同.
这种性质给加密等操作带来很大的方便.
PUT语句后面可以加上各种符号,表示原图与背景进行的运算方式.
前面说QB模式是XOR方式,还有PSET方式(直接覆盖),PRESET方式
(取非后覆盖),OR方式(与背景取或),AND方式(与背景取与)
看看下面这段程序,我们把前面的程序加上背景,事实上大多数动画都是要有
背景的.
--- =====================================
SCREEN 12
CONST r = 20
COLOR 14
bpp = 1
Planes = 4
l = 4 + INT(((r * 2 + 1) * (bpp) + 7) / 8) * Planes * (r * 2 + 1)
l = l / 2 + 1
DIM cir(l) AS INTEGER
CIRCLE (200, 200), r
PAINT (200, 200)
GET (200 - r, 200 - r)-(200 + r, 200 + r), cir
CLS
FOR i = 0 TO 640 STEP 10 '画背景
LINE (i, 0)-(i, 479), 3
NEXT
LINE (0, 300 + r)-(640, 300 + r), 10
FOR i = 20 TO 300 STEP .5
x = i * 2
y = 300 - ABS(SIN(i * .2) * 200)
PUT (x - 20, y - 20), cir, XOR '试把这里改成其他符号,如PSET等.
t1! = TIMER
WHILE TIMER < t1! + .01
WEND
PUT (x - 20, y - 20), cir '默认是XOR,可以在保留背景的同时擦图
'LINE (x - r, y - r)-(x + r, y + r), 0, BF '原来的方法把背景也给擦 了
IF INKEY$ = CHR$(27) THEN END
NEXT
--- =====================================
我们发现,XOR的缺点是只要不是黑色就会变色,跟以前不一样了.
因此要做得好一点应该先把要被覆盖的背景用个数组存下来,
当下一帧时再把那个"备份"覆盖回去,缺点是闪烁感很强.
其实擦图动画永远解决不了闪烁的问题的,因为屏幕上总有一段时间
那个图形消失了,一亮一灭,这就造成了闪烁,无论速度多快,只要
屏幕上显示了不该显示的东西,人眼就能反映出来.
真正的解决办法是用双缓冲技术.这种技术把要画在屏幕上的东西事先
在内存中画出来,然后把整个页全部覆盖. 然而QB对内存的控制很严,
给你操作内存的语句很少也很慢,根本不能完成这种技术,而且所有
绘图语句也都不能用,要用位运算来完成.
其实象JAVA语言一样,这些限制的好处是安全性提高了,但JAVA本身就提供
了双缓冲. :(
替代的办法是屏幕页技术. 显示卡从硬件上来说,显示内存足够存放
很多页.可由于编QB时内存还很贵,通常的显示卡只支持模式9的双
屏幕页,这也是为什么我要讲模式9的原因.
模式9从颜色属性来说跟模式12一样,但发色数和分辨率要低得多.
由于分辨率是640X350,象素是长的点而不是方点,如果你画个正方形
就会发现变长方形了,这需要注意. 但CIRCLE会自动调整纵横比,画出来
总是圆的.
屏幕页的控制用SCREEN语句的第三,四个参数:
SCREEN ,,绘图页,显示页
注意前面那两个逗号少不得.默认两个页都是0.
画图时在绘图页上画,在屏幕上显示不出来,屏幕上显示的是显示页,
两页无关,这样用户就不会看见绘图过程.当绘图页画好后你当然可以
用SCREEN语句把绘图页和显示页交换,但实际上交换时可能会发生闪烁,
这是硬件的原因.因此我们用PCOPY语句把一页拷贝到另一页去,速度很快.
再看下面这个例子:
--- =====================================
SCREEN 9
CONST r = 40 '加大半径,如果你把以前的例子也加大半径就会发生严重的闪烁
COLOR 14
bpp = 1
Planes = 4
l = 4 + INT(((r * 2 + 1) * (bpp) + 7) / 8) * Planes * (r * 2 + 1)
l = l / 2 + 1
DIM cir(l) AS INTEGER, backup(l) AS INTEGER
CIRCLE (200, 200), r
PAINT (200, 200)
GET (200 - r, 200 - r)-(200 + r, 200 + r), cir
CLS
FOR i = 0 TO 640 STEP 10
LINE (i, 0)-(i, 479), 3
NEXT
LINE (0, 300 + r)-(640, 300 + r), 10
PCOPY 0, 1
SCREEN , , 0, 1 ' 显示第0页绘图,第1页显示
FOR i = 20 TO 300 STEP .5
x = i * 2
y = 300 - ABS(SIN(i * .2) * 200)
GET (x - r, y - r)-(x + r, y + r), backup '先把要覆盖的背景备份
PUT (x - r, y - r), cir '你可以用任何方法贴图.
PCOPY 0, 1
t1! = TIMER
WHILE TIMER < t1! + .01
WEND
PUT (x - r, y - r), backup, PSET '恢复背景
'LINE (x - r, y - r)-(x + r, y + r), 0, BF
IF INKEY$ = CHR$(27) THEN END
NEXT
--- =====================================
不错吧?一点闪烁都没有.
但QB做动画还是不成,因为没有透明色方式. 在动画中动的物体通常被称
为"精灵",精灵的背景通常是黑色,但如果背景不是黑色,用PSET就会很难看,
如果用XOR精灵的"身体"就会变色.这时就需要一种"通明色",贴上去只显示
身体,不显示背景. 很不幸的,DOS下的程序都很难完成这种操作,WIN下恐怕
也要DIRECTX技术的帮助,我原来曾想用汇编语言编函数,可后来一想,都WIN95
时代了,DOS就凑合用吧.
动画技术基本讲完了,一般的书上还有"字符动画",我觉得意义不大,
字符模式可以有4到8页,但不能用PCOPY,闪烁好象更厉害. :P
可以参考我的BATTOOL3.2版,我想很多站的DOS工具区都有这个软件.
里面有用字符动画做的一只小猪的动画,还是用批处理做的,改成QB
程序太容易了.
但是绘图技术不仅这些,它需要很多的数学知识. 比如我那本最早的BASIC
书里讲述了各种用矩阵变换计算各种二维三维图形,各种三维图形的做法等.
<电脑爱好者>几年前也连载过用QB做动画的教程,似乎是能做出来三国志的
片头动画(但效果可是....嘿嘿,能看出来是人就不错了)
希望在实际应用时多运用数学工具,先想出数学模型,在转换成QB语句.
下面我们要讲模块化编程了.
主要有两个语句:SUB 和 FUNCTION
SUB的作用是自己定义一个过程,以后在QB里可以象调用语句那样调用它,
或者说就是"自定义语句"或是"子程序".
FUNCTION的作用就是"自定义函数",可以象调用函数那样调用FUNCTION过程.
使用过程除了简化一段反复使用的程序外,它可以使一个复杂的工程问题
简单化,先解决"主要矛盾",把一些难解决的细节问题放到过程里.
在过程的内部的变量和其他过程,主程序间是没有关系的.
也就是说你可以在过程外边用for i=....循环,循环里面有个过程,
过程里面也有个for i=...循环,但这两个变量i是没有关系的.
这就是过程的优点,当程序很大的时候,变量很多,闹不好就用重复的名字,
很容易出错,可过程间变量名相同也没关系.
当然你也可以让某些变量成为"全局变量",比如在主程序里输入:
DIM SHARED I AS INTEGER
那么无论是那个SUB或者FUNCTION,只要有I,都是指的同一个I.
QB会对过程进行"隔离"处理,使每个模块看起来都很小,心理上
似乎觉得容易了. 切换各模块是用*F2键*.
首先我们来看FUNCTION,其实就是"自定义函数",但这个名称在老式BASIC
里已经用了,我也不知道FUNCTION中文是什么. :P
我们知道函数的特点是有几个参数,一个返回值.
比如SIN(X),X是参数,返回一个数是计算结果.
我们也来定义一个函数:
--- ============================
DECLARE FUNCTION a% (x AS INTEGER) '这行是QB自动加的
WHILE (INKEY$ = "")
PRINT myfunc(y%)
WEND
FUNCTION myfunc% (x AS INTEGER) '函数开始
STATIC c AS INTEGER
c = c + 1
myfunc = c
END FUNCTION
--- ============================
当你输入FUNCTION时QB就会自动开个新的屏幕,好象是个新的程序,
这就是模块化,模块间的关系只通过参数传递来联系.
函数的返回值放到一个特殊的变量中,变量名跟函数名一样,
myfunc=c的意思就是整个函数的结果等于c.
需要注意的是这个变量只能赋值,不能用myfunc=myfunc+1,否则
可能会出现很严重的后果,因为这种调用叫做递归调用,见后面的解释.
STATIC表示静态变量,在第二行开头加上个"'"注释掉,看看会怎样?
静态变量的意思就是这个变量在内存的位置始终不变,每次调用都保持上次
的值.也可以在FUNCTION a(..)的后面加上STATIC表示这是个静态函数,
所有变量都是静态的.你可以加上/去掉试试看有什么区别.
默认情况下每一次调用都不同,值也不同. 这样才能进行递归调用.
所谓递归就是自己调用自己的一种算法,在计算机编程里是一种很重要的
算法,很多数学问题(比如著名的河内之塔问题)必须要用递归.
为了安全,QB递归有次数限制的,超过要出错误提示,不会象其他语言那样崩溃.
我们用QB编个计算阶乘的函数: 阶乘的定义是 a!=a*(a-1)*...3*2*1
(叹号是阶乘符号) 由此可以知道 a! = a*(a-1)!, (a-1)!=(a-1)*(a-2)!...
看下面这个程序:
--- ============================
DECLARE FUNCTION jc% (x AS INTEGER)
PRINT jc(3)
FUNCTION jc% (x AS INTEGER)
IF x <= 1 THEN
jc = 1
EXIT FUNCTION
END IF
jc = x * jc(x - 1)
END FUNCTION
--- ============================
a就是一个阶乘函数,主程序只有一句 print语句,因此你完全可以在
立即窗里输入 ?a(9) 立刻得到9的阶乘是多少.
为了理解递归的过程,你可以在jc=x*...那行的前后各加一个PRINT X
看看递归调用的顺序.注意千万不能 PRINT JC,因为那就成递归调用了.
有时我们要反复的使用一段程序,每次使用都相似,但某些变量稍有不同,
我们就可以用SUB简化程序.
当你输入SUB xxx(参数表)时QB会自动换一个新的屏幕,并替你添好END SUB,
这是SUB结束命令.
DECLARE命令是过程声明,表示你使用了哪个过程很不好拼写?没关系,你也不用写 .
但当混合语言编程时必须写DECLARE,不过我们暂时用不着.
调用过程时直接写
xxx 参数表 ,不要括号,就跟其他QB命令一样.也可以用
CALL xxx(参数表) 调用
下面的例子说明了SUB的作用,因为反复调用了两次
看下面这个例子:
DECLARE SUB hos (xv!, yv!, zv!, z1!, z2!) '这行是系统自动输入的,不用输
OPTION BASE 1 '设置所有的数组的最低下标为1,而不是0
SCREEN 9, , 0, 1 '你可以试着把9后面的东西去掉,可以看出使用双屏幕页的 优点
WINDOW SCREEN (-100, -100)-(200, 200)
FOR z = 400 TO 0 STEP -25
COLOR 15 - (z MOD 14)
hos -100, 80, -50, z, z + 20
NEXT
WINDOW
x = -300
Xstep = 15 '此处可调动画速度
LINE (0, 0)-(640, 170), 13,BF
COLOR 14
DO
IF INKEY$ <> "" THEN END
LINE (0, 70)-(370, 150), 0, BF
hos x, 80, -50, 2, 24
PCOPY 0, 1
x = x + Xstep
IF x < -300 OR x > 800 THEN Xstep = -Xstep '碰撞效果
LOOP
DATA 50,100,50,150,150,150,150,100,50,100,100,75,150,100 '五边型坐标
SUB hos (xv, yv, zv, z1, z2)
DIM x1(7) AS INTEGER, x2(7) AS INTEGER, y1(7) AS INTEGER, y2(7) AS INT EGER
RESTORE
p1 = -zv / (z1 - zv) '第一个五边形
p2 = -zv / (z2 - zv) '第二个五边形
FOR i = 1 TO 7
READ x, y
x1(i) = xv + (x - xv) * p1
y1(i) = yv + (y - yv) * p1
x2(i) = xv + (x - xv) * p2
y2(i) = yv + (y - yv) * p2
NEXT
FOR i = 1 TO 6
LINE (x1(i), y1(i))-(x1(i + 1), y1(i + 1))
LINE (x2(i), y2(i))-(x2(i + 1), y2(i + 1))
LINE (x1(i), y1(i))-(x2(i), y2(i))
NEXT
LINE (x1(7), y1(7))-(x2(7), y2(7))
END SUB
如何?是不是很兴奋?自己也能做出这样的三维动画! 你可以结合PALETTE
等命令不断改变房子的颜色,让它更加奇幻一些.
OPTION BASE 语句后只能跟0或者1,当为1时数组定义a(n)相当于a(1 to n)
房子的画法很简单,七个点是房子截面的五个角再加一条横线,画两个五
边形再把对应顶点连起来就是个房子了.把这14个点分别用透视变换转成
三维投影再用LINE连线就出房子了. 因为三维的直线的投影仍然是直线.
第一个FOR循环画一串的房子,为了使前面的房子盖住后面的房子,是
倒着画的. 房子很不好看,因为把应该被挡住的线也画出来了.
三维遮掩技术很复杂,如果简单的点构成的图形可以用个数组存放每个点
的Z轴投影,只把最前面(Z最小)的点显示出来,但对于复杂图形或者以面
构成的图形,需要判断多边形(POLYGON)是前是后,这需要高等数学
的向量矩阵变换,还有填充技术也很复杂,鉴于读者如果学过这些知
识多半就不会看本文了,本文也就不细讨论.而且现在的三维动画
通常都直接使用DIRECT3D技术,用不着考虑这么多数学问题.
后面的动画是不断移动视点位置,造成移动效果.由于刚讲完动画,就不多说了.
由于两次画房子的操作,而虽然画的房子不一样,但画房子的方法是一样的,
于是我们就定义一个HOS过程,只要传递给过程房子两个五边形的Z坐标,
眼睛的位置就能显示出来同样的房子模型,甚至可以用WINDOW改变房子
的比例.
这个程序的主要思想是画动画和一串房子,画房子虽然很麻烦,但我们
可以把它扔到SUB里去以后再想,专心琢磨如何编动画. 由于QB对过程
的"封装",他们之间都是"隔离"的,每个模块都不大,用不着翻很多页
去找另一段程序了,只要按一下F2就成了. 而且由于你的过程与具体数据
间联系不大,这个模块以后的程序还可以再用,不用重新编.
这种编程方法就叫模块化设计, 我们老师教的是要"由上至下",就是
先编主程序,再编模块. 可我的习惯总是相反,因为有的模块可能不容易
编出来,要用另外的方法去做,因此还要翻回去改主程序,这样就会影响
到整个程序. 无论怎样编,采用模块化的思想将会使你编大程序的时候
不会觉得枯燥,更容易调试,不容易出错.
比如上面那个房子,我在主程序还没编好时就可以看看画出来的房子
是否正确,如果所有程序都混在一起,很难挑出来错在哪里.
而且编到一半就能看到效果,也解了一部分编程的枯燥,因为编程
虽然枯燥无味,但成功的喜悦是非常美妙的. :)
... 还没完哦!
还有事件与声音、游戏杆、键盘、MODEM控制,和错误与文件处理,
系统功能调用和混合语言编程,高级QB编程专题(Mouse,声卡,EMS内存,
目录处理.......我想把我的QB经验都写出来,然而.......
可我还连提纲都没想好哪!
主要是没有动力,写了没人看。:(
有人说多贴点例子,我原来贴过不少的,似乎是没人看。
... -> ==>> ___whc.yeah.net____\ ┬── ┬─┐
~~~ -> ==>> ___________________ `> ├─ ├─┘
..> -> ==>> [email protected] / ┴ ┴
... Press Alt-A to continue...
--- GoldED/386
@ version: Fake [NR]
! Origin: Beijing West-Point Porgrammer's BBS * 010-65233230 (6:650/2 7)
-- ※ 来源:.月光软件站 http://www.moon-soft.com.[FROM: 202.106.214.190]
|
|