程序员入门:QBASIC经典教程系列——最吸引人之绘图

今天我们讲最吸引人的绘图&动画中的绘图功能….

讲绘图前,我们先讲一下QB的循环命令:

FOR…NEXT循环是定次数的循环.
FOR 变量=初值 TO 终值 STEP 步长
STEP步长可省略,默认是1,就是每循环一次加一.
循环的结尾用NEXT表示,我们通常用缩进格式防止FOR…NEXT不配对.比如:

FOR I=A TO B
    FOR J=C TO I STEP 10
        ....
    NEXT
    FOR...
    NEXT
NEXT

在QB里也支持象老式BASIC那样的NEXT I,J,K…一次代表多个FOR结束,但不推荐这样,容易出错.

不定次循环最简单的是WHILE …. WEND,称做"当循环".比如下面是个表:

WHILE INKEY$=""
    LOCATE 10,20
    ? time$
WEND

当INKEY$函数为空(没有按键盘),就继续循环,否则就跳到WEND后面。QB新加的循环是DO…LOOP循环.如果光是:

do
    locate 10,20
    ?time$
loop

那永远也退不出来,只能按CTRL-BREAK终止.所以要加上终止条件,有WHILE和UNTIL两种,WHILE是当后面的条件为真循环,UNTIL则正好相反,直到后面为真才退出.可以加在DO后也可加在LOOP后.比如

DO WHILE INKEY$=""
    LOCATE 10,20
    ? time$
LOOP

就跟第一个例子一模一样了.如果加在LOOP后面有什么区别呢?看下面这个例子:

I=0
DO
    ?"进入循环"
LOOP UNTIL I=0

如果你把UNTIL I=0加在DO的后面试试看.也就是说,LOOP UNTIL(WHILE)无论什么条件都要执行循环体至少一次.还有一种终止循环的方法,EXIT命令. 在循环体内部的任何地方,只要用EXIT DO就能退出DO循环,跳到LOOP的后面.此外也可以用EXIT FOR退出FOR循环…. 注意没有EXIT WHILE.

呼~该说图形了。想当初刚学会BASIC,刚用到新PC机,发现居然只能用4种颜色320X200的分辨率,连苹果机都不如,可明明波斯王子却画得那么漂亮,…….就不忆苦思甜了。 😉 就在那个分辨率下,我的BASICA书里居然做出来各种漂亮的二维三维图形和动画,下面有些例子就是从那里抄的,改成VGA的分辨率。先说一些计算机图形的知识。 显示卡可以以很多工作方式显示图象,每一种叫做一种显示模式,有些高手也能自己创造新的显示模式。每种显示模式有不同的分辨率,发色数,配色数,这我们在用WIN95时就很清楚了。

显示模式主要分为文本模式和图形模式两大类。我们只介绍VGA兼容模式。QB用SCREEN 命令设置显示模式。以前我们都用的是文本模式3,80X25字符,16色前/背景。用QB表示出来就是:SCREEN 0 ‘QB里称做模式0,是唯一的文本模式WIDTH 80 ‘设置宽度为80,也可设置成40。一般可以省略如果在其他模式画完图不运行上面这两行,退出程序后可能会很不好看。但一般来说QB会恢复默认模式的.

由于过去的技术落后,发色数和分辨率不能两全。QB也是如此.本文只介绍QB的三种模式:

SCREEN 9:640X350 X16配色器,64个发色数,双屏幕页,用于屏幕页动画可用于EGA显示器。

SCREEN 12:640X480 X16配色器,64的三次方发色数,是日本RPG常用的模式。

SCREEN 13:320X200 X256配色器,64的三次方发色数,是C&C的显示模式。 🙂

在96年以前,绝大多数游戏都用模式13,系统模式是13H,是用颜色换分辨率的一种手段,也是“金山影霸”比当时的XING1.3要快的原因。

什么叫做配色器呢?看下面这个程序:

?"TEST"
FOR I=0 TO 63 '文本模式只有64种发色数
    PALETTE 7,I
NEXT

大家知道计算机是用显存保存图象、图形信息的,在相同分辨率下,表示的颜色越多需要的内存也越多。

比如640列真彩色需要640x480x256^3/8字节的内存,是个相当大的数字,过去的CPU不能承受,因此用配色器的办法,存在显存里的只是颜色属性,显示卡自动从颜色属性代表的配色器里取出真正的颜色(真彩色),再显示在屏幕上。

这种方法不仅提高了速度,而且为编程者提供了一种编程方法,只要改动一个配色器的值就可以把屏幕上所有此属性的象素该成那种颜色,WIN95的启动画面就是用这种方法做的“动画”。

我们整理一下名词定义:

— 属性:存在显存里的数值,画图时直接用,比如以前我们见过的COLOR命令,后面跟的参数就是属性,而不是真正的颜色,真正的颜色取决于:

— 配色器:每个属性都对应一个配色器,其多少是由显示模式决定的。里面保存的是此属性真正的颜色代码。用 PALETTE命令改变。这里沿用EGA的名称,在VGA硬件里称做DAC寄存器。SVGA通常有24BIT DAC就是所谓24比特真彩色了。

— 最大发色数:表示真正的颜色代码的最大值,取决于显示模式。

当然,在SCREEN换模式时系统会自动把各配色器置默认值,所以通常我们总把属性0当作黑色,其实用PALETTE 0,X可以把背景改成任意颜色。

我们可以一次改一个配色器的属性:

PALETTE 属性,颜色代码(以后简称为颜色)。

但速度太慢,也可一次把所有配色器都改了:

PALETTE USING 颜色数组

颜色数组通常是一个LONG型的数组,可以有下标,也可以省略。比如PALETTE USING A&(13)表示从A&(13)开始n个变量表示配色器,n就是发色数。 也可以用PALETTE USING A&表示从A&(0)开始的n个配色器。

配色器的颜色代码是这样:

模式0和9都是64最大发色数,用个整数就能表示。它是按二进制数表示的:R’G’B’R G B 共六位,RGB就是红绿蓝了,加个撇代表亮度高点的RGB。二进制100100就代表最亮的红色(亮红+红=特亮的红),转成十进制就是32+4=36,在立即窗里输入PALETTE 0,36就能看到…..用不带参数的PALETTE可以恢复原来默认的配色器设置。默认是:

0黑 1深蓝 2深绿 3青 4红 5紫红 6棕 7浅灰
8深灰 9蓝 10绿 11浅青 12亮红 13亮紫 14黄 15亮白

发现了么?是按二进制IRGB的规律排列的,I是亮度位。

模式12和模式13是64的三次方种发色数,用三个字节表示,只能用LONG型变量。计算方法是:

C&=G &H10000 + B &H100 + R

注意,RGB的顺序颠倒过来了,是BGR。R,G,B分别是红绿蓝三个分量的值,范围是0-63。这样的颜色数远超过高彩色(65536)了,可惜不能同屏显示出来。但要显示一张模糊的照片还是可以的。 🙂

C&=G &H10000 + B &H100 + R

这是什么东西,看不明白. ;( 十六进制是两个数表示一个字节,比如FF就是255,是一个字节.而FFFF是两个字节,是65535. 这在一些游戏攻略上经常能见到.G是绿色,B是兰色,R是红色,各占一个字节,表示一种颜色.要把它们合在一起,就把第二个字节乘以&h100,第三个乘&h10000,这跟十进制乘法很象的.

关于模式12,在第一部分就讲过了,请读者回去看看,试试能不能用配色器把那个图形弄得更奇怪一些。 🙂

模式13是最有用的模式,因为它的配色器最多,同一屏幕显示的颜色曾经是最多的,所占显存又远小于模式12,因此适用于此模式的GIF格式图形曾经特别流行,直到现在真彩色模式的应用才使JPG普及开。看下面这个程序:

SCREEN 13
DIM pal(511) AS LONG

FOR i = 0 TO 255 STEP 2
    LINE (0, i)-(639, i * 2), i / 2, BF
NEXT
FOR i = 0 TO 511
    pal(i) = (i MOD 64) * &H10000
NEXT
j = 0
WHILE INKEY$ = ""
    PALETTE USING pal(j)
    j = (j + 1) MOD 256
WEND

运行时你会发现其速度之慢不可忍受,这是QB的一大BUG,于是我们用下面这段代码替换PALETTE那行,速度就快多了:

FOR i = 0 TO 255
    OUT &H3C8, i + j
    OUT &H3C9, pal(i) AND &HFF
    OUT &H3C9, (pal(i) AND &HFF00) \ &H100
    OUT &H3C9, (pal(i) AND &HFF0000) \ &H10000
NEXT

计算机的各种设备都连在设备总线上,都有一些端口用于操作。OUT是对计算机的端口进行操作的命令。前面是端口号,后面的是数据。端口&h3C8和&h3C9是DAC寄存器,&H3c8是索引,指出要操作哪个寄存器,&h3C9是数据寄存器,第一次是B,第二次G,第三次R。如果用INP函数可以读出DAC寄存器的值,比如:

OUT &h3C8,1
?"BLUE:";INP(&H3C9)
?"GREEN:";INP(&H3C9)
?"RED:";INP(&H3C9)

可显示属性1的各分量。上面那段程序仍然不是很快,没办法。以后我们讲混合编程时也许可以用汇编语言混合编程来解决。事实上,如果要用QB做比较快速的动画,通常要汇编语言的帮助,但你不并需要学习汇编,有现成的函数可用。

注意:由于端口号不同,模式9不能使用上面的方法,因为配色器少,使用PALETTE足够了。

我怎么知道哪个端口是干什么的呢,DAC是什么东西.这点有点乱.这就有点复杂了,要查资料,否则我也不知道.可以买本硬件的书,但你不懂汇编语言估计看不懂.怎么给你写的你就照抄就完了,不必管它是怎么实现的,把那一大堆语句当一句PALETTE就成了. 🙂

说了这么半天,还没讲怎么画图呢。别着急,还要先说一下坐标系统:QB默认以屏幕左上角为0,0点,右下角为最大位置,由分辨率决定,比如对于分辨率是640X480的模式12就是 (639,479)。这跟习惯上的笛卡尔坐标系不一样,Y轴方向反了。如果画图时超过这个限制会自动截断。QB本身提供了一些坐标变换和放缩的命令转换坐标。看下例:

SCREEN 12
'WINDOW SCREEN (-10, 0)-(100, 100)
'VIEW (0, 10)-(100, 100)
LINE (110, 120)-(130, 140), 3, B
LINE (40, 40)-(60, 60), 3, BF
CIRCLE (0, 0), 40, 10
PAINT (0, 0), 14, 10

输入后先运行一遍,再把WINDOW前面那个撇去掉试试,再把VIEW前面哪个撇去掉试试,最后把WINDOW后面那个SCREEN去掉试试还有可以去掉前面WIDTH那个撇号看看效果。

WINDOW命令是调整屏幕坐标系统用的。如果后面没有跟着SCREEN则会把后面坐标中小的当成左下角,跟笛卡尔坐标的习惯相同,否则还跟以前一样,小的是左上角。WINDOW能把显示区域移到物理屏幕外,并且把逻辑坐标放缩成物理坐标。具体的你可以改改上面那个例子看看。我的书上是用图来表示的,我没办法给大家画出来。 🙁

VIEW命令是设置可见区域,刚才说了,QB把超过屏幕范围的图形自动删掉,而VIEW可以把超过指定范围的图形删掉,好象在屏幕里做个小屏幕。

VIEW的语法定义是:

VIEW(x1,y1)-(x2,y2),颜色,边框颜色

颜色是在VIEW框内的颜色,边框颜色是框外的颜色。提醒一下,所谓颜色其实是属性,并非真正的颜色,要看配色器。没有任何参数的VIEW和WINDOW就能恢复原坐标系统,但画上的图是改不了了。把WINDOW语句虚拟的坐标系叫做逻辑坐标,系统本身的是物理坐标,用PMAP函数可以转换逻辑坐标和物理坐标,看HELP吧。 😉

我想通过上面这个例子,你可以理解怎样用LINE画实心和空心方块了。画圆是CIRCLE,也可以画弧和椭圆,方法是:CIRCLE (X,Y),半径,颜色,起始角度,结束角度,纵横比两个角度都是用弧度表示的,纵横比是画椭圆时用的,可以自己试试看。由于模式9和13的屏幕纵横比不是4:3,画出来的圆可能是椭圆,如果不指定,QB会自动调整纵横比,但是画出来的方块可就变成长方形了,需要自己算,或者用WINDOW命令调整。

想起一个问题,画扇面的程序我在学校编的时侯不太正常,斜线不是正好连接了两个角,有点歪.是不是学校显示器的问题?这就是QB画圆命令的"纵横比"的问题,由于你用的显示模式的每个象素不是1:1的,是长的,圆的纵横坐标也不相同.简单的解决的办法是用SCREEN 12.

PAINT是填充图形,PAINT(X,Y),填充色,边框色如果省略边框色默认是和填充色一样的。QB的填充方法跟PHOTOSHOP可不一样,它是寻找边框色而不是寻找不同颜色.如果没遇到边框色,会把整个屏幕都充满,有时遇到一些图形却不能充满,比如两个圆组成的环,你可以试试看。PAINT还可以填充花纹图形。我觉得实际用途也不大,而且要了解显示屏幕页面的知识,在M$的手册里讲了4大篇,我就懒得说了。在QB的范例里有个EDPAT.BAS文件,是示范如何使用花纹填充的。无论是QB还是MSBASIC,都附带许多示范,虽然做的挺漂亮,但不容易看懂。你可以运行试试看。

下面是我高中时在GWBASIC上编的一个程序,去了行号,是关于花纹填充的。

DATA 255,75,75,123,255,149,149,221,255,106,106
DATA 239,255,253,253,255,255,38,38,231,255,25
DATA 25,127,255,100,100,124,-1

SCREEN 9
PALETTE 0, 1

w:

READ a
IF a = -1 THEN GOTO E:
a$ = CHR$(a) + a$
i = i + 1
GOTO w:

E:

PAINT (12, 12), a$

由于GWBASIC不是结构化语言,上面这个程序编的不好,用了GOTO语句,其实完全可以用WHILE代替,读者自己改成WHILE吧。

READ命令是读取DATA语句存放的数据。如果有大量数据一般放到单独的文件里,但速度较慢。如果少量数据,不如就用DATA命令。DATA命令后各数据间用逗号分隔,写多少行都可以,QB认为他们都是连在一起的。DATA可以放到程序中任何位置,QB自动跳过。

DATA命令的存放方式是字符串,即使编译成EXE文件后也能用PCTOOLS等“看见”文本状态的数据。因此不能用引号,因为引号代表字符串,但没有引号也代表字符串。由READ命令判断输入数据是数字还是字符串。如果光输逗号则被认为是空字符串。

READ语句是顺序读取数据的,读到最后如果没有数据则会出错。READ语句可以同时读取不同类型的几个变量。可以用RESTORE语句恢复起始位置,从头开始。RESTORE命令后也可加行号,表示恢复到某一行。比如:

data1:
DATA 2,2

data2:
DATA &HFF,3,a,4,5,b

... ...

RESTORE data2

READ a,b,c$
? a,b,c$

输出结果:
255 3 a

其中前两个是数字,最后一个是字符串。

上面我们解决了二维变换的问题,如果程序需要放大缩小旋转等操作一般还是用数学方法来解决,这里就不多讨论了。

下面我们讨论三维图形:

SCREEN 12
xu = 350
yu = 80
zu = -30
z = 10 'Z的初值
WIDTH ,30 '你可以把这里换成60试试。模式12的文本模式有80X30和80X60两种

'注意逗号不可少,因为第一个参数是列,第二个才是行.
COLOR 13
LOCATE 27,1
PRINT "3D test"
COLOR 9
DO
  p = -zu / (z - zu)
  FOR x = 0 TO 1000 STEP 2 '步长值的大小决定画出来的速度和质量,改改看
    y = 10 * SIN(.05 * SQR(x * x + z * z))
    x1 = xu + (xu - x) * p
    y1 = yu + (xu - y) * p
    PSET (x1, y1)
  NEXT
  z = z + 5
LOOP WHILE INKEY$ = ""

三围….咳….三维变换公式是:

p = -zu / (z – zu)
x1 = xu + (xu – x) p
y1 = yu + (xu – y)
p

其中x,y,z是要显示象素的三维坐标,xu,yu,zu是视角(就是你的眼睛)的坐标,因为把Z轴方向指向屏幕里,所以你的眼睛的Z坐标是负的。X,Y坐标的指向跟以前一样。而x1,y1就是投影变换以后的屏幕坐标了。原理我不说了,通过这个公式可以把空间上一点投影到屏幕上,让眼睛感觉上是立体的。y = 10 SIN(.05 SQR(x x + z z))是一个三维曲线函数,就是你所看到的那个波浪图。

PSET是画点,后面可以加颜色,比如 PSET(10,10),10 但我省略了,QB按前面COLOR命令设的默认颜色画。实际上所有绘图命令都可以省略颜色。在图形模式下打印字跟文本模式一样只是文本分辨率不同。模式9还是80X25;模式12有两种,一个是80×30一个是80×60,可以用WIDTH设置;模式13只有40X25,而且返回模式0后仍然保持40X25,比如要用WIDTH 80改回80X25。打印的颜色取决于COLOR命令与PSET对应的还有一个PRESET语句,是用来擦点,我觉得没什么用。

我们以前已经见到过COLOR命令了,是在文本模式下。在模式9里背景颜色是整个屏幕的背景,只要一改整个全改,而不是个别字符。

在模式12和13里根本不能用背景颜色这项,COLOR命令只能有一个参数就是前景色。因此如果在上面写字的话文字的背景色一定是黑色的,除非用PALETTE改配色器。

绝对坐标和相对坐标:以前所用的都是绝对坐标,是相对(0,0)的坐标,QB也可用相对坐标,在每个坐标前加STEP就成了,这时括号里的坐标是在上一次绘图的坐标基础上移动的数值。

还有个绘图语句是DRAW,它是一种小型的语言,用字符串表示,有点象LOGO语言。看下面这段程序:

SCREEN 12
DRAW "bm200,100" '把绘图点移动到200,100,前缀b代表不画
DRAW "c1;s50;" 'c后跟的是颜色,S后是比例,可以改改比例看看。
FOR r = -360 TO 360 STEP 144
    DRAW "TA" + STR$(r)
    DRAW "r10"
    SLEEP
NEXT

SLEEP是按任意键继续的命令,后面可以加个数字,表示等待多少秒后继续,如果不加就只能永远期待你按键盘了。关于DRAW的绘图命令请自己看HELP吧,我认为没什么用,很少用过。比如下面这段程序是我刚翻出来的,是高中时编着玩的:

CONST pi = 3.1415926535#

SCREEN 12
VIEW (1, 1)-(280, 191), , 1
VIEW
PAINT (319, 199), 1, 1
a = 20 - 180
l = 90
x = 90
y = 55
PSET (x, y)
FOR i = 1 TO 9
  SLEEP
  a = a - 20 + 180
  y = y - l * SIN(a * pi / 180)
  x = x + l * COS(a * pi / 180)
  LINE -(x, y)
NEXT
LOCATE 20: PRINT "Copyright (c) W.H.C": PRINT "corporation"
SLEEP

通过这个九角星的程序能进一步理解VIEW和LINE语句的用法。LINE语句是可以省略第一个坐标参数的,这时就以上次画的最后一点为起点开始画。

以上就是QBASCI的绘图语句,留个"作业"吧,前面有钟表的例子,请用绘图命令编个漂亮点的钟表。