您的位置:首页 > 其它

【Direct3D9】用D3D9实现一个简单的Galgame #2 创建GraphicsDevice

2018-03-30 18:51 676 查看
接着上一篇文章,这次我们的任务是:
        1、创建一个GraphicsDevice类,并初始化Direct3D9,然后更换背景颜色

        2、使用Direct3D9创建设备,并使其运行

在上篇中,我在GameControl类中的InitWindow方法里写了m_wcex.hbrBackground = NULL;所以在高版本Windows下运行是白色,事实上我们在用那些宏的时候往往也只能得到几种颜色,为了获得更多的颜色,我们需要使用IDirect3DDevice9创建设备进行初始化
添加->类->C++类,命名"GraphicsDevice",完成
修改"GraphicsDevice.h"代码:#pragma once

#include "_GalBase.h"
#include <d3d9.h>
#include <d3dx9.h>

#pragma comment( lib , "d3d9.lib" )
#pragma comment( lib , "d3dx9.lib" )

class GraphicsDevice : public _GalBase
{
public:
GraphicsDevice ();
~GraphicsDevice ();

bool Initialize ( _In_ HWND _HostHandle , _In_ int _nWidth , _In_ int _nHeight , _In_ bool _Windowed , _Out_ HRESULT* _RtStat );

void BeginToDraw ();
void DrawTexture ( _In_ LPDIRECT3DTEXTURE9 _tex , _In_ D3DXVECTOR3 _pos , _In_ D3DCOLOR _color , _Out_ HRESULT* _RtStat );

LPD3DXSPRITE GetSpritePtr (); //对于之后创建的D3D字体,如果每一帧要多次调用渲染的情况下,使用我们自己创建的D3DXSprite将能获得更高的渲染效率
LPDIRECT3DDEVICE9 GetDevicePtr ();

void Endup ();

void Release ();

private: /** Attributes */

bool m_Initialized;

LPDIRECT3D9 m_Interface;
LPDIRECT3DDEVICE9 m_device;
D3DPRESENT_PARAMETERS m_d3dpp;

LPD3DXSPRITE m_Sprite;

};保存,切换至"GraphicsDevice.cpp",添加代码:#include "GraphicsDevice.h"

GraphicsDevice::GraphicsDevice ()
{
m_Initialized = false;
}

GraphicsDevice::~GraphicsDevice ()
{
m_Initialized = false;
}

bool GraphicsDevice::Initialize ( _In_ HWND _HostHandle , _In_ int _nWidth , _In_ int _nHeight , _In_ bool _Windowed , _Out_ HRESULT* _RtStat )
{
if( m_Initialized ) return false;

m_Interface = Direct3DCreate9 ( D3D_SDK_VERSION ); //Must be this
if( m_Interface == NULL )
{
return false;
}

int DeviceBehaveFlag;
D3DCAPS9 dCap;
m_Interface->GetDeviceCaps ( D3DADAPTER_DEFAULT , D3DDEVTYPE_HAL , &dCap ); //使用默认显卡枚举,DEVTYPE表示抽象设备层
if( dCap.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
{
DeviceBehaveFlag = D3DCREATE_MIXED_VERTEXPROCESSING; //或者可以是D3DCREATE_HARDWARE_VERTEXPROCESSING
}
else
{
DeviceBehaveFlag = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件的顶点处理
}

/** 获得顶点处理能力后,填充m_d3dpp以创建Direct3DDevice9设备对象 */
ZeroMemory ( &m_d3dpp , sizeof ( m_d3dpp ) );
m_d3dpp.hDeviceWindow = _HostHandle;
m_d3dpp.Windowed = _Windowed;
m_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
/** 对于其他的成员,我们可以暂时使用NULL代替,或者你也可以自己手动填充 */
if( !_Windowed )
{
/** 当我们全屏时,需要手动规定BackBuffer的参数,BackBufferCount = 0和BackBufferCount = 1是等价的 */
D3DDISPLAYMODE dm;
m_Interface->GetAdapterDisplayMode ( D3DADAPTER_DEFAULT , &dm );
m_d3dpp.FullScreen_RefreshRateInHz = dm.RefreshRate;
m_d3dpp.BackBufferFormat = dm.Format;
m_d3dpp.BackBufferWidth = dm.Width;
m_d3dpp.BackBufferHeight = dm.Height;
}

if( FAILED ( m_Interface->CreateDevice ( D3DADAPTER_DEFAULT , D3DDEVTYPE_HAL , _HostHandle , DeviceBehaveFlag , &m_d3dpp , &m_device ) ) )
{
/** 一般可能会在这里出问题,所以加上一个MessageBox */
MessageBox ( _HostHandle , TEXT ( "Cannot create a device" ) , NULL , NULL );
return false;
}

if( FAILED ( D3DXCreateSprite ( m_device , &m_Sprite ) ) )
{
MessageBox ( _HostHandle , TEXT ( "Cannot create a sprite" ) , NULL , NULL );
return false;
}

return true;
}

void GraphicsDevice::BeginToDraw ()
{
m_device->Clear ( 0 , 0 , D3DCLEAR_TARGET , D3DCOLOR_ARGB ( 255 , 0 , 255 , 255 ) , 1.0f , 0 ); //颜色怎么喜欢怎么来
m_device->BeginScene ();
}

void GraphicsDevice::DrawTexture ( _In_ LPDIRECT3DTEXTURE9 _tex , _In_ D3DXVECTOR3 _pos , _In_ D3DCOLOR _color , _Out_ HRESULT* _RtStat )
{
HRESULT hr = m_Sprite->Draw ( _tex , NULL , NULL , &_pos , _color ); //颜色随意
if( _RtStat ) *_RtStat = hr;
}

LPD3DXSPRITE GraphicsDevice::GetSpritePtr ()
{
return m_Sprite;
}

LPDIRECT3DDEVICE9 GraphicsDevice::GetDevicePtr ()
{
return m_device;
}

void GraphicsDevice::Endup ()
{
m_device->EndScene ();
m_device->Present ( 0 , 0 , 0 , 0 ); //四个0,不用明白为什么,现在不需要
}

void GraphicsDevice::Release ()
{
if( m_device )
{
m_device->Release ();
m_device = NULL;
}
if( m_Sprite )
{
m_Sprite->Release ();
m_Sprite = NULL;
}
if( m_Interface )
{
m_Interface->Release ();
m_Interface = NULL;
}
m_Initialized = false;
}保存,关闭。
至此,我们的GraphicsDevice类已经基本写完,现在编译,如果有问题,则说明你的代码可能有问题。
下面,我们将使用我们自己的GraphicsDevice,并用其初始化窗口。
首先,打开"GameControl.h",修改代码:#pragma once

#include "_GalBase.h"
#include "GraphicsDevice.h"

class GameControl : public _GalBase
{
public:
GameControl ();
~GameControl ();

/** 这里的 _RtStat 是为了DEBUG用的,在这里我们不去做额外的处理,可以理解为是一个LPVOID*参数 */
/** 对于DEBUG,我们可以返回一个具体的INT32类型的数据以便探寻在哪里出错,但我们要保证_RtStat == NULL 时也能正常运行(即非DEBUG时) */
bool Initialize ( _In_ HINSTANCE _hInstance , _In_ int _nWidth , _In_ int _nHeight , _In_ bool _Windowed , _Out_ HRESULT* _RtStat );

int Run ();

LRESULT CALLBACK MsgHandler ( HWND hWnd , UINT msg , WPARAM wParam , LPARAM lParam );

void Release ();

private: /** Attributes */

bool m_Initialized;

HWND m_hWnd;
WNDCLASSEX m_wcex; //这个结构体包含的元素非常多,根本不用在我们的类里添加像HINSTANCE这种没必要的东西
HCURSOR m_Cursor;

bool m_Windowed;

GraphicsDevice* m_device;

private: /** Functions */

bool InitWindow ( _In_ HINSTANCE _hInstance , _In_ int _x , _In_ int _y , _In_ int _nWidth , _In_ int _nHeight , _In_ bool _Windowed , _Out_ HRESULT* _RtStat );

};

static GameControl* AppMsgHandler = NULL;
static LRESULT CALLBACK WndProc ( HWND hWnd , UINT msg , WPARAM wParam , LPARAM lParam );保存,切换至"GameControl.cpp",修改代码:
请注意那些Initialize()和Release()代码和之前提到的有什么不同#include "GameControl.h"

GameControl::GameControl ()
{
m_Initialized = false;
m_device = NULL;
}

GameControl::~GameControl ()
{
m_Initialized = false;
}

bool GameControl::Initialize ( _In_ HINSTANCE _hInstance , _In_ int _nWidth , _In_ int _nHeight , _In_ bool _Windowed , _Out_ HRESULT* _RtStat )
{
if( m_Initialized ) return false;

/** 为什么这么写,留给你们自己思考 */
AppMsgHandler = this;

if( !InitWindow ( _hInstance , ( ( GetSystemMetrics ( SM_CXSCREEN ) - _nWidth ) / 2 ) , ( ( GetSystemMetrics ( SM_CYSCREEN ) - _nHeight ) / 2 ) , _nWidth , _nHeight , _Windowed , _RtStat ) )
{
return false;
}

m_Cursor = LoadCursor ( NULL , IDC_ARROW ); //普通的鼠标
m_Windowed = _Windowed;

//初始化GraphicsDevice对象
m_device = new GraphicsDevice ();
if( m_device == NULL )
{
return false;
}
if( !m_device->Initialize ( m_hWnd , _nWidth , _nHeight , _Windowed , _RtStat ) )
{
return false;
}

m_Initialized = true;
return true;
}

int GameControl::Run ()
{
MSG msg;
ZeroMemory ( &msg , sizeof ( msg ) );

while( true )
{
i
b58c
f( PeekMessage ( &msg , NULL , 0 , 0 , PM_REMOVE ) )
{
TranslateMessage ( &msg );
DispatchMessage ( &msg );
}

if( msg.message == WM_QUIT )
{
break;
}

/** 这里的功能是:按下ESC退出,为什么msg.message = WM_CLOSE 而不是 msg.message = WM_DESTROY 呢? 留给你们自己想了 */
if( msg.wParam == VK_ESCAPE )
{
msg.message = WM_CLOSE;
DispatchMessage ( &msg );
}

if( msg.message == WM_LBUTTONDOWN )
{
SetForegroundWindow ( m_hWnd );
}

SetCursor ( m_Cursor );

//Do your render here.

m_device->BeginToDraw ();

m_device->Endup ();

}

return msg.wParam;
}

LRESULT CALLBACK GameControl::MsgHandler ( HWND hWnd , UINT msg , WPARAM wParam , LPARAM lParam )
{
switch( msg )
{
case WM_CLOSE:
//Do something ask to close the hole program
return WndProc ( hWnd , WM_DESTROY , wParam , lParam );
default:
return DefWindowProc ( hWnd , msg , wParam , lParam );
}

return 0;
}

void GameControl::Release ()
{
if( !m_Windowed )
{
ChangeDisplaySettings ( NULL , 0 );
}

if( m_device )
{
m_device->Release ();
delete m_device;
m_device = NULL; //避免产生野指针,保证操作安全
}

if( m_hWnd )
{
DestroyWindow ( m_hWnd );
}

UnregisterClass ( m_wcex.lpszClassName , m_wcex.hInstance );
}

bool GameControl::InitWindow ( _In_ HINSTANCE _hInstance , _In_ int _x , _In_ int _y , _In_ int _nWidth , _In_ int _nHeight , _In_ bool _Windowed , _Out_ HRESULT* _RtStat )
{

ZeroMemory ( &m_wcex , sizeof ( m_wcex ) );

m_wcex.hInstance = _hInstance;
m_wcex.lpfnWndProc = WndProc;
m_wcex.lpszClassName = TEXT ( "FreelyToChangeThisOption" );
m_wcex.hIcon = LoadIcon ( NULL , IDI_SHIELD ); //这里的两个ICON可以自己试试其他的以IDI_开头的宏,会有不同的效果
m_wcex.hIconSm = LoadIcon ( NULL , IDI_SHIELD );
m_wcex.hCursor = NULL; //这里我们必须设置成NULL,然后在运行时(Run函数里)手动添加Cursor
m_wcex.hbrBackground = NULL; //这里随意,因为我们在D3D下会先Clear整个窗口,在WinXP下这里会出现【不会更新的】透明窗口,在Win8.1下会默认为白色
m_wcex.cbSize = sizeof ( m_wcex ); //这里非常重要,一般来说都是sizeof(m_wcex),而对于Advanced Players,可以自己设置,极其不推荐使用其他值
m_wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; //这里CS_DBLCLKS可加可不加,根据自己的需要和对消息的处理习惯,我们这里不设置鼠标双击事件,所以可以不要

if( !RegisterClassEx ( &m_wcex ) )
{
if( _RtStat )
{
//如果某一步出错,则可以在_RtStat返回一些值,以下不会再写出这些东西
//*_RtStat = SomeValue;
}
return false;
}

DWORD windowStyle;
if( _Windowed )
{
windowStyle = WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME;
}
else
{
_x = _y = 0;
windowStyle = WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
DEVMODE dm;
ZeroMemory ( &dm , sizeof ( dm ) );
dm.dmSize = sizeof ( dm );
dm.dmPelsWidth = _nWidth;
dm.dmPelsHeight = _nHeight;
dm.dmBitsPerPel = 32; //32位色
dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; //赋值的成员

ChangeDisplaySettings ( &dm , CDS_FULLSCREEN );

}

m_hWnd = CreateWindowEx ( WS_EX_LAYERED , m_wcex.lpszClassName , TEXT ( "窗口名称,随便取" ) , windowStyle , _x , _y , _nWidth , _nHeight , NULL , NULL , _hInstance , NULL );
if( m_hWnd == NULL )
{
return false;
}

/** 这里如果使用了 WS_EX_LAYERED,则加上下面的部分,如果没使用(CreateWindowEx第一个参数为NULL),则直接执行ShowWindow以下 */
SetLayeredWindowAttributes ( m_hWnd , 0 , 255 , LWA_ALPHA ); //使窗口完全不透明

ShowWindow ( m_hWnd , SW_SHOW );
SetForegroundWindow ( m_hWnd );
UpdateWindow ( m_hWnd );

return true;
}

LRESULT CALLBACK WndProc ( HWND hWnd , UINT msg , WPARAM wParam , LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
PostQuitMessage ( 0 );
break;
default:
return AppMsgHandler->MsgHandler ( hWnd , msg , wParam , lParam );
}

return 0;
}

保存,关闭,现在编译,如果有错误,请查看你的代码是否和我的代码有逻辑上的出入。
如果产生连接器错误,则查看你的工程是否添加了Direct3D9 SDK路径:



现在,可以在"Main.cpp"里分别使用不同的分辨率(即game->Initialize()的第二个和第三个参数),以及全屏/窗口(即game->Initialize()的第四个参数)测试一下,当然,m_device->Clear()的颜色也可以换成你喜欢的颜色,注意此时的Alpha值无效。

我的运行结果如下:



至此,我们已经可以使用D3D初始化整个窗口了,下一篇文章,我将讲述窗口渲染debug和建立我们自己的GameCharacter。

现在,开始解答上一篇的问题:
    1、为什么WS_OVERLAPPEDWINDOW要写成WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME?

        因为我们的目的是做galgame,而窗口的拉伸无疑破坏了原始图片的效果,我们要摒弃这一做法(如果你觉得不理解,可以删掉 " ^ WS_THICKFRAME"然后运行时拉伸窗口,如果窗口上有图案的话,图案会被怎样。

    2、把类继承自_GalBase的意义何在?

        在嵌入式系统中使用C++的一个常见问题是内存分配,即对new 和 delete 操作符的失控。  具有讽刺意味的是,问题的根源却是C++对内存的管理非常的容易而且安全。具体地说,当一个对象被消除时,它的析构函数能够安全的释放所分配的内存。  这当然是个好事情,但是这种使用的简单性使得程序员们过度使用new 和 delete,而不注意在嵌入式C++环境中的因果关系。并且,在嵌入式系统中,由于内存的限制,频繁的动态分配不定大小的内存会引起很大的问题以及堆破碎的风险。  作为忠告,保守的使用内存分配是嵌入式环境中的第一原则。  但当你必须要使用new 和delete时,你不得不控制C++中的内存分配。你需要用一个全局的new 和delete来代替系统的内存分配符,并且一个类一个类的重载new 和delete。  一个防止堆破碎的通用方法是从不同固定大小的内存持中分配不同类型的对象。对每个类重载new 和delete就提供了这样的控制。
        当然,除了这样的好处,我们同样避免了异常处理,比如在int或char申请内存时,如果系统没有那么多内存,则会抛出异常,而对于异常处理,增加很多行代码可能十分难受,而malloc()函数在遇到此类问题时会返回空指针,所以我们只需要判断指针是否为NULL即可。
    3、我们传递给InitWindow函数的参数_Windowed是否没用?

        显然,在本篇中,_Windowed参数的作用被充分体现,_Windowed = true代表窗口化,_Windowed = false代表全屏,而全屏意味着有很多参数需要修改。当然,这些修改是非常有意义的。

可能有些人会注意到,我们运行时可能会遇到一些小问题,这些问题一般都有详细的提示(比如D3D函数的返回值HRESULT),对于其中一些问题,请各位仔细思考,我会在下一篇回答一小部分。
See you.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  DirectX9 Direct3D9 D3D9 D3D
相关文章推荐