Razón Artificial

La ciencia y el arte de crear videojuegos

Pygame X: Fuentes tipográficas

Escrito por adrigm el 15 de febrero de 2010 en Desarrollo Videojuegos, Noticias, Programación | 37 Comentarios.

Continuamos con lo que dejamos a medias, en el tutorial anterior creamos un sistema de puntos, ahora vamos a ver como mostrarlo en pantalla mediante textos con Pygame.

Cargando tipografías

Pygame puede cargar las tipografías del sistema en el que se está ejecutando y si no la encuentra cargar la tipografía por defecto, yo prefiero proporcionar incluir mi propia fuente en el juego y así me aseguro de que todo se ve como quiero.

Para este juego usaré la tipografía DroidSans.ttf descargarla y meterla en la carpeta images. No es una imagen propiamente dicha, pero para una sola fuente no voy a crear una carpeta llamada fonts.

Hay que tener en cuenta cuando se trabaja con textos en Pygame es que este a última instancia trata a todos los textos como Sprites, una vez puesto se pueden manipular como si de una Sprite más se tratara, esto tiene muchas ventajas como veremos.

Lo primero que vamos a hacer es crear una función que nos facilite el trabajar con textos y nos automatice el proceso.

def texto(texto, posx, posy, color=(255, 255, 255)):
	fuente = pygame.font.Font("images/DroidSans.ttf", 25)
	salida = pygame.font.Font.render(fuente, texto, 1, color)
	salida_rect = salida.get_rect()
	salida_rect.centerx = posx
	salida_rect.centery = posy
	return salida, salida_rect

En la línea 1 creamos la función, vemos que recibe 4 parámetros: texto que debe ser una string, posx y posy que serán las coordenadas del centro de nuestro texto y color que es una tupla con los valores RGB, color es opcional y si no se define, por defecto será blanco.

La línea 2 asigna una tipografía a la variable fuente como vemos lo hacemos con pygame.font.Font() al que le pasamos como primer parámetro la ruta de la tipografía que queremos usar y como segundo el tamaño de la tipografía.

En la linea 3 creamos la variable salida asignandole pygame.font.Font.render() este método lo que hace es convertir un texto en un Sprite, como vemos como primer parámetro recibe una fuente tipográfica, le pasamos la creada en la línea anterior, como segundo recibe el texto a mostrar, el tercer parámetro es el antialias y puede ser verdadero o falso (con o sin antialias), por último recibe el color.

En la línea 4 obtenemos el rect como si de un Sprite más se tratare y lo almacenamos en salida_rect.

Las líneas 5 y 6 modifican el centro del Sprite en función de los valores posx y posy.

Por último la línea 7 retorna una tupla con el Sprite y su correspondiente rect. Ojo a la ahora de utlizarla, recordad que retorna dos valores y no uno.

Con esta función poner texto es muy fácil, vamos a utilizarla para mostrar nuestras puntuaciones, añadimos las siguientes líneas en el bucle del juego:

p_jug, p_jug_rect = texto(str(puntos[0]), WIDTH/4, 40)
p_cpu, p_cpu_rect = texto(str(puntos[1]), WIDTH-WIDTH/4, 40)

Esto nos crea dos Sprites con sus respectivos rects. El primero recibe como texto puntos[0], es decir los puntos del jugador (ojo con la conversión str(), recodad que debe de ser una string), como parámetro posx recibe WIDTH/4 es decir la WIDTH/2/2 que es la mitad de la mitad de la pantalla (para centrarlo en el campo del jugador) y como parámetro posy he situado a 40px del norde superior.

El otro Sprite exactamente lo mismo, pero para los puntos de la CPU y centrado en el campo de la CPU el posx (WIDTH-WIDTH/4).

por último solo debemos actualizarlo en pantalla como hacemos siempre.

screen.blit(p_jug, p_jug_rect)
screen.blit(p_cpu, p_cpu_rect)

Yo recomiendo ponerlas justo después del blit del fondo, porque si los ponemos al final la pelota pasara por “debajo” de los marcadores y eso queda algo feo.

El juego nos queda de la siguiente manera:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Módulos
import sys, pygame
from pygame.locals import *

# Constantes
WIDTH = 640
HEIGHT = 480

# Clases
# ---------------------------------------------------------------------

class Bola(pygame.sprite.Sprite):
	def __init__(self):
		pygame.sprite.Sprite.__init__(self)
		self.image = load_image("images/ball.png", True)
		self.rect = self.image.get_rect()
		self.rect.centerx = WIDTH / 2
		self.rect.centery = HEIGHT / 2
		self.speed = [0.5, -0.5]

	def actualizar(self, time, pala_jug, pala_cpu, puntos):
		self.rect.centerx += self.speed[0] * time
		self.rect.centery += self.speed[1] * time

		if self.rect.left <= 0:
			puntos[1] += 1
		if self.rect.right >= WIDTH:
			puntos[0] += 1

		if self.rect.left <= 0 or self.rect.right >= WIDTH:
			self.speed[0] = -self.speed[0]
			self.rect.centerx += self.speed[0] * time
		if self.rect.top <= 0 or self.rect.bottom >= HEIGHT:
			self.speed[1] = -self.speed[1]
			self.rect.centery += self.speed[1] * time

		if pygame.sprite.collide_rect(self, pala_jug):
			self.speed[0] = -self.speed[0]
			self.rect.centerx += self.speed[0] * time

		if pygame.sprite.collide_rect(self, pala_cpu):
			self.speed[0] = -self.speed[0]
			self.rect.centerx += self.speed[0] * time

		return puntos

class Pala(pygame.sprite.Sprite):
	def __init__(self, x):
		pygame.sprite.Sprite.__init__(self)
		self.image = load_image("images/pala.png")
		self.rect = self.image.get_rect()
		self.rect.centerx = x
		self.rect.centery = HEIGHT / 2
		self.speed = 0.5

	def mover(self, time, keys):
		if self.rect.top >= 0:
			if keys[K_UP]:
				self.rect.centery -= self.speed * time
		if self.rect.bottom <= HEIGHT:
			if keys[K_DOWN]:
				self.rect.centery += self.speed * time

	def ia(self, time, ball):
		if ball.speed[0] >= 0 and ball.rect.centerx >= WIDTH/2:
			if self.rect.centery < ball.rect.centery:
				self.rect.centery += self.speed * time
			if self.rect.centery > ball.rect.centery:
				self.rect.centery -= self.speed * time

# ---------------------------------------------------------------------

# Funciones
# ---------------------------------------------------------------------

def load_image(filename, transparent=False):
        try: image = pygame.image.load(filename)
        except pygame.error, message:
                raise SystemExit, message
        image = image.convert()
        if transparent:
                color = image.get_at((0,0))
                image.set_colorkey(color, RLEACCEL)
        return image

def texto(texto, posx, posy, color=(255, 255, 255)):
	fuente = pygame.font.Font("images/DroidSans.ttf", 25)
	salida = pygame.font.Font.render(fuente, texto, 1, color)
	salida_rect = salida.get_rect()
	salida_rect.centerx = posx
	salida_rect.centery = posy
	return salida, salida_rect

# ---------------------------------------------------------------------

def main():
	screen = pygame.display.set_mode((WIDTH, HEIGHT))
	pygame.display.set_caption("Pruebas Pygame")

	background_image = load_image('images/fondo_pong.png')
	bola = Bola()
	pala_jug = Pala(30)
	pala_cpu = Pala(WIDTH - 30)

	clock = pygame.time.Clock()

	puntos = [0, 0]

	while True:
		time = clock.tick(60)
		keys = pygame.key.get_pressed()
		for eventos in pygame.event.get():
			if eventos.type == QUIT:
				sys.exit(0)

		puntos = bola.actualizar(time, pala_jug, pala_cpu, puntos)
		pala_jug.mover(time, keys)
		pala_cpu.ia(time, bola)

		p_jug, p_jug_rect = texto(str(puntos[0]), WIDTH/4, 40)
		p_cpu, p_cpu_rect = texto(str(puntos[1]), WIDTH-WIDTH/4, 40)

		screen.blit(background_image, (0, 0))
		screen.blit(p_jug, p_jug_rect)
		screen.blit(p_cpu, p_cpu_rect)
		screen.blit(bola.image, bola.rect)
		screen.blit(pala_jug.image, pala_jug.rect)
		screen.blit(pala_cpu.image, pala_cpu.rect)
		pygame.display.flip()
	return 0

if __name__ == '__main__':
	pygame.init()
	main()

Ya tenemos nuestros marcadores, pero el juego ni se inmuta cuando marcas un punto, lo solucionaremos en próximas entregas.

37 Comentarios en "Pygame X: Fuentes tipográficas"

  1. ECAMA dice:

    Muy buen tutorial, soy programador pero nunca he tenido la oportunidad de trabajar con PYTHON, la verdad que este tutorial me anima a incursionar en este mundo aunque sea por pasatiempo ya que mi trabajo demanda otros lenguajes de programación. Gracias por el aporte.

  2. Morton dice:

    Excelente tutorial! Lo vengo siguiendo, ya tengo mi pong! Me parece, si no hice nada mal, que la IA quedó, en efecto, invencible. Espero la nueva entrega!

  3. admin dice:

    Morton,si la IA es casi invencible, pero puedes arreglarlo haciendo que la palas vayan mas lentas que la pelota, así no siempre llegará. Prueba a bajar el self.speed = 0.5 a 0.4 y verás.

  4. Morton dice:

    Genial, gracias, funcionó!

    Otro problema es que cuando la pelotita queda atrapada entre la pared y la pala, rebota y le suma muchos puntos de golpe al adversario, supongo que podría solucionarse con ponerle un tiempo mínimo de espera al sumado de puntos y que no sea inmediato…

  5. admin dice:

    Morton, si eso ya lo había detectado, el motivo de no corregirlo es que se supone que cuando se anota un punto la pelota vuelve al centro y se “saca”.

    La idea era que al anotar un punto la pelota vuelve automáticamente al centro y no hay posibilidad de que rebote otra vez, pero todavía no esta implementado en el juego, en próximos tutoriales lo haré.

  6. Morton dice:

    Que grande, espero ese post con ansias!

    Mientras tanto hice unas pruebitas y logré que la pelota vuelva al centro y salga para el lado del que sumó el punto, pero igual es muy repentino, lo tengo que revisar…

    Gracias por todo! ;)

  7. varetti dice:

    Muy bueno el tutorial. Yo tb logré hacer que la pelota vuelva al centro. Basta con llamar a self.__init__() al comprobar que toca una de las paredes.

    A modo de comentario, decir que sobran las líneas 33, 34 y 35 ya que la comprobación se hace en las líneas anteriores, donde se aumenta la puntuación.

    también me gustaría hacer una pregunta… ¿alguien sabe como crear un ejecutable del juego en mac?

  8. varetti dice:

    me respondo a mi mismo: py2app

    es algo lioso pero cuando le pillas el truco va bastante bien

    :)

  9. admin dice:

    varetti, no creo que usar esa línea sea lo mejor. Lo que eso hace es llamar al método constructor de clase y “reiniciar” la pelota.

    Es mejor que cuando detecte la pared añadas esto:

    self.rect = (WIDH/2, HEIGHT/2)

    esto hace que el rect de la bola tenga las coordenadas del centro de la ventana.

    Hago la comprobación dos veces porque ahorra código porque la línea 33 cambia la dirección de la pelota independientemente de que sea la pared izquierda o derecha, sin embargo, para los puntos necesitamos controlar en que pared da para asignárselos a uno u otro.

  10. Máximo dice:

    Para solucionar el problema de la pared edité la clase Bola de esta manera:

    class Bola(pygame.sprite.Sprite):
    def __init__(self):
    pygame.sprite.Sprite.__init__(self)
    self.image = load_image(“images/ball.png”, True)
    self.rect = self.image.get_rect()
    self.rect.centerx = WIDTH / 2
    self.rect.centery = HEIGHT / 2
    self.direccion = self.aleatorio(1, 1, 4)
    if self.direccion == 1:
    self.speed = [0.3, -0.1]
    elif self.direccion == 2:
    self.speed = [-0.3, 0.1]
    elif self.direccion == 3:
    self.speed = [-0.3, -0.1]
    elif self.direccion == 4:
    self.speed = [0.3, 0.1]

    def aleatorio(self, rango, a, b):
    for i in range(rango):
    return random.randint(a, b)

    def actualizar(self, tiempo, pala_jug, pala_cpu, puntos):
    self.rect.centerx += self.speed[0] * tiempo
    self.rect.centery += self.speed[1] * tiempo

    if self.rect.left = WIDTH:
    puntos[0] += 1
    self.__init__()

    if self.rect.left = WIDTH:
    self.speed[0] = -self.speed[0]
    self.rect.centerx += self.speed[0] * tiempo
    if self.rect.top = HEIGHT:
    self.speed[1] = -self.speed[1]
    self.rect.centery += self.speed[1] * tiempo

    if pygame.sprite.collide_rect(self, pala_jug):
    self.speed[0] = -self.speed[0]
    self.rect.centerx += self.speed[0] * tiempo

    if pygame.sprite.collide_rect(self, pala_cpu):
    self.speed[0] = -self.speed[0]
    self.rect.centerx += self.speed[0] * tiempo

    return puntos

  11. Jaboto dice:

    Hola,

    solo quería felicitarte por el tutorial, he llegado a él a través de la página de pygame.org y me ha parecido estupendo.

    ¡Gracias!

  12. Dokan dice:

    Enhorabuena por el tutorial. Me gusta con qué sencillez explicas las cosas, aunque deberías prestar atención a las faltas de ortografía y otros detalles. Eso no desmerece el trabajo que has hecho.

  13. Muchisimas gracias por el tutorial, ahora creoq ue puedo (empesar) con el engine del juego, y, si no, con lo que he visto creoq ue podre entender mas el codigo de algunos ejemplos de pygame.

    Saludos (PD, cuando tenga algo lo publico en mi Web, saludos)

  14. Andrés dice:

    Tengo una solución al problema de la pared con menos líneas de código:

    class Bola(pygame.sprite.Sprite):
    	def __init__(self):
    		pygame.sprite.Sprite.__init__(self)
    		self.image = load_image("images/ball.png", True)
    		self.rect = self.image.get_rect()
    		self.rect.centerx = WIDTH / 2
    		self.rect.centery = HEIGHT / 2
    		self.speed = [0.2, -0.2]
    	def actualizar(self, time, colisiones, puntos, jugador):
    		self.rect.centerx += self.speed[0] * time
    		self.rect.centery += self.speed[1] * time
    
    		#chocar con la pala
    		for elemento in colisiones:
    			if pygame.sprite.collide_rect(self, elemento):
    				self.speed[0] = -self.speed[0]
    				self.rect.centerx += self.speed[0] * time
    				jugador = colisiones.index(elemento)
    
    		#Sumar Punto
    		if self.rect.left <= 0 and jugador == 1:
    			puntos[1] += 1
    		if self.rect.right >= WIDTH and jugador == 0:
    			puntos[0] += 1
    
    		#chocar con la pared
    		if self.rect.left <= 0 or self.rect.right >= WIDTH:
    			self.speed[0] = -self.speed[0]
    			self.rect.centerx += self.speed[0] * time
    		if self.rect.top <= 0 or self.rect.bottom >= HEIGHT:
    			self.speed[1] = -self.speed[1]
    			self.rect.centery += self.speed[1] * time
    
    
    		return puntos, jugador

    lo único que hay que comprobar quien fue el último que le pegó a la pelota y si fue el contrario sumamos el punto, si no, nones.

    Dejo el script completo por pastebin:
    http://pastebin.com/4acUCu2F

    Felicidades al autor por el tutorial

  15. federico dice:

    me gustaria saber porq a mi el texto se me sobrepone y no se actualiza, me quedan los numeros arriba del otro

  16. adrigm dice:

    federico, debes dibujarlos después del fondo para que en cada paso del bucle el fondo se pinte encima.

  17. Carlos dice:

    Hola muchas gracias por el gran aporte que haces, bueno yo recién estoy empezando con pygame y estuve copiando el anterior código de puntuación y excelente, pero ahora me sale un error con lo de texto de esta linea:

    018 self.image = load_image("images/ball.png", True)

    y me dice:
    self.image = load_image("images/ball.png", True)
    ^
    SyntaxError: invalid syntax>>> Exit Code: 1

    Agradezco tu anticipada respuesta.

  18. Hola mirando el codigo del me di cuenta de que el problema q tenes es q no esta definida la funcion load_imagen(), esa funcion no pertenece a la libreria de pygame (pygame carga imagenes con pygame.image.load()), solo es una funcion para mejorar la carga de imagenes, creo que es para cargar formato png y convertirlos (esa es la opcion del segundo parametro) para asi renderizarla mas rapido.. creo el codigo de dicha funcion se encuentra mas atras..

    saludos

  19. Kurai dice:

    Solucion sencilla al problema de la pared:

    def update(self, t, pala_jug, pala_cpu, points):
            grup = pygame.sprite.Group(pala_jug, pala_cpu)
            point_jug = self.rect.right >= WIDTH
            point_cpu = self.rect.left <= 0
            
            if point_jug or point_cpu:   
                self.wait = time.time()
                self.speed = [0, 0]
                self.rect.centerx = WIDTH / 2
                self.rect.centery = HEIGHT / 2
                
                if point_jug:
                    points[0] += 1
                else:
                    points[1] += 1
            
            if self.wait != 0 and time.time() - self.wait  >= 1:
                self.speed = [0.5, -0.5]
                self.wait = 0
            
            if self.rect.left <= 0 or self.rect.right >= WIDTH:
                self.speed[0] = -self.speed[0]
                
            if self.rect.top <= 0 or self.rect.bottom >= HEIGHT:
                self.speed[1] = -self.speed[1]
    
            if pygame.sprite.spritecollide(self, grup, False):
                self.speed[0] = -self.speed[0]
                
            self.rect.centerx += self.speed[0] * t
            self.rect.centery += self.speed[1] * t
                
            return points
    

    en la linea 7 se obtiene la hora actual en segundos y se almacena en el atributo wait (introducido por mi), en la linea 8 la velocidad tanto vertical como horizontal de la bola se fijan a 0, y se posiciona la misma en el centro en las lineas 9 y 10. En la linea 17 se verifica que wait sea distinto de 0 (quiere decir que la bola esta quieta) y que al menos a pasado un segundo desde que fue anotado un punto y fue centrada la bola, en la linea 18 se fijan la velocidades originales de la bola y en la linea 19 wait se fija a 0

  20. Raziel dice:

    Buenas camaradas, les tengo algo nuevo sobre la variable puntos, aunque creo que AdriGM ya lo sabe.
    En python, los diccionarios, listas y/o tuplas, son pasadas por referencia, es decir, que hacer:

    puntos = bola.actualizar(time, pala_jug, pala_cpu, puntos)
    

    Es lo mismo que hacer:

    bola.actualizar(time, pala_jug, pala_cpu, puntos)
    

    Para los que ya implementaron la función de texto, se darán cuenta que es así, y me dí cuenta mientras trataba de comparar los puntos actuales con respecto a los devueltos por la función actualizar de la clase bola. Luego trataré de decirles para qué. Espero sea pronto. Saludos.

  21. Raziel dice:

    Comentario sobre lo anterior:
    En lenguajes de programación como C++, no sucede esto implícitamente aunque se trate con cadenas de caracteres, ya que se debe indicar al compilador que el parámetro se está pasando por referencia, ya sea creando una función que reciba punteros o indicandole la variable con un “&” (ampersand) antes de la variable. Bueno, eso será tema para otro blog, pero la idea principal es esa, y por tal motivo me confundí al principio ya que no sabía que python pasaba ese tipo de variables por referencia (Listas, tuplas y diccionarios), probaré con las otras.

  22. Diego dice:

    Excelente tutorial. Por curiosidad estuve viendo un poco sobre pygame. Me sorprende gratamente lo sencillo y entendible que es.
    El tutorial (los 10 tutoriales) están muy buenos.
    Felicitaciones por el trabajo!!

  23. Diego dice:

    Agrego: Hace 4 o 5 meses que estoy con Python y me sorprende cada día que pasa. Se puede decir que es hasta divertido programar en Python. Hace mucho tiempo no sentía tanto placer con un lenguaje.

  24. […] tipográficas. Actualizada: Febrero 15, 2010. Fecha de consulta: Enero 26, 2012. Disponible en: http://razonartificial.com/2010/02/pygame-10-fuentes-tipograficas/ Se Social, ¡Comparte! Entradas […]

  25. javifree dice:

    GRACIAS ADRIAN POR TODO TU TRABAJO
    es el mejor tutorial que he visto
    no soy porgramador, ni experto, en el tutorial fuentes tipográficas tengo este error, cargue DroidSans.ttf
    File “pong10.py”, line 90
    fuente = pygame.font.Font("images/DroidSans.ttf", 25)
    ^
    SyntaxError: invalid syntax

  26. javifree dice:

    QUE NADIE RESPONDA EL ANTERIOR ESTE ES MI NUEVO OBJETIVO GRACIAS A TODOS
    File “pong.py”, line 124
    p_cpu, p_cpu_rect = texto(str(puntos
    ^
    [1],WIDTH -WIDTH/4,40)

  27. javier dice:

    hola a todos. ¿como debo hacer si quiero pasar mi juego a un amigo en un ejecutable? (normalmente seria en windows)
    gracias

  28. maije dice:

    Muy buenos tutoriales. Lo único que echo en falta para dar un vistazo general a lo básico de PyGame es el acceso a persistencia. ¿Cómo cargar, leer y guardar un archivo de texto? (por ejemplo…). Es útil para la gente que desee hacer juegos de tipo tileable y poder cargar y guardar sus mapas de forma automática. Para mi gusto sería el tutorial Pygame XI: Guardando y cargando nuestra partida. Podrías abrir un archivo de texto, almacenar la puntuación, guardarlo y cerrarlo y luego crear un método para comenzar la partida desde la partida guardada con las puntuaciones almacenadas :-)

    Si alguien se quisiera animar también podría abrir textualmente archivos XML y con una función tokenizer y un árbol parsear el XML, que sería un tutorial un poco más avanzado, pero muy útil también.

    Es sólo una sugerencia. Ánimo y buen trabajo.

  29. Juan dice:

    Hola! Quería agradecerte por el tutorial, esta excelente!

  30. Gabriel Tovar dice:

    Saludos! Felicidades por el tutorial, al día de hoy lo realicé para practicar la elaboración de juegos simples en Python. Espero las entregas referentes a sonido y otros. Exitos.

  31. […] Pygame X: Fuentes tipográficas […]

  32. Aldo Vega dice:

    Hola! Es un muy buen tutorial, excelentemente explicado, felicidades! Pero quería saber si me podían solucionar un problema que me arrojó al agregar los textos del marcador, dice que la variable “fuente” no ha sido iniciada. Gracias!

  33. Pablo dice:

    l.split()

  34. Pablo dice:

    import networkx as nx
    def fills_comuns(g, a, b):
    x=set( g.successors(a))
    y=set( g.successors(b))
    c= x&y
    return c

  35. Jaime dice:

    gran tutorial

  36. Hola quería saber cuando se publicará la sgte parte del tutorial?? Gracias

Deja un comentario