DX11 游戏开发笔记 (三) DX11 实现类框架 (上)

demo  运行注意事项:

源码为.cpp   跟fx文件夹一起导入。

Effects.h  和两个Effects.lib(环境变量)导入:

 https://blog.csdn.net/lishuzhai/article/details/54135799

书籍推荐:《大话设计模式》。

阅读本文时需要对DX_INPUT  、Free—Camera 有一定理解。

强烈建议博客:

https://blog.csdn.net/poem_qianmo/article/details/8547531   dx_input

https://blog.csdn.net/poem_qianmo/article/details/8657656    free_camera                        

老鸟:小白,你发现没有,我们虽然只是实现一个小小的三角形,代码却依然达到300行左右。

小白:是呀,每次都要写那么多烦死了,幸好我每次都是复制粘贴。

老鸟:嗯,看来你已经学到程序员"最优秀"的操作了,只不过小白,你的C++学的怎么样?

小白:一般般,怎么了,你要考我吗?

老鸟:考谈不上,只是想问你一个最基础的问题,C++除了面对对象、抽象、多态 等等外,

           还有一个什么重要属性?

小白:哈哈,这你还来问我,c++最重要的基础不就是类吗!

老鸟:哦,原来你知道呀,我还以为你白学C++了。

小白:哇,你挖苦我,等等,你是不是要我把DX用类封装一下。

老鸟:小白还是很聪明的吗。

小白:可是我在学校写代码时,我知道类这个玩意,但也局限于简单封装过几个小函数,

           这个庞然大物我有点应付不来,大神,你帮帮我吧。

老鸟:一看你上课就不认真,作业恐怕也是copy的,将长代码提取属性封装成类是每个

          c++程序员必需的技能。

老鸟:就拿上次那个三角形demo来说吧:

//初始化
BOOL InitWin32();
//初始化D3D
BOOL InitD3D();
//应用初始化
BOOL InitApp();
//场景更新
void Update();
//场景绘制
void Render();
//主循环
int	 Run();;
//回收资源
void Release();

老鸟:其可分为三部分:win32、D3D、app。

老鸟:Win32管理窗口,D3D管理DX11的初始化、渲染、更新,App管理shader文件,

          主循环为消息处理机制。(资源回收由谁创建谁回收)

老鸟:故上面很简单的就分成了三个类:WindowClass、D3DClass、ShaderClass。

老鸟:这么简单的分类当然不是我们今天研究的对象,如果你去找过DX11龙书的源码,

           一般会有这样的资源视图:

老鸟:今天我们就其中几个现在需要的讲解:

(1) 摄像机class

(2)d3dclass (DX11class)

(3)图形class

(4)着色器class

(5)DX的底层按键输入class

(6)系统class

图形class   =摄像机class   +   DX11class  +  着色器class

系统class =图形class  +    DX的底层按键输入class

老鸟:将代码分为类,一般采用的方法为从下至上,即从底层到高层。这与代码

          书写不一样,一个完整的design的实现,往往是从抽象到具体;当然大项目

           会有架构师给你分配工作,整合你们代码时就是从细节到整体。

老鸟:代码书写顺序因人而异,但类的整合往往还是这样为佳。把底层的基础逻辑

           理清,一部分一部分解决,当然如果你对你的项目十分了解,从高级抽象到底层

           实现了然于心,从上至下也未尝不可,我一般推荐这种方法留着去检验你类封装

           的质量。

老鸟:就本例而言:我们首当其冲的就是整合Windows窗口创建的代码,把其中大部分固定

          的代码提取出来。

老鸟:Init_Window 代码需要的全局变量(全部应封装成类成员变量)


#define  Screen_Width  800
#define  Screen_Height 600

HWND g_hWnd;

老鸟:下面是 Init_Window具体代码(其中有些临时变量也应封装)

BOOL    Init_Window()
{
	TCHAR*  Title_Name = L"DX11 类简化";
	TCHAR*  Class_Name = L"Try";
	
	WNDCLASS Wnd = { 0 };
	Wnd.cbClsExtra = NULL;
	Wnd.cbWndExtra = NULL;
	Wnd.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
	Wnd.hCursor = NULL;
        Wnd.hIcon =(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE);
	Wnd.hInstance = GetModuleHandle(NULL);
	Wnd.lpfnWndProc = WndProc;
	Wnd.lpszClassName = Class_Name;
	Wnd.lpszMenuName = NULL;
	Wnd.style = CS_HREDRAW | CS_VREDRAW;

	if (!RegisterClass(&Wnd))
	{
		return FALSE;
	}

	int Window_Height = Screen_Height;
	int Window_Width = Screen_Width;

	HWND hWnd = CreateWindow(Class_Name,Title_Name,WS_OVERLAPPEDWINDOW,0,0,
		Window_Width,Window_Height,NULL,NULL,Wnd.hInstance,NULL);

	g_hWnd = hWnd;

	ShowWindow(hWnd, SW_SHOWNORMAL);
	SetForegroundWindow(hWnd);
	SetFocus(hWnd);
	//UpdateWindow(hWnd);

	return TRUE;

}

老鸟:处理完Init_Window()后,消息处理泵和消息处理函数也就是顺理成章的提取,

          这里有一点要提及,Init_Window()与消息处理机制应属于同层级,此次笔者将  

          Init_Window()整合为一函数,并位于Init_System()下,但实际上只是单纯函数整合。

           实际在龙书中Init_Window()名为:SystemClass::InitializeWindows。

老鸟:故读者应该理解为我将Init_Window和消息处理等整合为System_Class,而不是

           将Window窗口单独成类,因为其掌握消息处理,故与其它类耦合度较高,提取出来

           并没有很大收益,反倒是就以它作为总类(最高抽象)会比较符合情理。

小白:完蛋了,我们刚刚还说从底层到高层了,这一搞就是最高层了。

老鸟:慌什么,这对我们类的封装影响不大,我们还是能封装它呀,只不过改了个名字

           叫System而已,这也告诉我们类的区分不是单纯看其是否能整合代码的,而是看

          其是否与整个代码的耦合程度,如果影响全部函数,而且其并不属于重用率高的基类

          函数,虽然其看上去属于底层框架,我们还是考虑将其提升抽象等级。当然将其提取出来

         也很简单:

//System.cpp
BOOL System::Init_Window(WNDPROC  WndProc){}



//_____________________________________________________//

//main.cpp
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
	System* pSys=new System;

	if (!pSys->Init_Window(WndProc))
	{
		return -1;
	}


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

	delete pSys;
	return 0;
}

LRESULT CALLBACK  WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{}

老鸟:这里我只列出需要关注的wndproc,因为其属于系统处理的回调函数,我们无法

           取得源码,也没有提供接口,否则我们应该将wndproc也从窗口类提取出来,这样

           windowclass就离一致抽象更进一步。

老鸟:因为wndproc的关系,我们无法将WindomClass置于其它高级抽象下,比如GraphicsClass

           下,因为Init_Window()需要参数wndproc,这不属于图形抽象级,硬要实现就要把

           Init_Graphics()改成 Init_Graphics(WNDPROC  WndProc),这是不提倡的,不能因为底层

          实现需要某个参数,其高层函数也要传入相应参数;wndproc是属于system抽象级,我们给

          windowclass传参已经导致底层实现依赖高层,既然有这么多的不便,那么就把窗口

         初始化放在System抽象级下,虽然这会违背我们对system的期望:只控制流程,

          具体的实现全部由底层实现。

老鸟:当然,高层传递参数至下一层级是理所当然的,因为高层就是靠参数去改变底层实现细节的,

           但是我们要注意的一点是,如果没有高层传参,其底层也应该能实现其基本功能。

小白:那我在windowClass里实现winproc,再通过参数去选择改变就行了。

老鸟:但那样你的windowClass的抽象级就不一致了,再说,那样其与高层的system的功能就有     

           一部分 重复了,我们封装类时如果父类与子类有重复的函数,我们应该考虑将子类函数

           提至基类,并考虑是否将父类函数变为虚函数,本例主要是因为wndproc与窗口建立

           抽象级不一致,一个属于系统级别,一个属于窗口级别,因为我们无法分离两者,

           故我们将窗口级别提升至系统级别来实现。

小白:啊啊,原来这里面还有这么多道道,我以前就是单纯把函数跟相关的数据整合为一个类,

           需要什么我就增加函数参数,现在看来真的是糟糕的做法。

老鸟:现在你知道也不晚,好啦,我们把“开头难”理清了,接下来讲其它类就简单多了。

老鸟:d3dClass就简单多了,其所需的参数完全自给自足。

BOOL    Init_D3D(HWND hwnd);
void    Render_D3D(float red, float green, float blue, float alpha);
void    Update_D3D();
void    Clean_D3D();

老鸟:carmer和shader

BOOL    Init_Camera();
void    Update_Camera();
void    Clean_Camera();

BOOL    Init_Shader();
void    Clean_Shader();
BOOL    Init_Graphics();
BOOL    Frame_Graphics();
void    Clean_Graphics();



BOOL    Init_Graphics()
{
	if (!Init_D3D(g_hWnd))
	{
		return FALSE;
	}
	if (!Init_Shader())
	{
		return FALSE;
	}
	if (!Init_Camera())
	{
		return FALSE;
	}

	
	return TRUE;
}
BOOL    Frame_Graphics()
{

	float color[4];
	color[0] = 0.2f;
	color[1] = 0.4f;
	color[2] = 0.6f;
	color[3] = 1.0f;
	Render_D3D(color[0], color[1], color[2], color[3]);
	Update_D3D();	
	return TRUE;
}
void    Clean_Graphics()
{
	Clean_D3D();
	Clean_Shader();
	Clean_Camera();
}

老鸟:dx_input

BOOL    DX_Input_Init();
BOOL    DX_Input_Frame();
void    DX_Input_Run();
void    DX_Input_Clean();

老鸟:system

BOOL     Init_System();
bool     Frame_System();
void     Run_System();
void     Clean_System();

BOOL    Init_System()
{

	if (!Init_Window(WndProc))
	{
		return FALSE;
	}

	if (!Init_Graphics())
	{
		return FALSE;
	}
	if (!DX_Input_Init())
	{
		return FALSE;
	}
	
	return TRUE;
}
bool     Frame_System()
{
	if (!Frame_Graphics())
		return false;
	if (!DX_Input_Frame())
		return false;
	return true;
}
void    Run_System()
{

	MSG msg = { 0 };
	bool result;

	while (msg.message!=WM_QUIT)
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			result = Frame_System();
			if (!result)
			{
				MessageBox(g_hWnd, L"Frame Processing Failed", L"Error", MB_OK);
			}
		}
	}
	
}
void    ShutDown_System()
{
	Clean_Graphics();
	DX_Input_Clean();
}

老鸟:主函数

int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
	_In_ LPSTR lpCmdLine, _In_ int nShowCmd)
{
	if (!Init_System())
	{
		return -1;
	}

	Run_System();

	return 0;
}

老鸟:这是留给你的作业,把它封装成类;

源码链接:https://pan.baidu.com/s/1_7XfVpHmbFgsnhEH2SprzQ

猜你喜欢

转载自blog.csdn.net/yuyuchiyue/article/details/82049573
今日推荐