<<葵花宝典>>2000黄金版 RLE Sprite指南

    技术2022-05-11  102

    <<葵花宝典>>2000黄金版翻译:SEVECOL


    RLE Sprite指南    作者:Jonathan Griffiths(jpg@wave.co.nz)

    介绍:

    Sprite是游戏中最常用的一种元素,只在最近才被3D多边形引擎所超越.聪明的sprite系统将给游戏带来很大的帮助.这有一篇短文章介绍一个智能很高,且很有效率的RLESprite系统.

    这里所讨论的sprite是指矩形的子位图,子位图中的一种任意的颜色可被定义成透明的.当渲染sprite到屏幕上的时候,被定义为透明色的象素并不绘出.sprite在场景的上面就产生了上面的效果.

    RLE Sprite是一种使向屏幕绘图高效和节省内存的保存sprite的方法.下面 的讨论将说明如何实现基于RLE Sprite的引擎.*所有的代码都是用C写的.其中有一些用到了JLib(一个可移植的图形库.),它们能很容易的改到别的图形库.这些文档都是我用JLib和我对于RLE Sprite的经验编写的.你可以检查代码的完整性.剪贴下来可以编译.*JLib可以在下面找到:

    ftp://x2ftp.oulu.fi/pub/msdos/programing/djgpp2/jlib_X-X.zip;

    你应该用不成比例的字体阅读文件中的图表,否则ASCII图表将会混乱.

    简单的Sprite:

    让我们从简单的sprite开始.最简单的sprite系统是用一个2维数组来储存 sprite中每一个点的颜色.一个太空船的sprite看上去会是下面的样子:

    XXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXX XXXXXXXXXXXXX XXXXXXXXXXX XXXXX 'X' = 颜色值0 (在本例中是黑的)XXXX XXXX XXXX ' ' = 其他的颜色 (Say Green)XXX XXXX XXXXXX X X XXXXXX X X XXXXXX XXXX XXXXXX XXXXX X X XXX XX XX XXXXXXXXXXXXXXXXX 一个15X14 Cheesy太空船sprite.*

    如果你用这太空船做sprite,你应当创建一个15X14的数组,并且按上面的图片颜色填充它,如下:

    #define SHIP_WIDTH 15#define SHIP_HEIGHT 14unsigned char ship_sprite[SHIP_WIDTH*SHIP_HEIGHT]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,...}

    你可一用以下的代码来绘制sprite:int x,y;int xpos,ypos;...

    for (y = 0; y < SHIP_HEIGHT; y++ ) /*图形的每一行*/for (x = 0; x < SHIP_HEIGHT; x++ ) /*这行上的每一个象素*/if (ship_sprite[y*SHIP_HEIGHT+x] != 0) /*透明?*/draw_point(xpos+x,ypos+y,ship_sprite[y*SHIP_HEIGHT+x]);

    我们画了sprite中的每一象素,如果像素不透明,我们就画它.检查是不是0要快于其它值,所以我们把透明色设为0.

    用着种方法可以画任意的sprite,还能检查点是否在屏幕中,但它很慢,不是个好方法.

    第一个问题是你必须去检查每一个像素,看看它是否是透明色,不是便画它.

    *让我们跳到理论的大陆一会儿,思考理论上画sprite的速度极限.我们选一个接近它的算法.

    画sprite最快的方法是不检查任何一个像素,并且能正确的画在屏幕上,我们一个接一个的画需要画的像素,然后停止.你没有浪费时间在循环和比较上.有一种方法能象上面所说的一样,我们称它为编辑了的sprite.

    编辑了的sprite实际上是程序,如下:

    void draw_spaceship(int x, int y){/* these commands draw the sprites solid pixels */draw_point(x+20,y+20,1);draw_point(x+21,y+20,2);draw_point(x+22,y+20,2);draw_point(x+23,y+20,1);...}

    *第二个问题是这方法是困难的和危险的.并且不能移植到所有的计算机上.

    然而这方法给了我们对于怎么样才能更快的画出sprite在屏幕上很大的启示.我们应该想办法去排除检查象素,只画不透明的像素.

    进入RLE Sprite

    RLE是Run Length Encoding.是一种简单的压缩重复数据的方法.它提供了很好的压缩/解码时间比.

    RLE用很短的代码代替重复的数据.上面的例子:

    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,

    用RLE压缩为:(22 0's),(2 1's),(13 0's),(2 1's),(6 0's)

    RLE要源文件有很多重复数据才能得到很好的效果,如果源文件很少甚至没有重复,那用RLE压缩后可能比源文件还大.

    我们选用RLE来保存sprite,它可以很容易的实现忽略透明的像素,我们再也不要检查每一个像素在我们画它们之前.让我们看看如何实现RLE Sprite

    首先,我们用RLE编码来保存sprite,然后:

    我们的RLE代码不要保存任何数据,只保存信息.如下:

    0,0,0,0,0,0,3,3,3,5,6,0,0,0,0,0

    我们保存为:(blank 6),(solid 5),(blank 5)

    当我们解释我们的RLE代码时,我们得到每个像素的确切值从原始的数据中,我们要开始一个新的RLE序列对应sprite的每一行.(这能帮助我们用裁剪,下面会讨论)

    我们可以通过下面的代码画出sprite的一行:

    while 代码没结束得到下一个值

    if 值 is blankskip count pixelselsedraw count pixelsendend

    代码和RLE有所不同,第一个代码我用了两个字节:is_solid,length.如果is_solid是0,则是连续lenght个blank,否则是连续个颜色.我还有个更好的代码,它不用判断是否为blank.

    新的代码用一个字节来存颜色的信息和重复的次数.低4位.第5位为非透明色是置1,其它的3位没用上(7,6,4),它最多能存16个重复的像素.再长的就要用更多的字节来存储.这种方法的原因和速度下面再解释.:

    原始数据: 0,0,0,0,0,0,3,3,3,5,6,0,0,0,0,0RLE (blank 6),(solid 5),(blank 5)RLE 改进实现: 6, 5 | 32, 5最后RLE代码: 6, 37, 5

    为了更好的用RLE我们存的时候加入RLE代码的起点,最后这行代码输出为:

    最后的RLE 3,6,37,5

    第一个实现的步骤是从sprite数据中建立RLE数据.基本的算法:

    for each line in the spritewrite a dummy value for the number of runs in this linewhile there is data leftif data is solidcount until not solid, or no data, or run len > 16elsecount until solid, or no data, or run len > 16endput code and countendwrite the real value for the number of runs in this lineend

    你应该设一指针指向每一行RLE数据的起点.这样你可以更好用裁剪.

    ptr Pointer to RLE line pointers for each line

    |XXOXXX ptr-------------->(Num Codes,Code,...XXOOXX ptr-------------->(Num Codes,Code,...XOOOXX ptr-------------->(Num Codes,Code,...XOOOXX ptr-------------->(Num Codes,Code,...XOOOXX ptr-------------->(Num Codes,Code,...XOOOXX ptr-------------->(Num Codes,Code,...XXXXXX ptr-------------->(Num Codes,Code,...X by Y Array of data RLE codes for each lineJLib的内部函数generate_RLE()可以完成这些.你像上面那样建好了RLE数据,画出它就可以用下面的代码:

    for (y = 0; y < height_of_sprite ; y++){Read the number of RLE codes in this linewhile RLE codes remainif bit 5 of the RLE code is setdraw (RLE code & 15) pixels to the screenendadd (RLE code & 15) to "foo"endend

    它看起来并不比上个版本快,是的.*

    让我们假定我们可以直接用指针在屏幕上画点.假设256色的显示*

    我们的RLE代码让我们可以不要检查是否是透明色如果我们用switch语句按下面的宏:

    #define SPR_STENCIL_COPY(d,s,l) { /switch(l){ /case 31: d[14] = s[14]; /case 30: d[13] = s[13]; /...case 17: d[0] = s[0]; /} /}

    这里"l"是RLE代码,如果l小于16则什么都不做,否则画多个点在屏幕上(不用循环),这样就可以节省很多的时间.我们的sprite这样实现:

    Setup a pointer "foo" to the X by Y array of datafor (y = 0; y < height_of_sprite ; y++){Setup a pointer "bar" to the screen xpos,ypos+ line YRead the number of RLE codes in this linewhile RLE codes remainSPR_STENCIL_COPY(bar,foo,RLE code) { /add (RLE code & 15) to "foo"add (RLE code & 15) to "bar"end

    聪明的读者以经注意到我们可以省去每一行最后的blank,它们什么都不做,这样有能省不少:-)在JLib中你能画出sprite的背景,而我们现在的还不能.

    好了,我们的sprite已经有很高的效率了.

    一个问题有在我们眼前,如何裁剪?如何把sprite的一部分画在屏幕上?当sprite走出屏幕,我们应当如何画出sprite应该被看见的部分?

    裁剪总是不能被排除的.看看下面的代码:if ( (sprite_x + sprite_width < 0) /* Off LHS of screen */| (sprite_x > SCREEN_WIDTH) /* Off RHS of screen */| (sprite_y + sprite_height < 0) /* Off TOP of screen */| (sprite_y > SCREEN_HEIGHT) ) /* Off BOTTOM of screen */return; /* Don't draw it */end从上面的循环可看出Y方向的比X方向的简单一些.裁剪能公平的加入消耗给画出sprite.*

    怎样决定sprite是否裁剪:#define CLIPPED_X 0x1#define CLIPPED_Y 0x2int clipped=0;if (sprite_y > + sprite_height > SCREEN_HEIGHT) /* Hits screen BOTTOM? */clipped = CLIPPED_Y;}if (sprite_y < 0) /* Hits screen TOP? */clipped = CLIPPED_Y;}if (sprite_x > + sprite_width > SCREEN_WIDTH) /* Hits screen RIGHT? */clipped |= CLIPPED_X;}if (sprite_x < 0) /* Hits screen LEFT? */clipped |= CLIPPED_X;}/* If not clipped, use a faster non-clipping function */if (!clipped) {draw_sprite_without_clipping(...);return;}

    If a sprite is clipped only in the y direction, we can clip the sprite byaltering the outer Y loop:

    if(!(clipped & CLIPPED_X)){if (y < 0)top_lines = -y;elsetop lines = 0;Setup a pointer "foo" to the X by Y array of data + top_lines * data_widthif (bottom needs clipping){max_y = SCREEN_HEIGHT;elsemax_y = height_of_spritefor (y = 0; y < max_y ; y++){Setup a pointer "bar" to the screen xpos,ypos+ line YRead the number of RLE codes in this linewhile RLE codes remainSPR_STENCIL_COPY(bar,foo,RLE code) { /add (RLE code & 15) to "foo"add (RLE code & 15) to "bar"endendreturn;}

    Y方向的裁剪几乎和不裁剪一样,只是多了*有几种方法处理X方向的裁剪,最简单的在X裁剪是用原始的算法检查每一个像素.它是能够被改进的.

    另一个方法不管是X方向还是Y方向的裁剪都要比上面的要快.如下:

    如果左边被裁剪,我们要算出每一行有多少像素在屏幕的左边,可以略过这些点,画这一行下面的像素.

    如果右边被裁剪,我们算出第几行在屏幕的边上,就可以画其他在屏幕上的线这要比原始的X方向的裁剪算法复杂不少.

    width = width_of_spriteif (x < 0) {left = -x;width+=x; /* decriment width */}elseleft = 0;Setup a pointer "foo" to the X by Y array of data + leftif (rhs needs clipping)width+= SCREEN_WIDTH-rhs_xposfor (y = 0; y < height_of_sprite ; y++){Setup a pointer "bar" to the screen xpos,ypos+ line Ywhile width--if(sprite_data_array_value != 0)*bar = sprite_data_array_value; /* draw the point */bar++;endend

    X和Y方向的裁剪只需把上面的两方面组合就行了.你按上面的代码再结合JLib就能写出你的RLE Sprite引擎了.其中X方向的裁剪最重要.记住你的引擎要在sprite的下面留一些地方,这样可以移动*

    /*+------------------------------------------------------------------------+*//*|Draw a sprite in a buffer without clipping | *//*+------------------------------------------------------------------------+*/void buff_draw_spriteNC(sprite_system * ssys, int snum, buffer_rec * obuff){int frame = ssys->sprites[snum]->frame, x = ssys->sprites[snum]->x;int y = ssys->sprites[snum]->y, bwidth = B_X_SIZE(obuff) -ssys->sprite_data[frame]->width,height = ssys->sprite_data[frame]->height;    UBYTE *pattern = ssys->sprite_data[frame]->pattern, *data =ssys->sprite_data[frame]->data,    *out = B_OFFSET(obuff, y) + x;    JLIB_ENTER("buff_draw_spriteNC");    while (height--) {        int width = *pattern++;        while (width--) {            UBYTE datum = *pattern++;            SPR_STENCIL_COPY(out, data, datum);            out += (datum & 15);            data += (datum & 15);        }        out += bwidth;    }    JLIB_LEAVE;}


    Sevecol翻译于1999.10.18*表示翻译的不好,瞎翻译 ^-^由于本人英语实在是不怎么样,本版本存在大量bug,心脏病和高血压患者请勿阅读:-)


    最新回复(0)