最近心情有点波动,导致博客写的一塌糊涂,上篇博文草草就结束了,很对不起自己,整理好心情,准备好好写下博文,算是对自己的考验吧。
C++是一门很厉害的语言,在游戏这方面更是显得非常出色,C++最基础的东西就是类,怎么将写好的流程代码封装是一个技术活,推荐《effective c++》这系列的三本书,(当然其中有些约定不适合游戏)它或许无法提升一个游戏可玩性,甚至它还会增加你游戏负担,但会让你如鱼得水般编写出代码,并且代码可读性还不错,维护也方便,如果你按照书本上的建议做了的话,相信我,当你客户要求改变时,你会感激你自己的。
笔者每次编写程序时,一般都要拿起床头的工具书《代码大全2》,询问自己代码是否符合规定,游戏引擎是个高端技术活,底层代码编写越规范越方便整合、调试。
场景begin:
(1) 摄像机class
(2)d3dclass (DX11class)
(3)图形class
(4)着色器class
(5)DX的底层按键输入class
(6)系统class
图形class =摄像机class + DX11class + 着色器class
系统class =图形class + DX的底层按键输入class
老鸟:小白,上次让你封装DX11基础框架的作业完成了吗?
小白:嗯,可花了我不少时间,当然物有所值,我受益良多。
老鸟:哦,说来听听。
小白:单纯的将数据和行为封装并不难,只是简单的复制粘贴,把数据改为类私有,写好构造和析构,注意一下变量名和函数名,函数就是简单把行为分类。
老鸟:嗯恩。
小白:难的是类之间交互的数据,例如shaderclass需要一个矩阵:世界X取景X投影,其中世界矩阵和投影矩阵属于d3dclass,取景矩阵属于camerclass,所以我需要在graphicsclass里去处理这个流程,我给d3dclass和cameraclass增加“接口”函数,把世界矩阵、取景矩阵、投影矩阵暴露出来(传引用)。
BOOL graphicsclass::Frame_Graphics()
{
camera_class->Frame_Camera();
XMMATRIX WorldMatrix, ViewMatrix, ProjMatrix;
d3d_class->GetWorldMatrix(WorldMatrix);
d3d_class->GetProjectionMatrix(ProjMatrix);
camera_class->Get_ViewMatrix(ViewMatrix);
shader_class->SetMartix(WorldMatrix, ViewMatrix, ProjMatrix);
}
小白:后来我又遇到一个难题,shaderclass跟d3dclass在渲染部分高度耦合在一起:
void Render_D3D(float red, float green, float blue, float alpha)
{
float color[4];
// Setup the color to clear the buffer to.
color[0] = red;
color[1] = green;
color[2] = blue;
color[3] = alpha;
// Clear the back buffer.
g_deviceContext->ClearRenderTargetView(g_renderTargetView, color);
// Clear the depth buffer.
g_deviceContext->ClearDepthStencilView(g_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0);
//全部的场景绘制工作在这里面进行.....
//指定输入布局(Input Layout)
g_deviceContext->IASetInputLayout(g_inputLayout);
//指定顶点缓存
UINT stride = sizeof(Vertex);
UINT offset = 0;
g_deviceContext->IASetVertexBuffers(0, 1, &g_VB, &stride, &offset);
//指定图元拓扑类型
g_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
//从Technique获取全部pass并逐个渲染
ID3DX11EffectTechnique *tech = g_effect->GetTechniqueByName("BasicDraw");
D3DX11_TECHNIQUE_DESC techDesc;
tech->GetDesc(&techDesc);
for (UINT i = 0; i < techDesc.Passes; ++i)
{
tech->GetPassByIndex(i)->Apply(0, g_deviceContext);
g_deviceContext->Draw(3, 0);
}
g_swapChain->Present(0, 0);
return;
}
小白:我一开始对其完全没有办法,因为d3dclass的渲染被shaderclass的参数从中横插一脚,难不成我把shaderclass中影响到d3dclass的参数全部设置接口,然后在传参使用,而后我发现需要传的参数太多,暴露太多数据会导致shaderclass封装性遭到破坏,然后我参考了一下DX11龙书的代码,恍然大悟,把d3dclass的Render_D3D分成两部分:BeginScreen_D3D、EndScreen_D3D,把渲染的部分放入shaderclass类中,这样只需获得d3dclass中的g_deviceContext传出来即可。
BOOL graphicsclass::Frame_Graphics()
{
d3d_class->BeginScreen_D3D(color[0], color[1], color[2], color[3]);
shader_class->SetMartix(WorldMatrix, ViewMatrix, ProjMatrix);
if (!shader_class->Render(d3d_class->GetDeviceContext(), 3))
return FALSE;
d3d_class->EndScreen_D3D();
return TRUE;
}
小白:后来我在编写systemclass时,通过input_class获取按键状态去改变camera_class的变量时,突然发现cameraclass已经被我封装进graphicsclass中,如果传参调用要通过两次抽象层,显然是不行的,故我在graphicsclass中加了一个函数:
cameraclass* graphicsclass::Icamera()
{
return camera_class;
}
小白:这样简单调用就好了,
cameraclass* camera = graphics_class->Icamera();
小白:但我发现这样忠实的把camera的所有函数的暴露出来了,可是我也一时找不到好的办法,只不过用户不会这样操作的。
老鸟:嗯恩,用户的确不会这样操作,但是测试会,不是有句话叫:开发最大的敌人就是测试。
老鸟:其实这样的情况很常见,大致分为两种情况:一种只要数据,另一种就如上述需要行为;只需要数据,且数据重合度高,那就提取出来为结构体,只需要行为就提取出来写成基类虚函数,但本例提取出来成为基类,只是单纯的一对一,显然意义不大,读者若有意实现,想必是极好的,那样请务必在留言中贴出来,谢谢。(在某些工程项目中,会有这样的事,我们只是写一个简单项目,然后有一个复杂完善的模板,这时我们往往会封装模板,并只提供我们简单项目的接口,嗯恩对,我们又在”愚弄“客户了)
小白:这是我改装后的版本:https://pan.baidu.com/s/1S_kDOudK_-z4MAEzqhTyoQ
老鸟:小白,有这样一句话,好的程序应如良品佳文,能人人娓娓道来,你能道一道你的辛苦成果吗。
小白:这有何难:正所谓佳人倾城,难睹芳颜,佳人提出这样的要求:想一见芳容,需入WinMain大门,过一过system这道,这system有三步:一难为window创建,二难为input控制,三难为graphics艺术,一难接一难,一难胜一难。这第三难尤为其盛,一大难分三小难:d3d(判断你的图形基础)、camera(认定你看艺术的角度)、shader(实战你的绘图技术)。当通过system此道,便能见到属于你自己的佳人。