|
Iniciando… primer F5.
Este es un tutorial para iniciar el largo camino de la programación de videojuegos.
En esta primera entrega, tratare de dar una completa introducción a la tecnología DirectX, en especial Direct3D, pilar que soporta una amplia gama de características; abordaremos las características necesarias para desarrollar, depurar y ejecutar un videojuego.
Antes de continuar, debes saber que DirectX es propiedad de Microsoft, pero no te preocupes, no pagaras ni un centavo para comenzar a crear tus propios videojuegos, tampoco violaras alguna licencia, todo es sin costo alguno.
¿Que es DirectX?
DirectX es una colección de API’s propiedad de Microsoft, que cuenta con muchas características, mayormente multimedia, entre ellas Direct3D [la mas usada], DirectInput, DirectPlay, DirectSound, XACT, XInput, entre otros. Estas API’s proporcionan una interfaz muy cómoda para desarrollar aplicaciones, en esencia, eliminan la programación de complejas tareas en el desarrollo multimedia, principalmente en los videojuegos.
¿Que es Direct3D?
Direct3D se desprende de DirectX y es utilizado para la programación y el procesamiento de gráficos en tercera dimensión, se encarga de toda la mecánica que se necesita para mostrar una imagen 3D en la pantalla.
¿Que es el resto?
DirectInput, proporciona una interfaz con los dispositivos de entrada como el teclado, el joystick, el mouse y otros controles. Aunque es obsoleta, dando paso a XInput, aun es ampliamente utilizada.
DIrectPlay, sirve para comunicaciones en redes.
DirectSound control sobre el dispositivo multimedia de salida, muchos de los reproductores de música usan esta tecnología.
DirectDraw, ahora es obsoleta, pero se podría decir que era Direct2D, se encargaba de procesar gráficos en 2 dimensiones.
XInput, permite el manejo de los controles de la Xbox 360.
DirectSetup, sirve para la instalación de componentes de DirectX.
¿Como empiezo?
Antes que nada, debes de haberte imaginado que por formar parte de Microsoft, el desarrollo sobre DirectX es mucho mas sencillo con las herramientas de Microsoft que con cualquier otra, hoy en día estas herramientas son gratis, así que serán las usadas por este tutorial y en los subsecuentes.
Lo primero que necesitas, es obtener Visual C++, este se encuentra en varias presentaciones, una de ellas es Microsoft Visual C++ Express Edition, la edición gratuita, es una versión completamente funcional y gratis, lo consigues en el siguiente link:
http://www.microsoft.com/express/download/
Nota: de preferencia descarga la versión en Ingles, para hacerlo coincidir con el idioma de DirectX.
Después debes de obtener es el SDK de DirectX, este lo consigues en el siguiente link:
http://msdn2.microsoft.com/en-us/directx/default.aspx
El SDK esta pesadito, > 400 Mb, una vez que lo tengas, debes de instalarlo, como recién estamos comenzando en esto, haremos la instalación completa, que contiene bastante información y muy buenos ejemplos.
Esta instalación contiene el código, las interfaces, librerías, estructuras, clases y todo lo necesario para comenzar a desarrollar videojuegos.
Veras que hay varias versiones de Visual C++ y de DirectX, puedes usar la que gustes, en este tutorial y en siguientes se usara Visual C++ 2005 [al cual nos referiremos como VC++] y Directx SDK November 2007 [al cual nos referiremos como DX SDK].
Con estas dos herramientas podemos comenzar.
Lo primero que debemos de hacer es iniciar VC++ y comenzar un nuevo proyecto, en el menú:

Click para agrandar
En esta ventana seleccionamos Win32 Empty Project dentro de los Proyectos de C++.

Click para agrandar
Nos desplegara un menú en el cual le indicaremos que queremos un proyecto completamente limpio, sin ningún archivo extra, pre compilados, librerías u otras cosas.

Click para agrandar
Al finalizar la creación del proyecto debemos agregar unas librerías para que el programa logre linkearse [ligar], vamos al menú Project->Properties, aquí seleccionamos el directorio Link -> Input y agregamos las siguientes librerías:
d3dx9d.lib
d3d9.lib
winmm.lib

Click para agrandar
d3dx9d.lib es la versión de depuración, cuando un videojuego este listo para su fase de distribución se debe de utilizar d3dx9.lib, la cual es una versión optimizada, sin información de depuración y que incrementa el performance de la aplicación.
Hecho esto solo resta agregar un archivo para el código, en el explorador de soluciones, damos desplegamos el sub-menú del proyecto y seleccionamos, Add->New Item, en las opciones seleccionamos C++ File [.cpp].

Click para agrandar
Solo resta agregar el código, iremos línea a línea explicando de que se trata cada una.
Lo primero que debemos de hacer es incluir las librerías de DirectX, si alguna vez han programado, sabes que al hacer esto agregamos funciones extra a nuestro programa.
#include <d3d9.h>
En seguida creamos un macro, esto no es indispensable, solo que a su servidor se le complica eso de escribir null con mayúsculas, gracias C#, si lo deseas evita el macro y en donde veas null con minúsculas, remplázalo por NULL.
#define null NULL
A continuación creamos un par de variables globales, las cuales son visibles para todo el programa; esta una buena práctica de programación, pero así se comienza, después usaremos métodos mas avanzados como la programación orientada a objetos.
Estas son 2 clases, una contendrá la información de Direct3D per se, y otra la información del dispositivo que usara D3D, en este caso será la dispositivo de salida primario, tarjeta de video-monitor.
IDirect3D9* D3D9 = null;
IDirect3DDevice9* D3D9Dispositivo = null;
Agregamos nuestras funciones prototipo, por el momento veras algunos tipos propios de D3D, al momento de explicar el método serán explicados.
long Iniciar(HWND hWindow);
void Liberar();
void Renderear()
__w64 long __stdcall Procedimientos(HWND hWindow,
unsigned int Mensaje,
__w64 unsigned int wParametros,
__w64 long lParametros);
Una vez agregadas, pasemos al método principal, al igual que en cualquier lenguaje de programación, todo programa necesita un punto de entrada, para los que han programado en lenguajes de la familia ‘C’ y/o derivados, el método main es el punto de entrada, en este caso, se usa WinMain el cual indica que se trata de una aplicación Windows.
El tipo es int __stdcall [ó WINAPI] es tipo entero y el modificador __stdcall, altera la forma en que los parámetros serán leídos, normalmente son leídos y almacenados de izquierda a derecha, __stdcall hace que sean leídos de derecha a izquierda, alterando a los registros del CPU, esto debido a como trabaja MFC que es la API para aplicaciones Windows [graficas].
HINSTANCE hace referencia a las instancias [ventanas] que han generado esta, la cadena de caracteres [char*] se refiere al nombre de la clase de la instancia que lo genero, y el entero [int] hace referencia al código de la ventana padre.
Nota: Estos mecanismos son propios de MFC [Microsoft Foundation Class] no serán abordados en profundidad.
int __stdcall WinMain(HINSTANCE hInst, HINSTANCE, char*, int)
{
Lo primero que hay que hacer es definir nuestra ventana, para eso usamos la estructura WNDCLASSEX, con esto esencialmente le decimos como se comportara.
Se dicta el tamaño en memoria, el nombre de clase, que será “PrimerF5”, la ventana “padre” y muy importante le decimos que usara el método “Procedimientos” que definimos en los prototipos como el manejador de mensajes [eventos].
WNDCLASSEX ClaseWnd = {0};
ClaseWnd.cbSize = sizeof(ClaseWnd);
ClaseWnd.lpszClassName = L"PrimerF5";
ClaseWnd.lpfnWndProc = Procedimientos;
ClaseWnd.style = CS_CLASSDC;
ClaseWnd.hInstance = GetModuleHandle(null);
Una vez definido el comportamiento debemos registrarla y definir lo que se refiere al aspecto, en este caso, la ventana medirá 800 x 600, estará localizada a 10 pixeles en X y Y en el escritorio.
RegisterClassEx(&ClaseWnd);
HWND hWindow = CreateWindow(L"PrimerF5",
L"Hola",
WS_SYSMENU,
10,
10,
800,
600,
null,
null,
ClaseWnd.hInstance,
null);
Una vez creada la ventana, llamamos a nuestro método Iniciar(), usamos el macro SUCCEEDED, para controlar el mensaje que regrese el método, hay que pasarle como parámetro la ventana que usara D3D.
if (SUCCEEDED(::Iniciar(hWindow)))
{
Si Direct3D logra iniciar, debemos mostrar la ventana.
ShowWindow(hWindow, SW_SHOWDEFAULT);
Actualizarla.
UpdateWindow(hWindow);
Después creamos una estructura de tipo MSG que será la que transporte los mensaje de la ventana y el sistema hacia el método “Procedimientos”, que como arriba lo definimos será el que maneje los mensaje.
MSG Mensaje;
Ponemos el mensaje en ceros para evitar errores de basura en memoria.
ZeroMemory(&Mensaje, sizeof(Mensaje));
Y creamos un loop el cual se repetirá hasta que el mensaje que manda el sistema sea el de salir.
while (Mensaje.message != WM_QUIT)
{
Ahora usamos el método PeekMessage, para recoger y procesar el mensaje, en caso de que no exista mensaje, se llama al método Renderear, el cual se encarga de dibujar todo en pantalla.
if (PeekMessage(&Mensaje, null, 0U, 0U, PM_REMOVE))
{
TranslateMessage(&Mensaje);
DispatchMessage(&Mensaje);
}
else
{
Renderear();
}
}
}
Una vez que se termine la aplicación, solo queda borrar la clase del registro de Windows.
UnregisterClass(L"Hola", ClaseWnd.hInstance );
return 0;
}
Este es el método principal, en si, esto es MFC, no DirectX, lo anterior descrito es la rutina básica para crear aplicaciones Windows.
Pasemos a otro método base de MFC, lo explicaremos rápido, por que esto se trata de DirectX.
El procedimiento es del tipo __w64 long __stdcall [ó LRESULT], recibe la ventana vinculada al mensaje, el mensaje y los parámetros.
Hace un switch al mensaje por medio de case’s lo compara con la lista de mensaje, en este caso solo maneja le mensaje de destrucción, ósea que la ventana se debe de cerrar, en este caso llama al método Liberar que como su nombre lo dice, libera los recursos de DirectX, D3D y el dispositivo, manda el mensaje de salida [0 para NO ERRORES].
DefWindowsProc retorna la ventana, el mensaje y los parámetros.
__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);
}
Comenzamos con lo que respecta a DirectX, tratare de ser los mas claro posible. Esta es nuestra función encargada de iniciar el motor de DirectX, recordemos que en el WinMain, llamamos este método dentro del macro SUCCEEDED, esto es para controlar los mensajes que regrese el método, que son un set de bits dentro de una variable de tipo “long”. Este método recibe como parámetro la ventana en la que se dibujara el videojuego.
long Iniciar(HWND hWindow)
{
Lo primero que hacemos es crear una instancia de D3D9, llamando al método Direct3DCreate9, pasándole como parámetro el macro D3D_SDK_VERSION para informar que versión del SDK estamos utilizando.
Si Direct3DCreate9 regresa un valor nulo, no podemos continuar y regresamos como valor E_FAIL, el cual será reconocido por el macro SUCCEEDED del WinMain y se cerrara nuestro programa
if (null == (D3D9 = Direct3DCreate9(D3D_SDK_VERSION))) return E_FAIL;
Ya que se ha creado una instancia, ahora debemos de crear asignar el dispositivo que sera usado por Direct3D, lo primero que haremos es crear una estructura del tipo D3DDISPLAYMODE, usamos “= {0};” para iniciar en 0 toda la estructura
D3DDISPLAYMODE ModosDisplay = {0};
Invocamos el método GetAdapterDisplayMode de nuestra estructura D3D9, debemos de pasar como parámetros D3DADAPTER_DEFAULT, que es nuestro dispositivo primario de salida, ósea nuestra tarjeta de video, el otro parámetro es la dirección de la variable recién creada, para que sea llenada con la información que necesitamos.
D3D9->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &ModosDisplay);
Creamos una estructura que almacenara los parámetros de la presentación, y la iniciamos en 0, para evitar errores de basura en memoria y le decimos como queremos que se muestre.
D3DPRESENT_PARAMETERS pPresentacion = {0};
Sera en modo venatana [no full screen].
pPresentacion.Windowed = true;
No habrá swaping en el modo en que será rendereado, si existiera swaping las caras de los polígonos quedarían invertidas, y esto es algo muy importante en DirectX, ya que lo único que dibuja son las caras que están viendo hacia la cámara, extraño y difícil…
Toma una hoja de papel y un lápiz, ahora imagina que el lápiz es la normal del plano [un vector paralelo a este], el lado en el que pongas el lápiz, será la cara, ahora si la cámara apunta hacia el papel del lado en que no esta el lápiz, esta no será dibujada, ya que la cara no esta viendo hacia la cámara, si la cámara estuviera apuntando a la cara que tiene el lápiz [la normal], esta seria dibujada.
pPresentacion.SwapEffect = D3DSWAPEFFECT_DISCARD;
Usando nuestra estructura con los modos del display, le decimos como debe de manejar el buffer, cada dispositivo en que corra tu aplicación usara diferentes formatos, por eso dejamos que esta le diga como debe de hacerlo.
pPresentacion.BackBufferFormat = ModosDisplay.Format;
Activamos el modo automático del esténcil de profundidad.
pPresentacion.EnableAutoDepthStencil = true;
Y le decimos que sea de 16bits.
pPresentacion.AutoDepthStencilFormat = D3DFMT_D16;
Ahora solo debemos crear el dispositivo, usamos el método CreateDevice del objeto D3D9, debemos de pasar varios parámetros, el primero le indica que debe de usar el adaptador por default, ósea la tarjeta de video primaria [única si se cuanta con solo una], el segundo le dice que el tipo de dispositivo es HAL, ¿que significa?, que el dispositivo de video [tarjeta] soporta la aceleración por hardware, el manejo de los vértices por software o hardware, esta es la opción optima, la que aprovecha todo el potencial de Direct3D.
Otro parámetro es la ventana en la que se presentaran los gráficos.
El quinto, D3DCREATE_SOFTWARE_VERTEXPROCESSING, dicta como serán procesados los vértices, seguido se pasan los parámetros de presentación que acabamos de inicializar y por ultimo la dirección de nuestro objeto D3D9Dispositivo, para que la inicialice con toda la información requerida.
En caso de fallo en la creación regresamos el mensaje E_FAIL, de lo contrario S_OK. Y así nuestra aplicación puede continuar.
if (FAILED(D3D9->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWindow,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&pPresentacion,
&D3D9Dispositivo)))
{
return E_FAIL;
}
return S_OK;
}
Lo siguiente es el método renderear, que es el que se encarga de dibujar en pantalla nuestro juego [lo renderea] para esta aplicación es muy sencillo.
void Renderear()
{
Primero prevenimos que se intente renderear cuando aun no se ha creado el dispositivo que se encargara de esto.
if (::D3D9Dispositivo == null)
return;
Lo que hay que hacer, en un juego y en cada escena, es limpiar la pantalla.
D3D9Dispositivo->Clear(0, null, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0);
En seguida debemos de comenzar una escena, con ayuda del método BeginScene, después de este punto debemos de dibujar todo en pantalla, debemos de checar si se pudo iniciar la escena con ayuda de un “if”.
if (SUCCEEDED(D3D9Dispositivo->BeginScene()))
{
En esta ocasión no dibujaremos nada, así que terminamos la escena con el método EndScene.
::D3D9Dispositivo->EndScene();
}
Por ultimo debemos de Presentar la escena que acabamos de crear.
::D3D9Dispositivo->Present(null, null, null, null);
}
Lo último que haremos es agregar un método que se encargara de liberar la memoria ocupada por DirectX
void Liberar()
{
Hemos trabajo con punteros, así que verificamos si el puntero de nuestro dispositivo Direct3D no es nulo [apunta a la memoria], entonces liberamos el recurso.
if(::D3D9Dispositivo != null)
::D3D9Dispositivo->Release();
Lo mismo hacemos con Direct3D
if(::D3D9 != null)
::D3D9->Release();
}
Ahora estamos listos para correr nuestro primer videojuego, presionamos F5 y listo.
Una pantalla azul, ¿que clase de juego es este? Bueno pues esto es la base de cualquier videojuego, hemos creado la lógica, el dibujado y el loop de juego, tres elementos importantes.
Por favor no vayas corriendo con tu familia y les digas que te quieres dedicar al desarrollo de videojuegos y les muestres lo que acabamos de hacer, por que seguro te mandaran inmediatamente al colegio militar.
Pero no te preocupes, la gente de Nintendo, Capcom, Konami, EA, Ubisoft, Valve o cualquier desarrolladora de videojuegos empezó por esto, nadie nació sabiendo hacer videojuegos… bueno tal vez Shigeru Miyamoto, pero no cualquier persona inventa cosas como Mario Bros.
Anda a jugar un poco con el código, aunque no veras muchos cambias hasta que tengamos u juego de verdad… no te preocupes que para la próxima haremos nuestro primer videojuego… “Pong”.
PD. Se que hay muchas cosas que no fueron explicadas, muchas de estas son demasiado técnicas como para abordarlas en este tutorial de inicio, checa la documentación o espera a los próximos tutoriales.
|