|
Este es el cuarto tutorial sobre desarrollo de videojuegos
sobre la plataforma DirectX, en esta ocasión abordaremos otra tecnología… XACT,
la cual sirve para manejar audio.
Como se ha dicho se trabajara sobre los tutoriales previos,
así que antes de entrar de fondo a este podrías darle una checada a los 3
anteriores, para que aprendas o refresques algunos conceptos.
1.- PrimerF5 [Tutorial DirectX #1]
2.- Pong [Tutorial DirectX #2]
Siguiendo con lo acordado, aquí se mostrara el código de los
dos anteriores tutoriales, aunque se mostraran en color morado y no se
explicaran, aunque por ser mas el código viejo que el nuevo, también se
cambiara el tamaño y se pondrá en cursiva, el nuevo mantendrá la fuente básica
y por convención: Courier New 10 Normal y además tendrá los cambios de colores
a azul para tipos primitivos.
Para este tutorial hay que hacer un poco de trabajo extra ya
que debemos de crear nuestros “Bancos de Sonidos” y “Bancos de Ondas”.
Así que deberás seguir un pequeño tutorial, solo toma 10
min. Para poder continuar, el link es:
3.- XACT Creation Tool [Tutorial DirectX #3]
Lo primero [que ya es los segundo] que debemos hacer es
agregar a nuestro código una librería que nos ayudara a manejar el audio y
definir un macro
El macro es _WIN32_DCOM, necesario para el uso de múltiples
hilos [o procesos].
#define
_WIN32_DCOM
#define null NULL
#include <d3d9.h>
#include <d3dx9.h>
#include <d3dx9core.h>
La librería contiene las, clases, estructuras, rutinas y
definiciones básicas para poder ejecutar la plataforma XACT
#include
<xact.h>
struct Objeto
{
IDirect3DTexture9* Textura;
D3DXVECTOR3 Posicion;
};
struct ObjetoBola : public Objeto
{
int VelX, VelY;
};
Después debemos de crear una estructura que nombraremos AudioSt
y que nos ayudara a manejar el audio de nuestro proyecto [videojuego], esta
estructura contiene varios tipos, en seguida los explicaremos.
struct
AudioSt
{
Primero tenemos un puntero a una estructura del tipo IXACTEngine,
que es el motor que usaremos en nuestra aplicación.
IXACTEngine* pEngine;
Despues declaramos nuestros bancos de ondas y de sonidos
IXACTWaveBank* pWaveBank;
IXACTSoundBank* pSoundBank;
El indice de el sonido que vamos a usar, en este caso es
solo uno, en proyectos mas grandes pueden ser decenas o miles de sonidos y por
tanto la misma cantidad de indices.
unsigned short iSonido;
Finalmente dos punteros a los buffers que almacenaran la
información.
void* pbWaveBank;
void* pbSoundBank;
};
Nota: Puedes observar que hay nombres parecidos como pbWaveBank
y pWaveBank, ten en cuenta que son diferentes y checa bien en el código por
que pueden causar confusión, una es un puntero a un buffer y la otra es un
puntero a la estructura del Banco de Ondas, la única diferencia entre los
nombres es una ‘b’ que hace referencia al ‘b’uffer
Después tenemos declaraciones hechas anteriormente, solo
agregaremos 1 mas.
IDirect3D9* D3D9 = null;
IDirect3DDevice9* D3D9Dispositivo = null;
ID3DXSprite* Sprite;
Objeto* PaletaJugador;
Objeto* PaletaCPU;
ObjetoBola* Bola;
Esta será del tipo AudioSt
AudioSt
audio;
Después esta la función prototipo que libera los recursos y
enseguida la función que inicia todo, dentro de este debemos de agregar e
código encargado de inicializar la plataforma XACT.
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 = 3;
if (FAILED(D3DXCreateTextureFromFile(::D3D9Dispositivo,
L"tBola.jpg", &Bola->Textura)))
return E_FAIL;
::D3D9Dispositivo->SetSamplerState(0,
D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
Antes de crear e inicializar el motor de XACT debemos de
recolectar la información de los archivos que generamos con nuestra herramienta
de creación de audio. Así que declaramos algunas variables que nos ayudaran en
esta tarea.
Primero una variable de tipo long que contendrá los mensajes
que regresan las funciones que utilizaremos, recordemos que long es igual a
HRESULT, pero estamos usando declaraciones con nombre de los tipos primitivos
en el último tutorial usaremos los tipos que por convención se deben de usar.
long hr;
Un puntero que será el encargado de recorrer los archivos.
void* hFile;
Despues 2 variables para el tamaño del archivo y los bytes leídos
en cada archivo.
unsigned long
dwFileSize;
unsigned long
dwBytesRead;
Por ultimo otro puntero que se cargara de mapear los
archivos.
void* hMapFile;
En seguida ponemos en ceros la memoria que ocupa la variable
“audio” para evitar errores por basura en memoria.
ZeroMemory(&audio, sizeof(AudioSt));
Lo primero que haremos es llamar la función CoInitializeEx,
con la cual haremos que nuestro programa manejar varios hilos y ejecutar bien
la plataforma y los sonidos de XACT.
hr = CoInitializeEx(null, COINIT_MULTITHREADED);
Después llamamos a la función XACTCreateEngine para crear
nuestro motor, y checamos si hubo alguna falla para regresar de la función
avisando de que ocurrió algún problema.
if(SUCCEEDED(hr))
hr
= XACTCreateEngine(0, &audio.pEngine);
if(FAILED(hr) || audio.pEngine == null)
return
E_FAIL;
Después creamos los parámetros que se usaran para
inicializar el motor, recordemos que lo que hicimos antes fue solo crear el
motor ahora debemos inicializarlo. Los parámetros quedaran en cero, con lo cual
cargara los valores por default.
XACT_RUNTIME_PARAMETERS
xrParams = {0};
xrParams.lookAheadTime
= XACT_ENGINE_LOOKAHEAD_DEFAULT;
Para inicializar llamamos al método que se encuentra dentro
del motor y le pasamos los parámetros que deberá de utilizar para iniciar, una
vez más checamos si existía alguna falla para reportarlo y detener la
aplicación.
hr = audio.pEngine->Initialize(&xrParams);
if(FAILED(hr))
return
hr;
Los siguientes pasos aparecen algo complicados pero no lo
son, loes resumiré en 4 pasos:
1.- Abrir el archivo
2.- Obtener el tamaño del archivo
3.- Asignar el buffer pbWaveBank como el encargado de
mapear el archivo.
4.- Crear el banco de ondas [en memoria, en este caso] a
partir del buffer que contiene la información del archivo.
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);
}
Fácil, aunque hay que agregar que en cada paso se verifican
los resultados por si se genero algún error y se deben de “cerrar” los
punteros; para después checar si hubo algún error en la creación del Banco de
Ondas.
if(FAILED(hr))
return
E_FAIL;
Lo siguiente es crear el Banco de Sonidos, la operación es
parecida a la anterior la resumiré en 5 pasos:
1.- Abrir el archivo
2.- Obtener el tamaño del archivo
3.- Crear un buffer del tamaño del archivo.
4.- Leer el archivo y poner el contenido en el buffer
5.- Crear el banco de sonidos, con la información del
buffer.
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);
}
Esta vez también hay que “cerrar” los punteros usados y checar
si hubo errores.
if(FAILED(hr))
return
E_FAIL;
Finalmente ya que tenemos nuestros 2 bancos [de ondas y
sonidos] debemos de obtener el índice de los sonidos para cuando los queramos usar,
esto lo hacemos llamando a la funciones GetCueIndex del banco de sonidos
y asignamos el índice a la variable iSonidos de nuestra estructura
audio, así ya tenemos almacenado el índice del sonido hará un manejo más fácil.
audio.iSonido = audio.pSoundBank->GetCueIndex("sonido");
Regresamos S_OK, para indicar que todos los dispositivos,
motores, objetos, fueron creados satisfactoriamente.
return S_OK;
}
Solo nos faltan una cuantas líneas más, que serán las
encargadas de que el audio suene cada que la pelota rebote en una de las
paletas, ya sea del jugador o del CPU
void Update()
{
::Bola->Posicion.x += ::Bola->VelX;
::Bola->Posicion.y += ::Bola->VelY;
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;
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;
}
else
{
Justo en el momento en que se verifica que la pelota rebota
con la paleta del jugador llamado a la función Play del bando de sonidos, solo
hay que pasar el índice del sonido que queremos que toque y listo, creara el
sonido
Nota: los otros parámetros se refieren a efectos, offsets,
etc., que no usaremos, por eso están en ceros y null [XD que es cero]
audio.pSoundBank->Play(audio.iSonido,
0, 0, null );
Bola->VelX *= -1;
}
}
if (Bola->Posicion.x + 33.0f >
::PaletaCPU->Posicion.x)
{
Bola->VelX *= -1;
Hacemos lo mismo con la paleta del CPU.
audio.pSoundBank->Play(audio.iSonido,
0, 0, null );
}
Ahora debemos llamar a la función DoWork() para que el motor
XACT ejecute las tareas que debe, este método es delicado y se recomienda que
se ejecute cada vez que el juego se refresca ósea en la función de
actualización [Update en este tutorial] o en la función de rendereo.
audio.pEngine->DoWork();
}
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()
{
Una vez que vayamos a terminar nuestra aplicación debemos de
liberar los recursos usados.
if(audio.pEngine)
{
Si el puntero es valido debemos de apagar el motor y liberar
los recursos, eso lo hacemos con los métodos ShutDow() y Relace()
audio.pEngine->ShutDown();
audio.pEngine->Release();
}
Después debemos de “borrar” el puntero al buffer del Bando
de Sonidos, y después hacerlo un puntero “invalido” [o igual a cero [null]]
if(audio.pbSoundBank)
delete[]
audio.pbSoundBank;
audio.pbSoundBank = null;
Hacemos lo mismo con el puntero del Banco de Ondas, usando
la función UnmapViewOfField, para decirle que deje de mapear el contenido de
ese archivo. Y después lo hacemos un puntero nulo.
if(audio.pbWaveBank)
UnmapViewOfFile(audio.pbWaveBank);
audio.pbWaveBank = null;
Y finalmente llamamos al método CoUninitialize()
CoUninitialize();
Y así hemos creado el código que agrega sonido a nuestro
videojuego, el resto del código es exactamente igual al anterior, solo resta
ejecutarlo.
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();
}
__w64 long __stdcall Procedimientos(HWND hWindow,
unsigned
int Mensaje,
__w64 unsigned int wParametros,
__w64 long lParametros)
{
switch(Mensaje)
{
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;
}
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"Pong";
ClaseWnd.lpfnWndProc = Procedimientos;
ClaseWnd.style = CS_CLASSDC;
ClaseWnd.hInstance = GetModuleHandle(null);
RegisterClassEx(&ClaseWnd);
HWND hWindow = CreateWindow(L"Pong",
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;
}
|