Boletín GameDevLessons.com
Vol 5 - 8 de Agosto, 2009
¡Hola!
Soy Chris DeLeon (acerca de mi), y gracias por acompañarme en mi boletín mensual de desarrollo de videojuegos, vol. 5. Estos boletines son una de las formas en las que espero ayudar a empezar a nuevos desarrolladores, mientras ayudo a los que ya están en ello a llevar su trabajo en nuevas direcciones.
I.) Ediciones anteriores, suscripción
II.) Principiante - Fundamentos de programación
Fundamentos de programación para juegos
La instrucción If (condicional)
Las instrucciones Else y Else-If
III.) Nivel medio - Diseño de niveles
Filosofía del diseño de niveles
Métodos de prototipo rápido para diseño de niveles
IV.) Avanzado - Saber cuando hacer "hacking"
Hacking como programación apresurada
Los profesionales serios lo odian
Los desarrolladores de videojuegos profesionales saben cuando hacerlo
Hubo una época, en la que era la única forma
Soluciones de compromiso y circunstancias
I.) Ediciones anteriores, suscripción
Mantente al día con cada boletín - ¡Gratis!
Para leer ediciones anteriores, o suscribirte: http://gamedevlessons.com/?page=free (Cada boletín está en inglés, con un enlace a la traducción al español, si está disponible)
Si encuentras este enlace en algún otro lugar que no sea tu correo electrónico, y te gustaría que te informara cuando salgan las nuevas ediciones, únete a mi lista de correo (libre de spam). Sólo te llevará un minuto, nunca te enviaré más de un correo al mes (sólo para anunciar estos boletines), y si cambias de opinión puedes darte de baja en cualquier momento.
Los boletines no pretenden ser acumulativos - así que no tendrías por qué leer las ediciones anteriores para entender este - pero los diferentes temas tratados en cada uno pueden serte de ayuda. En particular, si eres un recien llegado a mis boletines, recomiendo Boletín Vol. 1 por su introducción a la programación de una forma no técnica, así como por sus enlaces a recursos gratuitos para editar imágenes, audio, modelar en 3D, y otros recursos necesarios en el desarrollo de videojuegos.
II.) Principiante - Fundamentos de programación
Fundamentos de programación para juegos
El objetivo de esta sección no es transformar en un instante a alguien que no tiene experiencia previa en programación en un programador de videojuegos. Eso lleva tiempo y práctica, leer e intentar, experimentar y pensar, en un periodo de semanas (para un nivel básico), meses (para un nivel intermedio), y años (para llegar a ser un experto). No pretendo que esto sustituya tampoco a un buen libro en programación, o práctica - esto está aquí para ponerte en marcha, revisar, o complementar otras fuentes que estén a tu alcance mediante un repaso a como se usan las estructuras de programación más comunes en el desarrollo de videojuegos.
Espero que pueda conseguir que todo se resulte familiar, y establecer un contexto para el uso de los variados elementos de programación. (Si estás algo perdido, o no tienes claro que es programar: ¡mira esto Boletín Vol. 1!).
Los siguientes ejemplos están en su mayoría en lenguaje C, tomando ventaja de la flexibilidad dada por el compilador C++. C++, Objective-C, ActionScript 3, y Java usan las mismas o idénticas estructuras para la misma funcionalidad, y estos son los lenguajes de programación más utilizados para desarrollar en las consolas de hoy, en los contenidos descargables, en móbiles, y videojuegos en web.
Por último, el texto en esta sección introduce mucho material, teniendo en cuenta el poco espacio que ocupa. Si eres nuevo en la programación, te llevará más de una lectura, pero hay bastantes posibilidades de que te enteres de algo nuevo de cada vez. Recuerda: programar es lo que hace que funcione cada videojuego en el mercado (Wii, online, móbiles, 360, PS3, SNES, Atari, Recreativas...), cada herramienta que usas (Office, Windows, Firefox...) y cada dispositivo electrónico que empleas. Hay que ser bastante paciente para entender como funciona todo esto. Sigue adelante - ¡te alegrarás de haberlo hecho!
Qué es:
El espacio en blanco se refiere a los huecos entre las letras en el código fuente de un programa, tanto si son tabulaciones, espacios o saltos de línea (la tecla enter o return). Muchos lenguajes de programación modernos tratan los espacios en blanco de la misma forma, lo que significa que no debes preocuparte por ellos, tanto si añades saltos de línea, endentas a una distancia determinada, pones dos espacios en vez de uno, o usas tabulaciones en vez de espacios, el código fuente funcionará sin problemas.
Como identificarlo:
for(int i=0;i<5;i++){muestraNumero(i);hazOtraCosaTambien();}
...funciona igual, pero no se lee tan bien como...
for(int i = 0; i < 5; i++) {
muestraNumero(i);
hazOtrasCosasTambien();
}
Ejemplo de uso en un videojuego:
Cuando el código fuente de un programa no compila correctamente, especialmente para un programador principiante, es con frecuencia debido a una equivocación o despiste con las llaves (los símbolos { y }). El uso efectivo de la endentación para indicar cuantos pares de llaves hay en el código ayuda a llevar un control de donde deberíamos cerrarlos. Todos los errores en programación son mostrados por el compilador utilizando números de línea, los cuales son sólo útiles para el programador si el código está troceado en líneas, añadiendo un salto de línea después de cada punto y coma.
Qué es:
Texto visible sólo para el lector humano, el ordenador lo ignora cuando tiene que generar ("compilar") el programa.
Como identificarlo:
// El texto que hay detrás de dos barras inclinadas a la derecha y en la misma línea es un comentario
/* ...y cualquier cosa que se escriba entre una barra y un asterisco y un asterisco y una barra, es también un comentario (incluyendo saltos de línea, muy útil para escribir varias líneas de comentario. */
Ejemplo de uso en videojuegos:
Si programas algo al comienzo de un proyecto, luego vuelves a esa parte del código semanas o meses después, posiblemente tengas problemas para saber que querías hacer cuando lo escribiste. ¿El algoritmo de detección de colisiones asume que dos objetos no estaban ya superpuestos en el frame (fotograma) anterior? ¿Hay un límite a cuantos enemigos se pueden dibujar a la vez? Los comentarios son una forma perfecta para registrar estas ideas con el código donde se implementan, para asegurar que llega a tu yo futuro, o a cualquiera que acabe leyendo el código fuente.
Qué es:
Es tarea del programador crear nombres para todo lo que se usa en el código fuente - nombres para registrar números que se usan de forma distinta, para referirse a frases de texto que sirven diferentes propósitos, y nombres para diferentes trozos de código de forma que describan su funcionalidad. Al ordenador le da igual lo que estos nombres digan ("dibujacosas", "ElvisElRey" y "BV3_aUiPO" son todos igualmente válidos) - pero a través de su uso consistente en diferentes partes del programa el programador almacena, cambia y comprueba valores para diferentes propósitos.
Aunque el ordenador no puede diferenciar entre una funcion para actualizar la pantalla llamada "ActualizaPantalla" y una llamada "LosPerrosSeExcitan", la primera opción es más inteligente porque refleja como el programador está usando la etiqueta. Observa que los nombres que utilizas en tu programa son 100% invisibles para el usuario y el mundo exterior - son sólo una forma para trabajar organizadamente y poder manipular tantos números y funcionalidades sin problemas.
Como identificarlo:
moverEnemigo(); // buen nombre para un trozo de código
int posicionJugadorX; // un nombre claro para un número
abc123(); // nombre muy confuso
int r; // malo. radio? radiación? redondez? rapidez?
Ejemplo de uso en videojuegos:
Si eres bueno con los nombres de todos los números, secciones de código, archivos, y otras etiquetas que necesite tu programa, el código puede ser muy agradable de leer para cualquiera. Eso significará menos tiempo a emplear en documentar con comentarios para explicar o clarificar qué se hace, y bastante menos tiempo se gastará en confusiones (o problemas surgidos por la confusión) por culpa de malas elecciones en los nombres.
Qué es:
Con sólo unas pocas excepciones (ej. algunos temas muy avanzados relativos a la PS3, Xbox 360, y programación en ordenadores modernos), el código del programa se ejecuta instrucción por instrucción, una de cada vez, de arriba abajo, igual que cuando lees frases en un texto. Cada instrucción va en su propia línea, para facilitar la lectura por humanos, y debe terminar con un punto y coma, para facilitar la lectura por la máquina.
Como identificarlo:
hazAlgo(); // ocurre primero
hazOtraCosa(); // luego esto
ultimaCosaEnOcurrir(); // ocurre al final
Ejemplo de uso en videojuegos:
muevePorInputJugador();
detectaColisiones();
dibujaTodoEnPantalla();
...es un orden más inteligente que...
detectaColisiones();
muevePorInputJugador();
dibujaTodoEnPantalla();
...porque en el segundo orden las posiciones de los personajes no son actualizadas por las colisiones que podrían suceder por el input del jugador hasta que el frame actual se dibuja en pantalla. Eso significa que el jugador podría ver cosas superpuestas una encima de otra, ¡lo que podría haberse evitado con solo cambiar de sitio 2 líneas de código!
Qué es:
Básicamente una solución autónoma a un problema recurrente. En su forma más simple, es sólo un grupo de código que puede ser llamado desde cualquier otro lugar en el código, cualquier número de veces, con una etiqueta de una palabra. Funciones más elaboradas pueden aceptar parámetros de entrada para uso en cálculos, y/o devolver resultados reflejando su procesamiento. El par de paréntesis siempre siguen al nombre de la función cuando se utiliza; si es una función que toma parámetros, esos valores deben ser indicados entre los paréntesis.
Como identificarlo:
Declaración
void hazAlgo(void) { // una funcion sin parámetros de entrada/salida
// ...aquí va el código...
// cada vez que la función se llama
// este código se ejecuta, de arriba abajo,
// desde la llave de apertura "{"
// hasta la llave de cierre "}"
}
Llamada
hazAlgo(); // ejecuta el "...aquí va el código..."
hazAlgo(); // lo hace de nuevo (la hemos vuelto a llamar)
Ejemplo de uso en videojuegos:
Agrupar código relacionado en funciones ayudan a organizar el código en secciones más legibles. Una función llamada dibujaCosas() debería contener llamadas a todas las otras funciones de dibujo, como las usadas para dibujar el fondo, el jugador, los enemigos, objetos, proyectiles e información sobre la salud, las cuales a su vez llamarán a otras funciones que manejan los detalles de como mostrar las imágenes necesarias en los lugares apropiados. Este acercamiento te permite echar un vistazo de forma separada y en detalle a como funcionan diferentes partes del programa, rompiendo la funcionalidad en bloques fáciles de entender. Tambien centraliza el código que es necesitado múltiples veces en 1 sólo sitio, donde puede ser reparado o actualizado sólo una vez si se encuentra que algo no va bien.
Inclusión de librerías (include)
Qué es:
Tecnicamente, coge el contenido de otro archivo y lo vuelca palabra por palabra en el archivo que tiene la instrucción include.
Practicamente, determina a que funcionalidad prefabricada tendrás acceso. Funciona de esta forma porque la mayoría de los archivos que incluimos son aquellos que definen funciones prefabricadas para que las llamemos desde nuestros programas - la instrucción include automáticamente inyecta esas definiciones en la parte superior de nuestro archivo. Los lenguajes de programación vienen con muchas librerías preparadas para cosas como mostrar texto, leer la entrada por teclado, acceder a la red, y realizar comparaciones/manipulaciones de texto.
Como identificarlo:
#include <stdio.h> // nos permite escribir texto en la pantalla
// "stdio" viene del inglés "STanDard Input Output" entrada y salida por defecto
...luego, entre el código...
printf("¡Esto se escribirá, gracias a stdio.h!");
Ejemplo de uso en videojuegos:
A través de internet se pueden descargar librerías adicionales (como Allegro, SDL, o Directx en este caso) eso te permite hacer simples llamadas a funciones para cargar imágenes/archivos de audio en memoria, cambiar la resolución de la pantalla, mostrar imágenes en la pantalla, reproducir efectos de sonido, y manejar de forma fluida la entrada de teclado/ratón/joystick. La gente se ha ganado sus titulaciones encontrando formas más rápidas para que un ordenador dibuje una línea - no gastes tu tiempo intentando reinventar la rueda, cuando generaciones de investigadores ya se han ocupado de buscar soluciones para que no tengas que hacerlo tu. En vez de eso, ¡encuentra e incluye librerías que implementen sus brillantes soluciones!. Esto te permitirá centrarte en tu programación y en todo lo que hace tu videojuego con esos gráficos, sonidos y eventos de entrada, en vez de preocuparte en como lo consiguen.
Qué es:
Una declaración de variable es una línea de código que establece un nombre para un contenedor de un valor. Es cosa tuya como programador el buscar un nombre apropiado. Ese valor es normalmente un número, una letra, una serie de letras (una cadena o "string"), o un objeto (un grupo de números, strings, y/o otros objetos) - para ser guardado, actualizado y comprobado a lo largo del código.
Como identificarlo:
int salud; // nos permite almacenar/cambiar/comprobar 1 número
int vidas; // para almacenar/cambiar/comprobar un número distinto
// int viene del inglés INTeger (entero: un número sin decimales)
Ejemplo de uso en videojuegos:
Cada posición horizontal - lo lejos que el jugador, un enemigo, una mejora, o una partícula está del lado izquierdo de la pantalla - debe ser registrada con una variable distinta.
Cada posición vertical, también.
Lo mismo se aplica para la cantidad de salud que cada personaje tiene, cuantos personajes están vivos al mismo tiempo, cuanto poder mágico le queda al jugador, que mejoras tiene el jugador en su inventario, en que nivel está el jugador... si es algo que tiene que ser recordado, incluso temporalmente, y no es siempre el mismo número, necesita una variable. Vas a declarar muchas y muchas variables.
Qué es:
Un "tipo" es una forma de variable. Algunos de los más comunes son int (integer, número entero no decimal), float (de punto flotante, se refiere a un número con decimales, que permite más precisión que los números enteros), y char (de character, caracter en inglés (una letra)).
Como identificarlo:
int esteEsUnNumeroEntero;
float estePuedeSerUnNumeroDecimal;
char aquiSePuedeGuardarLaInicialDelNombreDeAlguien;
Ejemplo de uso en videojuegos:
El número de vidas será un integer, ya que se cuenta en unidades completas. La velocidad de un cohete y su posición deberían ser floats, ya que para un movimiento suave con una aceleración con ligeras variaciones se necesita algo más que numeros enteros. Las iniciales de un jugador en la pantalla de tabla de puntuación serán almacenadas como char.
Qué es:
El signo igual en programación, a diferencia del signo igual en matemáticas, no significa "lo mismo" en los dos lados de la ecuación. De hecho, no se usa para escribir una ecuación - se usa para crear una instrucción de asignación. Donde hay un simple signo igual, lo que esté en el lado derecho se evalua (se suma, se multiplica, se sustituye, etc...) y luego se almacena ese resultado en la variable a la izquierda del signo igual. Es decir, un nuevo valor es asignado a ese nombre de variable.
Como identificarlo:
int almacenamientoTemporal; // crea una variable
almacenamientoTemporal = 5; // guarda 5 en esa variable
// se lee como: "mete el valor 5 en almacenamientoTemporal"
// La siguiente línea es un ejemplo de lo que NO hacer:
5 = almacenamientoTemporal; // ¡sin sentido! ¡MAL!
// Es como decir "mete almacenamientoTemporal en 5"
// Suena a disparate, ¿no?
// ¿Qué significa guardar algo en 5?
// El ordenador tampoco lo sabe :)
...otro ejemplo...
int num1;
int num2;
num1 = 10;
num2 = 35;
num1 = num1 + num2 - 2;
// num1 al final vale 43 (de 10 + 35 - 2)
// num2 se queda con 35, porque está a la derecha del =
Ejemplo de uso en videojuegos:
Configurando el número de vidas de un jugador (asumiendo que "int vidas" ha sido declarado antes):
vidas = 7;
Restando 1 vida:
vidas = vidas - 1;
Dando 2 vidas extra:
vidas = vidas + 2;
Cuidado con la línea siguiente, ya que no cambia el valor de vidas, ya que no hay signo igual que actue como operador de asignación (=):
vidas + 1; // ¡No cambia nada! ¡No hay signo =!
Ya que el método previo de añadir y restar requiere escribir el nombre de la variable dos veces, hay un método más rápido inventado por los programadores para indicar este tipo de operaciones con una misma variable, y que funciona como el operador de asignación (=):
Restando 1 vida:
vidas -= 1;
Dando 2 vidas extra:
vidas += 2;
Puedes escribirlo como quieras. "Var = Var + num;" es lo mismo que "Var += num;" y también sirve para la multiplicación (*), división (/), y la resta (-). Por último, la suma o la resta por 1 también es común escribirla sin el operador de asignación:
Restando 1 vida:
vidas--;
Añadiendo 1 vida:
vidas++;
(De aquí es de donde el lenguaje de programación C++ obtuvo su nombre. Es lo mismo que el lenguaje de programación C, pero con nuevas cosas añadidas.)
Qué es:
Una forma de organizar múltiples variables juntas en una agrupación con un nombre con sentido. Igual que los nombres de función y los nombres de variables, el programador inventa estos nombres, idealmente escogiendo un nombre que refleje como el grupo de variables se usarán. En C, se suele usar como un struct, que sólo contiene un grupo de variables, pero en C++ y la mayoría de otros lenguajes más recientes puede ser class, que tiene la habilidad de agrupar también funciones, además de otros beneficios.
Como identificarlo:
struct personaje {
int salud;
int x, y;
};
personaje elJugador;
personaje enemigo;
elJugador.x // variable para la posición x del jugador
enemigo.salud // variable para el valor de salud del enemigo
Ejemplo de uso en videojuegos:
Los objetos - tanto si son structs en C o clases en los nuevos lenguajes de programación - son esenciales para mantener un proyecto organizado y en crecimiento para incluir multitud de diferentes tipos de personajes, partes del entorno, formatos de datos, y otras agrupaciones de variables conceptuales. El ejemplo anterior es muy simple, un uso típico, pero se puede expandir para contener la imagen de cada personaje, su estado emocional, velocidad, rotación, frame de animación, inventario, etc...
Qué es:
Un array es una forma de almacenar una secuencia de variables, de forma que comparten una etiqueta común (imagínate que es el nombre de una calle) y con diferentes números de índice para disinguir una variable de otra (imagínate los números de los portales de cada casa).
Como identificarlo:
// El número entre [corchetes] declara el tamaño del array.
// Los array comienzan en 0, por lo que el número más alto
// que podemos usar como índice es 1 menos que el tamaño del array.
// Definiendo un tamaño de array de[3] nos da los elementos [0],[1], y [2].
int numeros[3]; // crea memoria para 3 variables int
numeros[0] = 1000;
numeros[2] = 70; // ¡funcionan como 3 números separados!
numeros[1] = numeros[2] + numeros[0];
// numeros[0] contiene 1000
// numeros[1] contiene 1070
// numeros[2] contiene 70
// 3 números separados, 1 etiqueta común
Ejemplo de uso en videojuegos:
En cualquier momento hay mucho de algo, y cada uno se comporta de la misma forma o están basados en reglas similares, es probable que se utilice un array o una estructura similar para ellos. Efectos de partículas, personajes enemigos, unidades del campo de batalla, proyectiles... siempre se suelen programar utilizando arrays, y utilizando la estructura objeto de un lenguaje para agrupar las variables individuales de cada uno.
Un array de letras ("carácteres" o chars) forma una "cadena" (string), o palabra/frase que puede ser usada como un nombre, mostrado en pantalla como texto, etc...
La instrucción If (Condicional)
Qué es:
Comprobar cuando una o más comparaciones matemáticas o funciones evaluan como verdadero, y ejecutar una seccion de código sólo si así es. Comparaciones válidas incluyen:
a < b (¿a es menor b?)
a > b (¿a es mayor que b?)
a <= b (¿a es menor o igual que b?)
a >= b (¿a es mayor o igual que b?)
a != b (¿a no es igual que b?)
a == b (¿a es igual que b? ¡recuerda, 1 signo igual es asignación!)
Se pueden combinar múltiples comparaciones usando lógica booleana:
(a < b) && (b > c) significa (¿a < b? Y TAMBIÉN ¿b > c?)
(a < b) || (b > c) meaning (¿a < b? O ¿b > c?)
Como identificarlo:
if(vidas > 0) { // "si el jugador está vivo"
// ...mover y dibujar al jugador en esta parte...
} // si no es así saltar el código hasta aquí
Ejemplo de uso en videojuegos:
Matar un personaje cuando se queda sin salud, forzar al jugador a permanecer en la pantalla si la posición x o y no pasan de un rango concreto, comprobar cuando una pistola necesita recargarse antes de permitir que se inicie la animación de recargar, etc...
Las instrucciones Else y Else-If
Qué es:
Usar una sección de código sólo si el resultado de una instrucción if es falso.
Como identificarlo:
if(vidas > 0) { // "¿está vivo el jugador?"
// ...mover y dibujar al jugador aquí...
} else if(continuaciones > 0) { // "¿tiene continuaciones?"
// ...ir a la pantalla de continuación...
} else { // "si el jugador no tiene"
// ...tocar música triste...
// ...ir a la pantalla de game over...
}
// Fijate que puedes tener tantos else-ifs como necesites
// La presencia de cualquier else o else-if es opcional
// Si hay un else, debe ser el último en la cadena
Ejemplo de uso en videojuegos:
Es muy común usar este tipo de evaluaciones en los juegos, para que un enemigo controlado por el ordenador evalue circunstancias con una prioridad predefinida, y para manejar una serie de circunstancias que podrían hacer que una colisión detone un proyectil, o para manejar la selección de una opción de menú.
La instrucción o secuencia switch-case (no cubierta aquí, pero fácil de encontrar en internet) es una construcción alternativa siempre que las evaluaciones se hagan con enteros consecutivos (¿es 1?, si no ¿es 2?, si no ¿es 3?, etc...).
Qué es:
Funciona muy parecido a la instrucción If, sólo que cuando se alcanza la llave del final, comprueba la condición del While de nuevo. Cuando la condición del while aún es cierta, vuelve a pasar por todo el código que incluye entre sus llaves una vez más; si la condición del while es falsa, la ejecución continua con la siguiente instrucción después de la llave de cierre del while.
Como identificarlo:
while( partidaPerdida == 0 ) {
analizaEntradaRaton();
mueveEnemigos();
actualizaPantalla();
if( salud < 0 ) {
partidaPerdida = 1;
}
}
Ejemplo de uso en videojuegos:
Su uso más habitual es el mostrado en el ejemplo anterior - la lógica del juego y las actualizaciones de pantalla ocurren constantemente dentro del bucle while, hasta que algo ocurre en el programa (se presiona una tecla, se escoge una opción de menú, se agotan las vidas, etc...) causando que la evaluación de su condición (en el ejemplo "partidaPerdida ES IGUAL A 0") ya no sea cierta.
Es importante que entiendas que con cualquier bucle existe el riesgo de un "bucle infinito" - código que nunca para de ejecutarse - y aparece cuando dentro del código que hay en un bucle no hay nada que modifique la expresión que se evalua en la condición del bucle para que sea falsa. Si tu programa se ejecuta en un terminal viejo, y parece estar bloqueado, CTRL+C cortará cualquier secuencia en ejecución. Si todo lo demás falla, CTRL+ALT+DEL (en PC y en Windows) o Forzar Quitar (Mac) te permitirá terminar de forma segura con un programa que está atrapado en un bucle infinito.
Fijate que el uso anterior ya no es común en ciertos lenguajes nuevos, particularmente ActionScript 3, el cual gestiona la lógica del juego mucho mejor estableciendo un temporizador (timer) para llamar a una función particular X veces por segundo. (No se muestra aquí.)
Qué es:
El bucle For agrupa varios patrones de programación comunes, y resulta muy útil: establece una variable para que se use en un bucle while, declara la expresión de comparación para que se evalue en el bucle while, y hace algo cada vez que se completa el bucle para afectar a la variable del bucle. Es muy comun que se use para contar desde un número a otro (recorrer un rango de valores), pero también puede ser utilizado para analizar cada letra de una palabra, comprobar cada pixel de una imagen, o llamar a una función de cada enemigo en el mundo del juego.
Como identificarlo:
for(int contador=0; contador<100; contador++) {
// ...haz algo aquí...
// se ejecutará 100 veces.
}
...y es lo mismo que...
int contador=0;
while(contador<100) {
// ...haz algo aquí...
// se ejecutará 100 veces.
contador = contador + 1; // ¡Recuerda! Es lo mismo que "contador++;"
}
Ejemplo de uso en videojuegos:
Recorrer la lista de enemigos y actualizar su posición, donde cada enemigo es un objeto de un array, llamando una función del enemigo en la posición del array indicada por la variable contador del bucle for.
Ejemplo de código fuente de un juego:
Para jugar, mueve tu ratón para mover la pala, y haz click para relanzar la pelota cuando salga de la pantalla. Pulsa la tecla Escape para salir. El archivo zip incluye binarios precompilados para MAC (breakout-mac) y PC/Windows (breakout.exe) - haz doble click en ellos para jugar. Las instrucciones para recompilar estos programas por tu cuenta están más abajo - de esta forma puedes experimentar haciendo cambios al código y viendo como funciona.
Descarga Dev-C++ 5.0 beta 9.2 (4.9.9.2) (9.0 MB) con Mingw/GCC 3.4.2. Cuando lo hayas instalado, dentro del Dev-C++ ve a "Herramientas" y "Buscar actualizaciones", luego bajo "Select devpak server" escoge "devpaks.org Community Devpaks". Pulsa el botón "Check for Updates", y marca las casillas para instalar Allegro (la librería para audio, entrada, y funciones gráficas) y Allegro Supplement (documentación y archivos de ejemplo). Después de eso, abre el archivo .dev que he incluido, el cual ya tiene la librería Allegro incluida en la sección de parámetros del linker bajo las opciones del proyecto (el comando es "-lalleg"). Luego usa los menús para ir a Ejecutar, "Compilar y Ejecutar". Cada vez que hagas cambios al fuente del programa, puedes actualizar el juego repitiendo el último paso de "Compilar y Ejecutar".
Descarga Xcode para Mac-sólo desarrollo- del sitio web de Apple para que te instale todas las herramientas de desarrollo. También descarga el código fuente de Allegro para Linux (lo encontré aquí). Haz doble click en el archivo descargado allegro tar.gz para convertirlo en una carpeta, luego usa el terminal para llegar hasta esa carpeta (comando 'cd') allegro-4.2.2 (¿necesitas aprender los comandos básicos del terminal?), luego escribe las siguientes líneas en el terminal para instalar Allegro:
chmod +x fix.sh
./fix.sh macosx
make
sudo make install
sudo make install-man
sudo make install-applehelp
sudo make install-framework
make
sudo make install
Después usa el terminal para navegar a la carpeta que contiene el código fuente del ejemplo Breakout. Luego teclea este comando:
g++ main.cpp core.cpp -o breakout-mac `allegro-config --libs`
FIJATE que el caracter ` es un apostrofo inverso, no una comilla simple/apostrofo normal.
Para editar el código, abre main.cpp usando un editor de texto normal (TextEdit, TextWrangler, DashCode...). Para recompilar el programa y ver los cambios hechos a main.cpp, repite el paso anterior para teclear "g++ main.cpp...[y el resto]" en el terminal.
Si usas Linux, creo que ya sabes como hacer esto :)
Pista: mira las instrucciones para la sección Mac.
III.) Nivel medio - Diseño de niveles
Filosofía del diseño de niveles
Cada decisión en el diseño de un nivel es un acto consciente del diseñador de niveles.
Hacer niveles no se trata de colocar muros; hacer niveles se trata de planificar. Una vez que un desarrollador de niveles está acostumbrado a las herramientas que tiene a mano, y al motor del juego que hay por debajo, el énfasis cambia de colocar "muros" a ubicar "habitaciones", y de colocar "enemigos" a diseñar "encuentros".
Es posible que sólo hayas jugado a juegos de una misma compañia con la misma filosofía de diseño de niveles. Lo que funciona en un juego, dada su IA, conjunto de armas e interfaz de usuario, por regla general no se traduce bien a otro título en el mismo género. Los niveles de Goldeneye N64 son niveles pobres para Doom; los niveles de Doom son pobres en el Unreal Tournament; los niveles del Mario no funcionarían en el Sonic y los niveles del Sonic no funcionarían en el Mario.
¿Por qué molestarse con la filosofía de diseño de niveles? ¿Por qué no hacer simplemente mapas que sean distintos cada vez? Al igual que hay conflictos entre filosofías, que no funcionan bien entre juegos, para cada juego hay algunos puntos importantes que pueden tenerse en cuenta para obtener lo mejor del motor del juego. Repasaremos unas cuantas formas de pensar acerca de ubicar los espacios en un nivel, ya que separándolos, podremos cambiar mejor entre ellos deliberadamente para ajustarnos a las necesidades de nuestro proyecto.
Algunos juegos se centran en entornos realistas, al punto de que la mayoría de los niveles están diseñados para que den la sensación de ser reales como los sitios donde ocurren esas experiencias de juego (ej. Hitman, Rainbow Six). Para estos juegos, la mayoría de las habitaciones, pasillos y áreas abiertas dan la sensación de estár ubicadas sin un énfasis especial en los puntos de inicio del jugador, cajas de munición/salud, o ubicación de enemigos. Un arquitecto, de forma natural, es casi seguro que diseñará este tipo de mapa, así que le llamaremos el "Diseño de arquitecto". Propone un fuerte sentido de immersión cuando está bien hecho, ya que los edificios reales no están diseñados para ser objetivos en misiones, pero se puede conseguir un flujo extraño que confunda a los que lo jueguen por primera vez.
Otros títulos se centran en el flujo de la acción. Halo es el ejemplo imbatible en este diseño de estrategia, utilizando el Diseño de Arquitecto para compensar el desorden del campo de batalla. El jugador rara vez se queda pensando donde debería ir, ya que suelen haber tiros, gritos y acción en la dirección por la que debería ir. Llamaremos a este diseño el "Diseño de bombero", ya que da como resultado que el jugador corre de un punto a otro para "apagar fuegos". Esto requiere una considerable cantidad de eventos programados (scripted events, en inglés, ya que se suelen programar mediante un lenguaje de script, interpretado por el motor del juego), y no deja demasiadas oportunidades para que el jugador descanse. Se corre el riesgo de dañar la rejugabilidad haciendo que las cosas interesantes ocurran predeciblemente en la segunda partida, pero puede ofrecer una extrema experiencia cinemática (ej. las franquicias Medal of Honor y Call of Duty).
Algunos juegos atraen al jugador a través de la exploración. Tomb Raider y Descent se basaron en parte en esta "atracción de la curiosidad" (se deja al jugador pensando "¿puede que este camino lleve a la salida...?"). Sin una cuidada atención puesta en atractivas marcas visuales en la distancia, y claras distinciones visuales entre diferentes áreas, puede llevar a diseños de mapas arbitrarios, dejando al jugador deambulando en circulos por las esquinas para encontrar el próximo área donde seguir buscando. Tomb Raider, en su mayor parte, fue un éxito al hacer esto bien, mientras que Descent (por desgracia) lo hizo bastante mal. En un juego más orientado a la acción, es aún peor si se hace deambular al jugador por zonas ya visitadas donde lo más interesante de este género (¡los enemigos!) ya están eliminados.
El nombre de esta técnica viene del cuento de los hermanos Grimm, Hansel y Gretel, en el cual un niño y una niña dejan un rastro de miguitas por el bosque para encontrar luego el camino a casa. Esta vez es "recogida" porque, en este método de diseño de niveles, hay miguitas esparcidas por todas partes por los diseñadores del juego, y el jugador encuentra el camino cogiéndolas todas.
id Software usó esta técnica en Doom, pero está entre nosotros al menos desde Pac-Man, y aparece incluso hoy en día en los modernos juegos FPS. Recoger miguitas enlaza esquinas, pasillos, habitaciones laterales, y áreas de acción con objetos baratos, de bajo valor y riqueza acumulativa: +1% de salud, +1% de armadura, y pequeños cartuchos de munición son ideales para esto, pero en algunos juegos se usan incluso enemigos muy facilones. Aparte de darte como diseñador de niveles un instrumento de instrucción para mostrar al jugador donde ir, también da un test visual inmediato para que el jugador marque o sepa si ya ha pasado por el área. Si no hay más items (o enemigos facilones) en una habitación con varios pasillos de salida, el jugador sólo tiene que explorar el pasillo que todavía tenga items para encontrar nuevas áreas del mapa.
Esta técnica recompensa constantemente al jugador, y lleva a que la mayoría de áreas del mapa sean visitadas. Es necesario tener cuidado para minimizar la profundidad de los callejones sin salida, para evitar que el jugador acabe perdido y sin items baratos como pista.
Aunque Painkiller está entre los juegos comerciales recientes que explotan esta estrategia de diseño, juegos como Robotron, Mega Man, y Zelda marcaron su lugar en la historia. El concepto detrás de la "Jaula de pelea" es tener al jugador luchando batalla tras batalla en arquitecturas aisladas. Esto evita que el jugador use zonas, que por diseño del nivel, estén a su favor y donde tome ventaja de una IA determinista. Implica que el mundo tenga una inteligencia maliciosa y dominante manipulando el entorno del jugador, pero esto funciona siempre que la historia lleve al jugador a una mazmorra, a un templo maldito, o a un nido de aliens.
Saltos, llaves, explotación del motor de físicas, e interruptores remotos o temporizadores dominan los juegos basados en puzzles. Es raro ver un juego completamente basado en puzzles, pero es habitual que en cada juego en venta encontremos algún tipo de puzzle o mecánica similar. Prince of Persia tenía unos cuantos puzzles disimulados en la historia, igual que Quake II y Tomb Raider. Recientemente Portal ha repopularizado los puzzles en la forma de navegación intrincada.
Es cuando el jugador es dirigido de una localización a otra, pero siente que siempre es decisión suya, el diseño del nivel disimula la linealidad. Cuando está bien hecho, describe un mapa que es lineal, pero no lo parece. Max Payne y Half-Life son los mejores super-ventas con esta estructura. Puede perjudicar la rejugabilidiad significativamente, y también puede crear algunas situaciones en las que el diseñador se lo pasa mejor que el jugador; si el jugador no decide ir donde el o ella tienen que ir a continuación, y en vez de eso el diseñador le fuerza a ir, ¿quién está jugando realmente? Por el lado bueno, esto suele significar que el jugador no se perderá, y el énfasis de la jugabilidad está en la acción o plataformas/puzzles más que en la exploración.
Un nivel con estructura no lineal (como uno con un nodo central y muchos pasillos) puede convertirse rápidamente en lineal usando llaves, interruptores remotos para las puertas, y otros mecanismos de "secuencia en orden". El truco para evitar que esta solución sea tán evidente es tomar un acercamiento como el de las armas de los jefes finales en Mega Man, de forma que aunque el orden de las acciones es decisión del jugador, hay un orden ideal que el jugador experto utilizará para optimizar el recorrido por los niveles. Para este tipo de diseño de niveles con linealidad creativa y no forzada funcione bien, el jugador debe tener pistas claras o señales de que es lo que encontrará en cada dirección - quizas por imposición de la arquitectura, patrones de avisos en rojo en el suelo, o charcos de barro verde radiactivo.
La mayoría de juegos comerciales no siguen una sóla fórmula. Las filosofías de diseño de niveles más interesantes son aquellas que consiguen extraer los elementos que funcionan de una variedad de estilos de diseño. No temas experimentar, pero cuando lo hagas, seguramente querrás hacerlo con mapas pequeños antes de lanzarte a construir algo crucial en un mapa grande.
Inventa algunas opciones, como las que usamos aquí, y categoriza tus ideas en conceptos discretos - hace que sea más fácil reutilizar los que funcionan bien, y más fácil evitar reutilizar los que resultan pobres o poco útiles, y finalmente, más fácil el mezclar combinaciones de aquellas cosas que funcionan bien para generar niveles más complejos.
Métodos de prototipo rápido para diseño de niveles
El método científico. Comienza dibujando unos círculos en una página en blanco para representar las zonas principales, áreas, plazas o intersección de pasillos. Dibuja unas líneas entre esos nodos, hasta que todo esté conectado al menos por un camino. Luego, pon tu mente en marcha para pensar como harás que el jugador recorra el mapeado - ¿pondrás objetos como cebos? ¿lo harás sólo con secuencias de puzzles del tipo llave de acceso (buscar X llave para X puerta)?
Ventaja: es una forma rápida de bocetar el nivel para tener interconexiones complejas, sin caer en el sistema convencional de pasillos ortogonales o caminos lineales que podrías usar en la mayoría de situaciones al crear un nivel.
Desventaja: deja MUCHAS preguntas sin respuesta acerca del tema del nivel, y la disposición de las habitaciones. Este prototipo definitivamente debería ser utilizado en conjunto con otro método, como el Boceto de Área o Canalización.
El método del constructor. Comienza con un papel con un rectángulo, pentágono o una forma más interesante, como una mano gigante (el mapa E3M2 de Doom esta hecho así), y luego recursivamente, divide ese espacio en habitaciones.
Ventaja: Puedes crear una edificación más realista (no se desperdicia espacio entre habitaciones o salas. Hazlo a mano alzada en papel y luego transfórmalo al formato del editor de niveles del juego, esto reduce la tendencia a trabajar estríctamente en ortogonal o en cuadrícula.
Desventaja: Es muy fácil que se acabe generando un mapa visualmente desordenado. Si la forma base no se trocea con cuidado, también puede provocar que el mapa parezca demasiado artificial.
El método del mecánico. Puede hacerse en la mayoría de herramientas de edición igual que se hace en papel, si estás muy acostumbrado a la herramienta y te mueves en ella rapidamente. Basicamente, monta el nivel con pasillos, colocando objetos importantes y llaves en intersecciones o esquinas. Revísalo y expande ciertas áreas del pasillo o "canal" en áreas de combate suficientemente grandes (habitación, plaza, etc...), añade algo de decoración basándote en un tema concreto, y listo.
Ventaja: Tan rápido, o incluso más, como cualquier otro método, y los resultados no suelen ser simples o triviales mapas.
Desventaja: A menudo se obtiene un entorno nada realista, y requiere bastante práctica y paciencia identificar qué áreas de la canalización deberían ser expandidas.
El método del principiante. Basicamente, roba de cualquier sitio todo lo que puedas. Copia áreas y diseños de edificios en los que hayas estado, pegales escenarios de películas que admires, y conectalo todo con un orden de eventos que hayas visto funcionando en tu juego preferido. Al mezclar múltiples fuentes, puedes obtener niveles bastante decentes mientras aprendes de los profesionales en el proceso.
Ventaja: Creas contenido mucho mejor de lo que podrías hacer por ti mismo.
Desventaja: Te hace sentir sucio y culpable. Hazlo si buscas una forma de aprender mientras generas contenido, pero intenta dejarlo atrás tan pronto puedas
El método del arquitecto. De hecho se trata de bocetar el área como si realmente existiera - ¿donde van estas tuberías?, ¿y el cableado eléctrico? ¿Cómo y porqué tenemos áreas diferentes (vestuarios, dormitorios, oficinas, almacenes) en esas ubicaciones y de que forma se relacionan con el resto?
Ventaja: Este método conduce a desarrollar los entornos más realistas.
Desventaja: Lento, lento, lento. Puedes intentarlo en algunos mapas al comienzo, pero al final, la mayoría del dinero, energías y tiempo deberían emplearse atendiendo al contenido que el jugador vé y experimenta diréctamente. Esfuérzate para ver si puedes abstraer algunos patrones o reglas que puedas seguir para simular el mismo efecto sin meterle tanto trabajo invisible.
El método del artista. Esta técnica es muy popular en la industria, y a menudo se muestra en los concept art (N.del T. conjunto de ilustraciones, bocetos y plantillas de diseño generados por artistas para después crear todos los elementos visibles del juego, como es muy habitual su uso en inglés he optado por dejarlo sin traducción) que se incluyen en las ediciones de coleccionista. Haz bocetos en 3D de las áreas en las que te gustaría que sucedan los encuentros - desde la perspectiva del jugador, o desde una perspectiva de "cámara de seguridad" en ángulo a 3/4 desde una esquina del área, en el techo. ¿Qué tal quedan todos los obstáculos juntos? ¿De donde salen los enemigos?
Ventaja: Crea áreas memorables, muchísimo más que cualquier otro método de diseño.
Desventaja: Requiere talento artístico, e inspiración "aleatoria". Hazlo cuando puedas, pero no esperes que funcione para cada habitación de cada nivel.
Este es el método del diseñador de juegos. Por desgracia, también es mucho menos formulable para definir como desarrollarlo con éxito. Conocer algunos de los otros métodos de prototipado y saber que están para establecer una base sobre la que trabajar, y como articular los diferentes aspectos a los miembros del equipo, son temas clave para tener éxito con un prototipo híbrido. Al igual que los esquemas de diseño pueden mezclarse, también se pueden mezclar los métodos de prototipado.
IV.) Avanzado - Saber cuando usar código de otro
"Si todo parece bajo control, es que no vas todo lo rápido que deberías."
-Mario Andretti
Hacking como programación apresurada
La palabra "hacking" (N. del T. la conservo en inglés porque es ya un término aceptado y reconocido entre todos) no significa para los programadores y desarrolladores de software lo mismo que para la gente de leyes. Se suele utilizar para expresar la ejecución de crímenes informáticos, pero hoy en día a eso se le llama "cracking", o utilizado como verbo para indicar intromisión no autorizada ("hackear un ordenador o un teléfono). Cuando algunos usuarios comenzaron a editar videojuegos comerciales, como Command & Conquer o Doom, utilizando herramientas caseras que los desarrolladores comerciales no habían aprobado, se le comenzó a llamar hacking, durante una temporada... pero hoy en día a eso se le llama "modding", modificaciones/mods. Así que cuando usamos hack o hacking en este texto, lo hacemos en su acepción más común entre los programadores modernos: escribir código apresurado, sin atención a los detalles y sobre la marcha que cubra lo mínimo para cumplir la tarea asignada, .
Los profesionales serios lo odian
El código generado de esta forma se conoce por ser difícil de entender, salvo por la persona que lo escribió, por ser dificultoso al actualizar/mantener, y, generalmente, por no reflejar buenos hábitos y principios de programación escalable.
Cualquier educación reputable en ingeniería informática desarrollará desdén y desagrado con todos los estudiantes que usen este tipo de programación. Los investigadores, profesores y profesionales de sistemas pondrán mala cara con solo oirlo. Y si en una entrevista un potenciál empleado muestra este tipo de hábitos conseguirá que lo encuentren inapropiado para el puesto.
Los desarrolladores de videojuegos profesionales saben cuando hacerlo
El desarrollo de videojuegos no es exáctamente como la investigación. Ni es como programar un sistema operativo, compilador, satélite de comunicaciones, o sistema de defensa de misiles. Esos proyectos tienen requisitos muy estrictos en términos de funcionalidad, estabilidad, facilidad de mantenimiento, y facilidad de lectura de su código. El tiempo y dinero puesto en estos proyectos serios está muy medido en cálculos financieros, sin mucha pasión, amor o creatividad por miembros del equipo, que sacasen horas extra de sus vidas personales para llenarlo con más amor y cosas chulas. Si algo va mal en este software serio, alguno de los que mandan tendrá el puesto en peligro, o el negocio perderá millones de dólares en "oportunidades de negocio perdidas", o morirán soldados. Si algo va realmente mal, pero realmente mal en un videojuegos, el jugador puede tener que rejugar 20 minútos de algo que, esperemos, disfrute haciendo de todas formas; y el juego también perderá una poca puntuación en los análisis.
Todo esto quiere decir que hay, sin lugar a dudas y positivamente, un tiempo y lugar para usar esta técnica en el desarrollo de un videojuego.
¿Pero cuando?
Hubo una época, en la que era la única forma
Los videojuegos originales en casi cada género fueron posibles sólo gracias a absurdas cantidades de código programado en modo "hacking". Eso incluye al primer videojuego de la historia. Y también incluye a Adventure, el primer videojuego de acción-aventura, de 1978 por Warren Robinett (diapositivas de una presentación en PDF, copia en versión PDF de un archivo PPT del sitio web de Warren) (N. del T. la traducción de los sitios webs es cosa de google, pero me temo que el pdf lo vereis en inglés, aún asi hay mucha información valiosa, pues da mucha información del proceso de creación que utilizó, y aunque a veces no sea muy exacto, siempre tendreis a mano el traductor de google). Y también es cierto en el caso de las innovaciones en el género de los FPS que introdujo John Carmack con id Software (Catacombs 3D, Wolfenstein, Doom, Quake), las cuales fueron posible mediante a la voluntad de Carmack de escribir código complejo y difícil de leer cuando era la única forma en ese momento para dibujar mundos con apariencia 3D en esas máquinas limitadas. Era la única forma debido a factores, no sólo de optimización de máquina, sino también por falta de comunicación entre programadores, limitaciones de presupuesto, y tiempo de desarrollo consumido puliendo otras características de los juegos.
Soluciones de compromiso y circunstancias
Más allá de los requisitos básicos, lo que se puede hacer en un videojuego es determinado por un conjunto de soluciones de compromiso, o coste de oportunidad. El coste de oportunidad es estimado en base al tiempo y personal requerido para hacer algo posible. Si puede ser hecho por 1 persona en 2 horas en la forma "mala" (estorbando a las cosas "buenas" que se hagan en el futuro), pero llevaría 5 personas durante 3 días el hacerlo "bien" (más fácil de actualizar, efectos más predecibles sobre el resto del sistema), entonces hay unas cuantas consideraciones acerca de que camino es el adecuado:
|
¿Hay veces en las que aplicar hacking para acabar rápido puede provocar problemas? Por supuesto que sí. ¿Es peligroso hacerlo continuamente? Un sí rotundo. ¿Aún así, no sería tonto ignorar que es una de las muchas opciones disponibles, si uno entiende los riesgos que conlleva y puede usarlo con cuidado? Afirmativo.
A veces la carrera la gana el conductor dispuesto a llevar su coche un poco más lejos, un poco más rápido, a través de sitios estrechos, cuando otros corredores siguen la ruta predefinida.
(Pero esa opción sólo es viable si el resto del coche está construido con la suficiente robustez como para sobrevivir el ser llevado al límite.)
Chris DeLeon
PD Estoy escribiendo estos boletines para ayudar a la gente, y quiero que su contenido llegue al máximo número de personas posible. Por favor pasa este enlace si sabes de alguien al que le podría ser útil; http://gamedevlessons.com/lessons/carta4.html Enviar el enlace es mejor que copiar el texto entero y enviarlo, ya que de esta forma siempre verán la última versión con preguntas y respuestas o correcciones.
Nota sobre la traducción: este boletín ha sido creado originalmente en inglés y para una audiencia anglosajona por Chris DeLeon. He procurado adaptar, en la medida de lo posible, el texto a una audiencia hispano parlante, y en particular, Española. En sus comentarios Chris habla de la industria del videojuego en el mundo anglosajón, aunque no se apliquen por igual en nuestro caso creo que son bastante útiles.
Aparte del sitio web de Gamedevlessons también podrás encontrar estas traducciones en mi propia página web, en http://www.proyecto-iris.com, donde siempre mantendré una sección dedicada a estos boletines para ayudar en su difusión.