Curso = Metodos Tecnicas y Modelos de Enseñanza.pdf
Programación con Pygame IX
1. PROGRAMA: DRIVE
CURSO: 1º BACHILLERATO
Cubo3D: Pyggel y OpenGL
(Basado en los tutoriales NeHe, http://nehe.gamedev.net )
Introducción
Cubo3D no es un juego, es un pequeño programa escrito para mostrar cómo se puede
trabajar en Python con las 3 dimensiones. El guión está basado en uno de los famosos
tutoriales NeHe, algunos de los cuales fueron convertidos a Python y PyGame por Paul
Furber
http://www.pygame.org/gamelets/games/nehe1-10.zip
Pero ésta no va a ser nuestra aproximación (aunque incluimos al final el código
correspondiente por completitud). Estos tutoriales utilizan una librería 3D genérica muy
potente llamada OpenGL
http://www.opengl.org
sin lugar a dudas, la más extendida y con la que se programan multitud de videojuegos
profesionales. Su implementación en Python se conoce como PyOpenGL
PÁGINA 1 DE 13
CC: FERNANDO SALAMERO
2. PROGRAMA: DRIVE
CURSO: 1º BACHILLERATO
http://pyopengl.sourceforge.net
En cualquier caso, al ser una librería avanzada, su manejo es muy complejo (sólo tienes
que mirar el código al que nos hemos referido antes). Existe una librería conocida como
Pyggel
http://code.google.com/p/pyggel/
que hace de intermediario de PyOpenGL; incorpora clases, constantes y funciones que
facilitan mucho el aprendizaje y uso de las tres dimensiones en nuestros programas,
encargándose ella de gestionar de forma transparente las rutinas correspondientes de
OpenGL. Además, la librería Pyggel se encarga de inicializar a su vez a PyGame. ¡Dos en
uno! Si esto fuera poco, no necesitamos instalar nada más (bueno, en realidad sí que hay
que tener instalado en el sistema a PyOpenGL y OpenGL); basta con que a nuestro
proyecto le incorporemos la carpeta de la librería y a funcionar.
En resumen, con este documento incluimos dos programas distintos:
1. Cubo3D_Pyggel.py es nuestro tutorial y el que se va a describir en las próximas
líneas. Utiliza el módulo Pyggel.
2. Cubo3D_PyOpenGL.py es el tutorial original de Paul Furber, conversión a
Python del tutorial NeHe correspondiente. Trabaja directamente con PyOpenGL.
Vamos allá.
Importación e Inicialización
Después de la típica línea de declaración de codificación y del nombre del programa, nos
encontramos con la importación del módulo pyggel. Observa la curiosa forma. Por una
parte se importa tal cual
import pyggel, sys
y, por otra parte, se importa de nuevo en la forma
from pyggel import *
El resultado es que podemos llamar a cualquier objeto refiriéndonos a él directamente o
con el prefijo pyggel. Es una cuestión de comodidad, aunque en general no es muy
recomendable. En pyggel es importante que lo hagamos así pues, como ya hemos
comentado previamente, esté módulo carga e inicializa automáticamente a su vez a
PyGame y, de esta manera, podemos también acceder a sus objetos directamente.
Lo siguiente es inicializar el entorno (recuerda que al inicializar Pyggel se inicializa
también PyGame). Fíjate que no escribimos simplemente init(). Pyggel admite en dicha
función varios parámetros que nos permiten ajustar las características de la ventana de
visualización. De paso se usa una característica de Python muy útil; no hace falta
recordar el orden en el que hay que escribir los parámetros, podemos
referirnos a ellos por su nombre. Así
pyggel.init(screen_size=(800,600))
tiene el efecto de invocar a la función init() del módulo pyggel dando el valor (800,600)
al argumento llamado screen_size en su definición. Como adivinarás, eso hace que la
ventana en la que veremos nuestra animación 3D tenga un tamaño de 800x600 pixeles.
PÁGINA 2 DE 13
CC: FERNANDO SALAMERO
3. PROGRAMA: DRIVE
CURSO: 1º BACHILLERATO
Composición de la ESCENA
Hay una diferencia fundamental entre trabajar con dos dimensiones (como hasta ahora,
con el estándar de PyGame) y trabajar con tres. Cuando dibujamos en 2D dibujamos
exactamente lo que queremos, ya que la superficie sobre la que se dibuja (la pantalla del
ordenador) también es bidimensional. Sin embargo, cuando queremos dibujar en 3D,
debemos hacer proyección de los objetos, realizar una perspectiva. Y en tal caso,
influyen muchos más factores; para empezar el lugar desde donde se mira (no es lo mismo
mirar un cubo desde delante que desde un lado, o desde muy lejos). Dibujar en 3D en
hacer un salto a una animación más realista en la que puedes tener en cuenta detalles
como iluminación, atmósfera... Debes ponerte en el papel de un director de cine o de un
fotógrafo:
1. Para empezar, todo lo que quieres incluir forma lo que se denomina una escena.
2. Tienes que determinar desde dónde vas a mirar, es decir, la posición de la cámara.
3. Y necesitas luces que iluminen los objetos, por supuesto.
Todo ello vas a tener que implementarlo. Veámoslo paso a paso.
La primera línea de código que encontramos es
escena = pyggel.scene.Scene()
es precisamente la creación de la escena. Observa que es un objeto de tipo Scene
(definido en el módulo scene, es decir, en el archivo scene.py de la carpeta pyggel). De
la misma manera que un Grupo de Pygame contenía una serie de Sprites, un objeto de
tipo Scene contiene todos los objetos que forman la escena y, por lo tanto, a medida que
los vayamos creando deberemos añadírselos.
En nuestro mundo tridimensional sólo vamos a tener un cubo, pero para que no sea
aburrido vamos a añadirle una textura. Las texturas son imágenes que se colocan en las
superficies de los objetos y simulan el tipo de material o la decoración. Si te has fijado en la
captura de pantalla al inicio de este documento, la que vamos a emplear es una imagen de
tipo vidriera (glass.bmp); pero si quisiéramos que nuestro cubo fuera de madera, por
ejemplo, podríamos tomar una imagen con listones o similar. Las texturas son objetos de
tipo Texture y se crean de la siguiente forma
textura = pyggel.data.Texture("glass.bmp")
Una vez cargada en memoria, usarla para crear un cubo es también sencillo:
cubo = pyggel.geometry.Cube(1, pos=(0, 0, 0), texture=textura)
En la línea anterior hemos creado un objeto de tipo Cube (como puedes adivinar, los
diferentes tipos de objetos tridimensionales nativos están definidos en el archivo
geometry.py de pyggel; analiza su código si tienes curiosidad). El parámetro pos indica
claramente en qué posición pondremos al cubo; se necesitan tres coordenadas pues
estamos en un mundo tridimensional (ya se encargará Pyggel, o más correctamente
OpenGL, de proyectar la escena en la pantalla). En nuestro mundo tridimensional, el cubo
está situado en el origen de coordenadas. Y el valor 1 del primer argumento indica su
tamaño; nuestro cubo tiene una arista de 1 unidad.
PÁGINA 3 DE 13
CC: FERNANDO SALAMERO
4. PROGRAMA: DRIVE
CURSO: 1º BACHILLERATO
Con los objetos tridimensionales, no sólo podemos situarlos en algún lugar, también
podemos orientarlos. ¿Qué tal se te da la geometría...? Para orientar un objeto podemos
girarlo adecuadamente, pero este giro podemos hacerlo con respecto a cualquiera de los
tres ejes X, Y y Z. Así que, en general, un giro puede ser la composición de tres giros,
uno en torno a cada eje coordenado. El que encontramos en el código,
cubo.rotation = (0,45,10)
el cubo se rota 45 grados con respecto al eje Y y 10 grados con respecto al eje Z.
Finalmente, hemos de incluir el cubo creado en la escena para que luego se visualice.
Ello se consigue con la línea
escena.add_3d(cubo)
Nuestra escena ya tiene un objeto tridimensional.
Añadiendo LUCES
Como hemos indicado, para poder la escena correctamente necesitamos iluminarla.
Como en la realidad, hay muchos tipos de luces; direccionales (como un foco), de
ambiente (repartida por todas partes de forma homogénea), puntuales (como una
bombilla)... La forma de crear luces desde una librería de programación, por lo tanto, ha de
ser muy flexible. Veamos cómo lo hemos implementado:
luz = pyggel.light.Light((0,0,2),
(0.5,0.5,0.5,1),
(1,1,1,1),
(50,50,50,10),
(0,0,0),
True)
Bien. Como viene siendo habitual, el sistema se basa en la creación de un objeto, en este
caso de tipo Light (definido en el módulo de pyggel light). Los diferentes parámetros
indican las propiedades que va a tener la iluminación:
1. (0,0,2)
Se corresponde con la posición, en el espacio, de la fuente que emite la luz.
2. (0.5,0.5,0.5,1)
Es el color de la luz ambiente; una luz que llena todo el espacio por igual (y que por
lo tanto no produciría sombras). Está en formato RGBA, es decir, valores entre 0 y 1
que indicam la cantidad de rojo, verde, azul y canal alfa (la opacidad; 1 indica que es
sólida; 0 completamente transparente).
3. (1,1,1,1)
Color de la luz difusa; tipo de luz suave que viene desde una dirección dada y que
produciría sombras (algo así como un Sol velado). También en formato RGBA
4. (50,50,50,10)
Luz especular; similar a la anterior pero más brillante, como un Sol intenso (que
puede producir brillos sobre las superficies que ilumina directamente).
PÁGINA 4 DE 13
CC: FERNANDO SALAMERO
5. PROGRAMA: DRIVE
CURSO: 1º BACHILLERATO
5. (0,0,0)
Dirección de la luz en el caso de que ésta sea direccional; con ello controlamos en
qué dirección aparecerán las combras.
6. True
Indica si la luz es direccional en cuyo caso se indica True, o la emite un punto de
luz concreto en todas direcciones, indicado con False.
Juega con las diferentes opciones y prueba todo lo que quieras; sólo así podrás
comprenderlo mejor.
Una vez creada la luz, hemos de añadirla a la escena para que el motor 3D de pyggel pueda
luego generarla correctamente:
escena.add_light(luz)
¡Conseguido!
Posicionando la CÁMARA
Ahora hay que decidir desde dónde hay que mirarlo, es decir, hay que situar la cámara.
Con Pyggel es muy sencillo...
camara = pyggel.camera.LookAtCamera((0,0,0), distance=3)
Fíjate en el código. Para crear una cámara se crea un objeto de la clase LookAtCamera,
definida en el módulo camera (si miras su código verás que hay otras formas de crear
cámaras). Los parámetros que hemos incluido son muy intuitivos; el primero es una tupla
que indica en qué dirección miramos (en este caso, miramos hacia el punto (0,0,0),
el lugar en el que hemos puesto el cubo) y el parámetro distance indica desde qué
distancia se hace (valores negativos indicarían algo así como ‘desde el otro lado’;
experimenta, como siempre, para ver la diferencia).
Bucle Principal
Justo antes de entrar en el bucle de la animación nos encontramos con el habitual
reloj = pygame.time.Clock()
que controlará posteriormente la velocidad de los fotogramas con reloj.tick(60).
La gestión de eventos puede hacerse de la forma habitual en PyGame, pero ya que estamos
usando en primer plano la librería Pyggel, podemos hacerlo con ella:
eventos = pyggel.event.Handler()
Con la línea anterior hemos creado un objeto de tipo Handler y lo hemos almacenado en
la variable eventos. Un objeto de este tipo centraliza la detección y uso de los eventos que
se producen en el programa. En seguida veremos cómo.
Una vez dentro del bucle, la línea
pyggel.view.set_title("FPS: %s"%int(reloj.get_fps()))
PÁGINA 5 DE 13
CC: FERNANDO SALAMERO
6. PROGRAMA: DRIVE
CURSO: 1º BACHILLERATO
tiene una misión muy interesante; mostrar en la barra de título de la ventana la velocidad
real conseguida en fotogramas por segundo; es un buen método para, en tiempo de
desarrollo, comprobar si conseguimos la velocidad que queremos (si el ordenador no es lo
suficientemente rápido, o le pedimos demasiadas tareas a la vez, es posible que no alcance
la que hemos indicado, en nuestro caso los 60 fps).
¿Cómo lo hace? La función pyggel.view.set_title() toma como argumento una cadena
de texto y la coloca como título de la ventana. Recuerda, además, que en “FPS: %s” el %s
indica que vamos a pasarle un entero a la cadena de texto en cuestión. Y ese entero lo
conseguimos usando el método get_fps() del objeto reloj, método que nos devuelve el
valor real de la velocidad de la animación (con decimales, de ahí el int para convertirlo).
Lo siguiente es actualiza la lista de eventos que se puedan haber producido con
eventos.update()
y, por consiguiente, hemos de mirar cuáles tenemos y actuar en consecuencia. La forma de
hacerlo es muy similar a la de PyGame, pero con sus características propias. Para empezar,
los eventos que se producen con el teclado se corresponden con el objeto keyboard y los
del ratón con el mouse. En este programa sólo vamos a responder a las pulsaciones de
ciertas teclas, así que estamos en el primer caso. Para saber si se acaba de pulsar una tecla,
hay que mirar si está en keyboard.hit mientras que la lista de teclas que están todavía
pulsadas se encuentra en keyboard.active. De esta forma, para salir del programa si se
cierra la ventana o si se pulsa la tecla escape escribimos
if eventos.quit or K_ESCAPE in eventos.keyboard.hit:
pyggel.quit()
sys.exit()
Si se pulsan ciertas teclas, lo que haremos es desplazar la cámara (y por lo tanto veremos la
escena desde otras posiciones o perspectivas). Así, por ejemplo,
if K_LEFT in eventos.keyboard.active:
camara.roty -= .5
mira si se ha pulsado la tecla de desplazamiento izquierda del cursor y, en tal caso, resta
0.5 grados al ángulo de rotación sobre el eje Y de la cámara. Ejecuta el programa
para ver el efecto que esto tiene. Observa que no es lo mismo girar la cámara que
girar un objeto (según sea lo que quieres conseguir o cómo quieres que el programa
responda a las acciones del usuario elegirás una u otra cosa); girar la cámara hace moverse
al observador que contempla la escena; girar los objetos hace que la propia escena cambie.
Los demás giros sobre los diferentes ejes se implementan de forma similar. Como sólo
tenemos las teclas de desplazamiento izquierda, derecha, arriba y abajo, para hacer el
giro sobre el eje Z hemos usado, como puedes ver, las teclas 1 y 2. Por otra parte, para
acercarnos o alejarnos de la escena, usando las teclas z y x, simplemente hemos de
modificar el valor de camara.distance.
Cuando giramos la cámara, lo que hacemos es orbitar en torno al punto que estamos
mirando, como hace la Luna con la Tierra. Desplazarla es algo distinto, pues es
simplemente cambiar su posición (lo que hace que entonces no mire necesariamente en
la dirección de antes). En el código, por poner un ejemplo, usamos las teclas a, d, s y w
para desplazarla en los ejes X y Z. De nuevo, ejecuta el script y verás lo que sucede.
PÁGINA 6 DE 13
CC: FERNANDO SALAMERO
7. PROGRAMA: DRIVE
CURSO: 1º BACHILLERATO
Una vez que hemos atendido a todos los eventos que queríamos usar en nuestro programa,
lo único que nos queda es realizar el dibujado en pantalla para que se muestre en toda su
gloria la imagen tridimensional. Análogamente a lo que ocurría en PyGame, en cada
fotograma tienes que vigilar que sean borradas las imágenes que han cambiado y redibujar
las que ahora han de visualizarse. Por lo tanto, lo primero es borrar toda la pantalla
pyggel.view.clear_screen()
y lo siguiente es generar la imagen tridimensional (lo que se conoce como renderizar)
escena.render(camara)
La línea anterior nos renderiza la escena tal como se vería por la camara. Para finalizar,
como es habitual, debemos volcar la escena generada. Ello nos lleva a la última línea del
programa:
pyggel.view.refresh_screen()
¡Perfecto! Nuestro cubo ha quedado realmente aparente. Y esto es sólo el comienzo. Con
tiempo e imaginación, puedes crear cualquier Mundo que te propongas...
# -*- coding: utf-8 -*-
# Ejemplo de escena en 3D
import pyggel, sys
from pyggel import *
pyggel.init(screen_size=(800,600))
escena = pyggel.scene.Scene()
textura = pyggel.data.Texture("glass.bmp")
cubo = pyggel.geometry.Cube(1, pos=(0, 0, 0), texture=textura)
cubo.rotation = (0,45,10)
escena.add_3d(cubo)
luz = pyggel.light.Light((0,0,2),
(0.5,0.5,0.5,1),
(1,1,1,1),
(50,50,50,10),
(0,0,0),
True)
escena.add_light(luz)
PÁGINA 7 DE 13
CC: FERNANDO SALAMERO
8. PROGRAMA: DRIVE
CURSO: 1º BACHILLERATO
camara = pyggel.camera.LookAtCamera((0,0,0), distance=3)
reloj = pygame.time.Clock()
eventos = pyggel.event.Handler()
while 1:
reloj.tick(60)
pyggel.view.set_title("FPS: %s"%int(reloj.get_fps()))
eventos.update()
if eventos.quit or K_ESCAPE in eventos.keyboard.hit:
pyggel.quit()
sys.exit()
if K_LEFT in eventos.keyboard.active:
camara.roty -= .5
if K_RIGHT in eventos.keyboard.active:
camara.roty += .5
if K_UP in eventos.keyboard.active:
camara.rotx -= .5
if K_DOWN in eventos.keyboard.active:
camara.rotx += .5
if K_1 in eventos.keyboard.active:
camara.rotz -= .5
if K_2 in eventos.keyboard.active:
camara.rotz += .5
if K_z in eventos.keyboard.active:
camara.distance -= .05
if K_x in eventos.keyboard.active:
camara.distance += .05
if K_a in eventos.keyboard.active:
camara.posx -= .1
if K_d in eventos.keyboard.active:
camara.posx += .1
if K_s in eventos.keyboard.active:
camara.posz -= .1
if K_w in eventos.keyboard.active:
camara.posz += .1
pyggel.view.clear_screen()
escena.render(camara)
pyggel.view.refresh_screen()
PÁGINA 8 DE 13
CC: FERNANDO SALAMERO
9. PROGRAMA: DRIVE
CURSO: 1º BACHILLERATO
Como hemos comentado, incluimos también el código de Paul Furber que usa
directamente las librerías OpenGL a través de PyOpenGL. Como verás, el programa es
mucho más complejo aunque más potente. Si lo ejecutas verás que, por ejemplo, con la
tecla l se activa o desactiva la iluminación, mientras que con la tecla b se activan
o desactivan efectos de transparencia...
#!/usr/bin/env python
# pygame + PyOpenGL version of Nehe's OpenGL lesson08
# Paul Furber 2001 - m@verick.co.za
import os
from OpenGL.GL import *
from OpenGL.GLU import *
import pygame, pygame.image
from pygame.locals import *
xrot = yrot = 0.0
xspeed = yspeed = 0.0
z = -5.0
textures = []
filter = 0
light = 0
blend = 0
LightAmbient = ( (0.5, 0.5, 0.5, 1.0) );
LightDiffuse = ( (1.0, 1.0, 1.0, 1.0) );
LightPosition = ( (0.0, 0.0, 2.0, 1.0) );
def resize((width, height)):
if height==0:
height=1.0
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45, 1.0*width/height, 0.1, 100.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def init():
glEnable(GL_TEXTURE_2D)
load_textures()
glShadeModel(GL_SMOOTH)
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glEnable(GL_DEPTH_TEST)
PÁGINA 9 DE 13
CC: FERNANDO SALAMERO