4. Clases de objetos de juego

Una vez que hayas cargado tus módulos y escrito tus funciones de manejo de recursos, querrás pasar a escribir algunos objetos de juego. La forma en que esto se realiza es bastante simple, sin embargo puede parecer complejo al principio. Escribirás una clase para cada tipo de objeto en el juego y después crearás una instancia de esas clases para los objetos. Luego podés usar los métodos de esas clases para manipular los objetos, dándoles algún tipo de movimiento y capacidades interactivas. Entonces tu juego, en pseudo-código, se verá así:

#!/usr/bin/python

# [load modules here]

# [resource handling functions here]

class Ball:
    # [ball functions (methods) here]
    # [e.g. a function to calculate new position]
    # [and a function to check if it hits the side]

def main:
    # [initiate game environment here]

    # [create new object as instance of ball class]
    ball = Ball()

    while True:
        # [check for user input]

        # [call ball's update function]
        ball.update()

Por supuesto, esto es un ejemplo muy simple, y tendrías que agregar todo el código en lugar de esos pequeños comentarios entre corchetes. Pero deberías entender la idea básica. Creás una clase, en la cual colocás todas las funciones de la pelota, incluyendo __init__,que crearía todos los atributos de la pelota, y update, que movería la pelota a su nueva posición antes de blittearla en la pantalla en esta posición.

Luego podés crear más clases para todos tus otros objetos de juego, y luego crear instancias de los mismos para que puedas manejarlos fácilmente en la función main y en el bucle principal del programa. En contraste con iniciar la pelota en la función main, y luego tener muchas funciones sin clase para manipular un objeto de pelota establecido, y espero que puedas ver por qué usar clases es una ventaja: te permite poner todo el código perteneciente a cada objeto en un único lugar; hace que sea más fácil usar objetos; hace que agregar nuevos objetos y manipularlos sea más flexible. En lugar de agregar más código para cada nuevo objeto de pelota, podés simplemente crear instancias de la clase Ball para cada nuevo objeto de pelota. ¡Mágia!

4.1. Una clase simple de pelota

Aquí hay una clase simple con el código necesario para crear un objeto pelota que se moverá a través de la pantalla, si la función update esa llamada en el bucleo principal:

class Ball(pygame.sprite.Sprite):
    """A ball that will move across the screen  (Una peleota se moverá a través de la pantalla)
    Returns: ball object
    Functions: update, calcnewpos
    Attributes: area, vector"""

    def __init__(self, vector):
        pygame.sprite.Sprite.__init__(self)
        self.image, self.rect = load_png('ball.png')
        screen = pygame.display.get_surface()
        self.area = screen.get_rect()
        self.vector = vector

    def update(self):
        newpos = self.calcnewpos(self.rect,self.vector)
        self.rect = newpos

    def calcnewpos(self,rect,vector):
        (angle,z) = vector
        (dx,dy) = (z*math.cos(angle),z*math.sin(angle))
        return rect.move(dx,dy)

Aquí tenemos la clase Ball con una función __init__ que configura la pelota, una función update que cambia el rectángulo de la pelota para que esté en la nueva posición, y una función calcnewpos para calcular la nueva posición de la pelota basada en su posición actual, y el vector por el cual se está moviendo. Explicaré la física en un momento. Lo único más a destacar es la cadena de documentación, que es un poco más larga esta vez, y explica los conceptos básicos del la clase. Estas cadenas son útiles no solo para ti mismo y otros programadores que revisen el código, sino también para las herramientas que analicen y documenten tu código. No harán mucha diferencia en programas pequeños, pero en los grandes son invaluables, así que es una buena costumbre de adquirir.

4.1.1. Digresión 1: Sprites

La otra razón por la cual crear una clase por cada objeto son los sprites. Cada imagen que se renderiza en tu juego será un objeto, por lo que en principio, la clase de cada objeto debería heredar la clase Sprite. Esta es una característica muy útil de Python: la herencia de clases. Ahora, la clase Ball tiene todas las funciones que vienen con la clase Sprite, y cualquier instancia del objeto de la clase Ball será registrada por Pygame como un sprite. Mientras que con el texto y el fondo, que no se mueven, está bien hacer un blit del objeto sobre el fondo, Pygame maneja los objetos sprites de manera diferente, lo cual verás cuando miremos el código completo del programa.

Básicamente, creas tanto un objeto pelota y un objeto sprite para la pelota, y luego llamás a la función update de la pelota en el objeto de sprite, actualizando así el sprite. Los sprites también te dan formas sofisticadas de determinar si dos objetos han colisionado. Normalmente, podrías simplemente comprobar en el bucle principal para ver si sus rectángulos se superponen, pero eso implicaría mucho código, lo cual sería una pérdida de tiempo porque la clase Sprite proporciona dos funciones (spritecollide y groupcollide) para hacer esto por vos.

4.1.2. Digresión 2: Física de vectores

Aparte de la estructura de la clase Ball, lo notable de este código es la física de vectores utilizada para calcular el movimiento de la pelota. En cualquier juego que involucre movimiento angular, no se llegará muy lejos a menos que se esté cómodo con la trigonometría, así que simplemente introduciré los conceptos básicos que necesitás saber para entender la función calcnewpos.

Para empezar, notarás que la pelota tiene un atributo llamado vector, que está compuesto por angle y z. El ángulo estpa medido en radianes y dará la dirección en la que la pelota se mueve. Z es la velocidad a la que se mueve la pelota. Entonces, usando este vector, podemos determinar la dirección y velocidad de la pelota, y por lo tanto, cuánto se moverá en los ejes x e y:

../_images/tom_radians.png

El diagrama anterior ilustra las matemáticas básicas detrás de los vectores. En el diagrama de la izquierda, se puede ver el movimiento proyectado de la pelota representado por una línea azul. La longitud de esa línea (z) representa su velocidad, y el ángulo es la dirección en la que se moverá. El ángulo para el movimiento de la pelota siempre se tomará desde el eje x a la derecha, y se mide en sentido horario desde esa línea, como se muestra en el diagrama.

A partir del ángulo y la velocidad de la pelota, podemos lograr calcular cuánto se ha movido a lo largo de los ejes x e y. Necesitamos hacer esto porque Pygame en sí no admite vectores, y solo podemos mover la pelota moviendo su rectángulo a lo largo de los dos ejes. Por lo tanto, necesitamos resolve (resolver) el ángulo y la velocidad en su movimiento en el eje x (dx) y en el eje y (dy). Esto es un asunto sencillo de trigonometría y se puede hacer con las fórmulas que se muestran en el diagrama.

Si has estudiado trigonometría elemental antes, nada de esto debería ser nuevo para vos. Pero en caso que seas olvidadizo, acá hay algunas fórmulas útiles para recordar, que te ayudarán a visualizar los ángulos (a mi me resulta más fácil visualizar los ángulos en grados que en radianes.

../_images/tom_formulae.png



Edit on GitHub