En este capítulo veremos cómo leer el movimiento del mouse, lo que hace el jugador con él y qué botones toca. Utilizaremos el mouse para determinados tipos de juegos (puzzles por ejemplo), pero también lo utilizaremos para pulsar los botones de las pantallas que haremos para nuestros juegos (por ejemplo, la pantalla del menú principal).
Eventos del Mouse
¿Recuerdas cómo leemos el teclado, capturando los eventos del teclado en el loop principal?, ahora capturaremos los eventos relacionados al mouse de forma similar.
A forma de recordatorio, cada vez que ocurre un evento en el sistema operativo, el evento es enviado a la ventana del juego. Estos eventos ocurren cuando se pulsa una tecla, o se cierra la ventana, o como veremos a continuación, cuando se pulsa un botón del mouse, se mueve el mouse, etc.
Abre el proyecto ubicado en la carpeta capitulo_16\001_eventos_mouse. Este proyecto contiene solamente un archivo main.py, dado que es simplemente un programa para mostrar en la consola mensajes de texto sobre los eventos que capturamos en el loop principal, al igual que el que hicimos cuando vimos los eventos del teclado,.
Veamos el código de main.py para ver cómo capturamos los eventos en el loop principal:
while not salir:
clock.tick(30)
# Procesar los eventos que llegan a la aplicación.
for event in pygame.event.get():
# Evento cuando se pulsa el botón del mouse.
if event.type == pygame.MOUSEBUTTONDOWN:
print("Se pulsa el botón del mouse: ",
pygame.mouse.get_pos())
# Evento cuando se libera el botón del mouse.
if event.type == pygame.MOUSEBUTTONUP:
print("Se libera el botón del mouse: ",
pygame.mouse.get_pos())
# Evento cuando se mueve el mouse.
if event.type == pygame.MOUSEMOTION:
(mouseX, mouseY) = pygame.mouse.get_pos()
print("Se mueve el mouse: (%d, %d)" % (mouseX, mouseY))
...
# Saber en el momento que botones del mouse están pulsados.
# mouse_buttons = pygame.mouse.get_pressed()
# print(mouse_buttons)
Al igual que con los eventos del teclado, cuando el jugador hace algo con el mouse, se envía un evento a la ventana del juego. Cuando se pulsa el botón del mouse, event.type equivale a pygame.MOUSEBUTTONDOWN y cuando se libera el botón, event.type equivale a pygame.MOUSEBUTTONUP. Cuando se mueve el mouse, se recibe un evento donde event.type equivale a pygame.MOUSEMOTION.
Usando la función pygame.mouse.get_pos() obtenemos una tupla con la coordenada x e y del mouse. Para cargar las coordenadas del mouse en nuestras variables mouseX y mouseY, hacemos:
(mouseX, mouseY) = pygame.mouse.get_pos()
Esta línea carga la coordenada x del mouse en mouseX y la coordenada y del mouse en mouseY. Si estuviéramos haciendo un juego en donde usamos el mouse para seleccionar y mover objetos, necesitamos estas coordenadas para saber qué objeto está debajo del mouse.
Detectando los Botones del Mouse
Es posible detectar en cualquier momento (sin necesidad de capturar el evento) qué botones están siendo presionados o no en el mouse, usando la función pygame.mouse.get_pressed(). En el ejemplo anterior, se encuentran comentadas estas líneas. Elimina los comentarios de esas líneas y corre el ejemplo para ver en la consola estos valores.
# Saber en el momento que botones del mouse están pulsados.
# mouse_buttons = pygame.mouse.get_pressed()
# print mouse_buttons
Esta función retorna una tupla de tres valores, uno para cada botón del mouse. Mira la siguiente figura:
igura 16-1: La función pygame.mouse.get_pressed() para leer los botones.
El valor de cada elemento de la tupla es uno si el botón está presionado y cero si no lo está.
De la misma forma que para el teclado podemos utilizar pygame.key.get_pressed() para saber el estado de cada tecla, para el mouse usamos pygame.mouse.get_pressed().
Nota: Podemos usar estas funciones, que hacen innecesario capturar los eventos, pero esto es algo que nos brinda Pygame. Si queremos en un futuro portar nuestro juego a otro lenguaje como Java o C++, debemos saber y conocer cómo funciona el sistema de eventos, y en general el funcionamiento del sistema.
La Clase CMouse
Lo que haremos, al igual que como hicimos con la clase CKeyboard, haremos una clase CMouse que maneje el puntero del mouse y la información relacionada al mismo, y tendrá funciones como click(), que servirá para saber cuando se hace un click con el mouse (un click es pulsar el botón por primera vez, cuando no estaba pulsado en el frame anterior).
Nota: Siempre que tratamos un tema nuevo, como por ejemplo el mouse, tenemos funciones de Pygame que nos permiten hacer ciertas cosas, pero no todo. El caso de la función click() es un claro ejemplo. Pygame no nos da nada para saber cuando el usuario hace click (cuando se pulsa el botón por primera vez). Siempre debemos escribir una clase para abstraer y para extender la funcionalidad básica y que sea aplicable a nuestros juegos. A esto se le denomina implementar el motor de juegos (engine). Nosotros estamos poniendo las clases del motor en la carpeta api.
Veamos el ejemplo ubicado en la carpeta capitulo_16\002_clase_mouse_y_puntero. Por ahora veamos solamente la clase CMouse, que es similar a la clase que hicimos para el teclado:
# -*- coding: utf-8 -*-
#--------------------------------------------------------------------
# Clase CMouse.
# Clase para manejar el estado de los botones del mouse y la posición.
#
# Autor: Fernando Sansberro - Batovi Games Studio
# Proyecto: Hacete tu Videojuego.
# Licencia: Creative Commons. BY-NC-SA.
#--------------------------------------------------------------------
# Importar Pygame.
import pygame
class CMouse(object):
mInstance = None
mInitialized = False
mLeftPressed = False
mRightPressed = False
mCenterPressed = False
mLeftPressedPreviousFrame = False
def __new__(self, *args, **kargs):
if (CMouse.mInstance is None):
CMouse.mInstance = object.__new__(self, *args, **kargs)
self.init(CMouse.mInstance)
else:
print("Cuidado: Mouse(): No se debería instanciar más de una vez esta clase. Usar Mouse.inst().")
return CMouse.mInstance
@classmethod
def inst(cls):
if (not cls.mInstance):
return cls()
return cls.mInstance
def init(self):
if (CMouse.mInitialized):
return
CMouse.mInitialized = True
CMouse.mLeftPressed = False
CMouse.mRightPressed = False
CMouse.mCenterPressed = False
CMouse.mLeftPressedPreviousFrame = False
def update(self):
CMouse.mLeftPressedPreviousFrame = CMouse.mLeftPressed
CMouse.mLeftPressed = pygame.mouse.get_pressed()[0]
CMouse.mRightPressed = pygame.mouse.get_pressed()[2]
CMouse.mCenterPressed = pygame.mouse.get_pressed()[1]
def leftPressed(self):
return CMouse.mLeftPressed
def rightPressed(self):
return CMouse.mRightPressed
def centerPressed(self):
return CMouse.mCenterPressed
def click(self):
return CMouse.mLeftPressed == True and CMouse.mLeftPressedPreviousFrame == False
def getPos(self):
return pygame.mouse.get_pos()
def getX(self):
return pygame.mouse.get_pos()[0]
def getY(self):
return pygame.mouse.get_pos()[1]
def destroy(self):
CMouse.mInstance = None
Esta clase es parecida a la clase del teclado. Es una clase singleton porque la utilizaremos sin necesitar una instancia de esa clase. Recuerda que las clases singleton las podemos acceder desde cualquier parte del código sin necesitar crearla cada vez que la usemos (se crea una vez sola).
En la función update() se guardan en variables los estados de cada uno de los botones del mouse. Luego tenemos varias funciones que son sencillas, para obtener el estado de cada uno de los botones, la posición del mouse (getX() y getY()) y la más importante es la función click(), que retornará True solamente en el frame en el cual se presiona el botón del mouse y antes no estaba presionando, o sea, en el frame en el cual se pulsa el botón del mouse. Luego esta función no vuelve a retornar True hasta que se suelte el botón y se vuelva a presionar.
En la clase main.py, debemos importar la clase, y en update() invocar a la función update() de la clase Mouse. Esto lo hacemos una vez, y ya nos queda para todos los juegos, porque es parte del motor. Es necesario hacer esta llamada porque esta función actualiza la posición del mouse y el estado de sus botones en cada cuadro:
...
# Importar la clase del mouse.
from api.CMouse import *
...
# Correr la lógica del juego.
def update():
...
# Llamar a update() de CKeyboard.
CKeyboard.inst().update()
# Llamar a update() de CMouse.
CMouse.inst().update()
...
Con esto ya tenemos el mouse funcional. Ahora lo que nos queda es crear un sprite para el puntero del mouse, que es lo que haremos a continuación.
El Puntero del Mouse
Como seguramente has visto si has jugado juegos en PC, los juegos no utilizan el puntero del mouse del sistema (la flechita blanca), sino que implementan su propio puntero para que vaya con el estilo gráfico del juego, ya que influye en la inmersión. Vamos a colocar nuestro propio sprite como puntero del mouse.
Figura 16-2: El sprite del puntero del mouse.
La imagen del cursor la ponemos como siempre en la carpeta assets/images. Copia esta imagen (cursor.png) a tu propio proyecto. Vamos a crear una clase para manejar este sprite, que llamaremos CMousePointer.
Lo que hacemos para implementar el cursor propio para el mouse es crear un sprite como siempre (como es para el mouse, lo hacemos en el programa main.py) y en cada frame (en cada update()) lo ponemos en la posición donde se encuentra el mouse.
Veamos la clase CMousePointer que es un sprite como cualquier otro en el juego (hereda de CSprite):
# -*- coding: utf-8 -*-
#--------------------------------------------------------------------
# Clase MousePointer.
# Sprite del puntero del mouse.
#
# Autor: Fernando Sansberro - Batovi Games Studio
# Proyecto: Hacete tu Videojuego.
# Licencia: Creative Commons. BY-NC-SA.
#--------------------------------------------------------------------
# Importar Pygame.
import pygame
# Importar la clase CSprite.
from api.CSprite import *
# Importar la clasee CMouse.
from api.CMouse import *
# La clase hereda de CSprite.
class CMousePointer(CSprite):
def __init__(self):
CSprite.__init__(self)
img = pygame.image.load("assets/images/cursor.png")
img = img.convert_alpha()
self.setImage(img)
def update(self):
CSprite.update(self)
# Colocar el sprite en donde está el mouse.
self.setXY(CMouse.inst().getX(), CMouse.inst().getY())
def render(self, aScreen):
CSprite.render(self, aScreen)
def destroy(self):
CSprite.destroy(self)
Como vemos, la única particularidad de este sprite es que en la función update() se pone el sprite en la posición en donde está el cursor del sistema. El resto es igual a como se maneja cualquier otro sprite del juego.
En el programa main.py, es donde manejamos el cursor. Lo hemos hecho en el programa principal, porque el cursor lo necesitaremos en todas las pantallas del juego. Para tener un puntero, lo que hacemos es básicamente lo de siempre: crear el sprite en init(), actualizarlo en update(), dibujarlo en render() y eliminarlo en destroy(). Veamos los lugares en main.py donde se manjea el sprite del puntero del mouse:
...
# Importar la clase del puntero del mouse.
from game.CMousePointer import *
...
# Sprite del puntero del mouse.
mousePointer = None
# Función de inicialización.
def init():
global screen
global imgBackground
global imgSpace
global clock
global player1
global player2
global mousePointer
# Inicializar Pygame.
pygame.init()
...
# Inicializar los datos del juego.
CGameData.inst().setScore1(0)
CGameData.inst().setLives1(3)
CGameData.inst().setScore2(0)
CGameData.inst().setLives2(3)
# Ocultar el mouse del sistema.
pygame.mouse.set_visible(False)
# Crear el sprite puntero del mouse.
mousePointer = CMousePointer()
# Correr la lógica del juego.
def update():
global salir
global screen
global isFullscreen
# Timer que controla el frame rate.
clock.tick(60)
# Llamar a update() de CKeyboard.
CKeyboard.inst().update()
# Llamar a update() de CMouse.
CMouse.inst().update()
# Actualizar el sprite del puntero del mouse.
mousePointer.update()
...
# Dibujar el frame y actualizar la pantalla.
def render():
# Dibujar el fondo.
screen.blit(imgBackground, (0, 0))
...
# Dibujar el puntero del mouse.
mousePointer.render(screen)
# Actualizar la pantalla.
pygame.display.flip()
# Función de destrucción.
def destroy():
global player1
global player2
global mousePointer
# Destruir la nave de los jugadores.
player1.destroy()
player1 = None
player2.destroy()
player2 = None
# Destruir las balas.
CBulletManager.inst().destroy()
# Destruir los enemigos.
CEnemyManager.inst().destroy()
# Destruir el puntero del mouse.
mousePointer.destroy()
mousePointer = None
pygame.mouse.set_visible(True)
# Cerrar Pygame y liberar los recursos que pidió el programa.
pygame.quit()
...
Como vemos en el código, lo que hacemos es básicamente lo que hacemos con cualquier sprite. En la función render(), dibujamos el sprite del mouse a lo último, de modo que aparezca por encima de todos los gráficos del juego. En la función update() actualiza su posición según la posición del mouse.
Ocultar o Hacer Visible el Mouse del Sistema
Cuando mostramos un sprite propio como el puntero del mouse, debemos ocultar el puntero del mouse del sistema. De esta forma, el puntero del sistema (la flecha blanca) será invisible, y en su lugar se mostrará nuestro sprite.
Para ocultar el puntero del mouse usamos la siguiente sentencia:
pygame.mouse.set_visible(False)
Esto hace que en la posición del mouse no se muestre la flecha blanca, aunque el mouse sigue estando ahí (imagínate una flecha invisible). Como nuestro sprite lo colocamos en la posición del mouse en cada update(), solamente se muestra nuestro sprite para el puntero del mouse.
Para mostrar el puntero del sistema hacemos lo mismo pero le pasamos True como parámetro a la función:
pygame.mouse.set_visible(True)
Por último, en la función destroy(), eliminamos lo que hayamos creado (el sprite del puntero por ejemplo) y ponemos visible nuevamente el puntero del sistema.
Nota: Recuerda que en la función destroy() debemos siempre liberar toda la memoria creada, liberando las imágenes, destruyendo los objetos creados y generalmente liberando lo creado en la función init().
Con esto hemos terminado el manejo del mouse. Tenemos nuestro propio puntero funcionando y sabemos detectar clicks del mouse. En el capítulo siguiente implementaremos las diferentes pantallas del juego.
Comments