GameMaker8.0 :新手教程 Part 23 -表面-

分类栏目:gamemaker教程

535

GameMaker8.0 :新手教程 Part 23 -表面-

1、概述

GM中有着绘图目标(drawing target)这一概念。绘制目标可以理解为画布,而draw_系列函数可以理解为画笔,现实中,在一块画布上作画不会影响其他画布上的内容,同样的,在GM中,draw_系列函数会将图案文字等绘制在指定的绘制目标上,不会影响其他绘制目标上的内容。
默认的情况下,GM只有一个绘制目标,那就是屏幕(虽然我觉得叫“屏幕”很容易搞混概念,但是既然官方给的“屏幕”那就用“屏幕”好了),因此,默认的情况下,我们使用draw_系列函数都是直接绘制在屏幕上给玩家看的。但是,我们有时候并不想直接绘制到屏幕上,而是绘制到看不见的表面上,在需要时再将表面的内容绘制到屏幕。表面只体现在内存(显存)中,不体现在游戏窗口中,因此无法直接展现给玩家,必须要先将其内容转移到屏幕上才行。
   这里再解释一下,为什么之前说,draw_系列函数一定要写在绘制事件中呢?屏幕是一个比较特殊的绘制目标,GM会控制它在每一帧都刷新一次(表面则不会自动刷新)。注意,屏幕也是绘制目标,因此它既有内存的体现,同时又有在游戏窗口中的体现。我们所说的“绘制”,其实是先写入到内存之中的,并不是直接改变了窗口内容。而屏幕刷新的顺序是:步结束事件->清空屏幕内存->绘制背景图片(到内存中,下同)->调用深度大于1000000的实例的绘制事件->绘制贴图->调用深度小于1000000的实例的绘制事件->将屏幕从内存输出到窗口->下一帧的步开始事件。简化一下,就是:其他事件->清空内存->绘制事件->内存输出到窗口->其他事件->清空内存->......如果你在绘制事件以外的地方使用了draw函数,那么显然,它在被输出到窗口之前就会残忍地清空,自然不可能被玩家看到了。
但是,屏幕会刷新,表面却不会啊!所以我们可以在任意事件中,将绘制目标设置为表面后,尽情使用draw_系列函数。最后要记得将绘制目标设置回屏幕。(话虽然这么说,但是实际上一般还是在end step或者draw事件里,因为只有这两个事件在"物体移动到新位置"之后执行)
看了上面的介绍,你可能觉得表面似乎没啥厉害的地方啊。这么想就对了!因为厉害的地方还没讲到。
第一,之前我们把绘制目标当成画布,但是它和画布不同的地方在于,不同的绘制目标之间可以随便转移内容,比如把表面1的指定部分画在表面2的指定位置,或者把屏幕的指定部分画在表面的指定位置。
第二,表面不会因为房间变换而消失,你在创建表面时,GM会返回一个表面的索引,只要传递索引(如global),就能跨房间传递表面,再加上可以把屏幕绘制在表面上,因此,你可以在一个房间内将屏幕绘制到表面上,再在另一个房间内把表面画出来。
第三,表面的像素透明度可以为任意值。注意了,这里才是大头戏。我们知道,GM窗口锁定透明度为1(即完全不透明),因此,我们在使用混色时,完全不考虑透明度的问题,最终Ao总是强制设置为1。但是表面是可以储存透明度的,这就意味着混色在表面中可以得到最好的体现。表面与混色的组合,就撑起了GM8特效的半壁江山(另外一半是粒子,事实上只能算一小半,就结果而言还是表面使用更多)。

2、基础函数

①创建表面
surface_create(w, h) 创建一个宽为w像素,高为h像素的表面。该函数返回被创建的表面的索引(id),请务必保存好该索引。若返回-1,则表面创建失败。注意:索引是从0开始的,因此,你在初始化用来储存索引的变量时,请初始化为-1而不是0。
②销毁表面
surface_free(id) 销毁索引为id的表面。正如上所说,表面不会因为房间变换而消失或改变,因此,切记要自己销毁表面。
③检测表面
surface_exists(id) 检测索引为id的表面是否存在。
再次强调,请将储存索引的变量初始化为-1,因为surface_exists(0)很有可能是true。
④设置绘制目标
surface_set_target(id) 将绘制目标设置到索引为id的表面上,此后所有draw_系列函数均绘制到这个表面上。
surface_reset_target() 将绘制目标重新设置回屏幕上。
⑤绘制表面
draw_surface(id, x, y) 将索引为id的表面的内容绘制在(x, y)的位置,表面的左上角与(x, y)重合。正如上所说,表面之间也可以随便转义内容,因此你可以把绘制目标设置在表面2上,然后draw_surface(表面1)也是可以的。
draw_surface_part(id, left, top, width, height, x, y) 将索引为id的表面的部分内容,左上角为(left, top),右下角为(left+width, top+height)之间的矩形部分绘制在(x, y)坐标处,左上角与(x, y)重合。
上述两个函数如果绘制在屏幕上,只能写在绘制事件,如果绘制在其他表面上,则可以写在任意事件。
事实上,draw_sprite_xxx的函数draw_surface_xxx几乎都有对应的函数,函数作用和参数也几乎是一样的,因此不再重复赘述。
surface_copy(destination, x, y, source) 除了先surface_set_target再draw_surface外,你也可以用这个函数将索引为source的表面内容绘制在索引为destination的表面的(x, y)处,无需改变当前绘制目标。
surface_copy_part(destination, x, y, source, xs, ys, ws, hs) 同上,将索引为source的表面的部分内容,左上角为(xs, ys),右下角为(xs+ws, ys+hs)的矩形部分绘制在索引为destination的(x, y)坐标处,左上角与(x, y)重合。

3、表面高级

①draw_clear_alpha(c_black, 0);
当绘制目标为某个表面时,清空其所有内容,回归到空白透明状态。表面不会自动刷新,因此在有需要的时候得自己手动刷新。
这个函数的原型是draw_clear_alpha(color, alpha),用指定颜色和指定透明度填充整个绘制目标。由于屏幕强制透明度为1,因此更多使用draw_clear(color)。但是表面是可以储存透明度的,因此可以用draw_clear_alpha(c_black, 0);将表面重新变回空白透明状态。
注意,虽然RGBA值为(任意, 任意 ,任意, 0)的时候在视觉上都是完全透明,但是只有(0, 0, 0, 0)是真正意义上的透明。在进行混色时,虽然A为0,但是如果RGB不为0,RGB值依然会对混色的计算产生影响,有时会造成不可预料的结果。而只有(0, 0, 0, 0)的纯透明才对混色不会造成任何多余的影响。
②surface_save(id, fname)
surface_save_part(id, fname, x, y, w, h)
将索引为id的表面储存到文件fname中(.png格式),后者为部分保存。
如果你要保存屏幕内容(即截屏),可以使用screen_save(fname)或者screen_save_part(fname, x, y, w, h)。
③screen_redraw()
重新调用一遍所有实例的绘制函数(包括背景,贴图),不会影响正常流程的中绘制。通常将绘制目标设置到表面后,调用这个函数把屏幕内容转移到表面上。
注意,调用screen_redraw会导致一帧之内执行两次绘制事件,我们之前说为什么不应该在绘制事件里做其他功能,尤其是变量的自增自减,这也是其中的原因之一。
④with(all)
  if(sprite_index != -1)
   drawSelf();

在设置绘制目标为表面后,将所有实例的图像绘制到表面上,但是不包含背景和贴图。注意:drawSelf()是在第十八章中提到的自定义脚本,其内容是draw_sprite_ext(sprite_index, image_index, x, y, image_xscale, image_yscale, image_angle, image_blend, image_alpha);,并非GM8自带的函数。由于可能有的实例没有sprite,所以要增加sprite_index != -1的判定,以免出现Trying to draw non-existing sprite的报错。
如果要剔除调用者本身,则可以使用:
with(all)
  if(id != other.id && sprite_index != -1)
   drawSelf();
同样的,你也可以with(objxxx)drawSelf();将特定的obj的实例绘制到表面上。这在处理混色时很有作用。
⑤draw_set_blend_mode(bm_subtract);
bm_subtract可以说是专门为表面设计的,当绘制目标为表面时,这个混色效果会给表面“挖洞”,即表面与图案重叠的部分会被清空透明。
⑥draw_set_blend_mode_ext(bm_zero, bm_src_alpha);
与挖洞相反,这个混色模式会让表面只保留与绘制图案重叠的部分,其他部分则清空为透明。

4、表面范例

①视野光圈限制
GameMaker8.0 :新手教程 Part 23 -表面-
注意:该实例深度应该最小,建议设置为负数,以免其他实例绘制在表面的上面。
create(创建)事件中:
surf = surface_create(room_width, room_height);
end step(步结束)事件中:
if(!surface_exists(surf))
surf = surface_create(room_width, room_height);
surface_set_target(surf);
draw_clear(c_black);
draw_set_blend_mode(bm_subtract);
//最好是白色,否则可能会出现“假透明”的问题
draw_set_color(c_white);
draw_set_alpha(1);
//下面两个with修改为你要在周围绘制光圈的obj,绘制圆也可以换成绘制任何图案
with(player)
draw_circle(x, y, 100, 0);
with(savePoint)
draw_circle(x, y, 100, 0);
draw_set_blend_mode(bm_normal);
surface_reset_target();
draw(绘制)事件中:
draw_surface(surf, 0, 0);
destroy(销毁)事件中:
if(surface_exists(surf))
surface_free(surf);
②电磁波干扰
同样,绘制表面的实例深度应该最低。
GameMaker8.0 :新手教程 Part 23 -表面-
create(创建)事件:
surf = surface_create(room_width, room_height);
isRedraw = false;
end step(步结束)事件:
if(!surface_exists(surf))
surf = surface_create(room_width, room_height);
surface_set_target(surf);
isRedraw = true;
screen_redraw();
isRedraw = false;
surface_reset_target();
draw(绘制)事件:
if(!isRedraw)
{
var i;
for(i = 0;i < room_width;i += 2)
  draw_surface_part(surf, i * 2, 0, 2, room_height, i * 2, random_range(-5,5));
}
destroy(销毁)事件中:
if(surface_exists(surf))
surface_free(surf);
横向干扰只需将draw事件改为:
if(!isRedraw)
{
var i;
for(i = 0;i < room_height;i += 2)
  draw_surface_part(surf, 0, i * 2, room_width, 2, random_range(-5,5), i * 2);
}
③化零为整
同样,绘制表面的实例深度应该最低。
注意:drawSelf()是在第十八章中提到的自定义脚本,其内容是draw_sprite_ext(sprite_index, image_index, x, y, image_xscale, image_yscale, image_angle, image_blend, image_alpha);,并非GM8自带的函数。
图像:
GameMaker8.0 :新手教程 Part 23 -表面-
地图:
GameMaker8.0 :新手教程 Part 23 -表面-
效果:
GameMaker8.0 :新手教程 Part 23 -表面-
create(创建)事件:
surf1 = surface_create(room_width, room_height);
surf2 = surface_create(room_width, room_height);
end step(步结束)事件:
if(!surface_exists(surf1))
surf1 = surface_create(room_width, room_height);
if(!surface_exists(surf2))
surf2 = surface_create(room_width, room_height);
surface_set_target(surf1);
draw_clear_alpha(c_black, 0);
//下面的with改成要被图像覆盖的obj
with(block)
drawSelf();
with(playerKiller)
drawSelf();
surface_set_target(surf2);
draw_clear_alpha(c_black, 0);
//下面的这个函数改成绘制你的图像,不限于绘制background,可以绘制sprite,绘制形状,甚至绘制其他表面
draw_background(backMiku, 0, 0);
draw_set_blend_mode_ext(bm_zero, bm_src_alpha);
draw_surface(surf1, 0, 0);
draw_set_blend_mode(bm_normal);
surface_reset_target();
draw(绘制)事件:
draw_surface(surf2, 0, 0);
destroy(销毁)事件中:
if(surface_exists(surf1))
surface_free(surf1);
if(surface_exists(surf2))
surface_free(surf2);
④放大镜
同样,绘制表面的实例深度应该最低。
GameMaker8.0 :新手教程 Part 23 -表面-
create(创建)事件:
surf1 = surface_create(room_width, room_height);
surf2 = surface_create(room_width, room_height);
isRedraw = false;
//放大镜的倍数,可以使用0.xx实现缩小
scale = 2;
//放大镜的半径
radius = 100;
end step(步结束)事件:
if(!surface_exists(surf1))
surf1 = surface_create(room_width, room_height);
if(!surface_exists(surf2))
surf2 = surface_create(room_width, room_height);

surface_set_target(surf1);
draw_clear_alpha(c_black, 0);
isRedraw = true;
screen_redraw();
isRedraw = false;

surface_set_target(surf2);
draw_clear_alpha(c_black, 0);
draw_set_color(c_black);
draw_set_alpha(1);
//跟随鼠标
draw_circle(mouse_x, mouse_y, radius, 0);
draw_set_color(c_white);
draw_set_blend_mode_ext(bm_dest_alpha, bm_one);
draw_surface_ext(surf1, (1 - scale) * mouse_x, (1 - scale) * mouse_y, scale, scale, 0, c_white, 1);
draw_set_blend_mode(bm_normal);
surface_reset_target();
draw(绘制)事件:
if(!isRedraw)
draw_surface(surf2, 0, 0);
destroy(销毁)事件中:
if(surface_exists(surf1))
surface_free(surf1);
if(surface_exists(surf2))
surface_free(surf2);
   以上代码仅适用于不使用视野的情况。使用视野的情况使用下面的代码(以视野0为例):
create事件:
surf1 = surface_create(view_wview[0], view_hview[0]);
surf2 = surface_create(view_wview[0], view_hview[0]);
isRedraw = false;
//放大镜的倍数,可以使用0.xx实现缩小
scale = 2;
//放大镜的半径
radius = 100;
   end step事件:
if(!surface_exists(surf1))
surf1 = surface_create(view_wview[0], view_hview[0]);
if(!surface_exists(surf2))
surf2 = surface_create(view_wview[0], view_hview[0]);

surface_set_target(surf1);
draw_clear_alpha(c_black, 1);
isRedraw = true;
screen_redraw();
isRedraw = false;

surface_set_target(surf2);
draw_clear_alpha(c_black, 0);
draw_set_color(c_black);
draw_set_alpha(1);
//跟随鼠标
draw_circle(window_mouse_get_x(), window_mouse_get_y(), radius, 0);
draw_set_color(c_white);
draw_set_blend_mode_ext(bm_dest_alpha, bm_one);
draw_surface_ext(surf1, (1 - scale) * window_mouse_get_x(), (1 - scale) * window_mouse_get_y(), scale, scale, 0, c_white, 1);
draw_set_blend_mode(bm_normal);
surface_reset_target();
   draw事件:
if(!isRedraw)
draw_surface(surf2, view_xview[0], view_yview[0]);
   destroy事件:
if(surface_exists(surf1))
surface_free(surf1);
if(surface_exists(surf2))
surface_free(surf2);
⑤纯色化
原图:
GameMaker8.0 :新手教程 Part 23 -表面-
纯色化后:
GameMaker8.0 :新手教程 Part 23 -表面-
对任何透明度的图像都适用。
create(创建)事件:
surf = surface_create(room_width, room_height);
end step(步结束)事件:
if(!surface_exists(surf))
surf = surface_create(room_width, room_height);
surface_set_target(surf);
draw_clear_alpha(c_black, 0);
//这里画你的图像
draw_sprite(sprMiku, 0, 0, 0);
draw_set_blend_mode_ext(bm_dest_alpha, bm_zero);
//这里设置纯色化后的颜色
draw_set_color(c_blue);
draw_set_alpha(1);
draw_rectangle(0, 0, room_width, room_height, 0);
draw_set_blend_mode(bm_normal);
surface_reset_target();
draw(绘制)事件:
draw_surface(surf, 0, 0);
destroy(销毁)事件中:
if(surface_exists(surf))
surface_free(surf);