top of page
Buscar
Foto del escritorFernando Sansberro

1. Introducción a la Programación de Videojuegos

Antes de ponernos a programar con Pygame, veremos una introducción a cómo se programa un videojuego, explicando las partes que lo componen y la arquitectura básica del mismo.


En este capítulo se muestran las principales partes que componen un videojuego y se realiza una introducción al tema, mostrando los conocimientos básicos que son necesarios para programar un juego.


La Pantalla


La pantalla (screen o display) es en donde el jugador ve el videojuego. Hay que pensar en la pantalla como una grilla o matriz de pequeños cuadraditos, los cuales se denominan píxeles. Cada uno de estos cuadraditos tiene un color y todos juntos forman la imagen que ve el jugador.


Nota: En español la palabra pixel lleva tilde, pero a lo largo del libro usaremos varios términos en inglés. El primer motivo de esto es que muchas veces es más fácil buscar documentación en Internet si los términos se escriben en inglés. El segundo motivo es que hoy en día es muy común hablar con estos términos en inglés, y los mismos ya son parte del vocabulario (jerga) de desarrollo de videojuegos.


Figura 1-1: La pantalla y las imágenes se componen de píxeles.



En la figura 1-1, vemos una parte ampliada de la imagen o de la pantalla, mostrando cómo la imagen se compone de píxeles de colores. Cada píxel tiene un color determinado.


Resolución de la Pantalla


La pantalla tiene una cantidad determinada de píxeles que la forman, y a la cantidad de píxeles que entran en horizontal y en vertical se le denomina resolución de la pantalla. La resolución de la pantalla viene a ser el tamaño de la pantalla en píxeles. Por ejemplo, si decimos que una pantalla tiene una resolución de 800 x 600 píxeles, lo que estamos diciendo es que la pantalla tiene 800 píxeles horizontales (columnas) por 600 píxeles verticales (filas).


No existe una única resolución de pantalla. Cada computadora o cada tarjeta de video puede soportar desde una sola a varias resoluciones de pantalla. Los juegos de PC, por ejemplo, pueden correr en diferentes resoluciones en diferentes máquinas. Las resoluciones de pantalla más comunes a lo largo de la historia son 320 x 240, 640 x 480, 800 x 600, 1024 x 768, 1366 x 768 y 1920 x 1080 píxeles, entre otras. Hoy en día con diferentes computadoras, laptops, tablets y celulares, hay prácticamente resoluciones de todos los tamaños y aspectos.

Nota: Siempre hay que tener en cuenta que cuanto más grande es la resolución de la pantalla, más píxeles hay que dibujar en cada cuadro del juego, y que cuanto más chica es la resolución, el juego funcionará más rápido porque hay menos área de pantalla para dibujar en cada cuadro.


La razón de querer usar una resolución menor en un juego es para dibujar menos. El programa tiene que dibujar en la pantalla todos los elementos del juego varias veces por segundo. Si la superficie a dibujar es más chica, eso toma menos tiempo y el juego puede alcanzar muchos cuadros (frames) por segundo. Si la resolución es mayor, la operación de dibujar tomará más tiempo, pero los gráficos se verán mejor.


FPS (Frame Rate, Cuadros por Segundo)


El manejo del tiempo es muy importante al programar videojuegos. Los juegos deben correr lo más rápido posible en la computadora. Los juegos hacen un uso intensivo del procesador y del display de la máquina (la tarjeta de video). Esto es porque un videojuego debe hacer muchos cálculos y dibujar muchas cosas, varias veces por segundo.


Un videojuego es como una película en cuanto a que varias veces por segundo (casi siempre 30 o 60 veces por segundo) tiene que mostrar un cuadro (denominado frame). Para eso, debe calcular dónde van a estar los objetos del juego y qué es lo que hacen, y luego dibujarlos en la pantalla al armar la escena y mostrarla al jugador. Es por esta razón que para jugar videojuegos necesitamos una máquina rápida, porque la computadora debe trabajar mucho para mostrar la escena que se genera 30 o 60 veces por segundo. A cada imagen completa que se muestra en pantalla varias veces por segundo se le llama cuadro de animación, o frame.


A la velocidad a la cual se realiza la actualización del dibujo se le denomina frame rate. O sea, el frame rate es cuantas veces por segundo se muestra un cuadro de animación (se abrevia FPS por sus siglas en inglés: frames per second).


Un frame rate mayor implica una animación más fluida, pero necesita más poder de procesamiento. Un frame rate bajo es una animación que se ve lenta. Si el frame rate es demasiado bajo, simplemente se ve mal (se pueden ver “saltos” en la animación). Por ejemplo, si movemos algo por la pantalla a un bajo frame rate, se verán los saltos de posiciones donde se dibuja y no se verá como un movimiento continuo. Las animaciones para que parezcan reales necesitan actualizarse muchas veces por segundo.


Figura 1-2: Un juego debe correr preferentemente a 60 FPS para sentirse y verse bien.



Un frame rate normal son 30 FPS. La televisión, por ejemplo, funciona a 24 FPS. Cuanto más FPS corra un videojuego, más capacidad de procesamiento de la computadora necesitará.


El objetivo siempre será lograr el frame rate más alto posible para que el juego se vea bien y corra bien en velocidad. También hay que lograr que el frame rate sea consistente. Si se logra que el frame rate sea constante, el juego se verá bien, y debería serlo para no ver saltos en las animaciones.


Hay un máximo frame rate, el cual está dado por la velocidad de refresco de la pantalla, o también por la capacidad del ser humano para detectarlo. Lo óptimo para un videojuego son 60 FPS, pero esto a veces es difícil de alcanzar (por el lenguaje, la máquina o la plataforma). Si este es el caso, lo mejor es apuntar a correr el juego a 30 FPS, lo cual es alcanzable y se verá razonablemente bien. El frame rate mínimo aceptable para un juego (en caso extremo) debería ser 24 FPS. En algunas plataformas como en web o en algunos dispositivos handheld, se suele apuntar a este frame rate.


El Game Loop


Uno de los grandes secretos de la programación de videojuegos es el concepto de game loop, el cual es un bucle o ciclo (loop en inglés) que se ejecuta permanentemente mientras el juego está en funcionamiento. Es lo primero que deberemos programar (o ya tenerlo hecho de antemano) al comenzar a desarrollar un juego.


En otras formas de programación que generalmente se enseñan en las carreras tradicionales de informática, como por ejemplo en la programación orientada a eventos, un programa no hace nada hasta que el usuario toca un botón (por ejemplo, en un formulario, en el cual se llenan los datos y luego se pulsa un botón “Enviar”). En este caso, el programa reacciona ante el evento de apretar el botón “Enviar”. Incluso, hasta es común que el botón mismo tenga código o ejecute un programa cuando se lo presiona. Pero mientras no se pulse el botón, el programa no hace prácticamente nada.


En los videojuegos, es todo lo contrario. Siempre hay muchos objetos moviéndose por la pantalla, como por ejemplo, animaciones, partículas, etc. y por lo tanto el programa siempre tiene que estar haciendo algo. Para lograr esto hay un loop repitiéndose indefinidamente hasta que se sale del programa. Esto es así tanto en los juegos 2D como en los juegos 3D. Generalmente el game loop es una estructura while, de la cual se sale cuando se cierra la ventana (la aplicación), o se decide salir del juego, o el juego termina de alguna manera.


Figura 1-3: El game loop se encarga de mover los objetos del juego y dibujarlos.



El game loop estará encargado de leer la entrada del usuario (mediante teclado, mouse, joystick, pantalla táctil, etc.) y determinar qué quiere hacer el jugador, actualizar y mover a todos los objetos del juego, chequear las colisiones entre los objetos y dibujar en pantalla los objetos visibles. Todo eso se hace para cada cuadro del juego.


Como se ve, toda la lógica del juego se encuentra dentro del game loop. El frame rate del juego depende de la velocidad de proceso del game loop, por lo cual, en realidad, el frame rate es la frecuencia del game loop. Si hay demasiado que procesar en el game loop, el juego correrá más lento (por lo tanto, tendrá un bajo frame rate). Lo ideal es correr el game loop 60 veces por segundo (60 FPS).


Un juego simple puede correr normalmente a 60 o más FPS, pero si hay muchos cálculos en el juego, o hay que dibujar mucho, es probable que el juego corra más lento.


La Estructura de un Videojuego


Casi todos los juegos tienen la misma estructura. Comienzan con una inicialización del sistema (por ejemplo, establecer la resolución o crear la ventana del juego) y luego viene la carga de los archivos necesarios (a éstos se le llaman los assets del juego: los gráficos, sonidos, niveles, o lo que sea necesario). Luego de inicializar el sistema y cargar los assets, se ingresa en el loop principal.


Entonces, todos los juegos que hagamos van a tener básicamente la siguiente estructura:


- Configuración del sistema y de la pantalla.

- Inicialización de los objetos del juego (función init()).

- Carga de los assets necesarios (gráficos, sonidos, etc.).

- El game loop:

- Obtener la entrada del jugador.

- Limpiar la imagen actual (limpiar la pantalla).

- Actualizar los objetos del juego (función update()).

- Dibujar los objetos del juego (función render()).

- Actualizar la pantalla (volcar el frame dibujado a la pantalla, para que se vea).

- Al final, al salir del game loop, liberar la memoria utilizada por todos los objetos y salir del programa (función destroy()).



Figura 1-4: En cada frame se mueven y dibujan todos los objetos de un juego.



Al principio del programa se configura lo necesario del sistema y de la pantalla, se cargan todos los assets necesarios (imágenes, sonidos, niveles, etc.) y se crean los datos que se necesitan para que el juego pueda comenzar. Luego de que todo esté inicializado, se entra al game loop, del cual no se saldrá hasta que el usuario decida irse del juego.


Dentro del game loop, lo que se hace es procesar los eventos para manejar la entrada del jugador (leer el teclado, el mouse, etc.), se actualizan los objetos en el juego (se ejecuta una función update() que corre la lógica de los objetos), se dibujan los objetos en la pantalla (se ejecuta una función render() que los dibuja), y al final se dibuja la imagen creada en la pantalla para que ésta sea visible al jugador.


A continuación se explica brevemente cada una de las partes. En capítulos posteriores iremos profundizando cada parte, a medida que vayamos construyendo nuestro juego.


Inicialización del Sistema y Carga de Assets


Antes de entrar al game loop, el programa debe configurar correctamente la ventana (llamada también display según el contexto), inicializar el sistema de video y de audio, por ejemplo, entre otras muchas cosas.


También debemos cargar los assets necesarios (generalmente las imágenes y los sonidos) y crear los objetos del juego e inicializarlos correctamente.


Es importante crear e inicializar todos los objetos y componentes que se van a usar en el juego antes de entrar en el game loop y no durante el mismo. Esto es para que el game loop no se tranque en ningún momento durante su ejecución.




Figura 1-5: Los assets del juego se deben cargar antes de entrar al game loop.



Si cargamos todos los assets durante la inicialización del juego, luego el game loop ya los tendrá listo en memoria y no demorara en cargarlos (cosa que haría que el juego se tranque un instante mientras se cargan los gráficos). Como no queremos que pase eso, siempre los gráficos se cargan en la parte de inicialización, antes de que el juego en sí comience (o sea, antes de entrar al game loop).


Una vez inicializados todos los componentes necesarios para el juego, se ingresa al game loop. A continuación se explican las partes que componen el mismo.


Obtener la Entrada del Jugador


El principal aspecto de un videojuego es la interactividad (si no tuviera interactividad, sería simplemente una animación). Para conseguir la interactividad, debemos leer los dispositivos de entrada que utilice el juego, como por ejemplo, el teclado, el mouse, la pantalla táctil (touch), el acelerómetro, un joystick, etc. y responder a las acciones del usuario. Por ejemplo, el personaje del jugador puede moverse y saltar si apretamos las teclas correspondientes, con el mouse podemos controlar los botones del juego, etc.




Figura 1-6: Un juego puede tener varios controles.



Pygame tiene funciones para para leer los dispositivos de entrada, el teclado por ejemplo, y saber si el jugador está apretando una tecla determinada en ese momento. Entonces leemos el estado de esa tecla y el juego responde a ella (por ejemplo, el jugador dispara si se apreta la tecla de disparar).


Actualizar los Objetos del Juego (la función update())


En cada frame, el programa debe calcular dónde va a estar cada objeto del juego (a los objetos en el juego también se le llaman sprites, game objects, entidades o game entities). Para cada objeto del juego, se calcula dónde va a estar en el siguiente frame de acuerdo a:


- La entrada del usuario (qué es lo que el jugador hace).

- La velocidad que tiene el objeto y la dirección que lleva (el movimiento o la física).

- Si choca con otro objeto o no (si choca hay que resolver las colisiones, explotar, etc.).

- Las acciones que realiza el objeto (su comportamiento o inteligencia artificial).


De esta manera, en cada frame se determina la nueva posición para cada uno de los objetos del juego. Si el objeto se está moviendo, se calcula su próxima posición, o se determina si debe morir o no (por ejemplo, si ha chocado con una bala), o se determina si una bala debe destruirse porque se fue de la pantalla, o lo que sea necesario hacer, por supuesto, esto dependiendo del juego del que se trate (la lógica o reglas del juego).



Figura 1-7: Todo lo que pasa en un cuadro se calcula en la función update().



Cada objeto tiene su lógica propia según lo que se deba hacer en el mismo. Por ejemplo, un fantasma del juego Pacman nos perseguirá, una nave del juego Space Invaders se moverá y cada tanto disparará una bala, etc. Entonces, decimos que la lógica del juego se encuentra en la función update(). Más adelante escribiremos esta función.


Todos los cambios que se realizan en los objetos, se hacen en sus propias variables (denominadas atributos del objeto), como por ejemplo, el cambio de sus coordenadas cuando se mueve, su velocidad, su aceleración, etc. El jugador ve los cambios porque hay una actualización visual en la pantalla (por ejemplo, un enemigo se mueve diez píxeles hacia la derecha y luego se dibuja en esa posición, con lo cual parece dar la idea de que se ha movido).


Dibujar los Objetos del Juego (la función render()) y Actualizar la Pantalla


Luego que en la función update() calculamos qué es lo que hace cada objeto del juego en el frame actual, sabemos dónde va a estar ubicado cada objeto y qué imagen debemos mostrar. Con esta información, ya se puede dibujar la pantalla.


A la operación (que se realiza en cada frame) de dibujar los objetos del juego, se le llama actualizar la pantalla y consiste en crear una imagen con el frame que se va a mostrar en la pantalla. Esta función render() lo que hace es dibujar cada uno de los objetos del juego que están visibles en la pantalla. Más adelante escribiremos esta función.


Antes de dibujar los objetos donde están en el frame actual, es necesario borrar la imagen del frame anterior. Es posible borrar toda la imagen del frame anterior y recrear el nuevo frame completamente, aunque es un proceso lento, o podemos optimizar y dibujar solamente lo que ha cambiado desde el frame anterior, lo cual es más rápido. Más adelante veremos cómo hacer todo esto.


Liberación de los Recursos Utilizados


Al salir del game loop, la última tarea que realiza el programa, antes de terminar, es liberar todos los recursos utilizados por el juego. Es necesario hacer esto para evitar problemas, como por ejemplo, que el sistema operativo se quede sin memoria o que aparezcan otros problemas relacionados.


Como regla general, cada vez que no usemos más un objeto, lo deberemos destruir, liberando la memoria utilizada. Más adelante veremos cómo hacer esto. Por ahora solamente diremos que todos los objetos del juego tendrán una función destroy(), que escribiremos más adelante, que se encargará de eliminar todo lo que ha creado. De la misma manera, al salir del programa, todos los recursos pedidos al sistema deben liberarse.


340 visualizaciones0 comentarios

Entradas recientes

Ver todo
bottom of page