Ir al contenido principal

Python + Pygame (XXIX): Tutorial - Salto de personajes (II)

En el anterior post puse un script con un código que puede servir (se puede hacer de otras muchas formas - utilizando la función seno, etc.) para implementar el salto del personaje de un videojuego 2D,  y en éste lo completo con algunos aspectos adicionales (animación del personaje al andar y saltar, se incluyen algunos sonidos y detección de la colisión entre el personaje y los tramos de la plataforma utilizando sprites para estos últimos).

El código incluido y modificado más relevante es el siguiente (al igual que en el  anterior post, los comentarios en el código hacen que éste sea autoexplicativo, por lo que me limitaré a realizar sólo nuevos comentarios que puedan aportar algo a la explicación):

1.- Inicializar determinados aspectos:
    # Cargar archivos de sonidos.
    sonido_andar = pygame.mixer.Sound(
                                   'recursos/sonidos/andar.wav')
    sonido_saltar = pygame.mixer.Sound(
                                   'recursos/sonidos/saltar.wav')
    # Crear canal para sonidos.
    canal1 = pygame.mixer.Channel(0)
    # Grupo de sprites. Agrupamos los tramos del suelo usando la clase
    # sprite.Group de Pygame para hacer que dicha clase se encargue de
    # actualizarlos y dibujarlos, sin necesidad de hacerlo manualmente.
    grupo_tramos = pygame.sprite.Group()
    # Colocar los tramos (el suelo del  piso 0 en la pantalla).
    for num_tramo in range(14):
        tramo = Tramo(24 + 48 * num_tramo, 396)
        grupo_tramos.add(tramo)

    # Variables booleanas que indican si Jumpman se encuentra en el suelo o
    # está saltando, respectivamente.
    en_suelo = True
    en_salto = False
    # Sentido del movimiento de Jumpman, tanto cuando anda como cuando salta.
    sentido = 'DERECHA'
    # Velocidad de movimiento en ambos ejes.
    vel_x, vel_y = 3, 0
    # Variables correspondientes al salto.
    sentido_salto = 0
    altura_salto = 10
    gravedad = 1
    # Establecer el tiempo transcurrido (valor de un contador) desde el inicio
    # del movimiento de Jumpman, tanto cuando está andando como cuando salta, y
    # el que debe transcurrir hasta su finalización.
    contador_inicio_mover = 0
    contador_fin_mover = 10
    # Variable booleana que indica si se debe finalizar de mostrar el
    # movimiento de Jumpman (andar o saltar).
    finalizar_mover = False
Es decir, básicamente, se incluye el código necesario para: cargar los archivos de sonido y crear el canal para reproducirlos,  colocar los tramos (sprites) del piso 0 - instancias de la clase 'Tramo' , que veremos más adelante - y añadirlos al grupo correspondiente, e inicializar una serie de variables relativas al movimiento.

Sonidos:

Andar:
Saltar:

2.- Manejo de teclas pulsadas:
        # Obtener la lista de teclas pulsadas y gestión de las mismas.
        teclas = pygame.key.get_pressed()
        # Movimiento horizontal (tanto si Jumpman está en el suelo como si está
        # saltando).
        if en_suelo:
            vel_x = 3
            if teclas[pygame.K_LEFT]:
                canal1.play(sonido_andar)
                sentido = 'IZQUIERDA'
                jumpman.mover('ANDAR', sentido, 2, -vel_x, 0)
                finalizar_mover = True
            elif teclas[pygame.K_RIGHT]:
                canal1.play(sonido_andar)
                sentido = 'DERECHA'
                jumpman.mover('ANDAR', sentido, 2, vel_x, 0)
                finalizar_mover = True
        elif en_salto:
            jumpman.mover('SALTAR', sentido, 1, sentido_salto * 6, vel_y)
        # Salto.
        if teclas[pygame.K_SPACE] and en_suelo:
            canal1.play(sonido_saltar)
            vel_x = 0
            vel_y = -altura_salto
            jumpman.mover('SALTAR', sentido, 1, vel_x, -altura_salto)
            en_suelo = False
            en_salto = True
            # Guardar el sentido en el que se inicia el salto.
            if teclas[pygame.K_LEFT]:
                sentido_salto = -1
            elif teclas[pygame.K_RIGHT]:
                sentido_salto = 1
            else:
                sentido_salto = 0
        if en_salto:
            # Aplicar gravedad.
            vel_y += gravedad
            # Comprobar si ha aterrizado (colisiona con un tramo del piso 0).
            tramos_colision = pygame.sprite.groupcollide(grupo_jumpman,
                                                         grupo_tramos,
                                                         False, False)
            if len(tramos_colision) > 0:
                vel_x = 0
                # Colisión con el suelo.
                jumpman.mover('SALTAR', sentido, 1, vel_x,
                              tramos_colision[grupo_jumpman.sprites()[0]][0].
                              rect.top -
                              grupo_jumpman.sprites()[0].rect.bottom -
                              tramo.alto_tramo // 2 - 1)
                jumpman.finalizar_mover()
                vel_y = 0
                en_suelo = True
                en_salto = False
        # Animar movimientos de Jumpman.
        if finalizar_mover:
            if contador_inicio_mover > contador_fin_mover:
                jumpman.finalizar_mover()
                contador_inicio_mover = 0
                finalizar_mover = False
            else:
                contador_inicio_mover += 1 
La gestión de las teclas pulsadas se traslada del método update() de la clase 'Jumpman' al bucle principal del juego, y se incluyen la gestión de las colisiones de 'Jumpman' con los tramos del piso 0  (aterrizaje después del salto) y la animación de los movimientos de 'Jumpman'.

3.- Clases:
class Jumpman(pygame.sprite.Sprite):
    # Jumpman (Mario) estará definido por la clase Jumpman, que hereda de la
    # clase sprite.Sprite de Pygame. Clase base para objetos visibles del
    # juego.
    def __init__(self, x, y):
        super().__init__()
        self.x, self.y  = x, y
        self.image = pygame.image.load('recursos/imagenes/' +
                                       'jumpman_derecha_1.png').convert_alpha()

    def mover(self, movimiento, sentido, paso, vel_x, vel_y):
        # Método que permite que Jumpman se mueva (ande o salte) en el sentido
        # indicado.
        self.movimiento = movimiento
        self.sentido = sentido
        self.paso = paso
        self.x += vel_x
        self.y += vel_y
        if self.movimiento == 'ANDAR':
            self.image = pygame.image.load('recursos/imagenes/'            +
                                           'jumpman_' + self.sentido + '_' +
                                                  str(self.paso) + '.png') \
                                                          .convert_alpha()
        elif self.movimiento == 'SALTAR':
                self.image = pygame.image.load('recursos/imagenes/'   +
                                               'jumpman_saltar_'      +
                                               self.sentido + '.png') \
                                                     .convert_alpha()

    def finalizar_mover(self):
        # Método que permite completar el movimiento de Jumpman, tanto cuando
        # anda como cuando salta, mostrando la imagen correspondiente a Jumpman
        # parado.
        self.image = pygame.image.load('recursos/imagenes/' +
                                       'jumpman_' + self.sentido + '_1.png') \
                                        .convert_alpha()

    def update(self):
        # El método update() se ejecutará en cada frame y permite actualizar la
        # posición de Jumpman.
        self.rect = self.image.get_rect(center=(self.x, self.y))


class Tramo(pygame.sprite.Sprite):
    # Los tramos del suelo por donde anda y salta Jumpman estarán definidos por
    # la clase Tramo, que hereda de la clase sprite.Sprite de Pygame. Clase
    # base para objetos visibles del juego.
    def __init__(self, x, y):
        super().__init__()
        self.x, self.y  = x, y
        self.image = pygame.image.load('recursos/imagenes/tramo.png') \
                                                     .convert_alpha()
        self.alto_tramo = self.image.get_height()

    def update(self):
        # El método update() se ejecutará en cada frame, y permite actualizar
        # los tramos.
        self.rect = self.image.get_rect(center=(self.x, self.y))
Las clases 'Jumpman' y 'Tramo' son las encargadas de gestionar las imágenes y de actualizar su posición.

4.- Poniendo todo junto:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SALTAR 2:
#
# Segunda aproximación al salto de un personaje de videojuego.
#
# Autor: Mikel García Larragan.
# https://mikelgarcialarragan.blogspot.com/

import pygame
import os


class Jumpman(pygame.sprite.Sprite):
    # Jumpman (Mario) estará definido por la clase Jumpman, que hereda de la
    # clase sprite.Sprite de Pygame. Clase base para objetos visibles del
    # juego.
    def __init__(self, x, y):
        super().__init__()
        self.x, self.y  = x, y
        self.image = pygame.image.load('recursos/imagenes/' +
                                       'jumpman_derecha_1.png').convert_alpha()

    def mover(self, movimiento, sentido, paso, vel_x, vel_y):
        # Método que permite que Jumpman se mueva (ande o salte) en el sentido
        # indicado.
        self.movimiento = movimiento
        self.sentido = sentido
        self.paso = paso
        self.x += vel_x
        self.y += vel_y
        if self.movimiento == 'ANDAR':
            self.image = pygame.image.load('recursos/imagenes/'            +
                                           'jumpman_' + self.sentido + '_' +
                                                  str(self.paso) + '.png') \
                                                          .convert_alpha()
        elif self.movimiento == 'SALTAR':
                self.image = pygame.image.load('recursos/imagenes/'   +
                                               'jumpman_saltar_'      +
                                               self.sentido + '.png') \
                                                     .convert_alpha()

    def finalizar_mover(self):
        # Método que permite completar el movimiento de Jumpman, tanto cuando
        # anda como cuando salta, mostrando la imagen correspondiente a Jumpman
        # parado.
        self.image = pygame.image.load('recursos/imagenes/' +
                                       'jumpman_' + self.sentido + '_1.png') \
                                        .convert_alpha()

    def update(self):
        # El método update() se ejecutará en cada frame y permite actualizar la
        # posición de Jumpman.
        self.rect = self.image.get_rect(center=(self.x, self.y))


class Tramo(pygame.sprite.Sprite):
    # Los tramos del suelo por donde anda y salta Jumpman estarán definidos por
    # la clase Tramo, que hereda de la clase sprite.Sprite de Pygame. Clase
    # base para objetos visibles del juego.
    def __init__(self, x, y):
        super().__init__()
        self.x, self.y  = x, y
        self.image = pygame.image.load('recursos/imagenes/tramo.png') \
                                                     .convert_alpha()
        self.alto_tramo = self.image.get_height()

    def update(self):
        # El método update() se ejecutará en cada frame, y permite actualizar
        # los tramos.
        self.rect = self.image.get_rect(center=(self.x, self.y))


def donkey_kong():
    # Inicializar módulos internos de pygame.
    pygame.init()
    # Establecer tamaño de la ventana.
    # Crear una superficie de visualización: la ventana, y centrarla.
    os.environ['SDL_VIDEO_CENTERED']='1'
    ventana = pygame.display.set_mode((674, 430))
    # Título de la ventana.
    pygame.display.set_caption('Donkey Kong')
    # Cargar la imagen del fondo y cambiar el formato de píxel para crear una
    # copia que se dibujará más rápidamente en la pantalla.
    fondo = pygame.image.load('recursos/imagenes/fondo_2.png')
    fondo = fondo.convert()
    # Cargar archivos de sonidos.
    sonido_andar = pygame.mixer.Sound(
                                   'recursos/sonidos/andar.wav')
    sonido_saltar = pygame.mixer.Sound(
                                   'recursos/sonidos/saltar.wav')
    # Crear canal para sonidos.
    canal1 = pygame.mixer.Channel(0)
    # Grupo de sprites. Agrupamos los tramos del suelo usando la clase
    # sprite.Group de Pygame para hacer que dicha clase se encargue de
    # actualizarlos y dibujarlos, sin necesidad de hacerlo manualmente.
    grupo_tramos = pygame.sprite.Group()
    # Colocar los tramos (el suelo del  piso 0 en la pantalla).
    for num_tramo in range(14):
        tramo = Tramo(24 + 48 * num_tramo, 396)
        grupo_tramos.add(tramo)
    # Posición inicial de Jumpman.
    x, y = 120, 365
    # Grupo individual para Jumpman y dibujarlo.
    grupo_jumpman = pygame.sprite.GroupSingle()
    jumpman = Jumpman(x, y)
    grupo_jumpman.add(jumpman)
    # Variables booleanas que indican si Jumpman se encuentra en el suelo o
    # está saltando, respectivamente.
    en_suelo = True
    en_salto = False
    # Sentido del movimiento de Jumpman, tanto cuando anda como cuando salta.
    sentido = 'DERECHA'
    # Velocidad de movimiento en ambos ejes.
    vel_x, vel_y = 3, 0
    # Variables correspondientes al salto.
    sentido_salto = 0
    altura_salto = 10
    gravedad = 1
    # Establecer el tiempo transcurrido (valor de un contador) desde el inicio
    # del movimiento de Jumpman, tanto cuando está andando como cuando salta, y
    # el que debe transcurrir hasta su finalización.
    contador_inicio_mover = 0
    contador_fin_mover = 10
    # Variable booleana que indica si se debe finalizar de mostrar el
    # movimiento de Jumpman (andar o saltar).
    finalizar_mover = False
    # Reloj para ejecutar el videojuego.
    reloj = pygame.time.Clock()
    # El juego se ejecutará a 60 frames por segundo.
    fps = 60
    while True:
        # Ejecutar el siguiente frame.
        reloj.tick(fps)
        # Escuchar cada uno de los eventos de la lista de eventos de Pygame.
        for evento in pygame.event.get():
            # Finalizar el juego cuando se produzca el evento de terminación
            # del programa (cierre de la ventana).
            if evento.type == pygame.QUIT:
                pygame.quit()
                return
        # Obtener la lista de teclas pulsadas y gestión de las mismas.
        teclas = pygame.key.get_pressed()
        # Movimiento horizontal (tanto si Jumpman está en el suelo como si está
        # saltando).
        if en_suelo:
            vel_x = 3
            if teclas[pygame.K_LEFT]:
                canal1.play(sonido_andar)
                sentido = 'IZQUIERDA'
                jumpman.mover('ANDAR', sentido, 2, -vel_x, 0)
                finalizar_mover = True
            elif teclas[pygame.K_RIGHT]:
                canal1.play(sonido_andar)
                sentido = 'DERECHA'
                jumpman.mover('ANDAR', sentido, 2, vel_x, 0)
                finalizar_mover = True
        elif en_salto:
            jumpman.mover('SALTAR', sentido, 1, sentido_salto * 6, vel_y)
        # Salto.
        if teclas[pygame.K_SPACE] and en_suelo:
            canal1.play(sonido_saltar)
            vel_x = 0
            vel_y = -altura_salto
            jumpman.mover('SALTAR', sentido, 1, vel_x, -altura_salto)
            en_suelo = False
            en_salto = True
            # Guardar el sentido en el que se inicia el salto.
            if teclas[pygame.K_LEFT]:
                sentido_salto = -1
            elif teclas[pygame.K_RIGHT]:
                sentido_salto = 1
            else:
                sentido_salto = 0
        if en_salto:
            # Aplicar gravedad.
            vel_y += gravedad
            # Comprobar si ha aterrizado (colisiona con un tramo del piso 0).
            tramos_colision = pygame.sprite.groupcollide(grupo_jumpman,
                                                         grupo_tramos,
                                                         False, False)
            if len(tramos_colision) > 0:
                vel_x = 0
                # Colisión con el suelo.
                jumpman.mover('SALTAR', sentido, 1, vel_x,
                              tramos_colision[grupo_jumpman.sprites()[0]][0].
                              rect.top -
                              grupo_jumpman.sprites()[0].rect.bottom -
                              tramo.alto_tramo // 2 - 1)
                jumpman.finalizar_mover()
                vel_y = 0
                en_suelo = True
                en_salto = False
        # Animar movimientos de Jumpman.
        if finalizar_mover:
            if contador_inicio_mover > contador_fin_mover:
                jumpman.finalizar_mover()
                contador_inicio_mover = 0
                finalizar_mover = False
            else:
                contador_inicio_mover += 1
        # Actualizar sprites.
        grupo_tramos.update()
        grupo_jumpman.update()
        # Copiar la imagen de fondo de la ventana en el canvas que la mostrará.
        ventana.blit(fondo, (0, 0))
        # Dibujar sprites.
        grupo_tramos.draw(ventana)
        grupo_jumpman.draw(ventana)
        # Mostrar la ventana dibujada (cambiar buffers, buffer pantalla a
        # disponible para dibujar y viceversa.
        pygame.display.flip()
    pygame.quit()


if __name__ == '__main__':
    donkey_kong()
Si lo ejecutamos:
Quizás también te interese:

Comentarios

Entradas populares de este blog

Criptografía (I): cifrado Vigenère y criptoanálisis Kasiski

Hace unos días mi amigo Iñaki Regidor ( @Inaki_Regidor ), a quien dedico esta entrada :), compartió en las redes sociales un post titulado "Criptografía: el arte de esconder mensajes"  publicado en uno de los blogs de EiTB . En ese post se explican ciertos métodos clásicos para cifrar mensajes , entre ellos el cifrado de Vigenère , y , al final del mismo, se propone un reto consistente en descifrar un mensaje , lo que me ha animado a escribir este post sobre el método Kasiski  para atacar un cifrado polialfabético ( conociendo la clave descifrar el mensaje es muy fácil, pero lo que contaré en este post es la forma de hacerlo sin saberla ). El mensaje a descifrar es el siguiente: LNUDVMUYRMUDVLLPXAFZUEFAIOVWVMUOVMUEVMUEZCUDVSYWCIVCFGUCUNYCGALLGRCYTIJTRNNPJQOPJEMZITYLIAYYKRYEFDUDCAMAVRMZEAMBLEXPJCCQIEHPJTYXVNMLAEZTIMUOFRUFC Como ya he dicho el método de Vigenère es un sistema de sustitución polialfabético , lo que significa que, al contrario que en un sistema...

¿Qué significa el emblema de la profesión informática? (I)

Todas o muchas profesiones tienen un emblema que las representa simbólicamente y en el caso de la  informática: " es el establecido en la resolución de 11 de noviembre de 1977  para las titulaciones universitarias superiores de informática, y  está constituido por una figura representando en su parte central  un  núcleo toroidal de ferrita , atravesado por  hilos de lectura,  escritura e inhibición . El núcleo está rodeado por  dos ramas : una  de  laurel , como símbolo de recompensa, y la otra, de  olivo , como  símbolo de sabiduría. La  corona  será la  de la casa real  española,  y bajo el escudo se inscribirá el acrónimo de la organización. ". Veamos los diferentes elementos tomando como ejemplo el emblema del COIIE/EIIEO (Colegio Oficial de Ingenieros en Informática del País Vasco/ Euskadiko Informatikako Ingeniarien Elkargo Ofiziala ) . Pero no sólo el COIIE/EIIEO adopta el emblem...

Criptografía (XXIII): cifrado de Hill (I)

En este post me propongo explicar de forma comprensible lo que he entendido sobre el cifrado de Hill , propuesto por el matemático Lester S. Hill , en 1929, y que se basa en emplear una matriz como clave  para cifrar un texto en claro y su inversa para descifrar el criptograma correspondiente . Hay tres cosas que me gustan de la criptografía clásica, además de que considero que ésta es muy didáctica a la hora de comprender los sistemas criptográficos modernos: la primera de ellas es que me "obliga" a repasar conceptos de matemáticas aprendidos hace mucho tiempo y, desgraciadamente, olvidados también hace demasiado tiempo, y, por consiguiente, que, como dice  Dani , amigo y coautor de este blog, me "obliga" a hacer "gimnasia mental"; la segunda es que, en la mayoría de las ocasiones, pueden cifrarse y descifrase los mensajes, e incluso realizarse el criptoanálisis de los criptogramas, sin más que un simple lápiz y papel, es decir, para mi es como un pasat...