学习笔记29——程序框架篇

 内容目录:

1、Win32程序程序创建

2、窗口创建

3、游戏循环介绍

4、消息处理

5、游戏程序框架

1、Win32程序程序创建

首先教你用VS创建项目出来:

1、你打开VS之后的页面右边点击 创建新项目

 

2、选中Windows桌面向导,然后下一步

 3、填写好项目名称和位置,然后创建:

 4、这时候会有一个弹窗,你需要按照下图进行设置,点击确定

5、 此时已经进入项目文件,咱们需要创建一个文件来写咱们的代码。

这时候窗口内会有一个浮窗,解决方案资源管理器

如果你没有这个东西,那么你可以:在最上方找到视图--点击里面的解决方案资源管理器

然后就是:右键点击源文件---源文件---添加---新建项

 这时候会出来下面的弹窗:

 

你要点击右边Visual C++,然后C++文件,你就可以在下面修改你想要的名称了,然后点击添加即可!

至此创建项目结束!

 这时候你就可以把下面的代码直接粘贴到你的文件中,点击调试器就可以运行了

#include<Windows.h>

int WINAPI WinMain(
    _In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPSTR lpCmdLine,
    _In_ int nCmdShow)
{
    OutputDebugString(L"hello world!");
    return 0;
}



上边把程序创建出来之后,你会发现程序刚刚运行就死了!这时候咱们来了一个经典的helloworld。咋没看着呢?

在这里可以找到我们的helloworld,表示我们的程序成功运行过! 

对于上边的代码:

先说WinMain:在c/c++控制台(就是初学c++/c时候的黑框框)程序中会有一个main函数,而这里是Win32程序,他的主函数是WinMain,和之前的main是类似的,就是程序的入口。

另外他有几个参数,这些如果你没接触过,你就抄上就完了,我这里不讲是因为我讲了你可能还是一头雾水,有很多你不知道的概念,我在初学的时候就算学了,我也很快忘了,你暂时就先抄上!

还有OutputDebugString,之前的c++/c程序有printf或者cout 之类的东西来帮程序进行输出,但是到了Win32程序就没了,因为咱们没有控制台窗口了。那怎么进行输出打印一些程序中的数据呢?这里的OutputDebugString就可以。

为啥在字符串之前有个L,是因为字符类型的匹配问题,你加上就完了,先不深究这些次要内容。

2、窗口创建

首先对于这个窗口创建,它主要涉及的是Win32编程的一些知识。

所以对于这块的代码我不会给你讲太细致,你直接复制粘贴拿走用即可。为啥?如果你没接触过Win32编程,就算我给你讲了,你可能也有点一头雾水,理解了你暂时也记不住,所以为了不打击大家的热情,我这里就大概讲一下,你能宏观上把握创建一个窗口的流程就够了:

想要屏幕上显示出一个窗口分为三步:

        1、注册窗口类

        2、创建出窗口

        3、移动、显示、更新窗口三连

#include<Windows.h>

#define WINDOWTITLE L"致我们永不熄灭的游戏开发梦想~"
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600

int WINAPI WinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR lpCmdLine,
	_In_ int nCmdShow)
{
    //注册窗口类
	WNDCLASSEX wndClass = { 0 };
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = DefWindowProc;
	wndClass.hInstance = hInstance;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hIcon = nullptr;
	wndClass.hCursor = nullptr;
	wndClass.hbrBackground = nullptr;
	wndClass.lpszMenuName = nullptr;
	wndClass.lpszClassName = L"ForTheDreamOfGameDevelop";


	if (!RegisterClassEx(&wndClass))
	{
		MessageBox(0, L"RegisterClass Failed.", 0, 0);
		return false;
	}

    //创建窗口
	HWND hwnd = CreateWindow(L"ForTheDreamOfGameDevelop", WINDOWTITLE,
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
		WINDOW_HEIGHT, NULL, NULL, hInstance, NULL);

	if (!hwnd)
	{
		MessageBox(0, L"CreateWindow Failed.", 0, 0);
		return false;
	}

    //窗口移动,显示,更新
	MoveWindow(hwnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true); 
	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);


	return 0;
}

代码解析

        对于注册,他就是干一些填表的事情,也就是填结构体,就和你平时注册一些东西一样的,填一些相关的信息!

这里面有一个你需要明白的点,咱们是注册一个窗口类,你要指定类名,也就是lpszClassName这个东西。我们这里把类名设置为 L"ForTheDreamOfGameDevelop" 致我们的游戏开发梦想(来自浅墨,我初学的时候他的教程时写的类名,咱们沿用下来)。

然后就可以调用RegisterClassEx这个函数,来把填写好的注册信息给提交了!他的参数就是上边我们刚刚填写的那个东西的地址,这里有个MessageBox一会再说!

        窗口创建:CreateWindow调用,参数只说前四个,第一个就是咱们注册好的类名,第二个是窗口的名字,这里定义了宏WINDOWTITLE。后面有窗口的宽高指定,也是宏WINDOW_WIDTH, WINDOW_HEIGHT。其他的暂且不管。

        ps:说一下MessageBox,这个你目前需要知道的就是:第二个参数写你要在弹窗中输出的内容,其他的暂且不管。你还需要知道一点,就是这个操作会让你的程序阻塞住,也就是代码不再往下执行,直到你去点击这个弹窗的按钮。

        窗口移动、显示、更新 :分别三个函数,窗口创建出来之后他的位置你给他安排一下就用MoveWindow,这个第二三个参数就是窗口的右上角坐标(坐标轴是以你屏幕的左上角为原点的)。然后就是ShowWindow把窗口显示出来。最后一个是对窗口更新,一般跟在ShowWindow后面。

至此你运行上边的程序,你会发现窗口他是一闪而过的。首先他的原因是什么?你看看代码就知道了,展示窗口之后,很快代码就执行完了,也就是程序执行完了,当然窗口就应该没了。

那么想要让他停住咋办?

首先说一种方法,想让窗口显示出来,你控制它显示之后,不往下执行代码不就行了吗。你想到了啥,刚刚咱们刚说的    MessageBox不就有这个功能吗!你在程序结束的return 0;之前,在ShowWindow之后,给他加一句   MessageBox(0, L"i am a box ", 0, 0);  就行了。

那么上边这种方法,我只是为了让你见识一下 MessageBox的阻塞是怎么个操作。咱们游戏程序实际上是如何操作的呢?——这就是我们游戏循环要干的事情,且看下面详细介绍!

3、游戏循环介绍

说循环之前,先说一些铺垫性的东西:就是咱们的游戏的动态画面是如何实现的?首先核心思想就是:将一个个静止的图像在短时间连续显示出来,这样静止图像之间就一个个形成了连贯,其实每一个静止的图像就是我们常说的每一帧的画面。既然是一帧接着一帧,那么我们就要把绘制这个操作放在循环里面,画完了一帧又一帧,一直循环着。

那么总不能我们提前把每一帧都准备好,然后放在程序里面放映吧,那不就成了看电影了吗?

游戏显示的逻辑是啥?首先游戏画出的东西都在数据+一些图片等资源文件等等的,比如你在屏幕上画一个三角形,那么你需要指定他的三个点的位置等等信息,你可能还会读取一些文件然后贴在屏幕上,最后程序帮你把你设置的数据资源画出来,然后显示到屏幕上。

那么游戏的循环的逻辑就是:最开始我搞了一些数据和图片准备要画上去,然后这一帧就画上去了。等到了下一帧,两帧之间可能有什么改动,也可能没有什么改动,首先我内存中会有上一帧的数据和图片,如果我想要改动,比如说三角形移动动一下,那么我就对数据进行一些加减等操作,我就得到了移动之后的新坐标。改动完成之后,就可以画到屏幕上了。如果没有任何数据的改动,那么说明咱这一帧和上一帧是一摸一样的。所以上边就说明了游戏运行中屏幕图像会出现的两种情况,屏幕里的东西动了,屏幕里的东西和上一帧一样,没有动。

说了这么多,可能懂了,但是没啥感觉,来个小例子测试一下:

    HDC hdc = GetDC(hwnd);
    float posX = 30.0f;
	while (1)
	{
        posX += 0.001f;
		TextOut(hdc, posX, 150, L"hello", 5);
	}
	ReleaseDC(hwnd, hdc);

 你只需要在之前的代码return 0之前加上上边几行,就可以实现一个动图的效果,

上边代码咱们主要干的事情就是写一串文字利用TextOut实现,然后在每一帧的时候对这个点的位置数据进行修改,这样我就实现了游戏动态的画面,如果我不修改,一直是相同的坐标,那么就是一直把hello写在相同的位置,那看上去就是静止不动的。(对于这里代码加了Get DC啥的你不用管,你只需要看懂Text Out参数中有一个posX表示文字输出的x坐标,然后和输出的内容,以及posX在循环中一直变化就行。)

你自己尝试着加上那么几行代码运行一下,你会发现有一个问题,就是我的鼠标放上去一直转圈圈,然后也没有办法点击右上角的×来关闭。

只能在VS中:

结束调试来关闭程序。

原因是什么呢?就是咱们写的程序他是一直循环执行,你并没有去检查任何的鼠标信息!也就说你把鼠标移动到右上角的×上点击,要想有反应需要你去检查鼠标的点击行为。说白了就是程序外部会往程序发送一些信息,你要对这些信息进行一个处理,才能实现正确关闭的效果。

4、消息处理

1、消息循环

这里先说一个点,你要处理程序外部给你的消息,比如鼠标键盘发来的消息,那么你首先得收到这个消息,这里消息是不会自动拍你一下然后给你说来信了,而消息是从键盘输入经过操作系统把信息放在一个桶里,程序需要去桶里面看!

要说的点就是信息他是需要你去那个桶主动去看有没有信息的,那么问题就是你去往桶里看的操作要放在哪里?

答案很显然了,因为任何时候程序都可能收到一些外来信息,所以你需要在每一次循环的内部往桶里面看一看有没有来信。

看下面的代码:

    MSG msg = { 0 };
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) 
		{
			TranslateMessage(&msg); 
			DispatchMessage(&msg); 
		}
	}

这里就是定义一个MSG变量,就是信息的结构体,不初始化会有警告哦~。然后循环的条件是上一次收到信息的内容是否为WM_QUIT,也就是退出信息,如果是那就退出循环否则就进入循环。(为啥是上一次的,因为这次的你还没有去看!)

看一下这里的三个函数:

PeekMessage,作用就是去看一眼桶里面有没有信息,然后如果有会把信息放在第一个参数的位置,并且返回非0值。如果看一眼桶里没有信息,那么他就会返回0。注意无论桶里面有无信息,这个函数往桶里看一眼之后都会立即返回。其他参数暂且不谈,先把握住他的核心即可。

对于下面的俩函数,我就要先讲解一下这里消息处理的机制了。

先来一个图:

咱们现在就拿键盘输入为例,比如你按了某个键。因为操作系统是掌控这些硬件的,所以硬件输入之后,先把输入信息交给了操作系统。然后就是操作系统把信息放在了一个消息队列中,消息队列就是咱们刚刚口头上说的桶的概念。接着就是游戏程序中一直PeekMessage,来这个桶里面看一看有没有信息。 

如果有了信息,那么你根据咱们的代码,if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))他就会判断成功,执行操作TranslateMessage,这时候就是对于收到的消息进行预处理,他会把信息翻译加工一下(或者说他会忽略掉一部分信息等等)。如果对信息进行了加工,那么就要把信息交还给操作系统进行处理信息。

这一步也就是DispatchMessage。然后操作系统怎么处理的呢,就是调用一个叫窗口处理函数的东西,里面记录了信息的处理方式。这样就完成了一条条信息的处理。

回顾一下消息处理整体的代码流程,先看之前的消息是否是退出,如果上次收到退出消息,这次就别进入循环了,否则就进去。进去之后,先看看有没有消息,如果没有那就本轮循环结束,继续下一轮。如果有信息,那就翻译处理一下,然后交给操作系统去处理。

消息具体怎么处理?我按了什么键不应该就是我来指定游戏中该做一些什么动作吗?我应该在哪去控制这一点。——那就是我们要说的重点了,消息处理函数。

2、消息处理函数

你可能有疑问,我之前没指定任何的消息处理函数,他为啥我关闭右上角×就真把窗口关闭了呢?这就是因为系统存在默认的消息处理函数,帮你去以一种默认的处理方式来处理信息。

咱们之前在注册窗口类的时候,填写了这么一项,首先DefWindowProc,就是default Window procedure。default就是默认,这里也就是我们选择了默认的消息处理,并不是没有指定。

好,接下来我们就可以发挥主动性,来自己决定这个消息怎么处理了,也就是我们自己去写消息处理函数。

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_KEYDOWN:
		if (wParam == VK_ESCAPE)
			DestroyWindow(hwnd); 
		break;
	default: 
		return DefWindowProc(hwnd, message, wParam, lParam);
	}
	return 0;
}

上边写的就是我们自定义的消息处理函数,首先你要知道这个函数是操作系统来调用的,他肯定会把信息的具体内容传递进来,比如说message,这个就是消息的类型,还有wParam,lParam这俩都是消息的具体内容。

你只需要在内部对消息进行判断,先进行switch对消息的类型进行判断,咱们这里看消息是否是WM_KEYDOWN类型,表示是否是键盘按下的消息,是之后看看按的键是不是VK_ESCAPE,他表示Esc键,也就是键盘左上角的键。如果是这个键,那么我们会执行DestroyWindow函数,也就是销毁窗口。

那么这里还有一个default,也就是说这个信息的类型你并没有定义处理,比如说,这里咱们并没有进行鼠标信息的处理。那么就会执行default分支,咱们调用DefWindowProc,这个是不是有点熟悉,就是咱们之前写的默认处理的函数。

因为程序执行时会收到很多和程序运行相关的信息,这些信息都是系统性的,我们不需要管,不需要自己去写处理,都交给默认处理即可,所以我们总是在default中这么写。

也就是说:我们自定义我们想要处理的信息的处理方式,然后剩余的那些都交给系统来做。

好,那么我们的消息处理函数怎么去发挥作用呢?就是在注册类的时候写成我们自己的窗口处理函数就行了:

 然后我们写出来我们自定义的函数的实现就可以成功运行了!现在你运行之后,按下Esc键整个窗口就会被销毁,发挥你的想象,你是不是就可以利用键盘来控制屏幕里面的文字移动了,而不是咱们之前写的文字自动去每一帧移动!我们后面会对这个东西有一个实现。

现在我们来思考一个问题,就是我们写了一个游戏循环,然后又写了一个消息循环,他俩啥关系呢?

5、游戏程序框架

首先游戏循环应该是游戏时一直绘制每一帧的地方,我们只需要把消息循环放在游戏循环内即可,这样我们每一帧绘制的时候,都先去判断一下当前是否收到了消息,然后进行相应的处理,处理完消息之后,就去执行我们的绘制工作。

咱们现在就来实现一个键盘控制字体的移动:

#include<Windows.h>

#define WINDOWTITLE L"致我们永不熄灭的游戏开发梦想~"
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
//----------------begin-----------------
float g_posX = 0;
float g_posY = 0;


LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) 
	{
	case WM_KEYDOWN: 
		if (wParam == VK_ESCAPE) 
			DestroyWindow(hwnd); 
		else if (wParam == VK_LEFT)
			g_posX -= 1.0f;
		else if (wParam == VK_RIGHT)
			g_posX += 1.0f;
		break; 
	default: 
		return DefWindowProc(hwnd, message, wParam, lParam); 
	}
	return 0;
}
//----------------end-------------------

int WINAPI WinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR lpCmdLine,
	_In_ int nCmdShow)
{

	WNDCLASSEX wndClass = { 0 };
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
//----------------begin-----------------
	wndClass.lpfnWndProc = WndProc;
//----------------end-------------------
	wndClass.hInstance = hInstance;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hIcon = nullptr;
	wndClass.hCursor = nullptr;
	wndClass.hbrBackground = nullptr;
	wndClass.lpszMenuName = nullptr;
	wndClass.lpszClassName = L"ForTheDreamOfGameDevelop";



	if (!RegisterClassEx(&wndClass))
	{
		MessageBox(0, L"RegisterClass Failed.", 0, 0);
		return false;
	}


	HWND hwnd = CreateWindow(L"ForTheDreamOfGameDevelop", WINDOWTITLE,
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
		WINDOW_HEIGHT, NULL, NULL, hInstance, NULL);

	if (!hwnd)
	{
		MessageBox(0, L"CreateWindow Failed.", 0, 0);
		return false;
	}


	MoveWindow(hwnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true); 
	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);

//----------------begin-----------------
	g_posX = 400.0f;
	g_posY = 1.0f;

	MSG msg = { 0 };
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) 
		{
			TranslateMessage(&msg); 
			DispatchMessage(&msg); 
		}
		else 
		{
			g_posY += 0.001f;
			HDC hdc = GetDC(hwnd);
			TextOut(hdc, g_posX, g_posY, L"hello", 5);
			ReleaseDC(hwnd, hdc);
		}
	}
//----------------end-------------------
	return 0;
}

上边的代码就是实现了字体在y方向上,自动向下移动,而x方向上你可以按键→或者←来移动字体。同时上边的代码相对于之前的修改部分,我加了//----------------begin/end-------------------这种提示,希望你能迅速找到改动的地方。

有一处改动还是说一下:我先创建了两个全局变量作为字体绘制的位置坐标,然后我在程序中又对他们进行了初始化,再然后我对他们在循环内部进行了相应的更新,最后在绘制的时候用到他们。这里写这几个的目的,就是为了帮你搭建整个游戏的框架!

怎么说呢?对数据的初始化——数据更新——根据数据完成绘制——后面两项循环进行

这就是游戏的框架结构,下面我对咱们的代码进行修改一下,就是把初始化,更新,绘制操作单独封装成函数,这样程序在代码多之后,能保证代码逻辑依旧是清晰的。

//-----------------------------------【头文件包含】-----------------------------------
#include<Windows.h>
//------------------------------------------------------------------------------------


//-----------------------------------【宏定义部分】-----------------------------------
#define WINDOWTITLE L"致我们永不熄灭的游戏开发梦想~"
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
//------------------------------------------------------------------------------------


//-----------------------------------【全局变量部分】-----------------------------------
float g_posX = 0;
float g_posY = 0;
HINSTANCE g_hInstance = 0;
int g_nCmdShow = 0;
HWND g_hwnd = 0;
//------------------------------------------------------------------------------------



//-----------------------------------【函数声明部分】-----------------------------------
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
void InitWindow();//初始化窗口
void Init();//初始化程序
void UpdateScene();//更新绘制的数据
void DrawScene();//进行绘制操作
void Run();
//------------------------------------------------------------------------------------




//-----------------------------------【WinMain函数】-----------------------------------
//Win32程序的入口
//------------------------------------------------------------------------------------
int WINAPI WinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR lpCmdLine,
	_In_ int nCmdShow)
{
	g_hInstance = hInstance;
	g_nCmdShow = nCmdShow;

	Init();

	Run();

	return 0;
}
//-----------------------------------【初始化创建窗口】-----------------------------------
// 初始化窗口
//--------------------------------------------------------------------------------------
void InitWindow()
{
	WNDCLASSEX wndClass = { 0 };
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;
	wndClass.hInstance = g_hInstance;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hIcon = nullptr;
	wndClass.hCursor = nullptr;
	wndClass.hbrBackground = nullptr;
	wndClass.lpszMenuName = nullptr;
	wndClass.lpszClassName = L"ForTheDreamOfGameDevelop";



	if (!RegisterClassEx(&wndClass))
	{
		MessageBox(0, L"RegisterClass Failed.", 0, 0);
		return;
	}


	g_hwnd = CreateWindow(L"ForTheDreamOfGameDevelop", WINDOWTITLE,
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
		WINDOW_HEIGHT, NULL, NULL, g_hInstance, NULL);

	if (!g_hwnd)
	{
		MessageBox(0, L"CreateWindow Failed.", 0, 0);
		return;
	}


	MoveWindow(g_hwnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true);
	ShowWindow(g_hwnd, g_nCmdShow);
	UpdateWindow(g_hwnd);
}


//-----------------------------------【窗口处理函数】-----------------------------------
// 处理外来信息
//--------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_KEYDOWN:
		if (wParam == VK_ESCAPE)
			DestroyWindow(hwnd);
		else if (wParam == VK_LEFT)
			g_posX -= 1.0f;
		else if (wParam == VK_RIGHT)
			g_posX += 1.0f;
		break;
	default:
		return DefWindowProc(hwnd, message, wParam, lParam);
	}
	return 0;
}
//-----------------------------------【程序初始化】-----------------------------------
// 初始化窗口和数据等
//--------------------------------------------------------------------------------------
void Init()
{
	InitWindow();

	g_posX = 400.0f;
	g_posY = 1.0f;
}



//-----------------------------------【更新绘制的数据】-----------------------------------
// 处理外来信息
//--------------------------------------------------------------------------------------
void UpdateScene()
{
	g_posY += 0.001f;
}
//-----------------------------------【进行绘制】-----------------------------------
// 处理外来信息
//--------------------------------------------------------------------------------------
void DrawScene()
{
	HDC hdc = GetDC(g_hwnd);
	TextOut(hdc, g_posX, g_posY, L"hello", 5);
	ReleaseDC(g_hwnd, hdc);
}

//-----------------------------------【游戏运行函数】-----------------------------------
// 游戏循环框架封装在其中
//--------------------------------------------------------------------------------------
void Run()
{
	MSG msg = { 0 };
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			UpdateScene();
			DrawScene();
		}
	}
}

这里我封装了几个函数,从声明部分你可以看到他们,他们的作用和结构:

Init包括了程序中所有东西的初始化,包括调用InitWindow初始化窗口,还有一些数据的初始化!

UpdateScene和DrawScene,就是数据更新和绘制操作了。

这时候你去看一下WinMain主函数,是不是更加的清晰了呢。当然你会发现我在程序中多引入了几个全局变量,我这里说一下,我引入全局变量的操作是因为咱们封装成了函数,那势必有些变量就不能直接用,需要参数传递了,但是我这里把需要用的参数,拷贝到了全局变量上,这样所有函数可以直接访问,避免了参数传递,在这里全都写成全局变量并不好。

但是在以后我们会把上面的东西封装在一个class中,然后把这些变量作为成员变量,这时候函数就可以直接使用了而不必参数传递,数据也不会暴露在全局中。

咱们这里主要是引出游戏的主要框架,所以暂且这么写着。

写在最后

到此,咱们今天的讲解就接近尾声了。我想在这里嘱咐几句,就是上边的框架讲解的时候,有很多细节我没有说,比如很多函数的参数代表什么意义,该怎么写。这些对于一些后面会用到的,我会再进行详细介绍。

对于本节内容,希望你能去把整个代码框架理解透彻,因为后面写代码都是对这个框架进行填充,为什么我填充的内容写在了A位置,而不是写在B,这些的问题理解都是基于你对框架的理解。

下面图中这句话是我初学时,浅墨(毛星云)在教程中提到的,我给他p到了图上,这里我也把他送给你,让我们一起为我们的游戏开发梦想奋斗!——致我们永不熄灭的游戏开发梦想!

(本文难免会有一些错误纰漏,各位如若发现,还请感谢指正。如果你认真看完了这篇文章,还请你留下你的评价,写下你的感受,这是我继续写下去的动力!感谢!) 

猜你喜欢

转载自blog.csdn.net/yinianbaifaI/article/details/124532881