@ agnasg

¿Dónde están los fuentes? Segunda Parte

21-10-2010 8:09 AM

A veces la gente me llama autista. Y de verdad, si lo soy, mi autismo no me permite ver que soy autista. La mayoría de las veces. Ahora que sé que tengo algunos sintomas de autismo los puedo reconocer con facilidad. Por ejemplo, cuando me preguntan algo y me quedo estático como si no me hubieran preguntado nada. O como si no fuera conmigo. Autista. Pero peor aun es cuando respondo una pregunta diferente a la que me formulan. Este post se originó cuando alguien me preguntó dónde estaban los fuentes. Y ahora me pregunto si la pregunta era “¿dónde están los fuentes en este sitio? Al fin y al cabo en el encabezado dice algó sobre código fuente. Pues aqui no hay fuentes. O por lo menos no los había hasta hoy.  Estoy creando una nueva categoria y una nueva etiqueta llamada código fuente.

¿Cuál es el primer juego que debemos hacer? Tal como dice en estas páginas debe ser algo sencillo. Fácil de diseñar y fácil de terminar. Nuestro primer proyecto no debe ser un motor gráfico. Debe ser algo simple. Pong por ejemplo. El objetivo es darle a la pelota y vencer al programa. Fácil. Simple. Rápido. Algo que se puede hacer en una sentada. Hace siglos estuve en un grupo de programación de juegos donde pasábamos la mayoría del tiempo hablando sobre cómo hacer un juego. Al final a alguien se le ocurrió la brillante idea de dejarnos de habladera y hacer un juego entre todos. De hecho el grupo en sourceforge se llamó averygoodidea.  Hicimos un diagrama, hablamos y hablamos. Formamos grupos de trabajo. Nunca hicimos nada.

Así que lo más importante de un juego es lo siguiente: terminarlo. Calidad, detalles y optimizaciones vienen después. ESTOY HABLANDO EN SERIO.

Como esta versión solamente corre sobre windows vamos a darle un nombre bien original: WinPong.

Esta versión utiliza el api Win32 y está basada en una plantilla estándard. No fue mi primer programa, de hecho lo he estado puliendo recientemente. Incorpora fuentes y pedazos de fuentes de todas partes, quizás algunos con copyright pero como ha ido cambiando de un programa a otro ya les perdí la pista.

Sugerencia:  una de las pesadillas que debemos sufrir como programadores es estudiar código fuente de otros programadores. Cada uno de nosotros tiene su estilo y su idiosincrasia. Si a eso le sumamos el tema del idioma, la pesadilla se convierte en infierno. Yo he visto programas con comentarios en inglés, las variables nombradas con una combinación de estilos y en alemán.

La metodología más usada es la notación Hungara: yo no la uso. Apenas le coloco el prefijo g_ a las variables globales, y “m_” a las variables member. Hasta ahi llegó la notación. Además hay varios estilos: gameObject (primerapalabra en minúscula y la segunda palabra con la primera letra en mayúscula); GameObject (que es la que yo uso para los nombres de las clases); game_object: todo en minúscula y un underscore entre las palabras (esta última es la que utilizo últimamente, porque para colmo voy cambiando a medida que pasa el tiempo). Una regla que aplico para que el código no se vea como una ensalada es utilizar el inglés para todo, incluyendo los comentarios. De esta forma el código luce más uniforme. Los he traducido para este caso. Como quiera que sea la norma establecida es que los hackers escriben en inglés. Finalmente la forma de colocar los paréntesis es la K & R, basada en la forma en que Brian  Kernighan y Dennis  Ritchie utilizaron en el libro original de programación C. Básicamente se distingue por colocar la llave que abre { en la misma línea de la instrucción.

EL código fuente y un ejecutable se puede encontrar aqui. Antes de continuar examine el código fuente para que tenga una idea de su estructura.

La piedra angular de todo juego debe ser la clase GameObject. Esta clase contiene los datos y rutinas de los objetos del juego, que en nuestro caso es la pelota, la raqueta controlada por el computador y la raqueta controlada por el jugador. En nuestro caso debemos controlar la posición (x, y) de nuestro objeto (en las variables m_x y m_y), su velocidad (m_dx, m_dy) y su color. Proveemos rutinas para acceder a estos valores (get_x (); get_y (), etc..) y las rutinas draw () para desplegar la posición del objeto, y update () para actualizar la posición del objeto.


// clase base de los objetos del juego
// pelota, computador y jugador
class GameObject {
protected:
int m_x; // posición x del objeto
int m_y; // posición y del objeto
int m_dx; // delta x (velocidad horizontal)
int m_dy; // delta y (velocidad vertical)
int m_width; // tamaño horizontal
int m_height; // tamaño vertical
HBRUSH m_color; // color del objeto
public:
GameObject(int x, int y, int dx, int dy, int width, int height, COLORREF color)
: m_x (x), m_y (y), m_dx (dx), m_dy(dy), m_width (width), m_height (height)
{
m_color = CreateSolidBrush (color);
};
~GameObject (void)
{
DeleteObject(m_color);
};
// accessors
int get_x (void) { return m_x; };
int get_y (void) { return m_y; };
int get_width (void) { return m_width; };
int get_height (void) { return m_height; };
// los objetos necesitan ser desplegados y y su posición actualizada
virtual void draw (HDC hdc, RECT* prc) = 0;
virtual void update (RECT* prc) = 0;

};

Luego derivamos 3 clases para los 3 objetos. Proveemos el constructor y las dos rutinas draw () y update (), que en la clase base declaramos como virtuales. De esta forma cada objeto tiene su rutina propia para funcionar. Los constructores utilizan la sintaxis Clase (…) : inicialización donde inicialización a su vez es una llamada al constructor de la clase base.


// la pelota es derivada de la clase GameObject
// con sus funciones para desplegar y actualizar su posición
class Ball : public GameObject {
public:
Ball(int x, int y, int dx, int dy, int width, int height, COLORREF color)
: GameObject (x, y, dx, dy, width, height, color) {};
void draw (HDC hdc, RECT* prc);
void update (RECT* prc);
};

// igual la raqueta de la computadora es derivada de la clase GameObject
// con sus funciones para desplegar y actualizar su posición
class Computer : public GameObject {
public:
Computer(int x, int y, int dx, int dy, int width, int height, COLORREF color)
: GameObject (x, y, dx, dy, width, height, color) {};
void draw (HDC hdc, RECT* prc);
void update (RECT* prc);
};

// la raqueta del jugador
class Human : public GameObject {
public:
Human(int x, int y, int dx, int dy, int width, int height, COLORREF color)
: GameObject (x, y, dx, dy, width, height, color) {};
void draw (HDC hdc, RECT* prc);
void update (RECT* prc);

};

Para deplegar los objetos utilizamos una llamada a la función del api Win32 Rectangle. Previamente hemos asignado el color de la imagen haciendo un SelectObject ().

// la función para desplegar el objeto
void GameObject::draw (HDC hdc, RECT* prc)
{
HGDIOBJ old=SelectObject(hdc,m_color);

Rectangle(hdc, this->m_x, this->m_y, this->m_x+this->m_width, this->m_y+this->m_height);

SelectObject(hdc,old);
};

Para actualizar la posición de los objetos debemos aplicar una lógica para establecer su velocidad y recalcular su posición. Por ejemplo, la función update de la raqueta del computador es como sigue:

void Computer::update(RECT* prc)
{
this->m_y += this->m_dy;
if(this->m_y < prc->top) {
this->m_dy = COMPUTER_SPEED;
} else if(this->m_y + this->m_height > prc->bottom) {
this->m_dy = -COMPUTER_SPEED;
}

};

Finalmente capturamos el evento WM_TIMER en WindowProc y colocamos las llamadas a todas estas funciones para que se ejecuten cada cierto tiempo, que hemos definido en WM_CREATE con la llamada a SetTimer (). WindowProc () es la función que se va a ejecutar cada vez que se produzca un evento en el juego. El vento WM_TIMER se genera al dispararse la alarma del timer y WM_CREATE se genera al momento de la creación de la aplicación. Definimos WindowProc como la función que va a manejar los eventos en la WindowsClass, en el WinMain del programa. Fácil.

PS: la idea para este post se me ocurrió hace dos meses.