分享
 
 
 

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

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

<<葵花宝典>>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可以在下面找到:

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

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

简单的Sprite:

让我们从简单的sprite开始.最简单的sprite系统是用一个2维数组来储存 sprite中每

一个点的颜色.一个太空船的sprite看上去会是下面的样子:

XXXXXXXXXXXXXXXX

XXXXXXX XXXXXXX

XXXXXXX XXXXXXX

XXXXXX XXXXXX

XXXXX XXXXX 'X' = 颜色值0 (在本例中是黑的)

XXXX XXXX XXXX ' ' = 其他的颜色 (Say Green)

XXX XXXX XXX

XXX X X XXX

XXX X X XXX

XXX XXXX XXX

XXX XXX

XX X X XX

X XX XX X

XXXXXXXXXXXXXXXX 一个15X14 Cheesy太空船sprite.*

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

#define SHIP_WIDTH 15

#define SHIP_HEIGHT 14

unsigned 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 blank

skip count pixels

else

draw count pixels

end

end

代码和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,0

RLE (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 sprite

write a dummy value for the number of runs in this line

while there is data left

if data is solid

count until not solid, or no data, or run len > 16

else

count until solid, or no data, or run len > 16

end

put code and count

end

write the real value for the number of runs in this line

end

你应该设一指针指向每一行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 line

JLib的内部函数generate_RLE()可以完成这些.你像上面那样建好了RLE数据,画出它就可以用下面的代码:

for (y = 0; y < height_of_sprite ; y++){

Read the number of RLE codes in this line

while RLE codes remain

if bit 5 of the RLE code is set

draw (RLE code & 15) pixels to the screen

end

add (RLE code & 15) to "foo"

end

end

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

让我们假定我们可以直接用指针在屏幕上画点.假设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 data

for (y = 0; y < height_of_sprite ; y++){

Setup a pointer "bar" to the screen xpos,ypos+ line Y

Read the number of RLE codes in this line

while RLE codes remain

SPR_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 0x2

int 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 by

altering the outer Y loop:

if(!(clipped & CLIPPED_X)){

if (y < 0)

top_lines = -y;

else

top lines = 0;

Setup a pointer "foo" to the X by Y array of data + top_lines * data_width

if (bottom needs clipping){

max_y = SCREEN_HEIGHT;

else

max_y = height_of_sprite

for (y = 0; y < max_y ; y++){

Setup a pointer "bar" to the screen xpos,ypos+ line Y

Read the number of RLE codes in this line

while RLE codes remain

SPR_STENCIL_COPY(bar,foo,RLE code) { add (RLE code & 15) to "foo"

add (RLE code & 15) to "bar"

end

end

return;

}

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

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

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

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

width = width_of_sprite

if (x < 0) {

left = -x;

width+=x; /* decriment width */

}

else

left = 0;

Setup a pointer "foo" to the X by Y array of data + left

if (rhs needs clipping)

width+= SCREEN_WIDTH-rhs_xpos

for (y = 0; y < height_of_sprite ; y++){

Setup a pointer "bar" to the screen xpos,ypos+ line Y

while width--

if(sprite_data_array_value != 0)

*bar = sprite_data_array_value; /* draw the point */

bar++;

end

end

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,心脏病和高血压患者请勿阅读:-)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有