TI-BASIC 计算器游戏开发之文字、图形、音频教程 II:图形处理
【第二部分】
TI-BASIC 图形处理教程
还没到结束的时候,我们查看一下内存,就会发现,一个8*8的汉字,用点阵数据保存,只需要 8 byte,但是用行列坐标数据保存,行坐标和列坐标都需要好多个 byte 来保存,这就是空间换时间的策略,不过这个行列坐标确实太占地方了,我们看看怎么把它再压缩一下,不妨继续粗暴一下,把这个已经显示到屏幕上的汉字截个图如何?反正就那几个点,这里要用到 TI-BASIC 的另一个函数 StoPic ,专门保存屏幕图形的,用法如下:
StoPic picName,raw,col,weith,heigth
意思就是把计算器屏幕上,先找到坐标为(raw,col)的点,从这个点出发,向右宽度为 weigth,向下高度为 heigth 的矩形区域内的图形保存到图形变量 picName 里,现在查看一下内存,看看这个 picName 只占了很少的几个 byte ,你就会觉得我们这么从点阵图到坐标值,再从坐标值到图形变量折腾来折腾去还是有所收获的。以后就可以通过直接调用这个图形变量来显示这个图形。
这样我们就可以把游戏中用到的汉字先转换为行列坐标列表数据,再转换为图形变量数据,就可以在程序中使用汉字了,这其实也是一种适合TI-89T下显示汉字的解决方案。
【局限性】只能在你自己的程序中显示汉字,没办法在TI自己的程序中显示汉字菜单,没怎么研究过汉化技术,理论上应该是可以的,因为程序菜单显示英文其实也是调用的英文字符的点阵图形,不过在TI计算器上搞估计比较麻烦,所以就不深入研究了。
简单说一下在屏幕上显示图形变量的函数,TI-BASIC提供了多个函数来通过调研内存中的图形变量来显示图形,有RlcPic,RplcPic,XorPic,AndPic,OrPic,这些函数的功能有细微的差别,具体可以查看TI-89T的手册,我们这里会用到RlcPic,RplcPic,XorPic这3种。
先说RplPic,用法如下:
RplPic picName,raw,col
上面的语句直接把计算器内存中保存为 picName 的图形绘制到左上角坐标为 (raw,col) 的区域内,那么右下角坐标就是 (raw + heigth , col + weigth),所以在用这条语句时一定要注意计算好位置,免得因为你的坐标值超过了屏幕范围而出错。
用这个函数可以把你指定的汉字图形显示在屏幕上的任何位置,等等,任何位置?那么如果用循环语句来让raw和col递增会如何呢?聪明,递增或者递减都会让汉字图形在屏幕上动起来,这个就是动态图形的基本原理,短时间内让指定图形不停地在不同的位置出现,那么我们用循环来试试,代码如下:
先来一个向右平行移动的
For i,1,30,1 RplPic picName,raw,col+i EndFor
哈,是不是发现图形经过的位置都变成一道黑影了,为什么会这样呢?因为你只是把图形画到一个位置上,然后改变坐标值,再把它画到新的位置上,但是你没去管原来位置的图形,所以只需一会儿功夫你的屏幕就被画满了,怎么办?先把原来位置的图形擦掉,再画到新位置,然后再把新位置的图形擦掉,再画到下一个新位置,这样就可以实现清晰的动态图形效果了。
这里需要用到擦除图形的函数,TI-BASIC提供了两个类似的函数: PxlOff 和 XorPic ,一看就明白,PxlOff 是操作像素点的,XorPic 是操作图形变量的,我们现阶段需要的是 XorPic,XorPic 有个好处,就是第一次执行时是画图,第二次在同一个坐标执行时就是擦图了,也就是说原来的位置如果有图,就擦掉它,原来位置如果没有图就显示它,是不是不太好理解?没关系,实际执行几个例子就清楚了,具体用法如下:
XorPic picName,raw,col
那么上面的例程就改成这样:
向右平行移动,raw不变,只要把col的值循环增加就可以了
For i,1,80,1 XorPic picName,raw,col+i XorPic picName,raw,col+i EndFor
向下平行移动,col不变,只要把raw的值循环增加就可以了
For i,1,80,1 XorPic picName,raw+i,col XorPic picName,raw+i,col EndFor
向左平行移动是raw不变,把col循环减少:
For i,1,80,1 XorPic picName,raw,col-i XorPic picName,raw,col-i EndFor
向上平行移动是col不变,把raw值循环减少。
For i,1,80,1 XorPic picName,raw-i,col XorPic picName,raw-i,col EndFor
有人希望能斜着移动,这个问题提得好,斜着移动也有4个方向,只要同时改变raw和col的值就可以了,那么怎么写程序呢?有人想用两个循环,嘿嘿,那就错了,仔细想一下就明白为什么错了,实际只要这样就行:raw+i,col+i,不过这么移动太单调了,你可以给raw增加的i值乘以一个系数,比如0.5,结果就是这样 raw+0.5*i,col+i ,程序就是这样的;
For i,1,80,1 XorPic picName,raw+0.5*i,col+i XorPic picName,raw+0.5*i,col+i EndFor
使用不同的系数就会以不同的角度斜方向移动。
又有人说了,你移动来移动去都是直线移动,能不能搞个曲线移动,比如迫击炮的炮弹就是抛物线的运动轨迹,地球绕太阳那是椭圆的移动轨迹,很好,我只能说你非常有思想,让我们一起来思考一下,先在 For 的框架内实现,搞个抛物线的运动轨迹,很显然,需要抛物线的方程了,比如用这个最简单的 y = x*x ,我们知道前面的循环主要是拿循环变量 i 作为坐标值的增量(假设为j),也就是说直接使用了i = j ,然后raw和col分别增加i和j,注意这里用的是“+”,也就是说变化后的坐标值跟原值是一种线性变化,而且没哟考虑raw和col之间的关系,那么我们现在要让图形沿着抛物线运动,需要在raw和col之间建立一种对应变化关系,应该满足抛物线方程,具体说就是:
raw = col*col
或者
col = raw*raw
记住这时就不能简单地把增量加到坐标值上去了,我们可以验证一下,程序如下:
raw = col*col For i,1,80,1 XorPic picName,raw,col XorPic picName,raw,col EndFor
这里验证了 raw = col*col 的情况,还有 col = raw*raw 的情况,自己验证,不要偷懒啊!
嘿嘿,有人又提问了,上面提到的所有的移动都是保持图形点阵本身的行平行于屏幕,能不能让图形旋转着移动呢?很好,举一反三地提问我最喜欢了,我们先明确定义一下什么是图形的旋转移动,具体说就是点阵图形中的某一像素点的坐标不变,其他像素点以这个点为圆心,顺时针或者逆时针旋转同样的角度,注意这里作为圆心的像素点可以是有内容的,也可以是空白点,既然旋转问题的需求已经清晰地分析出来了,那么问题的解决方案也基本有了,像这种角度计算就不可避免地用到三角函数了,或者自己用距离比值来计算也可以。
一些高级语言会内置的这种旋转图形的操作函数,比如C语言,不过TI-BASIC貌似没提供这个功能,那么我们就自己写一个,经过上述分析,我们发现想要旋转图形,必须做到的就是计算每个像素点到圆点的距离,并提供一个旋转角度作为参数(包括顺时针和逆时针),然后每个点都以自己到圆心的距离作为半径,按照指定角度进行旋转。
好在需要画在屏幕上的是那些黑色点,空白点是不需要考虑得,因此可以节省一点计算量。当然了这个算法只是揭示这种图形变换的基本原理,具体实现时会有很多不同的选择,比如最常见的就是拿坐标数据跟不同类型的矩阵进行计算,来达到各种不同的变换效果,因为矩阵运算的算法不太直观,这里就不写了,感兴趣的拼音可以自行研究,我们的算法代码如下:
(实际还没写这段代码,后面补)
很好,现在对于这个图形的处理已经基本圆满了,什么?有人说我们搞了半天都是在二维平面上打转转,想做一个三维立体空间里的图形变换,太好了,看来我们的讨论又深入了,按照我们解决问题的一贯手段,先来分析三维空间的运动,首先是让我们的图形前进和后退,换句话说也就是放大和缩小,放大好办,比如8*8的图形,每个像素点扩充4倍,变成32*32,从一个“口”字型变为一个“田”字型,不光黑色点要扩充,白色点也要扩充,这样一个图形就变大了,看起来就好像是离它更近了,那么如果持续地做这种变化,带来的效果就是图形好像在朝我们走近一样。这种变化是三维空间变化的基础,其他方式的变换都可以在此基础上推导得出,再结合前面提到的各种平行移动、斜线移动、曲线轨迹移动以及三角函数旋转等方法,我们的图形想要实现各种三维空间里的移动就都不成问题了。
说到这里,想必有人也看出来了,计算机上所有的图形处理,说到底就是对图形坐标值的计算,只要预先设置好计算的方法,基本上就没什么神秘了,唯一限制的因素就是计算量的大小,因此程序员们想出了各种技巧来回避复杂计算,用简单计算来取代,以便提升效率。比如求三角函数时可以用查表法来代替计算,这种思想在人类历史上由来已久,在人类还没有发明电子计算机前,人类就把各种角度的三角函数值预先计算出来,然后印刷成书,等到使用时直接查表就可以获取到较为精确的数值,这样就避免了每次的计算。可见提升效率的思路是一致的,这种简化思路正是我前面提到过的“以空间换时间”,当然也可以逆过来用,以“时间换空间”。这里再给一个放大缩小的例子:
(实际还没写这段代码,后面补)
其他的各种三维空间的变换算法就不再一一列举了,自己钻研吧,最好能设计出高效的算法,写成函数,这样就能被其他人使用了,呵呵。
【更多的思考】另外还需要考虑鉴于这种计算的结果不完全是整数值,需要进行四舍五入后才能做为新坐标使用,如果有两个相邻点经过旋转计算的四舍五入之后重叠了该怎么办?(这就意味着图形被有损压缩了),下次再旋转时它们按照一个点来算还是按照两个点来算,如果按两个点来算,是否需要保存原来计算得到的四舍五入之前的数值?以便在下次计算时提供精确的输入,以避免这种因误差引起的信息损失。可见一种算法如果想要做得更完善,满足更多的意外场景,是没有止境的,
【提示】在软件开发的很多时候,如果我们能把一个功能需求清晰地描述出来,那就意味着这个功能需求的具体算法也出来了,或者当我们能够清晰描述出某些高难度的需求时,我们同时也会清楚这个需求是否能在现有系统上实现。是不是图形开始旋转了,很好,这就是图形旋转的数学原理,是不是觉得很简单?确实很多东西的基本原理都是非常简单的。还有无数的典型数学函数可以放到坐标增量来玩,比如什么对数函数、指数函数等等。
现在既然图形已经动起来了,我们稍微关注一下图形的动态效果,是不是发现移动得很慢,而且闪烁得很厉害,都快成了淡淡的灰影了,怎么办?简单,调整我们的参数,移动得慢是因为移动步子迈得小,把 For 语句最后一个参数 step 修改得大一些,图形就跑得快多了,图形太淡是因为画图和擦图的间隔太短,所以它在屏幕上就显示得太淡,唯一的办法就是画完图形之后不要马上擦,停留那么一小会儿,还以向右平行移动为例:
For i,1,80,1 XorPic picName,raw,col+i Loop u+1->u EndLoop XorPic picName,raw,col+i EndFor
这里我们在画图和擦图命令之间加了一个累加的循环,加到一定值之后停止循环,可以通过设置这个值的大小来控制中间的停顿时间。(也许还有其他更好的办法,期待有人给出)。
常用的图形操作就这些内容了,基本上掌握了这些就可以在TI-89T计算器上做出动画片头、动画效果来了,后面讲一下如何通过键盘来控制图形在屏幕上的运动。