win32游戏进度异步加载

翻看自己以前练习的小demo,忽然感觉代码写的一团糟,故今而重整一下代码,也算是温习win32。

90年后,是感受到互联网、it技术浪潮很深的一代,我等尤为其盛,更为游戏原理之巧妙、精湛而赞叹不已,因此便借温习之由,将笔者拙劣的实现抛砖引玉与诸君,同时希望为看到此篇文章小萌新的编程之路上添一块脚石。

win32不同于我们黑乎乎的console,在这里我们能创建动态的窗口,实现控件,更能自己做一个小游戏。

我们首先要知道的就是win32窗口的基本构造流程:这里我贴两个csdn的blog链接:

 https://blog.csdn.net/zltpc007/article/details/1867671

https://blog.csdn.net/qq_36746738/article/details/72901339

我就不多做解释,主要是win32框架是基础的东西,用多了自然就懂了(本来笔者是想详细介绍win32框架,但又觉得不符合此篇文章的主题,且不熟悉win32框架的同学看此篇文章也是及其费劲,即便在此解释也难以接受实际的运用)

我们直接切入主题,这是程序运行时截图:

运行完截图:

 

我只简单做了一个界面切换。

首先大笔一挥:引入头文件。

然后定义好窗口大小,标题等。

#define WINDOW_HEIGHT 600
#define	WINDOW_WIDTH  800
#define WINDOW_TITLE  L"【游戏进度条的异步加载】"

 然后就到了基础的部分:建造win32框架:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdline, int nShowCmd)
{
	WNDCLASS Wnd;
	Wnd.cbClsExtra = NULL;
	Wnd.cbWndExtra = NULL;
	Wnd.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
	Wnd.hCursor = NULL;
	Wnd.hIcon = (HICON)::LoadImage(NULL, _T("icon.ico"), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_LOADFROMFILE);
	Wnd.hInstance = hInstance;
	Wnd.lpfnWndProc = WndProc;
	Wnd.lpszClassName = _T("demo");
	Wnd.lpszMenuName = NULL;
	Wnd.style = CS_HREDRAW | CS_VREDRAW;

	if (!RegisterClass(&Wnd))
	{
		return -1;
	}

	 HWND hWnd = CreateWindow(_T("demo"), WINDOW_TITLE,WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU, 
        200, 50, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL);

	ShowWindow(hWnd,nShowCmd);//SW_SHOWDEFAULT
	UpdateWindow(hWnd);
	
    MSG msg = { 0 };
	while (msg.message!=WM_QUIT)
	{
	   if (PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE))
	      {
		     TranslateMessage(&msg);
		     DispatchMessage(&msg);
     	  }
	}
	return 0;
}

写好消息处理的回调函数:

回调函数参见:https://blog.csdn.net/u014337397/article/details/80328277

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
	switch (message)
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hwnd, message, wparam, lparam);
	}
	return 0;
}

这样一个窗口就出来了,整体构架出来了,我们才好开始实行我们的建造,好添砖加瓦。

游戏实行的三要素是什么?

1:资源的加载

2:画面的绘制

3:对用户的响应(消息处理机制去运作)

故我们写下三个函数:

void Game_Init(HWND hWnd, HINSTANCE hInstance);
void Game_Update();
void Game_Render();

以及相对应的资源定义:设备环境句柄、画布句柄和当前GUI的名字。

HDC hdc, buffDC, hbDC, processDC, manDC;
HBITMAP buffBitmap, hb, processBitmap, manBitmap;
wchar_t GUI_Name[20];
HINSTANCE g_hInstance;

俗话说的好:一口吃成大胖子,故我们需将程序分成几部分,一步一步吃下去。

(1)

先给窗口画上背景:

1:资源初始化

void Game_Init(HWND hWnd, HINSTANCE hInstance)
{
         wcscpy(GUI_Name, _T("Main_UI.bmp"));

	 hdc = GetDC(hWnd);
	 buffDC = CreateCompatibleDC(hdc);
	 buffBitmap = CreateCompatibleBitmap(hdc, 1024, 768);
	SelectObject(buffDC, buffBitmap);

	 hb = (HBITMAP)LoadImage(hInstance, GUI_Name, IMAGE_BITMAP, 790, 600, LR_LOADFROMFILE);
	 hbDC = CreateCompatibleDC(hdc);//创建兼容DC
	SelectObject(hbDC, hb);

	 processBitmap = (HBITMAP)LoadImage(hInstance, _T("进度条.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
	 processDC = CreateCompatibleDC(hdc);//创建兼容DC
	 SelectObject(processDC, processBitmap);

	manBitmap = (HBITMAP)LoadImage(hInstance, _T("ShopNpc.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
	 manDC = CreateCompatibleDC(hdc);//创建兼容DC
	 SelectObject(manDC, manBitmap);

}

2:写好Game_Update()函数和 Game_Render()函数 。

void Game_Update()
{
	
}

void Game_Render()
{	
BitBlt(hdc, 0, 0, 1024, 768, hbDC, 0, 0, SRCCOPY);
}

这样我们就把背景贴上去了。

接下来便是贴上一个会动的进度条,那么进度条是怎么动的呢?

有很多种实现办法,这里笔者列举两种:

一:运用修图工具,实现长度与背景中框槽一样大小的进度条,然后依照进度和长度相匹配来贴图。

二:从网上随便裁剪一段进度条,每次循环贴图。

这里笔者采用第一种方法,代码会简单一些,不会ps的游戏程序员不是好程序员。

代码实现也非常简单,加寥寥几笔就行。

void Game_Update()
{
	
}

void Game_Render()
{	
static int length=0;
int speed=3;
BitBlt(hdc, 0, 0, 1024, 768, hbDC, 0, 0, SRCCOPY);
BitBlt(hdc, 20, 518, length, 18, processDC, 0, 0, SRCCOPY);
length+=speed;
}

BitBlt(hdc, 20, 518, speed, 18, processDC, 0, 0, SRCCOPY);

hdc:是当前主设备的句柄。

我们将processDC(画板)所关联的  processBitmap(画布)给了 hdc,即将进度条画了上去,画在hdc上x=20,y=518的位置上,画的大小是从原图的0,0,坐标开始截取length长,18宽度的图片,并以speed的速度前进。

ps:我们应先画背景,再画进度条。

 

 因为调用Game_Render()很快,所以截取的图就是进度条直达边框,这显然是不符合我们游戏实际,所以上述代码要修改。

void Game_Render()
{
	static int length = 0;
	static float beginTime = timeGetTime() / 1000.0f;//得到系统毫秒时间除完后变成开始的计时的秒数
	float endTime = timeGetTime() / 1000.0f;//得到新的秒数

	int speed = 3;
	BitBlt(hdc, 0, 0, 1024, 768, hbDC, 0, 0, SRCCOPY);
        BitBlt(hdc, 20, 518, length, 18, processDC, 0, 0, SRCCOPY);
	if (endTime - beginTime >= 0.1f)//时间相减大于一个时间差执行
	{ 
                if(length < 740)
		length += speed;
		beginTime = endTime;//重新设置开始时间
	}

}

程序运行截图: 

这样进度条就能以我们想要的速度动起来,并不会超出边框,但这样的进度条有点乏善可陈,我们决定在进度条前面加个小人一直跑呀跑,于是上网找资源,但往往会找到这样的图片:

这样的图片是怎样实现动态的呢?很简单,把每行的人物按照次序裁剪然后贴图就实现了;

但我们实际使用时发现白色的背景也贴了上去,而且会有闪屏出现。

这个时候就需使用TransparentBlt()这个函数以及缓冲体系。

其实很简单,不是有背景吗,那我就透明贴图,你闪屏是吗,那我就多加一层画板,画布,给你做缓冲。 

于是代码就改成了这样(同学可以尝试用定时器实现相同功能):

    void Game_Render()
{  
        static int i = 0;//动画帧设置
	static int length = 0;
	static float beginTime = timeGetTime() / 1000.0f;//得到系统毫秒时间除完后变成开始的计时的秒数
	float endTime = timeGetTime() / 1000.0f;//得到新的秒数
	int speed = 3;
	BitBlt(buffDC, 0, 0, 1024, 768, hbDC, 0, 0, SRCCOPY);
	BitBlt(buffDC, 20, 518, length, 18, processDC, 0, 0, SRCCOPY);
	TransparentBlt(buffDC, length, 768, 40, 40, manDC, i * 90, 0, 90, 90, RGB(255, 255, 255));
	BitBlt(hdc, 0, 0, 1024, 495, buffDC, 0, 0, SRCCOPY);
	if (endTime - beginTime >= 0.1f)//时间相减大于一个时间差执行
	{   
                if(length < 740)
		length += speed;
		++i;//人物动画帧更改
		if (i >= 4)
		i = 0;//超出图片帧范围,从0重新开始
		beginTime = endTime;//重新设置开始时间
	}

}

就是中间加了一层buffDC,然后再转入hdc(主设备环境句柄)。

TransparentBlt(buffDC,  length, 495, 40, 40, manDC, i * 90, 0, 90, 90, RGB(255, 255, 255));

把从macDC中x= i * 90,y=0,大小为90x90的图片变成40x40的图片并贴入buffDC中x=length,y=495的位置。

程序运行截图如下:

这样我们进度条运行便没什么问题了,但这很明显是假进度条加载,我们很清楚的知道进度条是以speed的速度匀速运动,完全没有加载资源,那么怎么联系上了?  (游戏加载界面的作用:游戏加载资源时,给用户一个良好的体验)。

同学们应该知道进程、线程,游戏中资源加载往往是多线程,

一个加载音乐资源,一个加载纹理资源,一个加载模型资源,等等。

下面我们便来实现多线程,在实现它之前,先定义一些状态变量:


enum Status
{
	thread,
	thread2,
	thread3
};
Status  t_status;//线程状态定义

enum State
{
	Main_GUI,
	Start_GUI
};
State  state; //GUI界面定义
HANDLE hThread, hThread2, hThread3;//线程句柄定义


在  Game_Init(HWND hWnd, HINSTANCE hInstance)里面创造线程。

void Game_Init(HWND hWnd, HINSTANCE hInstance)
{
	g_state = Main_GUI;//设置GUI。
	t_status = thread;//设置第一个检查的线程编号为thread。
	wcscpy(GUI_Name, _T("Main_UI.bmp"));//设置GUI背景图。

	DWORD ThreadID;
	hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)threadLoadFun, (LPVOID)thread, 0, &ThreadID);//创建线程。

	DWORD Thread2ID;
	hThread2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)threadLoadFun, (LPVOID)thread2, 0, &Thread2ID);

	DWORD Thread3ID;
	hThread3 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)threadLoadFun, (LPVOID)thread3, 0, &Thread3ID);

	 hdc = GetDC(hWnd);
	 buffDC = CreateCompatibleDC(hdc);
	 buffBitmap = CreateCompatibleBitmap(hdc, 1024, 768);
	SelectObject(buffDC, buffBitmap);

	 hb = (HBITMAP)LoadImage(hInstance, GUI_Name, IMAGE_BITMAP, 790, 600, LR_LOADFROMFILE);
	 hbDC = CreateCompatibleDC(hdc);//创建兼容DC
	SelectObject(hbDC, hb);

	 processBitmap = (HBITMAP)LoadImage(hInstance, _T("进度条.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
	 processDC = CreateCompatibleDC(hdc);//创建兼容DC
	 SelectObject(processDC, processBitmap);

	manBitmap = (HBITMAP)LoadImage(hInstance, _T("ShopNpc.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
	 manDC = CreateCompatibleDC(hdc);//创建兼容DC
	 SelectObject(manDC, manBitmap);

}

写好线程加载函数:

void threadLoadFun(LPVOID param)
{
	switch (int(param))
	{
	case thread:
		Sleep(5000);//加载资源函数,本例写成sleep模拟加载
		break;
	case thread2:
		Sleep(9000);
		break;
	case thread3:
		Sleep(15000);
		break;
	}
	
}

在Game_Update()里写好对应线程的程序语句:

void Game_Update()
{
	switch (g_state)
	{
	case Main_GUI:
		break;
	case Start_GUI:
		break;
	}

}

在Game_Render()里写好对应线程的程序语句:

void Game_Render()
{
	switch (g_state)
	{
	case Main_GUI:
		Main_Draw();//将函数封装为Main_Draw
		break;
	case Start_GUI: 
		Start_Draw();
		break;
	}
}

 在 Main_Draw()里写好对应线程的程序语句:

void Main_Draw()
{
	static int i = 0;//动画帧设置
	static int length = 0;
	static float beginTime = timeGetTime() / 1000.0f;//得到系统毫秒时间除完后变成开始的计时的秒数
	float endTime = timeGetTime() / 1000.0f;//得到新的秒数

	BitBlt(buffDC, 0, 0, 1024, 768, hbDC, 0, 0, SRCCOPY);
	BitBlt(buffDC, 20, 518, length, 18, processDC, 0, 0, SRCCOPY);
	TransparentBlt(buffDC,  length, 495, 40, 40, manDC, i * 90, 0, 90, 90, RGB(255, 255, 255));
	BitBlt(hdc, 0, 0, 1024, 768, buffDC, 0, 0, SRCCOPY);

	if (endTime - beginTime >= 0.1)//时间相减大于一个时间差执行
	{
		switch (t_status)
		{
		case thread:
		if (length <=180)
		{	
			if (isFinshed(hThread))
			{
				t_status = thread2;
				length = 180;
			}
			if (length < 180)
			{
				length += 5;
			}
			
		}
			break;
		case thread2:
			if (length <= 500)
			{
				if (length<500)
				{
					length += 8;
				}
				if (isFinshed(hThread2))
				{
					length = 500;
					t_status = thread3;
				}	
			}
			break;
		case thread3:
			if (length <= 750)
			{
				if (length <750)
				{
					length += 10;
				}
				if (isFinshed(hThread3))
				{
					length = 750;
					g_state = Start_GUI;
				}
			}
			break;
		}

		++i;//人物动画帧更改
		if (i >= 4)
			i = 0;//超出图片帧范围,从0重新开始
		beginTime = endTime;//重新设置开始时间
	}

}

在 Start_Draw()里写好对应线程的程序语句:

void Start_Draw()
{
	if (wcscmp(GUI_Name, L"Start_UI.bmp"))
	{
		wcscpy(GUI_Name, _T("Start_UI.bmp"));
		HBITMAP hb = (HBITMAP)LoadImage(g_hInstance, GUI_Name, IMAGE_BITMAP, 790, 600, LR_LOADFROMFILE);
		hbDC = CreateCompatibleDC(hdc);//创建兼容DC
		SelectObject(hbDC, hb);
	}//因为循环很多次,如果每次循环都要执行getDC操作,内存会一直占用。
	BitBlt(hdc, 0, 0, 1024, 768, hbDC, 0, 0, SRCCOPY);

}

到此,写完收工。

本次多线程处理没有抢占DC的情况,故无需用到event、互斥、原子锁等等。

完整代码:https://pan.baidu.com/s/1HPIGes0bVgt_MXgQLHTVIw

雁过留痕,人走有声。 

猜你喜欢

转载自blog.csdn.net/yuyuchiyue/article/details/81207291