|
Este es el segundo tutorial sobre DirectX, en esta ocasión
vamos hacer uno de los juegos mas famosos, Pong, aprenderemos a cargar
texturas, manejar el “loop de juego”, un poquito de programación orientada a
objetos entre otras cosas.
Para realizar este ejercicio se debe pasar por el anterior,
este lo encuentran en este link o en la sección de Tutoriales con el nombre de
“Primer F5”. Tambien necesitan las texturas, esas estan aqui: http://elias.exofire.net/downloads/Pong/TexturasPong.zip
Esto es necesario ya que en este utilizaremos el mismo
proyecto y agregaremos código para realizar el Pong, y en este tutorial no
describiremos el código anterior y solo lo pondremos en letra gris, aunque
sigue siendo necesario.
Comencemos.
Lo primero que debemos hacer es abrir nuestro proyecto
anterior y agregar las siguientes librerías.
#include <d3d9.h>
#include
<d3dx9.h>
#include
<d3dx9core.h>
Estas nuevas librerías contienen más estructuras y métodos
que vamos a utilizar, por ejemplo las Texturas.
Después agregamos dos estructuras.
Nota: En C++ la estructura [struct] y la clase [class] son
idénticas en comportamiento, solo que la clase por default define todos sus
atributos como privados y las estructuras como públicos, ¿entonces, que debemos
de usar?, fácil, si la interfaz es el objeto per se, entonces se usa una
estructura de lo contrario se usa una clase.
#define null NULL
struct
Objeto
{
IDirect3DTexture9*
Textura;
D3DXVECTOR3
Posicion;
};
struct
ObjetoBola : public Objeto
{
int VelX, VelY;
};
La primera define lo que será un objeto dentro del juego [en
este caso las paletas y la pelota] por eso contiene una D3DVECTOR3 de posición
en la pantalla y una IDirect3DTexture9* que contendrá la imagen del objeto.
La segunda estructura hereda de la estructura Objeto para
definir mas detalladamente a la pelota, la cual necesita velocidad, así tiene
una textura y posición y además velocidades en los ejes X y Y.
Ahora agregamos un sprite
IDirect3D9* D3D9 = null;
IDirect3DDevice9* D3D9Dispositivo = null;
ID3DXSprite*
Sprite;
El sprite es el encargado de dibujar todo el contenido en 2D
en la pantalla, normalmente es usado para realizar gestos en los personajes,
mostrar nueve o lluvia, además de que nos puede ayudar a mostrar menús o HUD’s.
Después de esto debemos crear los objetos del juego
Objeto*
PaletaJugador;
Objeto*
PaletaCPU;
ObjetoBola*
Bola;
Como se lee, definimos la paleta del jugador y del CPU
[contra quien jugaremos] y finalmente la pelota que llamaremos Bola.
A continuación van nuestras funciones prototipo, como podrás
notar aquí habrá un gran cambio, el método:
__w64 long __stdcall Procedimientos(HWND hWindow, unsigned
int Mensaje, __w64 unsigned
int wParametros,
__w64 long lParametros)
Y el método:
int __stdcall WinMain(HINSTANCE hInst, HINSTANCE, char*, int)
Serán movidos hasta la parte inferior del código, lo único
que tienes que hacer es copiarlos y pegarlos en la última línea tu código
fuente y así solo quedara una función prototipo:
void Liberar();
Y el siguiente método [o el primero] que tendrás será:
long Iniciar(HWND hWindow)
En el método Iniciar debemos agregar el código que se
encargara de inicializar el sprite, las texturas.
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;
}
Recuerda que el código en gris, ya lo tienes, desde el
primer tutorial por eso esta en color, el resto es código nuevo.
Ahora debemos agregar un estado de renderización, activamos
el canal alfa, ósea el canal de las imágenes, que permite las transparencias.
::D3D9Dispositivo->SetRenderState(D3DRS_ALPHABLENDENABLE,
true);
Después de esto debemos de crear el sprite, llamando al
método D3DXCreateSprite, claro, controlando errores con el macro FAILED.
if (FAILED(D3DXCreateSprite(::D3D9Dispositivo,
&Sprite)))
return E_FAIL;
En seguida debemos instanciar nuestros objetos, usando el
operador ‘new’ debido a que estamos manejando punteros.
Nota: La reservación de memoria es un tema inmenso y no lo
abordaremos, solo aceptaremos el hecho de que estamos obteniendo memoria
suficiente para la información de nuestro objeto.
::PaletaJugador
= new Objeto();
Después de instanciar el objeto, debemos de definir la
posición y cargar la textura.
::PaletaJugador->Posicion
= D3DXVECTOR3(5.0f, 200.0f, 0.0f);
En la posición debemos de asignar las posiciones en ‘X’, ‘Y’
y ‘Z’, ya que este es un juego en 2D, la posición en Z es irrelevante así que
le asignamos 0.
Ponemos la paleta del jugador a 5 pixeles en el eje ‘X’ y
200 en el eje ‘Y’.
La textura la usamos con el método [macro, correctamente]
D3DXCreateTextureFromFile, al cual debemos de pasar el dispositivo que se
encargara del juego; como segundo parámetro el nombre del archivo de la textura
y como ultimo parámetro la referencia a memoria de la textura de nuestra paleta
del jugador. Una vez más controlando el error que se pueda generar de esta
operación.
if (FAILED(D3DXCreateTextureFromFile(::D3D9Dispositivo,
L"tJugador.jpg",
&PaletaJugador->Textura)))
return E_FAIL;
Repetimos estos 2 pasos con nuestros otros 2 objetos, que
son la paleta del CPU y la pelota, cada una en su respetivo sitio y con su
respectiva imagen.
::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);
A la pelota debemos de agregarle la velocidad que serán 2
pixeles en las dirección de ‘X’ y ‘Y’.
::Bola->VelX
= ::Bola->VelY = 2;
if
(FAILED(D3DXCreateTextureFromFile(::D3D9Dispositivo, L"tBola.jpg",
&Bola->Textura)))
return E_FAIL;
Despues asignamos otro estado, solo que esta vez sera al
sampleador, le asignamos un filto para las imágenes.
::D3D9Dispositivo->SetSamplerState(0,
D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
return S_OK;
}
Este método es nuevo y un poco largo y complicado, pero mas
que explicárselos paso a paso ustedes lo deben de analizar, es la lógica del
juego.
void
Update()
{
Lo primero es mover la pelota en los ejes ‘X’ y ‘Y’ con la
ayuda de las velocidad que definimos anteriormente.
::Bola->Posicion.x
+= ::Bola->VelX;
::Bola->Posicion.y
+= ::Bola->VelY;
Después tenemos nuestra Inteligencia Artificial de la época
de las cavernas, esta se basa en verificar la posición de la pelota y
compararla con la posición de la paleta del CPU, si la pelota esta por encima
de la mitad de la paleta se moverá hacia arriba, en caso que este por debajo,
se moverá hacia abajo.
¿Merezco un premio novel no?
La neta no, por que con este método el CPU nunca pierde, ya
que las velocidades nunca cambian y siempre se moverá mas rápido que la pelota,
recuerda que la pelota se mueve a 2 pixeles y el CPU a 3, pero esto nos reduce
un poco el código por que este tutorial no se enfoca en estos temas.
//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;
Ahora los choques. Debemos de manejar los
rebotes.
Primero controlamos que la pelona no se vaya
hacia el infinito [o menos infinito] en el eje Y, así que cuando su posición
sobrepase los limites de la pantalla, la hacemos rebotar, muy fácil,
multiplicando por -1 sus velocidades, claro, todo depende del eje que estemos
checando, aunque en ‘X’ no se debería de manera ya que cuando rebasa las
paletas el jugador pierde.
Notaras que a las posiciones se le suman
varios valores, esto es para manejar el largo de la pelota
//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 (Bola->Posicion.x <= 0.0f)
Bola->VelX
*= -1;
else if
(Bola->Posicion.x + 32.0f >= 800.0f)
Bola->VelX
*= -1;
Ahora debemos de controlar que las paletas no salgan de
pantalla, igualmente usamos la posición en ‘Y’ [en ‘X’ no hay movimiento] y lo
verificamos con los limites de la pantalla. Debemos de controlar la colisión de
la paleta del CPU y del jugador.
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;
Y por ultimo creamos el código para los rebotes en las
paletas, primero debemos de verificar la posición en ‘X’ de la pelota. Se
sumaran 32 a la posición en ‘X’ de la paleta ya que esto representa el ancho,
debido a que las posiciones están en la esquina superior izquierda de los
objetos.
if (Bola->Posicion.x < ::PaletaJugador->Posicion.x
+ 32)
{
Si sobrepasa la paleta, debemos checar que la paleta este en
ese lugar para hacer rebotar la pelota, esto se hace checando las posiciones en
‘Y’ de la pelota y la paleta. Hemos sumado 16 ya que este representa la mitad
de la pelota.
if (Bola->Posicion.y + 16.0f <
::PaletaJugador->Posicion.y ||
Bola->Posicion.y
+ 16.0f > ::PaletaJugador->Posicion.y + 128.0f)
{
Si no se encuentra entre los límites de la paleta, mandamos
un mensaje de que se ha perdido el juego y en seguida, llamamos al método que
liberara los recursos usados y se sale de la aplicación con el método ‘exit’
con código de salida 0, que significa que salió sin error.
Bueno, del jugador si, pero del código no.
MessageBox(null,
L"Has perdido", L"Has perdido", MB_OK);
::Liberar();
exit(0);
// GAME OVER
}
else
En caso contrario multiplicamos por -1 la velocidad en ‘X’
de la pelota para hacerla rebotar.
Bola->VelX
*= -1;
}
Para terminar hacemos rebotar la bola en la paleta del CPU,
no es necesario el código que controla el hecho de que el CPU pierda ya que
esto no sucede en este ejemplo, como fue explicado anteriormente.
if (Bola->Posicion.x + 33.0f >
::PaletaCPU->Posicion.x)
Bola->VelX
*= -1;
}
void Renderear()
{
if (::D3D9Dispositivo == null)
return;
::D3D9Dispositivo->Clear(0, null, D3DCLEAR_TARGET,
0xFFFFFFFF, 1.0f, 0);
if (SUCCEEDED(::D3D9Dispositivo->BeginScene()))
{
Ya dentro del método que se encarga de renderear nuestro
juego y dentro de la escena, debemos de iniciar el spirte llamando al método
Begin, pasando como parámetro el modo de rendereo, que en este caso se usa el
alpha-blending que permite transparencias y suaviza
if
(SUCCEEDED(::Sprite->Begin(D3DXSPRITE_ALPHABLEND)))
{
Después de iniciar el sprite podemos iniciar a dibujar en
pantalla todos los elementos del juego. Esto lo hacemos con la ayuda del método
Draw, al cual hay que pasarle como primer parámetro la textura que se dibujara,
después 2 parámetros como null, que son offsets para el rectángulo en la imagen
y el centro de esta, el cuarto parámetro es la posición en la pantalla y el
ultimo es la tinta de la textura, para conservar los colores originales se debe
de pasar el color blanco en valor hexadecimal.
::Sprite->Draw(::PaletaJugador->Textura,
null, null, &::PaletaJugador->Posicion, 0xFFFFFFFF);
Repetimos esto para la paleta del CPU.
::Sprite->Draw(::PaletaCPU->Textura,
null, null, &::PaletaCPU->Posicion, 0xFFFFFFFF);
También para la pelota.
::Sprite->Draw(::Bola->Textura,
null, null, &::Bola->Posicion, 0xFFFFFFFF);
Finalizamos el sprite con el método End, finalizamos la
escena y la presentamos.
::Sprite->End();
}
::D3D9Dispositivo->EndScene();
}
::D3D9Dispositivo->Present(null, null, null, null);
}
Continuamos con la función que nos permitirá liberar los
recursos de DirectX, es parecido al anterior, solo esta vez hay que liberar la
memoria que uso el sprite y las texturas.
void
Liberar()
{
Primero verificamos si el puntero va a la memoria y no a 0.
En caso positivo llamamos al método Relase.
if (::Sprite != null)
::Sprite->Release();
Repetimos estos pasos para las tres texturas y después
liberamos el dispositivo, tal como en el tutorial anterior.
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();
}
Solo quedan 2 funciones mas, que ya teníamos solo fueron
cambiadas a la parte baja del archivo, estas quedan iguales, salvo una
excepción en la siguiente función.
__w64 long __stdcall Procedimientos(HWND hWindow,
unsigned int Mensaje,
__w64 unsigned int wParametros,
__w64 long lParametros)
{
switch(Mensaje)
{
Al manejar el mensaje del sistema verificamos si existe el
caso en que una tecla ha sido presionada, si es así verificamos si es ‘ARRIBA’,
‘ABAJO’ o ‘ESC’, con esto podremos salir del juego presionando la tecla
‘escape’, y movemos la paleta del jugador.
Nota: Este no era el método acostumbrado de manejar la
entrada de datos, normalmente se usaba DirectInput aunque actualmente es como
se maneja.
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"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;
}
Solo resta correr el programa y ver los resultados, el codigo fuente lo encuentras aqui: http://elias.exofire.net/downloads/Pong/Programa.cpp
|