|
Este es el quito y ultimo tutorial sobre DirectX.
Hemos cubierto las tecnologías principales para el
desarrollo de videojuegos sobre la plataforma directx que son:
Direct3D
XACT
DirectInput
La idea es que poco a poco veamos más a fondo cada una de
estas tecnologías.
Cabe señalar que estas tecnologías encierran algunas otras,
pero ya verán en su momento.
Otra vez partimos del supuesto de que han seguido los 4
tutoriales, así que todo el código que se genero en ellos aquí no se explicara
aunque se entregara el proyecto final [de todos los tutoriales] y en el, esta código
esta bien documentado.
DirectInput nos permite interactuar con el usuario, específicamente,
saber que quiere que hagamos, mediante el teclado o el mouse.
Así que comenzamos agregando dinput8.lib a las dependencias
de nuestro proyecto, ya debemos de tener todas las anteriores, mas las que son
heredadas por default.
Después agregar una macro y una librería.
#define _WIN32_DCOM
#define null NULL
#include <d3d9.h>
#include <d3dx9.h>
#include <d3dx9core.h>
#include <xact.h>
La macro solo dicta la versión de DirectInput que usaremos,
en este caso será la 8.
#define
DIRECTINPUT_VERSION 0x0800
Después agregamos la librería dinput.h, la cual contiene
todas las funciones, interfaces, clases, tipos y macros que necesitaremos.
#include
<dinput.h>
struct Objeto
{
IDirect3DTexture9* Textura;
D3DXVECTOR3 Posicion;
};
struct ObjetoBola : public Objeto
{
int VelX, VelY;
};
struct AudioSt
{
IXACTEngine* pEngine;
IXACTWaveBank* pWaveBank;
IXACTSoundBank* pSoundBank;
unsigned short iSonido;
void* pbWaveBank;
void* pbSoundBank;
};
IDirect3D9* D3D9 = null;
IDirect3DDevice9* D3D9Dispositivo = null;
ID3DXSprite* Sprite;
Objeto* PaletaJugador;
Objeto* PaletaCPU;
ObjetoBola* Bola;
IXACTEngine* pXACT3Engine;
AudioSt audio;
A continuación debemos de crear nuestras variables que nos ayudaran
a manejar DirectInput [ej. obtener información del teclado.
IDirectInput8*
DirectInput = NULL;
IDirectInputDevice8*
DispositivoDITeclado = NULL;
Ahora, al resto del código que se encarga comenzar todos los
motores, debemos agregarle las instrucciones que se encargaran de inicializar
DirectInput, así que buscamos la siguiente linea: audio.iSonido =
audio.pSoundBank->GetCueIndex("sonido") y debajo va el nuevo código.
Recuerda que el código en verde no será explicado por que ya
lo tienes.
void Liberar();
long Iniciar(HWND hWindow)
{
if (null == (D3D9 = Direct3DCreate9(D3D_SDK_VERSION)))
return E_FAIL;
D3DDISPLAYMODE ModosDisplay = {0};
D3D9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,
&ModosDisplay);
D3DPRESENT_PARAMETERS pPresentacion = {0};
pPresentacion.Windowed = true;
pPresentacion.SwapEffect = D3DSWAPEFFECT_DISCARD;
pPresentacion.BackBufferFormat = ModosDisplay.Format;
pPresentacion.EnableAutoDepthStencil = true;
pPresentacion.AutoDepthStencilFormat = D3DFMT_D16;
if (FAILED(D3D9->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWindow,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&pPresentacion,
&D3D9Dispositivo)))
{
return E_FAIL;
}
::D3D9Dispositivo->SetRenderState(D3DRS_ALPHABLENDENABLE,
true);
if (FAILED(D3DXCreateSprite(::D3D9Dispositivo,
&Sprite)))
return E_FAIL;
::PaletaJugador = new Objeto();
::PaletaJugador->Posicion = D3DXVECTOR3(5.0f, 200.0f,
0.0f);
if (FAILED(D3DXCreateTextureFromFile(::D3D9Dispositivo, L"tJugador.jpg",
&PaletaJugador->Textura)))
return E_FAIL;
::PaletaCPU = new Objeto();
::PaletaCPU->Posicion = D3DXVECTOR3(758.0f, 200.0f,
0.0f);
if (FAILED(D3DXCreateTextureFromFile(::D3D9Dispositivo, L"tCPU.jpg",
&PaletaCPU->Textura)))
return E_FAIL;
::Bola = new ObjetoBola();
::Bola->Posicion = D3DXVECTOR3(350.0f, 250.0f, 0.0f);
::Bola->VelX = ::Bola->VelY = 2;
if (FAILED(D3DXCreateTextureFromFile(::D3D9Dispositivo, L"tBola.jpg",
&Bola->Textura)))
return E_FAIL;
::D3D9Dispositivo->SetSamplerState(0, D3DSAMP_MINFILTER,
D3DTEXF_LINEAR);
long hr;
void* hFile;
unsigned long dwFileSize;
unsigned long dwBytesRead;
void* hMapFile;
ZeroMemory(&audio, sizeof(AudioSt));
hr = CoInitializeEx(null, COINIT_MULTITHREADED);
if(SUCCEEDED(hr))
hr = XACTCreateEngine(0, &audio.pEngine);
if(FAILED(hr) || audio.pEngine == null)
return E_FAIL;
XACT_RUNTIME_PARAMETERS xrParams = {0};
xrParams.lookAheadTime = XACT_ENGINE_LOOKAHEAD_DEFAULT;
hr = audio.pEngine->Initialize(&xrParams);
if(FAILED(hr))
return hr;
hFile = CreateFile( L"audioWB.xwb",
GENERIC_READ, FILE_SHARE_READ, null, OPEN_EXISTING, 0, null );
if( hFile != INVALID_HANDLE_VALUE )
{
dwFileSize = GetFileSize( hFile, null );
if(dwFileSize != -1)
{
hMapFile = CreateFileMapping( hFile, null,
PAGE_READONLY, 0, dwFileSize, null );
if(hMapFile)
{
audio.pbWaveBank = MapViewOfFile(
hMapFile, FILE_MAP_READ, 0, 0, 0 );
if(audio.pbWaveBank)
{
hr =
audio.pEngine->CreateInMemoryWaveBank( audio.pbWaveBank, dwFileSize, 0, 0,
&audio.pWaveBank );
}
CloseHandle(hMapFile);
}
}
CloseHandle(hFile);
}
if(FAILED(hr))
return E_FAIL;
hr = E_FAIL;
hFile = CreateFile( L"audioSB.xsb",
GENERIC_READ, FILE_SHARE_READ, null, OPEN_EXISTING, 0, null );
if( hFile != INVALID_HANDLE_VALUE )
{
dwFileSize = GetFileSize( hFile, null );
if( dwFileSize != -1 )
{
audio.pbSoundBank = new BYTE[dwFileSize];
if( audio.pbSoundBank )
{
if( 0 != ReadFile( hFile,
audio.pbSoundBank, dwFileSize, &dwBytesRead, null ) )
{
hr =
audio.pEngine->CreateSoundBank(audio.pbSoundBank, dwFileSize, 0, 0,
&audio.pSoundBank);
}
}
}
CloseHandle( hFile );
}
if( FAILED( hr ) )
return E_FAIL;
audio.iSonido = audio.pSoundBank->GetCueIndex("sonido");
Lo primero es crear una instancia de DirectInput ayudándonos
con el método DirectInput8Create al cual se le pasan algunos los parámetros
de creación, como la versión de DirectInput, una identificador de la interfaz
que usaremos, en este caso interfaces de DirectInput8, también se le pasa un
puntero [de hecho, puntero a puntero) con nuestra variable para que llene los
campos del objeto.
if (FAILED(DirectInput8Create(GetModuleHandle(null),
DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&DirectInput,
NULL)))
return E_FAIL;
Enseguida creamos el dispositivo al que estará vinculado
DirectInput, en este caso será con el teclado, como lo podemos ver en los parámetros
que se le pasan a la función.
if
(FAILED(DirectInput->CreateDevice(GUID_SysKeyboard,
&::DispositivoDITeclado, NULL)))
return E_FAIL;
Una vez que hemos creado el dispositivo debemos darle el
formato de los datos que obtendremos, en este caso seran el formato por default
del teclado, osea que ya que lo vinculamos con un dispositivo debemos decirle
como nos mandara los datos.
if (FAILED(DispositivoDITeclado->SetDataFormat(&c_dfDIKeyboard)))
return E_FAIL;
Por ultimo le damos el nivel de operatividad del dispositivo
con el entorno, es decir como interactúa con Windows, los parámetros que le
pasamos es la ventana al que estará vinculado el dispositivo, después le
decimos que será de tipo Exlusive, ósea que la información del teclado que
entre cuando la ventana este activa solo será vista por esta venta y por nadie
mas, así que si seleccionas esta y durante la ejecución presionas Alt + F4
o Alt + Tab, no se ejecutaran los comandos ya que la entrada es
exclusiva y no hace eco en el sistema, esta opción de exclusividad se combina
mediante un OR con el tipo Foreground, ósea que la pantalla solo puede recibir información
cuando es la ventana activa y no es alguna de las que se encuentra detrás de la
ventana activa.
if
(FAILED(DispositivoDITeclado->SetCooperativeLevel(hWindow, DISCL_EXCLUSIVE |
DISCL_FOREGROUND)))
return E_FAIL;
Todo lo anterior lleva un manejo de errores para salir de la
aplicación en caso de algún fallo.
return S_OK;
}
Dentro del método update debemos a agregar el código para
manejar la información sobre que tecla esta presionando el jugador.
void Update()
{
::Bola->Posicion.x += ::Bola->VelX;
::Bola->Posicion.y += ::Bola->VelY;
//IA
if (::PaletaCPU->Posicion.y + 64 <
Bola->Posicion. y)
PaletaCPU->Posicion.y += 3.0f;
else if (::PaletaCPU->Posicion.y + 64 >
Bola->Posicion. y)
PaletaCPU->Posicion.y -= 3.0f;
//Colisiones
if (::Bola->Posicion.y <= 0.0f)
::Bola->VelY *= -1;
else if (::Bola->Posicion.y + 32.0f + 24.0f >=
600.0f)
Bola->VelY *= -1;
if (::PaletaJugador->Posicion.y <= 0.0f)
::PaletaJugador->Posicion.y = 0.0f;
if (::PaletaJugador->Posicion.y + 160.0f >= 600.0f)
::PaletaJugador->Posicion.y = 440.0f;
if (::PaletaCPU->Posicion.y <= 0.0f)
::PaletaCPU->Posicion.y = 0.0f;
if (::PaletaCPU->Posicion.y + 160.0f >= 600.0f)
::PaletaCPU->Posicion.y = 440.0f;
if (Bola->Posicion.x <
::PaletaJugador->Posicion.x + 32)
{
if (Bola->Posicion.y + 16.0f < ::PaletaJugador->Posicion.y
||
Bola->Posicion.y + 16.0f >
::PaletaJugador->Posicion.y + 128.0f)
{
MessageBox(null, L"Has perdido", L"Has
perdido", MB_OK);
::Bola->Posicion = D3DXVECTOR3(350.0f,
250.0f, 0.0f);
::Bola->VelX *= -1; // GAME OVER
}
else
{
audio.pSoundBank->Play(audio.iSonido, 0, 0,
null );
Bola->VelX *= -1;
}
}
if (Bola->Posicion.x + 33.0f >
::PaletaCPU->Posicion.x)
{
Bola->VelX *= -1;
audio.pSoundBank->Play(audio.iSonido, 0, 0, null
);
}
audio.pEngine->DoWork();
Primero creamos un arreglo con capacidad de 256 elementos y
lo ponemos todo en ceros.
unsigned char
Teclas[256];
ZeroMemory(&Teclas,
sizeof(Teclas));
Obtenemos el dispositivo, es decir tomamos una muestra de
este y enseguida obtenemos el estado del dispositivo al momento de tomar la
muestra.
DispositivoDITeclado->Acquire();
DispositivoDITeclado->GetDeviceState(sizeof(Teclas), &Teclas);
Y verificamos que teclas se presionaron, si fue DIK_UP, ósea
flecha->arriba, decrementamos la posición de la paleta del jugador.
if (Teclas[DIK_UP] & 0x80)
{
::PaletaJugador->Posicion.y
-= 3.0f;
}
Después checamos si se presiono DIK_DOWN [flecha-abajo] y si
fue presionada incrementamos la posición de la paleta del jugador en 3 pixeles.
else if
(Teclas[DIK_DOWN] & 0x80)
{
::PaletaJugador->Posicion.y
+= 3.0f;
}
Solo nos falta el código para liberar los recursos usados
por DirectInput, que esta mas abajo, en el método Liberar.
}
void Renderear()
{
if (::D3D9Dispositivo == null)
return;
::D3D9Dispositivo->Clear(0, null, D3DCLEAR_TARGET,
0xFFFFFFFF, 1.0f, 0);
if (SUCCEEDED(::D3D9Dispositivo->BeginScene()))
{
if
(SUCCEEDED(::Sprite->Begin(D3DXSPRITE_ALPHABLEND)))
{
::Sprite->Draw(::PaletaJugador->Textura,
null, null, &::PaletaJugador->Posicion, 0xFFFFFFFF);
::Sprite->Draw(::PaletaCPU->Textura,
null, null, &::PaletaCPU->Posicion, 0xFFFFFFFF);
::Sprite->Draw(::Bola->Textura, null,
null, &::Bola->Posicion, 0xFFFFFFFF);
::Sprite->End();
}
::D3D9Dispositivo->EndScene();
}
::D3D9Dispositivo->Present(null, null, null, null);
}
void Liberar()
{
if(audio.pEngine)
{
audio.pEngine->ShutDown();
audio.pEngine->Release();
}
if(audio.pbSoundBank)
delete[] audio.pbSoundBank;
audio.pbSoundBank = null;
if(audio.pbWaveBank)
UnmapViewOfFile(audio.pbWaveBank);
audio.pbWaveBank = null;
CoUninitialize();
Antes de cualquier cosa debemos de des-ligarnos del
dispositivo.
if (::DispositivoDITeclado)
::DispositivoDITeclado->Unacquire();
Los siguientes pasos son básicos, verificamos si existe el
puntero, si es así, liberamos los recursos usados y finalmente hacemos nuestro
puntero inválido.
if (::DispositivoDITeclado)
::DispositivoDITeclado->Release();
::DispositivoDITeclado
= null;
Ya lo hicimos con el dispositivo ligado a DirectInput, ahora
debemos de hacerlo con el mismo DirectInput.
if (::DirectInput)
::DirectInput->Release();
::DirectInput
= null;
if (::Sprite != null)
::Sprite->Release();
if (::PaletaCPU->Textura != null)
::PaletaCPU->Textura->Release();
if (::PaletaJugador->Textura != null)
::PaletaJugador->Textura->Release();
if (::Bola->Textura != null)
::Bola->Textura->Release();
if(::D3D9Dispositivo != null)
::D3D9Dispositivo->Release();
if(::D3D9 != null)
::D3D9->Release();
}
Para terminar debemos borrar el siguiente código de la función
de procedimientos de la ventana.
case WM_KEYDOWN:
{
switch(wParametros)
{
case VK_UP:
::PaletaJugador->Posicion.y -= 4.0f;
break;
case VK_DOWN:
::PaletaJugador->Posicion.y += 4.0f;
break;
case VK_ESCAPE:
{
Liberar();
PostQuitMessage(0);
return 0;
}
}
break;
}
Ya que en caso de que exista alguna tecla presionada,
DirectInput es el que se encargara de manejar este evento y ya no este método.
Quedara como a continuación:
__w64 long __stdcall Procedimientos(HWND hWindow,
unsigned int
Mensaje,
__w64 unsigned
int wParametros,
__w64 long
lParametros)
{
switch(Mensaje)
{
case WM_DESTROY:
{
Liberar();
PostQuitMessage(0);
return 0;
}
}
return DefWindowProc(hWindow, Mensaje, wParametros,
lParametros);
}
int __stdcall WinMain(HINSTANCE hInst, HINSTANCE, char*, int)
{
WNDCLASSEX ClaseWnd = {0};
ClaseWnd.cbSize = sizeof(ClaseWnd);
ClaseWnd.lpszClassName = L"PrimerF5";
ClaseWnd.lpfnWndProc = Procedimientos;
ClaseWnd.style = CS_CLASSDC;
ClaseWnd.hInstance = GetModuleHandle(null);
RegisterClassEx(&ClaseWnd);
HWND hWindow = CreateWindow(L"PrimerF5",
L"PrimerF5",
WS_SYSMENU,
10,
10,
800,
600,
null,
null,
ClaseWnd.hInstance,
null);
if (SUCCEEDED(::Iniciar(hWindow)))
{
ShowWindow(hWindow, SW_SHOWDEFAULT);
UpdateWindow(hWindow);
MSG Mensaje;
ZeroMemory(&Mensaje, sizeof(Mensaje));
while (Mensaje.message != WM_QUIT)
{
if (PeekMessage(&Mensaje, null, 0U, 0U,
PM_REMOVE))
{
TranslateMessage(&Mensaje);
DispatchMessage(&Mensaje);
}
else
{
::Update();
::Renderear();
}
}
}
UnregisterClass(L"PrimerF5", ClaseWnd.hInstance
);
return 0;
}
|