Ir al contenido principal

Python + Pygame (XXX): Tutorial - Salto de personajes (III)

En los posts anteriores ('Python + Pygame (XXVIII): Tutorial - Salto de personajes (I)' y 'Python + Pygame (XXIX): Tutorial - Salto de personajes (II)') puse sendos scripts para implementar el salto de un personaje en un videojuego 2D; en el primero de ellos me centré única y exclusivamente en el salto, mientras que en el segundo completé el primero con algunos aspectos adicionales (animación del personaje al andar y saltar, algunos sonidos y detección de la colisión entre el personaje y los tramos de la plataforma utilizando sprites).

Pues bien, en éste completo el script para considerar la implementación del movimiento del personaje cuando la plataforma no es perfectamente horizontal, como es el caso, por ejemplo, del juego que he tomado como referencia, 'Donkey Kong', ya que el cafre del gorila, en la pantalla de presentación y para dificultarle a 'Jumpman' (Mario) la existencia, se pone a saltar sobre la plataforma como un poseso y deforma los tramos de los diferentes niveles o pisos, dejándo éstos con forma escalonada.

Al igual que en los anteriores posts, 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.

Dicho esto, básicamente, el código modificado e incluido en el script del post anterior es el siguiente:

1.- Inicialización de determinados aspectos:

- Se incluye el sonido correspondiente a la muerte de 'Jumpman' cuando éste cae al piso inferior o salta fuera de la ventana.

    sonido_morir = pygame.mixer.Sound(
                                   'recursos/sonidos/morir.wav')

- Se crean dos grupos para los tramos del suelo, uno para cada piso o nivel de la plataforma, en lugar de uno.

    # Grupo de sprites. Agrupamos los tramos del suelo de los diferentes pisos
    # 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_0 = pygame.sprite.Group()
    grupo_tramos_1 = pygame.sprite.Group()
    # Colocar los tramos del suelo del piso 0 en la pantalla.
    for num_tramo in range(14):
        tramo = Tramo(24 + 48 * num_tramo, 396)
        grupo_tramos_0.add(tramo)
    # Colocar los tramos del suelo del piso 1 en la pantalla.
    for num_tramo in range(13):
        tramo = Tramo(72 + 48 * num_tramo, 327 - num_tramo * 2)
        grupo_tramos_1.add(tramo)

- Se crea e inicializa la siguiente variable:

    # Variable booleana que indica si Jumpman está vivo o muerto.
    jumpman_vivo = True

2.- Manejo de teclas pulsadas:

- El control del aterrizaje de 'Jumpman' en el suelo del primer piso, piso en el que se le sitúa al principio, se realiza de idéntica forma que en el post anterior, es decir, mediante las colisiones del sprite de 'Jumpman' con los de los tramos de ese piso, y ese mismo sistema se utiliza para controlar el caminar de 'Jumpman' sobre el suelo escalonado, tanto hacia la derecha como hacia la izquierda.

- Se controla si 'Jumpman' cae al piso inferior mediante la colisión con los tramos de ese piso, y si salta fuera de la ventana mediante su posición en la misma, produciéndose su muerte en ambos casos.

- Y, finalmente, el manejo de las teclas pulsadas sólo se realiza si 'Jumpman' está vivo.  

        if jumpman_vivo:
            # 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, 1)
                    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, 2, 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
            # Aplicar gravedad.
            vel_y += gravedad
            # Comprobar si colisiona con algún tramo del piso en el que está,
            # tanto cuando anda como cuando salta.
            tramos_colision = pygame.sprite.groupcollide(grupo_jumpman,
                                                         grupo_tramos_1,
                                                         False, False)
            if len(tramos_colision) > 0:
                vel_x = 0
                # Colisión con el suelo.
                if en_suelo:
                    jumpman.mover('ANDAR', sentido, 2, vel_x, -1)
                elif en_salto: 
                    jumpman.mover('SALTAR', sentido, 1, vel_x,
                                  tramos_colision[jumpman][0].
                                  rect.top -
                                  jumpman.rect.bottom -
                                  tramo.alto_tramo // 2 - 1)
                #jumpman.finalizar_mover()
                finalizar_mover = True
                vel_y = 0
                en_suelo = True
                en_salto = False
            elif jumpman.rect.bottomright[0] <= grupo_tramos_1.sprites()[0]. \
                                                rect.topleft[0] or           \
                jumpman.rect.bottomleft[0] >= grupo_tramos_1.sprites()[-1].  \
                                                rect.topright[0]:
                vel_x = 0
                jumpman.mover('ANDAR', sentido, 1, vel_x, 3)
                # Comprobar si colisiona con algún tramo del piso
                # inmediatamnete inferior al que está.
                tramos_colision = pygame.sprite.groupcollide(grupo_jumpman,
                                                             grupo_tramos_0,
                                                             False, False)
                if len(tramos_colision) > 0:
                    canal1.play(sonido_morir)
                    jumpman.morir()
                    jumpman_vivo = False
                elif jumpman.rect.bottom > 475:
                    canal1.play(sonido_morir)
                    grupo_jumpman.remove(jumpman)
                    jumpman_vivo = False

3.- Clases:

Se incluye el método morir en la clase 'Jumpman':

    def morir(self):
        # Método que permite mostrar la muerte de Jumpman cuando cae al piso
        # inmediatamente inferior al que se encuentra o salta fuera de la
        # ventana.
        self.image = pygame.image.load('recursos/imagenes/' +
                                       'jumpman_morir.png').convert_alpha()

4.- Poniendo todo junto:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SALTAR 3:
#
# Tercera 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()
        self.rect = self.image.get_rect(center=(self.x, self.y))

    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 morir(self):
        # Método que permite mostrar la muerte de Jumpman cuando cae al piso
        # inmediatamente inferior al que se encuentra o salta fuera de la
        # ventana.
        self.image = pygame.image.load('recursos/imagenes/' +
                                       'jumpman_morir.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.rect = self.image.get_rect(center=(self.x, self.y))
        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_3.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')
    sonido_morir = pygame.mixer.Sound(
                                   'recursos/sonidos/morir.wav')
    # Crear canal para sonidos.
    canal1 = pygame.mixer.Channel(0)
    # Grupo de sprites. Agrupamos los tramos del suelo de los diferentes pisos
    # 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_0 = pygame.sprite.Group()
    grupo_tramos_1 = pygame.sprite.Group()
    # Colocar los tramos del suelo del  piso 0 en la pantalla.
    for num_tramo in range(14):
        tramo = Tramo(24 + 48 * num_tramo, 396)
        grupo_tramos_0.add(tramo)
    # Colocar los tramos del suelo del  piso 1 en la pantalla.
    for num_tramo in range(13):
        tramo = Tramo(72 + 48 * num_tramo, 327 - num_tramo * 2)
        grupo_tramos_1.add(tramo)
    # Posición inicial de Jumpman.
    x, y = 120, 296
    # Grupo individual para Jumpman y dibujarlo.
    grupo_jumpman = pygame.sprite.GroupSingle()
    jumpman = Jumpman(x, y)
    grupo_jumpman.add(jumpman)
    # Variable booleana que indica si Jumpman está vivo o muerto.
    jumpman_vivo = True
    # 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 = 9
    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
        if jumpman_vivo:
            # 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, 1)
                    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, 2, 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
            # Aplicar gravedad.
            vel_y += gravedad
            # Comprobar si colisiona con algún tramo del piso en el que está,
            # tanto cuando anda como cuando salta.
            tramos_colision = pygame.sprite.groupcollide(grupo_jumpman,
                                                         grupo_tramos_1,
                                                         False, False)
            if len(tramos_colision) > 0:
                vel_x = 0
                # Colisión con el suelo.
                if en_suelo:
                    jumpman.mover('ANDAR', sentido, 2, vel_x, -1)
                elif en_salto: 
                    jumpman.mover('SALTAR', sentido, 1, vel_x,
                                  tramos_colision[jumpman][0].
                                  rect.top -
                                  jumpman.rect.bottom -
                                  tramo.alto_tramo // 2 - 1)
                #jumpman.finalizar_mover()
                finalizar_mover = True
                vel_y = 0
                en_suelo = True
                en_salto = False
            elif jumpman.rect.bottomright[0] <= grupo_tramos_1.sprites()[0]. \
                                                rect.topleft[0] or           \
                jumpman.rect.bottomleft[0] >= grupo_tramos_1.sprites()[-1].  \
                                                rect.topright[0]:
                vel_x = 0
                jumpman.mover('ANDAR', sentido, 1, vel_x, 3)
                # Comprobar si colisiona con algún tramo del piso
                # inmediatamnete inferior al que está.
                tramos_colision = pygame.sprite.groupcollide(grupo_jumpman,
                                                             grupo_tramos_0,
                                                             False, False)
                if len(tramos_colision) > 0:
                    canal1.play(sonido_morir)
                    jumpman.morir()
                    jumpman_vivo = False
                elif jumpman.rect.bottom > 475:
                    canal1.play(sonido_morir)
                    grupo_jumpman.remove(jumpman)
                    jumpman_vivo = 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_0.update()
        grupo_tramos_1.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_0.draw(ventana)
        grupo_tramos_1.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...