光环(halo)

  • 一次draw时间片,只会画有部分片断落在当前视区的光环。

图1中咒术师发射的火球、道士发出的光束都是光环,这些光环是动画一部分。但图中出现的“减速”和数字没用光环,而是用floating_label。图2表示墙的红线,表示位置的箭头,以及旁边的文字“充电”、“窗台”都是光环。

图1 光环

光环是大地图一部分,用于增强大地图中的图形内容。它增强的图形包括来自文件的图像、字符串、自画的图形。

光环,也称为特效(effect),光环子系统的任务就是要在大地图画出这些特效。

 

一、app如何使用光环

使用光环分两种,一种是在动画中使用光环,二是在大地图覆盖上图形。对于第一种在动画中定义。以下讨论第二种。

1.1 操作:增加特效

在display的派生类重载add_halos。在该函数调用halo::add增加特效。

int music_cursor_halo_;
void chart_display::add_haloes()
{
	......
	if (music_cursor_halo_ != halo::NO_HALO) {
		halo::remove(music_cursor_halo_);
	}
	mapped_col = controller_.cursor_draging()? 0xffff0000: 0xffffffff;
	blit = image::tblit(mapped_col, create_point(0, 0), create_point(0, area.h - 1));
	music_cursor_halo_ = halo::add(cursor_pos_, ypos_, false, blit);
}

以上代码可画出红色或白色竖线。cursor_pos_是地图坐标,于是在调用halo::add时把第三个参数置为false,告知输入的x、y是地图坐标。由于要实现的竖线是可拖动的指针,该指针要随着拖动位置变化而变化,而要画出新位置就须要移除先前位置。

 

1.2 操作:删除特效

在add_halos调用halo::remove删除特效。

 

二、一次会画哪些光环

一次draw时间片,只会画有部分片断落在当前视区的光环。

<librose>/halo.cpp
------
bool effect::render()
{
	...
	image::tblit blit = current_image();

	// map coor --> screen coor
	int screenx = x_;
	int screeny = y_;
	disp->map_2_screen(screenx, screeny);

	const int xpos = screenx - (xy_is_center_? blit.width / 2: 0);
	const int ypos = screeny - (xy_is_center_? blit.height / 2: 0);

	const SDL_Rect rect = create_rect(xpos, ypos, blit.width, blit.height);

rect是此个光环占据的矩形区域,在xy_is_center_是false时,rect的左上角总是add()传下的x_、y_。

	SDL_Rect clip_rect = disp->main_map_view_rect();
	...
	if (rects_overlap(rect, clip_rect) == false) {

如果光环占据的矩形,和当前视区clip_rect没交集,那不画此个光环。

		return false;
	}
	...
}

在xy_is_center_是false时,rect的左上角总是halo::add()传下的x_、y_。这就意味着,app传blit的rect时,如果设置了一个负的x或y,会造成光环即使有落在当前视区,也不会显示。

图2 注意右下角光环

对图2中最右侧横着光环,它旋转了将近-180度,传下了负的blit.rect.x、blit.rect.y。

x_: 1100
y_: 700
blit.rect: {-198, -1, 198, 19}

由effect::render()算出的光环矩形是{1100, 700, 198, 19},但正确的光环矩形应该是{902, 699, 198, 19}。这会导致,如果当前视区有显示(1100, 700)左侧了,但只要没到(1100, 700),就会错误地不画该光环。

依旧会让app传负的blit.rect.x、blit.rect.y,解决办法是在blit放入images时,将blit.rect的x、y归0化。

<librose>/halo.cpp
------
int add(int x, int y, bool screen, const image::tblit& blit, const map_location& loc)
{
	const int id = next_halo_id();
	animated<image::tblit>::anim_description image_vector;
	if (blit.type != image::BLITM_SURFACE || (blit.x == 0 && blit.y == 0)) {
		image_vector.push_back(animated<image::tblit>::frame_description(100, blit));

对于blit.x、blit.y都是0的,已经归0,直接放入images。

	} else {
		image::tblit blit2 = blit;
		x = x + blit.x;
		blit2.x = 0;

		y = y + blit.y;
		blit2.y = 0;
		image_vector.push_back(animated<image::tblit>::frame_description(100, blit2));

对于blit.x、blit.y有不是0的,要执行归0化,操作就是把它们“加”到x_、y_,然后置0。归0化的blit再放入images。

	}
	haloes.insert(std::pair<int, effect>(id, effect(x, y, screen, image_vector, loc, NORMAL, true, false)));
	new_haloes.insert(id);
	return id;
}

 

三、特效中内容是动画

effect存放内容的变量是images_,该变量类型是animated<image::tblit>,它被定义成动画。

但实际使用时,该动画往往只有一帧,即使是大量使用动画的《王国战争》都没让images_是多帧动画。在这里,有人直观会认为使用带有逐进变量光环的单元动画会产生多帧的images_,其实不是这样的,即使是带有逐进变量光环的单元动画,unit_frame::redraw已经拆分了逐进变量,是根据当前拆分到的图像生成特效。

把images_设为支持多帧,这么设定是为支持能画出具有动画效果的光环,像让出现带闪烁照明光环的法师。

 

四、擦除和渲染

擦除有两个任务,一是增加、删除特效(display::add_haloes),二是根据当前特效置脏相关栅格(halo::unrender())。add_haloes是display的虚函数,派生类有特效时重载它。halo::unrender()则由halo实现,app不用管。擦除时机在invalidate_animations之后,invalidate_units之前。

渲染(halo::render)指在大地图画特效。渲染时机在draw_units后,drawing_buffer_commit之前,具体见“6.4.1 display::draw”。

 

五、effect中的x_、y_、loc_

为了和滚动无关,effect成员变量x_、y_是地图坐标,它来自于halo::add或effect::set_location。依自个方便,app在这两个函数可传入屏幕坐标或地图坐标。

特效的尺寸和栅格无关。但它涉及到范围需转换成涉及到栅格。

特效可以和特定一个栅格关联。一旦关联后的特点:1)关联的栅格处于黑幕时,特效不会被显示;2)栅格只能在构造时挂接一次,中间不能再改。举个例子,白袍法师有照明光环,就可让照明光环和法师所在栅格关联,当该栅格被黑幕时,同时不显示光环。光环只能挂接一次,但法师会移动,那么移动时怎么确定光环位置是对的?每一次unit::set_location时会调用unit::draw_unit,后者会销毁旧的光环构建新光环。

 

全部评论: 0

    写评论: