现在的位置: 首页 > 综合 > 正文

VC++游戏编程—-基础动画显示1

2018年04月02日 ⁄ 综合 ⁄ 共 4718字 ⁄ 字号 评论关闭

一、定时器

Timer定时器对象可以再每隔一段时间发出一个时间消息,程序收到消息后,就可以执行一些操作。比如,可以设置定时器来播放静态的连续图片,就可以产生动画效果。这也是通常显示动画的一种方式。

Windows API中有这样的函数:SetTimer()为我们定义一个定时器。函数原型:

UINT_PTR SetTimer(
  HWND hWnd,              // 窗口句柄
  UINT_PTR nIDEvent,      // 定时器代号
  UINT uElapse,           // 时间设定的值,单位为毫秒
  TIMERPROC lpTimerFunc   // 定时器响应函数
);

这里举一个小例子:

SetTimer(hwnd,                  // 窗口句柄 
         IDT_TIMER1,            // 代号
         10000,                 // 10秒
        (TIMERPROC) NULL);      // 没有响应函数
 
SetTimer(hwnd,             
         IDT_TIMER2,            
         5000,                
        (TIMERPROC) NULL);     

case WM_TIMER: 
    switch (wParam) 
    { 
        case IDT_TIMER1: 
            // 执行10秒的操作
             return 0;  
        case IDT_TIMER2: 
            // 执行5秒的操作
            return 0; 
}

创建后自然需要删除定时器。KillTimer()就是用来终止某个定时器的

 

BOOL KillTimer(
  HWND hWnd,          // 窗口句柄
  UINT_PTR uIDEvent   // 定时器代号
);

 

运用定时器使预先做好的连续的静态图片播放,形成动画的效果。
新建Win32程序,在VS2008中默认使用默认的生成窗口的代码。

这里顺便提一提,在VS中使用多字节字符集设置可以在解决方案资源管理器中右击方案,选择属性,打开的属性页里可以设置。

如图:


 


接下来,在程序头部添加全局变量:

HBITMAP girl[7]; // 用于7张人物的位图数组
HDC mdc,hmdc;
int num; // 用于计数循环

 

InitInstance()函数中添加代码:

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	HWND hWnd;
	char filename[20] = "";
	int i;
    
	hInst = hInstance; // 将实例句柄存储在全局变量中

	hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
	  CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

	if (!hWnd)
	{
	  return FALSE;
	}

	MoveWindow(hWnd, 10, 10, 600, 450, true);
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	hmdc = GetDC(hWnd);
	mdc = CreateCompatibleDC(hmdc);

	//将7张位图载入
	for (i = 0; i < 7; i++)
	{
        sprintf(filename, "girl%d.bmp", i);
		girl[i] = (HBITMAP)LoadImage(NULL,filename,IMAGE_BITMAP,640,480,LR_LOADFROMFILE);
	}

	num = 0;
	SetTimer(hWnd, 1, 500, NULL); // 设定0.5秒的定时器

	MyPaint(hmdc);

	return TRUE;
}

 

WndProc()中添加WM_TIMER消息:

case WM_TIMER:
		MyPaint(hmdc);
		break;

程序退出是需要析构所有创建的对象:

case WM_DESTROY:
		//删除和释放工作
		DeleteDC(mdc);
		ReleaseDC(hWnd, hmdc);
		for (i = 0; i < 7; i++)
		{
			DeleteObject(girl[i]);
		}
		KillTimer(hWnd, 1);

		PostQuitMessage(0);
		break;

MyPaint()函数的实现如下:

void MyPaint(HDC hdc)
{
	if(num == 7)
		num = 0;

	SelectObject(mdc,girl[num]);
	BitBlt(hdc,0,0,600,450,mdc,0,0,SRCCOPY);

	num++;
}

这样,程序就算完成了。运行,会看到小女孩在摆动的动画。


我们可以加快定时器的速率,使动画看起来更加连贯。

这里是源代码文件:点击

 

二、游戏循环

 

在上例中,通过Timer来设定动画的帧来显示游戏,这事实上市仅仅使用在小型游戏中的方式。一般来说,我们为了是游戏显示得更加顺畅,每秒钟必须更新画面25次以上。在此之中,我们还需要处理其他大量的游戏操作。而用定时器来驱动,往往得不到我们想要的画面效果。这里提出了一种叫做“游戏循环”的概念。

游戏循环是将原先程序中的消息循环加以修改,方法是判断其中的内容目前是否要处理的消息,如果有则进行,否则按照设定的时间间隔来重绘画面。

 

这里借助一个例子讲解:

在WinMain函数中

//游戏循环
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			tNow = GetTickCount(); // 获得系统开启时间
			if (tNow - tPre >= 100)
				MyPaint(hdc); // 绘图函数
		}
	}

msg.message收到不是WM_QUIT消息时,进行运行循环。PeekMessage检测是否有处理消息(包括WM_QUIT)。没有消息就返回0,否则返回非0。值得注意的是,不能用GetMessage()取代PeekMessage()

 

此例子和上面的例子是同样的原理,显示一个动画。

运行后,显示的也是一个动画。本动画由七副静态图循环粘贴形成。即,BITMAP man[7]

整个程序如下:

#include <windows.h>
#include <stdio.h>

// 全局变量
HINSTANCE hInst;
HBITMAP man[7];
HDC hdc,mdc;
HWND hWnd;
// 分别记录上次绘图时间,本次准备绘图时间,记录每秒开始的时间
DWORD tPre, tNow, tCheck;
int num, frame, fps;

// 函数声明
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void MyPaint(HDC hdc);

// 入口函数
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	MSG msg;
	ZeroMemory(&msg, sizeof(MSG));
	MyRegisterClass(hInstance);

	if( !InitInstance(hInstance, nCmdShow) )
	{
		return FALSE;
	}

	//游戏循环
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			tNow = GetTickCount(); // 获得系统开启时间
			if (tNow - tPre >= 100)
				MyPaint(hdc); // 绘图函数
		}
	}
	return msg.wParam;
}

//注册窗口类函数
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX); 
	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= (WNDPROC)WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= NULL;
	wcex.hCursor		= NULL;
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= NULL;
	wcex.lpszClassName	= "canvas";
	wcex.hIconSm		= NULL;

	return RegisterClassEx(&wcex);
}

// 初始化窗口
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	char filename[20] = "";
	int i;

	hInst = hInstance;

	hWnd = CreateWindow("canvas", "游戏循环" , WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

	if (!hWnd)
	{
		return FALSE;
	}

	MoveWindow(hWnd,10,10,600,450,true);
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	hdc = GetDC(hWnd);
	mdc = CreateCompatibleDC(hdc);

	// 循环载入各个静态图
	for(i=0;i<7;i++)
	{
		sprintf(filename,"man%d.bmp",i);
		man[i] = (HBITMAP)LoadImage(NULL,filename,IMAGE_BITMAP,640,480,LR_LOADFROMFILE);
	}

	num = 0;
	frame = 0;

	MyPaint(hdc);

	return TRUE;
}

// 绘制图形
void MyPaint(HDC hdc)
{
	char str[40] = "";

	if(num == 7)
		num = 0;
	frame++; // 更新画面次数加1
	if(tNow - tCheck >= 1000)
	{
		fps = frame;
		frame = 0;
		tCheck = tNow;
	}

	SelectObject(mdc,man[num]);
	sprintf(str,"每秒钟显示 %d 个画面",fps);
	TextOut(mdc,0,0,str,strlen(str));
	BitBlt(hdc,0,0,600,450,mdc,0,0,SRCCOPY);

	tPre = GetTickCount(); // 记录此次绘图时间
	num++;
}

// 消息循环
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int i;

	switch (message)
	{
	case WM_DESTROY: // 清理回收
		DeleteDC(mdc);
		for(i=0;i<7;i++)
			DeleteObject(man[i]);
		ReleaseDC(hWnd,hdc);
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

相信有了第一部分的例子,对于这部分的例子理解并不难。

抱歉!评论已关闭.