top of page
Buscar

3. Moviendo Nuestro Primer Objeto

  • Foto del escritor: Fernando Sansberro
    Fernando Sansberro
  • 21 oct 2021
  • 24 Min. de lectura

Actualizado: 25 dic 2021

En este capítulo veremos cómo crear y mover objetos por la pantalla. Empezaremos con el sistema de coordenadas de Pygame, y luego veremos cómo se crean los objetos en el juego y cómo los movemos teniendo en cuenta los bordes de la pantalla. Por último, crearemos nuestra primera clase, para un objeto movible. Más adelante en el libro, utilizaremos Sprites para hacer movimientos más avanzados y con animaciones.


El Sistema de Coordenadas


Pygame usa un sistema de coordenadas denominado Sistema de Coordenadas Cartesianas, el cual está compuesto por una grilla de cuadrados (píxeles) con un par de ejes de coordenadas que forman un ángulo recto. El eje horizontal es el eje x y el eje vertical es el eje y.


Es esencial para un desarrollador de videojuegos conocer los elementos matemáticos necesarios para la programación, porque se utilizan en todo momento. Por ejemplo, si queremos colocar un objeto del juego en una posición en la pantalla, lo que tenemos que hacer es darle su ubicación utilizando sus coordenadas (x, y), mediante sentencias de programación que ya veremos más adelante.



Figura 3-1: El Sistema de Coordenadas en Matemática.



Como hemos visto en cualquier clase de matemática, la coordenada horizontal crece hacia la derecha y la coordenada vertical crece hacia arriba. Esto se muestra en la figura 3-1.


En Pygame, y en general en la mayoría de los sistemas o programas de computación, el sistema de coordenadas cambia y la coordenada vertical crece hacia abajo. La figura 3-2 muestra el sistema de coordenadas que vamos a usar en los juegos.



Figura 3-2: El Sistema de Coordenadas en Pygame.



De esta forma, en Pygame, el eje de coordenadas tiene como origen a la esquina superior izquierda de la pantalla, esto es, el punto (0, 0). Recordemos que el origen del sistema de coordenadas es el punto donde los ejes se cruzan. Luego, la coordenada horizontal (x) crece hacia la derecha y la coordenada vertical (y) crece hacia abajo.


En realidad, salvo la dirección del eje y, no hay diferencia entre los dos sistemas de coordenadas. El sistema de coordenadas de la pantalla lo usaremos de la misma forma que lo hacemos en matemática. Lo único que cambia es la forma en la que lo observamos, pero las fórmulas que utilicemos se aplicarán igual que a lo que estamos acostumbrados de la matemática normal.


Como vemos en la figura 3-2, los valores positivos para x y para y se encuentran dentro de la pantalla (si no son muy grandes). Debemos imaginar la pantalla del juego como una grilla de pixeles, en un sistema de coordenadas. Miremos la figura 3-3.



Figura 3-3: El juego visto en el sistema de coordenadas de la pantalla.



Si la pantalla tiene un tamaño de 640 x 360 píxeles, todo lo que se encuentre entre los puntos con coordenadas (0, 0) y (639, 359) se verá en la pantalla.


Ubicando un Objeto en la Pantalla


Para ubicar un objeto en la pantalla (o sea, en el sistema de coordenadas), lo que hacemos es dar un par de coordenadas que indican dónde está el objeto. En matemática, las dos coordenadas se agrupan con paréntesis, como por ejemplo: (5, 6). Mira la figura 3-4.



Figura 3-4: Ubicando un objeto en el sistema de coordenadas.



La primera coordenada representa la coordenada horizontal (la coordenada x) y la segunda coordenada representa la coordenada vertical (la coordenada y). Si decimos que un objeto está ubicado en (5, 6), esto quiere decir que el 5 es la distancia sobre el eje x, y el 6 es la distancia sobre el eje y.


Moviendo nuestro primer Objeto


Ahora que sabemos cómo ubicar un objeto en la pantalla, vamos a poner un cuadrado en la pantalla y a moverlo (más adelante cambiaremos el cuadrado por un gráfico). Para esto partiremos desde el game loop que construimos en el capítulo anterior y le iremos agregando código para hacer esto.


Lo primero que haremos será crear una superficie cuadrada (una imagen) y agregaremos dos variables llamadas cuadradoX y cuadradoY que tendrán las coordenadas del cuadrado. Luego, dentro del game loop, cambiaremos esas variables para que el cuadrado cambie de posición y también dentro del game loop dibujaremos el cuadrado en su nueva posición. Antes de dibujar el cuadrado en la nueva posición, dibujaremos el fondo, para borrar de esta forma lo que había antes en la pantalla.


Mover un objeto en el juego, se resume en los siguientes pasos:


- Crear el objeto.

- Inicializar sus coordenadas (establecer la posición inicial).

- Dentro del game loop:

- Mover el objeto (cambiar sus coordenadas).

- Dibujar el fondo (borrar el objeto de la pantalla de su posición anterior).

- Dibujar el objeto en su nueva posición.


Abre y ejecuta el ejemplo de la carpeta capitulo_03\001_mover_cuadrado. Examina un poco el código y observa las diferencias que hay con el último ejemplo del capítulo anterior.


Nota: Los ejemplos en el libro se van haciendo incrementales, lo que quiere decir que se le van agregando partes, de a una a la vez. Cuando algo se hace por primera vez, las sentencias correspondientes se mostrarán en negrita en el listado de código, para que sea más fácil de entender.


Nota: El código está completamente documentado. Como programador tendrás tus propias preferencias con respecto a los comentarios en el código. A algunos programadores les gusta documentar bastante y a otros poco o nada. Es conveniente encontrar un equilibrio. Yo recomiendo documentar absolutamente todo.


Al correr el ejemplo, vemos que un cuadrado se mueve por la pantalla de izquierda a derecha y se va de la pantalla. A continuación se explican los pasos necesarios para mover un objeto. Primero veamos el listado completo del programa, que es similar al game loop del ejemplo anterior, y en negrita se muestran las líneas que se han agregado.


# -*- coding: utf-8 -*-

#--------------------------------------------------------------------
# Mover un cuadrado por la pantalla.
#
# Autor: Fernando Sansberro - Batovi Games.
# Proyecto: Hacete tu Videojuego.
# Licencia: Creative Commons. BY-NC-SA.
#--------------------------------------------------------------------

# Importar e inicializar Pygame.
import pygame
pygame.init()

# Control del modo ventana o fullscreen.
RESOLUTION = (640, 360)
isFullscreen = False

# Poner el modo de video en ventana e indicar la resolución.
screen = pygame.display.set_mode(RESOLUTION)
# Poner el título de la ventana.
pygame.display.set_caption("Mi Juego")

# Crear la superficie del fondo o background.
imgBackground = pygame.Surface(screen.get_size())
imgBackground = imgBackground.convert()
imgBackground.fill((0, 0, 255))

# Crear un cuadrado de 32x32.
imgCuadrado = pygame.Surface((32, 32))
imgCuadrado = imgCuadrado.convert()
imgCuadrado.fill((255, 0, 0))

# Coordenadas del cuadrado.
cuadradoX = 0
cuadradoY = 180

# Inicializar las variables de control del game loop.
clock = pygame.time.Clock()
salir = False

# Loop principal (game loop) del juego.
while not salir:

    	# Timer que controla el frame rate.
    	clock.tick(60)

# Procesar los eventos que llegan a la aplicación.
    	for event in pygame.event.get():
        
# Si se cierra la ventana se sale del programa.
if event.type == pygame.QUIT:
salir = True

# Si se pulsa la tecla [Esc] se sale del programa.
if event.type == pygame.KEYUP:
if event.key == pygame.K_ESCAPE:
salir = True

# Si se pulsa la tecla [F], se cambia entre ventana y fullscreen.
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_f:
isFullscreen = not isFullscreen
if isFullscreen:
screen = pygame.display.set_mode(RESOLUTION, 
pygame.FULLSCREEN)
else:
screen = pygame.display.set_mode(RESOLUTION)

# Modificar la posición del cuadrado (mover el cuadrado).
cuadradoX += 2

# Dibujar el fondo.
screen.blit(imgBackground, (0, 0))

# Dibujar el cuadrado en la nueva posición.
screen.blit(imgCuadrado, (cuadradoX, cuadradoY))

# Actualizar la pantalla.
pygame.display.flip()

# Cerrar Pygame y liberar los recursos que pidió el programa.
pygame.quit()


Hemos resaltado en negrita las líneas agregadas, de forma tal de entender dónde es que va cada parte. Por ejemplo, la parte de inicialización va al comienzo del programa. Luego, la parte de movimiento y de dibujo, va dentro del game loop. A continuación explicaremos las sentencias necesarias para crear el cuadrado y, en el game loop, moverlo y dibujarlo en la pantalla.


Al ejecutar el programa, veremos el cuadrado rojo atravesando la pantalla como muestra la figura 3-5..



Figura 3-5: El cuadrado moviéndose por la pantalla.



Crear el Objeto


El cuadrado va a ser una superficie de 32 x 32 píxeles. Para eso creamos una superficie y la usaremos con una variable que llamaremos imgCuadrado. Esto lo hacemos antes de entrar al game loop.


# Crear un cuadrado de 32x32.
imgCuadrado = pygame.Surface((32, 32))
imgCuadrado = imgCuadrado.convert()
imgCuadrado.fill((255, 0, 0))

Con este código, creamos la superficie utilizando la función pygame.Surface(), al igual que lo hicimos antes para el fondo, y le pasamos el ancho y el alto de la superficie, que en este caso será de 32 píxeles de ancho y 32 píxeles de alto. Luego, convertimos la superficie creada al formato de Pygame, al igual que como lo hicimos con el background, y con la función fill() llenamos la superficie con un color, en este caso, con el color rojo (255, 0, 0).


Recordemos que un color en formato RGB tiene tres números: el primer número corresponde al componente rojo, el segundo al componente azul y el tercero al componente verde.


Colocar el Objeto en su Posición Inicial


Cada vez que creamos un objeto, deberemos colocarlo en su posición inicial. En general, al crear un objeto en el juego, siempre le daremos unos valores iniciales. A esta tarea se le denomina inicializar el objeto. En este caso, usaremos dos variables para mantener la posición del cuadrado. La coordenada x irá en la variable cuadradoX y la coordenada y irá en la variable cuadradoY.


# Coordenadas del cuadrado.
cuadradoX = 0
cuadradoY = 180

El cuadrado, inicializado con las coordenadas (0, 180), comenzará situado en el borde izquierdo y en la mitad de la altura de la ventana (esto es así, porque la ventana mide 640 x 360 píxeles). El punto usado para situar el cuadrado, es la coordenada de la esquina superior izquierda del cuadrado. La siguiente figura muestra el cuadrado en su posición inicial.



Figura 3-6: El cuadrado en su posición inicial: (0, 300).



Una vez que tenemos inicializado el objeto, ya podremos pasar al game loop, en donde en cada frame moveremos y dibujaremos el objeto.


Mover el Objeto


Dentro del game loop movemos el cuadrado cambiando su posición. Por ejemplo, lo que vamos a hacer en cada frame es mover el cuadrado 2 píxeles hacia la derecha.


# Modificar la posición del cuadrado (mover el cuadrado).
cuadradoX += 2

Esta sentencia lo que hace es incrementar el valor de la coordenada x del cuadrado en 2 píxeles. Por sí solo, esta variable no mueve el cuadrado, solamente almacena su nueva posición. Pero cómo utilizamos esta variable para dibujar el cuadrado en su nueva posición, esto será lo que realmente causa que el cuadrado se mueva en la pantalla.


En la figura 3-7 podemos ver cómo cambiando la posición del objeto y luego dibujándolo en la nueva posición, haremos que el objeto se mueva.



Figura 3-7: Al cambiar las coordenadas del objeto, cambia su posición.



Dibujar el Objeto


Si recordamos cómo funciona el game loop, habíamos dicho que una parte actualizaba los objetos y otra los dibujaba. En la sección anterior movimos el objeto de lugar (cambiamos sus coordenadas), pero esto por sí solo no hace nada si no se dibuja. Ahora dibujaremos el objeto en su nueva posición.


Al final del game loop tenemos la parte que dibuja la escena:


# Dibujar el fondo.
screen.blit(imgBackground, (0, 0))

# Dibujar el cuadrado en la nueva posición.
screen.blit(imgCuadrado, (cuadradoX, cuadradoY))

# Actualizar la pantalla.
pygame.display.flip()

La primera línea dibuja el fondo, al igual que como lo hacíamos en el ejemplo anterior. Esto lo que hace es dibujar completamente el fondo, haciendo que el cuadrado que había en el frame anterior, sea borrado.

Luego viene la sentencia screen.blit(imgCuadrado, (cuadradoX, cuadradoY)) que lo que hace es dibujar el cuadrado en la pantalla, en las coordenadas indicadas por las variables cuadradoX y cuadradoY (que son las coordenadas que ya han sido actualizadas al moverlo). Por esta razón es que cada vez que se cambien estas coordenadas, el cuadrado se moverá de lugar. La tercera línea actualiza la pantalla como ya hemos visto antes (realiza el flip o volcado de pantalla).


Nota: Estas líneas tienen que estar en orden. Si primero dibujamos el cuadrado y luego el fondo, entonces el fondo taparía el cuadrado. Por esto es que tenemos que dibujar primero el fondo y encima del fondo dibujamos el cuadrado. Al dibujar el fondo, esto tiene como efecto que el cuadrado se borra de su posición anterior (porque es tapado por el fondo).


Al ejecutar el ejemplo vemos que el cuadrado va de izquierda a derecha y se va de la pantalla. Esto sucede porque aún no estamos controlando los bordes y la coordenada x continúa incrementándose indefinidamente, y llega un momento que el cuadrado tiene un valor de x que supera el ancho de la pantalla (y por lo tanto, queda fuera de la pantalla). Más adelante, en este capítulo, controlaremos los bordes de la pantalla para que el cuadrado no se vaya de la pantalla.


Podemos mover el cuadrado más rápido si aumentamos el incremento de la variable cuadradoX, o también podemos hacer que vaya hacia la izquierda si restamos un valor a la coordenada x en lugar de sumarle. Podemos hacer que vaya para arriba o para abajo si modificamos la coordenada y. ¡Más adelante moveremos los objetos del juego de maneras muy divertidas!


Nota: En el capítulo 4 veremos en mayor profundidad el manejo de imágenes. Por ahora nos alcanza con saber que el uso de la función blit() es imgDestino.blit(imgOrigen, (destX, destY)), en donde imgDestino es la imagen en la cual se dibuja, imgOrigen es la imagen que vamos a copiar a imgDestino, y (destX, destY) son las coordenadas en imgDestino donde vamos a dibujar. Estas coordenadas corresponden a dónde se coloca la esquina superior izquierda del rectángulo a copiar.



Agregando Otro Objeto al Juego


En este momento tenemos el juego con un cuadrado rojo que se mueve por la pantalla. Pero como los juegos no tienen solo un objeto, lo que haremos ahora es agregar al juego, otro cuadrado amarillo.


En el ejemplo que vimos anteriormente, usamos dos variables para describir la posición del cuadrado (cuadradoX y cuadradoY). También usamos una variable para contener la imagen del cuadrado rojo (imgCuadrado).


Para agregar otro cuadrado en el juego, lo que haremos es duplicar los pasos que hicimos anteriormente: crear el objeto y colocarlo en su posición inicial, y dentro del game loop, actualizaremos su posición y dibujaremos el cuadrado en su nueva posición. Para esto, duplicaremos las variables y les llamaremos: cuadrado2X, cuadrado2Y e imgCuadrado2.


Más adelante tendremos un objeto (una clase) con sus atributos internos para hacer esto más fácil, así el programa principal no se llena de variables y queda todo muy prolijo, además de que podremos reusar los objetos que hagamos en otros juegos y tendremos muchas ventajas más que ya veremos. En este ejemplo vamos a duplicar todas las variables, aunque ésta no es la mejor forma de agregar nuevos elementos al juego. Lo vamos a hacer de esta forma para entender más adelante lo bueno que es usar objetos (clases) en un juego, ya que usando objetos es mucho más fácil agregar elementos al juego, dado que el comportamiento básico de los objetos muchísimas veces se reutiliza. Ya veremos clases y objetos más adelante. Por ahora agregaremos otro cuadrado haciéndolo “manualmente”.


Vamos a agregar un cuadrado amarillo al último ejemplo, que comenzará situado a la derecha de la pantalla y se moverá hacia la izquierda.

Veamos el ejemplo ubicado en la carpeta capitulo_03\002_mover_dos_cuadrados. Abre el programa y ejecútalo.


El ejemplo es el mismo que el de la sección anterior, y se agregan las líneas relacionadas al segundo cuadrado. En el siguiente listado se encuentran resaltadas las líneas que tienen que ver con el segundo cuadrado: la inicialización antes del game loop, y luego dentro del game loop, su actualización y dibujo.


...

# Crear un cuadrado de 32x32.
imgCuadrado = pygame.Surface((32, 32))
imgCuadrado = imgCuadrado.convert()
imgCuadrado.fill((255, 0, 0))

# Coordenadas del cuadrado.
cuadradoX = 0
cuadradoY = 180

# Crear otro cuadrado de 32x32.
imgCuadrado2 = pygame.Surface((32, 32))
imgCuadrado2 = imgCuadrado2.convert()
imgCuadrado2.fill((255, 255, 0))

# Coordenadas del segundo cuadrado.
cuadrado2X = 608
cuadrado2Y = 212

# Inicializar las variables de control del game loop.
clock = pygame.time.Clock()
salir = False

# Loop principal (game loop) del juego.
while not salir:

# Timer que controla el frame rate.
clock.tick(60)

# Procesar los eventos que llegan a la aplicación.
for event in pygame.event.get():
		...

   	# Modificar la posición de los cuadrados (mover los cuadrados).
   	cuadradoX += 2
   	cuadrado2X -= 2

# Dibujar el fondo.
screen.blit(imgBackground, (0, 0))

# Dibujar los cuadrados en la nueva posición.
screen.blit(imgCuadrado, (cuadradoX, cuadradoY))
screen.blit(imgCuadrado2, (cuadrado2X, cuadrado2Y))

# Actualizar la pantalla.
pygame.display.flip()

# Cerrar Pygame y liberar los recursos que pidió el programa.
pygame.quit()

Nota: Cuando en el código veamos tres puntos (...), esto quiere decir que en ese lugar va el mismo código que había en el ejemplo anterior. Esto es a fines de ubicar mejor dónde van los cambios que hacemos desde el ejemplo anterior.


El segundo cuadrado lo hemos puesto en una posición inicial con coordenadas (608, 212), es decir, en el borde derecho de la pantalla y 32 píxeles más abajo que el primer cuadrado. ¿Porqué pusimos 608 como coordenada x? Pues bien, como queremos que el cuadrado comience recostado a la derecha de la pantalla, y el cuadrado mide 32 píxeles de ancho, tenemos que restar 640 (la coordenada del borde derecho de la pantalla) menos 32, lo cual da 608. Observa la siguiente figura.



Figura 3-8: El segundo cuadrado en su posición inicial: (768, 400).



Si pusiéramos el cuadrado en una coordenada x inicial 640, el cuadrado aparecería fuera de la pantalla. Es importante tener en cuenta el ancho y el alto de los objetos cuando los inicializamos, de modo tal de que aparezcan exactamente donde queremos.


Para que el segundo cuadrado se mueva hacia la izquierda, lo que hacemos en el game loop es restarle 2 a la posición en x, en lugar de sumarle como hicimos con el primer cuadrado. De esta forma, si restamos, el cuadrado se mueve hacia la izquierda. Decimos entonces que este cuadrado se mueve con una velocidad de -2 píxeles por frame.


Cuando corremos el ejemplo vemos que los cuadrados se van de la pantalla. Esto ocurre porque continúan moviéndose en la dirección que llevan y no estamos haciendo nada para controlar que no se vayan más allá de los bordes de la pantalla. A continuación vamos a agregar código para asegurarnos de que los cuadrados siempre estén dentro de la pantalla.


Chequear los Bordes de la Pantalla


En el ejemplo de la sección anterior, los cuadrados se van de la pantalla porque no estamos controlando los bordes de la misma. Ahora vamos a hacer que los cuadrados siempre permanezcan dentro de la pantalla. Para esto, existen varias cosas que se pueden realizar, por ejemplo, que cuando un cuadrado se vaya por la izquierda, o bien que aparezca por la derecha, o bien que rebote, etc.


En general, cada vez que se cambia la posición de un objeto en el juego, hay que chequear si pasan los bordes de la pantalla (en este caso la pantalla es todo el mundo del juego), y cuando se mueve un objeto también hay que chequear colisiones con otros elementos del juego (por ejemplo con las balas o con el jugador).


Lo que vamos a hacer ahora es simple. Haremos que si el cuadrado se va por un borde, que aparezca por el lado contrario, como hacen los asteroides en el famoso juego Asteroids. La figura 3-9 muestra una captura de este juego.


Figura 3-9: Asteroids © 1979 - Atari Inc.



Abre y ejecuta el ejemplo capitulo_03\003_controlar_los_bordes. En este ejemplo veremos que un cuadrado aparece desde la derecha y el otro desde la izquierda. Ambos se mueven en diagonal. Para lograr que los cuadrados se muevan en diagonal, lo que hacemos es modificar tanto la posición horizontal como la vertical, de la siguiente manera:


# Mover los cuadrados.
cuadradoX += 2
cuadradoY += 1

cuadrado2X -= 2
cuadrado2Y -= 1

De esta manera, el primer cuadrado (rojo) se moverá hacia la derecha 2 píxeles por frame, y hacia abajo 1 pixel por frame. El segundo cuadrado (amarillo) se moverá hacia la izquierda y hacia arriba (si tienes alguna duda sobre esto, mira el sistema de coordenadas para ver que restar en la vertical es “subir”). En la figura 3-10 vemos el movimiento en diagonal del primer cuadrado.



Figura 3-10: Movimiento del cuadrado en diagonal.



Cada vez que movemos los cuadrados, debemos chequear para ver si los mismos no han quedado ubicados fuera de la pantalla. En general, cada vez que un objeto del juego es movido, chequeamos las colisiones y controlamos que no se vayan de los bordes de la pantalla. Más adelante, para algunos objetos, preguntaremos si se fueron de los bordes para eliminarlos (por ejemplo, en el caso de las balas). Por ahora cuando un cuadrado se vaya de la pantalla, lo haremos aparecer desde el lado contrario.


Para controlar los bordes, agregamos en el game loop, el código de control de bordes, inmediatamente luego de modificar la posición de los cuadrados:


    # Controlar los bordes del primer cuadrado.
    if cuadradoX > screen.get_width():
        cuadradoX = 0
    if cuadradoX < 0:
        cuadradoX = screen.get_width()
    if cuadradoY > screen.get_height():
        cuadradoY = 0
    if cuadradoY < 0:
        cuadradoY = screen.get_height()

    # Controlar los bordes del segundo cuadrado.
    if cuadrado2X > screen.get_width():
        cuadrado2X = 0
    if cuadrado2X < 0:
        cuadrado2X = screen.get_width()
    if cuadrado2Y > screen.get_height():
        cuadrado2Y = 0
    if cuadrado2Y < 0:
        cuadrado2Y = screen.get_height()

Lo que hace este código es preguntar si la coordenada x del cuadrado es mayor que la coordenada x del borde derecho de la pantalla. Si ese es el caso, el cuadrado se ha salido de la pantalla y entonces lo colocamos en el borde izquierdo, o sea, en la coordenada x = 0. Esto es lo que causa que el objeto aparezca por el otro lado (aparecer por el otro lado de la pantalla se denomina wrap).



Figura 3-11: Chequeando los bordes de la pantalla.



En la figura 3-11 se muestra la situación que queremos controlar. La función screen.get_width() nos dice el ancho de la ventana (o pantalla) en píxeles. Este valor puede ser diferente según cómo hayamos inicializado la ventana (recordemos la función pygame.display.set_mode() a la cual le indicamos la resolución del juego). En esta figura el ancho de la pantalla es de 640 píxeles y el alto es de 360 píxeles.


# Controlar los bordes del primer cuadrado.
if cuadradoX > screen.get_width():
cuadradoX = 0

Cuando se cumple la condición en la cual la coordenada x del cuadrado es superior a screen.get_width(), colocamos la coordenada x en cero, con lo cual el cuadrado se posiciona en el borde izquierdo de la pantalla. Esto se muestra en la figura 3-12.



Figura 3-12: Wrapping de derecha a izquierda.



Del mismo modo, hacemos lo mismo para los bordes izquierdo, superior e inferior. Esto resulta en cuatro sentencias if, una para cada borde.


Cuando escribimos y ejecutamos el programa, debemos asegurarnos siempre de testear todos los casos. Para asegurar que los cuadrados hacen wrap en los cuatro bordes de la pantalla, debemos cambiar su movimiento en la horizontal y en la vertical, usando números positivos y luego negativos para que vaya para el otro lado (en las sentencias donde los cuadrados se mueven, en el game loop). Tenemos que probar bien todos los casos.


Si ejecutamos el programa, vemos que cuando el cuadrado va hacia la izquierda, apenas toca el borde izquierdo, aparece por el otro lado, pero cuando va hacia la derecha, el cuadrado se tiene que ir completamente de la pantalla para que aparezca por el otro lado. Esto es así por las condiciones que pusimos. Si quisiéramos que el cuadrado se vaya completamente de la pantalla, por ejemplo, cuando el cuadrado se va por la izquierda, debemos cambiar la condición para tener en cuenta el ancho del cuadrado (para dejar que se vaya completamente). Mira la figura 3-13 para entender lo que queremos hacer.



Figura 3-13: Wrapping de derecha a izquierda, corregido.



En el ejemplo capitulo_03\004_controlar_los_bordes_v2 se encuentra hecha esta corrección. Mira el siguiente código:


# Ancho y alto del cuadrado.
CUAD_WIDTH = 32
CUAD_HEIGHT = 32

Primero definimos dos constantes con el ancho y el alto de los cuadrados. Esto es para no repetir los números en el código (recordemos que el uso de constantes deja más claro el código y permite entenderlo mejor).


    # Controlar los bordes del primer cuadrado.
    if cuadradoX > screen.get_width():
        cuadradoX = -CUAD_WIDTH
    if cuadradoX < 0 - CUAD_WIDTH:
        cuadradoX = screen.get_width()
    if cuadradoY > screen.get_height():
        cuadradoY = -CUAD_HEIGHT
    if cuadradoY < 0 - CUAD_HEIGHT:
        cuadradoY = screen.get_height()

    # Controlar los bordes del segundo cuadrado.
    if cuadrado2X > screen.get_width():
        cuadrado2X = -CUAD_WIDTH
    if cuadrado2X < 0 - CUAD_WIDTH:
        cuadrado2X = screen.get_width()
    if cuadrado2Y > screen.get_height():
        cuadrado2Y = -CUAD_HEIGHT
    if cuadrado2Y < 0 - CUAD_HEIGHT:
        cuadrado2Y = screen.get_height()

Este código es el código modificado para dejar que el cuadrado salga completamente de la pantalla antes de hacerlo entrar por el otro lado. Por ejemplo, cuando el cuadrado se va por el borde derecho, en lugar de ubicarlo en x = 0, lo que hacemos es posicionarlo en x = -CUAD_WIDTH, de forma tal que quede fuera de la pantalla, recostado contra el borde derecho, a punto de entrar a la pantalla.


Por ejemplo, cuando estamos chequeando si el cuadrado se va por el borde izquierdo de la pantalla, en lugar de preguntar si x es menor que cero, lo que hacemos es preguntar si x es menor que -CUAD_WIDTH, lo que significa chequear si el cuadrado se ha ido completamente de la pantalla.


Ejecuta nuevamente el ejemplo y mira si todo funciona correctamente. A continuación se lista el ejemplo completo con las líneas relacionadas a las constantes y al control de bordes resaltadas.


# -*- coding: utf-8 -*-

...

# Ancho y alto del cuadrado.
CUAD_WIDTH = 32
CUAD_HEIGHT = 32

# Crear un cuadrado de 32x32.
imgCuadrado = pygame.Surface((CUAD_WIDTH, CUAD_HEIGHT))
imgCuadrado = imgCuadrado.convert()
imgCuadrado.fill((255, 0, 0))

# Coordenadas del cuadrado.
cuadradoX = 0
cuadradoY = 180

# Crear otro cuadrado de 32x32.
imgCuadrado2 = pygame.Surface((CUAD_WIDTH, CUAD_HEIGHT))
imgCuadrado2 = imgCuadrado2.convert()
imgCuadrado2.fill((255, 255, 0))

# Coordenadas del segundo cuadrado.
cuadrado2X = 608
cuadrado2Y = 212

# Inicializar las variables de control del game loop.
clock = pygame.time.Clock()
salir = False

# Loop principal (game loop) del juego.
while not salir:

    # Timer que controla el frame rate.
    clock.tick(60)

    # Procesar los eventos que llegan a la aplicación.
    for event in pygame.event.get():
        
        ...

    # Mover los cuadrados.
    cuadradoX += 2
    cuadradoY += 1

    cuadrado2X -= 2
    cuadrado2Y -= 1

    # Controlar los bordes del primer cuadrado.
    if cuadradoX > screen.get_width():
        cuadradoX = -CUAD_WIDTH
    if cuadradoX < 0 - CUAD_WIDTH:
        cuadradoX = screen.get_width()
    if cuadradoY > screen.get_height():
        cuadradoY = -CUAD_HEIGHT
    if cuadradoY < 0 - CUAD_HEIGHT:
        cuadradoY = screen.get_height()

    # Controlar los bordes del segundo cuadrado.
    if cuadrado2X > screen.get_width():
        cuadrado2X = -CUAD_WIDTH
    if cuadrado2X < 0 - CUAD_WIDTH:
        cuadrado2X = screen.get_width()
    if cuadrado2Y > screen.get_height():
        cuadrado2Y = -CUAD_HEIGHT
    if cuadrado2Y < 0 - CUAD_HEIGHT:
        cuadrado2Y = screen.get_height()

    # Dibujar el fondo.
    screen.blit(imgBackground, (0, 0))

    # Dibujar los cuadrados en la nueva posición.
    screen.blit(imgCuadrado, (cuadradoX, cuadradoY))
    screen.blit(imgCuadrado2, (cuadrado2X, cuadrado2Y))

    # Actualizar la pantalla.
    pygame.display.flip()

# Cerrar Pygame y liberar los recursos que pidió el programa.
pygame.quit()


Evitar Código Duplicado


Como podemos ver en el ejemplo, todo el código de control de bordes para el segundo cuadrado es idéntico al que tenemos para el primer cuadrado, salvo que usa las variables correspondientes al segundo cuadrado. Esta no es una buena práctica de programación. No es bueno que el código esté repetido. Lo hemos hecho así porque de alguna manera hay que empezar, pero ahora lo vamos a corregir.


Imagina si quisiéramos cambiar el control de bordes para los dos cuadrados. Deberíamos corregir el código dos veces. O peor aún, imagina si hubieran más cuadrados en el juego, el código en este caso quedaría muy extenso, dado que todo el bloque de control de bordes lo deberíamos repetir para cada cuadrado. Por este motivo, debemos evitar siempre tener código duplicado.


Lo que debemos hacer para evitar tener código duplicado, es programar una clase, a la que le llamaremos CCuadrado, que contendrá el código una vez sola, y también será más fácil de usar. Esto es lo que haremos en la siguiente sección.


Nota: A las clases le pondremos el prefijo “C” (por Clase) en el nombre para mostrar que es un nombre de clase. Esta es una nomenclatura bastante común en la orientación a objetos y la estaremos usando en este libro.


Escribiendo la Clase CCuadrado


Los videojuegos generalmente tienen varios objetos moviéndose en la pantalla. Hasta ahora en el juego tenemos dos cuadrados, y si vemos el código del ejemplo anterior, veremos que para cada cuadrado estamos usando tres variables: una variable con la imagen (superficie) y otras dos variables con las coordenadas (x, y).


Imagina si agregamos muchos cuadrados al juego. Si cada cuadrado tiene al menos tres variables, poner muchos cuadrados con tantas variables en el código haría muy largo el programa. Por esta razón (y por otras razones que ya veremos) es que debemos usar objetos y clases (técnica denominada OOP: Programación Orientada a Objetos). En este caso lo que vamos a hacer es una clase denominada CCuadrado. La idea detrás de esto es tener todo el comportamiento del cuadrado encapsulado (contenido) en una clase y luego utilizaremos una sola variable por cada cuadrado que haya en el juego, en lugar de tener todas las variables sueltas en el código. El cuadrado tendrá sus propias coordenadas dentro de él mismo (sus propiedades o atributos dentro de la clase), al igual que su comportamiento (en este caso, el comportamiento del cuadrado es moverse y chequear los bordes).


Ahora necesitamos comenzar a escribir la base de nuestra primera clase, en este caso, para representar un cuadrado. A la clase la llamaremos CCuadrado, e irá en un archivo CCuadrado.py (el nombre del archivo debe ser igual al nombre de la clase).


El cuadrado entonces tendrá como atributos (variables internas) las coordenadas x e y, y la imagen como hasta ahora. En la clase CCuadrado también pondremos atributos para tener las coordenadas de los límites de su movimiento, esto es, las coordenadas mínima y máxima en la horizontal y las coordenadas mínima y máxima en la vertical. También tendremos el ancho y el alto de la imagen (más adelante lo usaremos para detectar las colisiones) y el color que usamos para pintar el cuadrado. Todas estas variables internas del cuadrado son sus propiedades.


Abre el ejemplo ubicado en la carpeta capitulo_03\005_la_clase_cuadrado y ejecútalo. Verás el mismo comportamiento que en el ejemplo anterior, pero el programa ahora está armado utilizando una clase para representar el cuadrado.


Las funciones (denominadas métodos) que tendremos en la clase CCuadrado son: la función __init__() (denominada constructor), que se encarga de inicializar los atributos, la función setXY() para ubicar el objeto, la función setBounds() para definir los límites de su movimiento, la función update() que moverá el objeto (ejecuta su comportamiento) y por último la función render() que dibuja la imagen del cuadrado en la pantalla.


Entonces, la clase CCuadrado contiene la programación necesaria para implementar el objeto cuadrado. Esto supone definir sus propiedades e implementar sus métodos (escribir las funciones). La clase CCuadrado completa (en el archivo CCuadrado.py) se muestra en el siguiente listado:


# -*- coding: utf-8 -*-

#--------------------------------------------------------------------
# Clase CCuadrado.
# Simple cuadrado que se mueve por la pantalla chequeando los bordes.
#
# Autor: Fernando Sansberro - Batovi Games.
# Proyecto: Hacete tu Videojuego.
# Licencia: Creative Commons. BY-NC-SA.
#--------------------------------------------------------------------

import pygame

class CCuadrado(object):

    #----------------------------------------------------------------
    # Constructor.
    # Parámetros:
    # aWidth: Ancho del cuadrado.
    # aHeight: Alto del cuadrado.
    # aColor: Color del cuadrado en formato RGB (r,g,b).
    #----------------------------------------------------------------
    def __init__(self, aWidth, aHeight, aColor):
        
        # Coordenadas del cuadrado.
        self.mX = 0
        self.mY = 0
        
        # Imagen (superficie).
        self.mImg = None
        
        # Variables para controlar los bordes.
        self.mMinX = 0
        self.mMaxX = 0
        self.mMinY = 0
        self.mMaxY = 0
        
        # Ancho y alto.
        self.mWidth = aWidth
        self.mHeight = aHeight
        
        # Color.
        self.mColor = aColor

        # Crear la superficie y llenarla con el color.
        self.mImg = pygame.Surface((aWidth, aHeight))
        self.mImg = self.mImg.convert()
        self.mImg.fill(aColor)

    # Establece la posición del objeto.
    # Parámetros:
    # aX, aY: Coordenadas x e y del objeto.
    def setXY(self, aX, aY):
        
        self.mX = aX
        self.mY = aY

    # Define los límites del movimiento del objeto.
    # Parámetros:
    # aMinX, aMinY: Coordenadas x e y mínimas del mundo.
    # aMaxX, aMaxY: Coordenadas x e y máximas del mundo.
    def setBounds(self, aMinX, aMinY, aMaxX, aMaxY):
        
        self.mMinX = aMinX
        self.mMaxX = aMaxX
        self.mMinY = aMinY
        self.mMaxY = aMaxY

    # Mover el objeto.
    # Parámetros:
    # aIncX: Cantidad de píxeles que se mueve en la horizontal.
    # aIncY: Cantidad de píxeles que se mueve en la vertical.
    def update(self, aIncX, aIncY):
        
        # Mover el objeto.
        self.mX += aIncX
        self.mY += aIncY

        # Controlar los bordes haciendo wrap.
        if self.mX > self.mMaxX:
            self.mX = self.mMinX
        if self.mX < self.mMinX:
            self.mX = self.mMaxX
        if self.mY > self.mMaxY:
            self.mY = self.mMinY
        if self.mY < self.mMinY:
            self.mY = self.mMaxY

    # Dibuja el objeto en la pantalla.
    # Parámetros:
    # aScreen: La superficie de la pantalla en donde dibujar.
    def render(self, aScreen):
        
        aScreen.blit(self.mImg, (self.mX, self.mY))

Nota: La función __init__() lleva dos caracteres underscore (_) de cada lado. Ten cuidado con esto porque puede no notarse bien en el texto. Si ponemos más o menos caracteres el programa dará un error.


Nota: Como vemos en el código, todos los parámetros de las funciones tienen una “a” como prefijo del nombre, y una “m” como prefijo de los atributos (a las variables dentro de una clase se le denomina variables miembro de la clase). Esta es una regla de nomenclatura del código (una forma de nombrar las cosas) que hace más legible el programa. Es la nomenclatura que usaremos en este libro.


Nota: En Python, todas las funciones de una clase deben tener como primer parámetro la referencia self, que es una referencia al objeto, y se debe utilizar siempre que se accedan a variables miembro (de lo contrario no se reconocerían).


Una vez que tenemos la clase CCuadrado pronta, desde la clase principal (main.py) es mucho más simple manejar los objetos. El código de main.py queda bastante más corto, debido a que el código que estaba duplicado ya se encuentra ahora una vez sola dentro de la clase CCuadrado.


Veamos a continuación el código de main.py. Se han resaltado las líneas que cambian.


...

# Importar Pygame.
import pygame

# Importar la clase CCuadrado.
from CCuadrado import *

# Inicializar Pygame.
pygame.init()

# Control del modo ventana o fullscreen.
RESOLUTION = (800, 600)
isFullscreen = False

# Poner el modo de video en ventana e indicar la resolución.
screen = pygame.display.set_mode(RESOLUTION)
# Poner el título de la ventana.
pygame.display.set_caption("Mi Juego")

# Crear la superficie del fondo o background.
imgBackground = pygame.Surface(screen.get_size())
imgBackground = imgBackground.convert()
imgBackground.fill((0, 0, 255))

# Ancho y alto del cuadrado.
CUAD_WIDTH = 32
CUAD_HEIGHT = 32

# Crear los cuadrados: ancho, alto, color (RGB).
c1 = CCuadrado(CUAD_WIDTH, CUAD_HEIGHT, (255, 0, 0))
c2 = CCuadrado(CUAD_WIDTH, CUAD_HEIGHT, (255, 255, 0))

# Colocar los cuadrados en su posición inicial.
c1.setXY(0, 300)
c2.setXY(768, 400)

# Marcar los límites del mundo.
c1.setBounds(-CUAD_WIDTH, -CUAD_HEIGHT, screen.get_width(),
                  screen.get_height())
c2.setBounds(-CUAD_WIDTH, -CUAD_HEIGHT, screen.get_width(),
                  screen.get_height())

# Inicializar las variables de control del game loop.
clock = pygame.time.Clock()
salir = False

# Loop principal (game loop) del juego.
while not salir:

    # Timer que controla el frame rate.
    clock.tick(30)

    # Procesar los eventos que llegan a la aplicación.
    for event in pygame.event.get():
        
        ...

    # Mover los cuadrados y controlar los bordes.
    c1.update(2, 1)
    c2.update(-2, -1)

    # Dibujar el fondo.
    screen.blit(imgBackground, (0, 0))

    # Dibujar los cuadrados en la nueva posición.
    c1.render(screen)
    c2.render(screen)

    # Actualizar la pantalla.
    pygame.display.flip()

# Cerrar Pygame y liberar los recursos que pidió el programa.
pygame.quit()

Como vemos en el código del programa principal, la primera línea importa la clase CCuadrado, lo cual siempre es necesario hacer si queremos usar la clase. Para esto usamos la sentencia:


# Importar la clase CCuadrado.
from CCuadrado import *

Luego, en la parte de inicialización, se crean dos objetos de la clase CCuadrado (a esto se le denomina instanciar, o crear los objetos):


# Ancho y alto del cuadrado.
CUAD_WIDTH = 32
CUAD_HEIGHT = 32

# Crear los cuadrados: ancho, alto, color (RGB).
c1 = CCuadrado(CUAD_WIDTH, CUAD_HEIGHT, (255, 0, 0))
c2 = CCuadrado(CUAD_WIDTH, CUAD_HEIGHT, (255, 255, 0))

Definimos dos constantes para tener el ancho y el alto de los cuadrados y esos son los parámetros que se le pasan a la función que crea los objetos. A esta función encargada de crear el objeto se le denomina método constructor del objeto (en este caso CCuadrado() que invoca a __init__() en la clase CCuadrado).


Los parámetros que se le pasan al constructor de CCuadrado son el ancho, el alto y el color, que se define como una tupla con los valores rojo, verde y azul (color RGB). Esto se puede hacer de la forma que se prefiera. Lo hemos hecho así para poder tener cuadrados de diferentes tamaños y colores.


Luego de creados los dos objetos, inicializamos su posición y establecemos los límites por donde se podrán mover:


# Colocar los cuadrados en su posición inicial.
c1.setXY(0, 300)
c2.setXY(768, 400)

# Marcar los límites del mundo.
c1.setBounds(-CUAD_WIDTH, -CUAD_HEIGHT, screen.get_width(),
screen.get_height())
c2.setBounds(-CUAD_WIDTH, -CUAD_HEIGHT, screen.get_width(),
screen.get_height())

Nota: Cuando a los objetos del juego le marcamos los límites para que no se vayan de la pantalla, en realidad estamos indicando los límites del mundo, esto es, las coordenadas mínima y máxima que el objeto puede tener, tanto en la vertical como en la horizontal. En los primeros juegos que hagamos, las coordenadas de pantalla y del mundo serán las mismas, pero esto no siempre es así (por ejemplo en los juegos con desplazamiento de pantalla, o scrolling, donde el mundo es más grande que la pantalla).


Finalmente, en el game loop, lo que se hace es invocar a las funciones update() y render() para cada cuadrado. La función update() mueve el objeto y la función render() lo dibuja en su nueva posición. El ciclo update/render lo utilizaremos para todos los objetos que tenga nuestro juego:


# Loop principal (game loop) del juego.
while not salir:

    ...

    # Mover los cuadrados y controlar los bordes.
    c1.update(2, 1)
    c2.update(-2, -1)

    # Dibujar el fondo.
    screen.blit(imgBackground, (0, 0))

    # Dibujar los cuadrados en la nueva posición.
    c1.render(screen)
    c2.render(screen)

    ...

Nota: La Programación Orientada a Objetos (OOP) es fundamental en el desarrollo de videojuegos. Si lo pensamos, es muy probable que cada elemento en un juego se corresponda con una clase. Cuando hagamos nuestro juego, tendremos una clase para el jugador, otra para los enemigos, otra para las balas, etc. Si tienes dudas sobre cómo escribir clases y utilizarlas, debes consultar los conceptos sobre programación OOP de Python o pedirle ayuda a un experto.


Nota: Si te fijas en la clase CCuadrado, para referirse a las propiedades de la clase, utilizamos el operador self. Este operador quiere decir “yo mismo”. Si no utilizamos el operador self para referirnos a una propiedad, estaremos diciendo que es otra variable, lo que seguro ocasionará un error.


Con esto terminamos nuestro primer objeto que se mueve por la pantalla. Pero como los juegos no se basan en cuadrados, en el siguiente capítulo aprenderemos a manejar imágenes, de forma de poder poner gráficos y que nuestros objetos en el juego se vean lindos.


 
 
 

Comments


bottom of page