慢羊羊的空间

无为,无我,无欲,居下,清虚,自然

一步步拓展程序,实现文字在背景上的移动 铜牌收录

本文适用读者:已经学完 C 语言基本知识,并对 EasyX 略有了解。

本文范例实现的功能:文字在背景上移动,并且不破坏背景。

本文范例编译平台:VC6 / VC2010 + EasyX 20140321(beta)。

本文并不直接写出最终代码,而是从最开始绘制背景,一步步拓展到我们需要的功能。希望这个过程能对初学者有所启发。

一、绘制背景

为了实现文字移动而不破坏背景的效果,我们先画个背景。做法很简单,就是画一连串的竖线,并且每次变换颜色,这样就能弄一个渐变色的背景,代码如下:

#include <graphics.h>
#include <conio.h>

// 主函数
void main()
{
	// 创建绘图窗口
	initgraph(640, 480);
	
	// 绘制背景
	for(int i = 0; i < 640; i++)
	{
		setlinecolor(i);
		line(i, 0, i, 479);
	}
	
	// 退出
	_getch();
	closegraph();
}

这个效果不够好看,因为这样通过 setlinecolor 设置一个整数,只能设置 RGB 颜色的最后表示红色的一个字节,所以我们只能看到红色的渐变色。于是,我们考虑用 HSL 颜色模型实现一个彩虹渐变色。HSL 模型的分量如下:

  • H 是 HSL 颜色模型的 Hue(色相) 分量,0 <= H < 360。
  • S 是 HSL 颜色模型的 Saturation(饱和度) 分量,0 <= S <= 1。
  • L 是 HSL 颜色模型的 Lightness(亮度) 分量,0 <= L <= 1。

只需要将前面代码的颜色部分修改一下就好,将:

setlinecolor(i);

修改为:

setlinecolor( HSLtoRGB(i, 1, 0.5) );

修改之后,尝试编译,发现一个警告:

warning C4244: 'argument' : conversion from 'int' to 'float', possible loss of data

因为 HSLtoRGB 要求参数是 float 类型,但是传入了一个 int 类型的参数 i,所以 VC 给了警告。这个容易解决,强制转换一下:

setlinecolor( HSLtoRGB((float)i, 1, 0.5) );

(这里提醒一下初学者:警告虽然不影响程序执行,但是务必要修改,切记切记!)

为了将之后文字的效果突出,我们再把亮度分量降低:

setlinecolor( HSLtoRGB((float)i, 1, 0.25) );

好了,这回背景效果满意了。

二、添加文字移动效果

现在有背景了,再实现文字的移动。这个不难,用一个循环加 Sleep 延时,实现文字从左到右的移动,代码如下:

#include <graphics.h>
#include <conio.h>

// 主函数
void main()
{
	// 定义字符串
	TCHAR s[] = _T("测试文字");

	// 创建绘图窗口
	initgraph(640, 480);
	
	// 绘制背景
	for(int i = 0; i < 640; i++)
	{
		setlinecolor( HSLtoRGB((float)i, 1, 0.25) );
		line(i, 0, i, 479);
	}
	
	// 设置文字效果
	settextcolor(WHITE);		// 设置文字颜色为白色
	setbkmode(TRANSPARENT);		// 设置文字背景为透明色

	// 绘制移动的文字
	for(int j = 0; j < 600; j++)
	{
		outtextxy(j, 100, s);
		Sleep(20);
	}
	
	// 退出
	_getch();
	closegraph();
}

这个代码在文字移动后没有擦掉文字,所以每次的文字输出都重叠在一起,看不出来效果。但是,如果用黑色矩形“覆盖”文字实现擦掉文字,那就会使文字移动后的区域变成黑色,达不到不破坏背景的要求。

三、重绘全部场景实现不破坏背景的文字移动

这里先尝试一个霸道点的办法:每次文字移动前,将背景重新画一遍,以便擦掉上次的文字,代码如下:

#include <graphics.h>
#include <conio.h>

// 主函数
void main()
{
	// 定义字符串
	TCHAR s[] = _T("测试文字");
	
	// 创建绘图窗口
	initgraph(640, 480);
	
	
	// 设置文字效果
	setbkmode(TRANSPARENT);		// 设置文字背景为透明色
	
	// 绘制移动的文字
	for(int j = 0; j < 600; j++)
	{
		// 绘制背景
		for(int i = 0; i < 640; i++)
		{
			setlinecolor( HSLtoRGB((float)i, 1.0, 0.25) );
			line(i, 0, i, 479);
		}

		settextcolor(WHITE);	// 设置文字颜色为白色

		// 绘制文字
		outtextxy(j, 100, s);
		Sleep(20);
	}
	
	// 退出
	_getch();
	closegraph();
}

以上代码虽然实现了效果,但是由于每次都花大量时间绘制背景,导致闪烁厉害。这里用批量绘图方法将所有绘图一次性显示出来,以解决闪烁的问题。代码如下:

#include <graphics.h>
#include <conio.h>

// 主函数
void main()
{
	// 定义字符串
	TCHAR s[] = _T("测试文字");
	
	// 创建绘图窗口
	initgraph(640, 480);
	
	// 开启批量绘图模式
	BeginBatchDraw();
	
	// 设置文字效果
	setbkmode(TRANSPARENT);		// 设置文字背景为透明色
	
	// 绘制移动的文字
	for(int j = 0; j < 600; j++)
	{
		// 绘制背景
		for(int i = 0; i < 640; i++)
		{
			setlinecolor( HSLtoRGB((float)i, 1.0, 0.25) );
			line(i, 0, i, 479);
		}

		settextcolor(WHITE);	// 设置文字颜色为白色

		// 绘制文字
		outtextxy(j, 100, s);

		FlushBatchDraw();		// 绘制
		Sleep(20);
	}
	
	// 退出
	EndBatchDraw();				// 关闭批量绘图模式
	_getch();
	closegraph();
}

这段代码已经实现了我们要求的。不过,由于每次都重新绘制背景,代价有点大。下面再尝试另一个办法。

(注:这个“代价”也是相对而言的。对于现在流行的许多游戏,很多绚丽的效果都会导致全屏幕的改动,因此与其恢复局部,不如全部重绘。)

四、恢复局部背景实现不破坏背景的文字移动

这个方法在绘制文字前,先把将要破坏的背景临时保存起来,等需要擦掉文字的时候,再把保存的局部背景显示出来。这里涉及到两个语句:

  • getimage();  // 保存指定区域的图像
  • putimage();  // 在指定位置显示图像

两个函数的使用可以在 EasyX 的在线帮助中找到,点击函数名称可以直接跳转,这里不再多说。看代码:

#include <graphics.h>
#include <conio.h>

// 主函数
void main()
{
	// 定义字符串
	TCHAR s[] = _T("测试文字");
	
	// 创建绘图窗口
	initgraph(640, 480);
	
	// 初始化
	int w = textwidth(s);		// 获取字符串占用的宽度
	int h = textheight(s);		// 获取字符串占用的高度
	IMAGE tmp;					// 定义临时对象,保存被文字破坏的背景

	// 绘制背景
	for(int i = 0; i < 640; i++)
	{
		setlinecolor( HSLtoRGB((float)i, 1.0, 0.25) );
		line(i, 0, i, 479);
	}
	
	// 设置文字效果
	settextcolor(WHITE);		// 设置文字颜色为白色
	setbkmode(TRANSPARENT);		// 设置文字背景为透明色
	
	// 绘制移动的文字
	for(int j = 0; j < 600; j++)
	{
		// 保存区域
		getimage(&tmp, j, 100, w, h);

		// 绘制文字
		outtextxy(j, 100, s);

		Sleep(20);

		// 恢复区域
		putimage(j, 100, &tmp);
	}
	
	// 退出
	_getch();
	closegraph();
}

这个代码看起来没问题了吧。

五、最后一步:封装代码

虽然前面的代码没有问题,但是代码间的耦合度有点大,我们希望有一种清晰明了的方式将我们实现的功能封装起来,方便以后使用。封装的方式有很多,封装的格式也有很多,这里说一个最简单的方法:将代码写到一个函数中,如下:

#include <graphics.h>
#include <conio.h>


// 文字输出(不破坏背景)
// 参数:
//  x, y: 输出文字的位置
//  s: 输出文字的指针。如果指针为 NULL,表示恢复背景。
void myouttextxy(int x, int y, LPCTSTR s)
{
	static IMAGE tmp;				// 用来保存被文字覆盖的区域背景
	
	if (s == NULL)
	{
		// 恢复区域
		putimage(x, y, &tmp);
	}
	else
	{
		int w = textwidth(s);		// 获取字符串占用的宽度
		int h = textheight(s);		// 获取字符串占用的高度

		// 保存区域
		getimage(&tmp, x, y, w, h);
		
		// 文字输出
		outtextxy(x, y, s);
	}
}


// 主函数
void main()
{
	// 定义字符串
	TCHAR s[] = _T("测试文字");
	
	// 创建绘图窗口
	initgraph(640, 480);
	
	// 绘制背景
	for(int i = 0; i < 640; i++)
	{
		setlinecolor( HSLtoRGB((float)i, 1.0, 0.25) );
		line(i, 0, i, 479);
	}
	
	// 设置文字效果
	settextcolor(WHITE);		// 设置文字颜色为白色
	setbkmode(TRANSPARENT);		// 设置文字背景为透明色
	
	// 绘制移动的文字
	for(int j = 0; j < 600; j++)
	{
		// 绘制文字
		myouttextxy(j, 100, s);

		Sleep(20);

		// 擦掉文字
		myouttextxy(j, 100, NULL);
	}
	
	// 退出
	_getch();
	closegraph();
}

至此,大功告成!

如果其中有哪个步骤讲述的不很清楚,欢迎到百度 EasyX 贴吧交流!

添加评论