viernes, 30 de septiembre de 2011

Gestión de imagenes (Tiles)


Hoy vamos a explicar como gestionar las imágenes que usaremos en el juego.

Tanto, las imágenes del terreno, como la de los personajes, etc... se gestionarán de la misma forma.

Aquí tenéis un ejemplo de como es la imagen del terreno:


Primero debéis descargaros las imagenes.
Las imágenes las puedes descargar desde aquí:
Angband

Una vez descargadas, las descomprimimos en la carpeta data/imagenes/Angband.

Dentro se os crearán subcarpetas como Characters, Ground, etc ...
Allí podéis ver las imágenes que usaremos para el juego.

Bueno empezamos, un tileset es lo que veis en la imagen de arriba un conjunto de imágenes pequeñas que forman una más grande.
Como podéis observar, todas las porciones (tiles) de la imagen tienen el mismo tamaño, en este caso 32x32 pixels.

Para poder trabajar con esa imagen tan grande, lo que vamos a hacer es crear un array unidimensional en el que cada uno de sus elemento va a ser un trocito de esa imagen tan grande.
Así, si luego queremos imprimir el tile del césped, solo deberemos acceder a la posición 10 del array y obtener la imagen.

Es muy sencillo, ahora veréis.

NOTA: mucha de la información que veréis en este tutorial la he sacado de esta página http://razonartificial.com.

Creamos la carpeta utilidades/ y dentro el archivo imagen.py.



import pygame
from pygame.locals import *

def cargar_imagen(filename, transparent=False, pixel=(0, 0)):
""" Carga una imagen y estblece un color como transparente si se desea. """
try: image = pygame.image.load(filename)
except pygame.error, message:
raise SystemExit, message
image = image.convert()
if transparent:
color = image.get_at(pixel)
image.set_colorkey(color, RLEACCEL)
return image

def cortar_tileset(filename, (w, h), con_None=False):
""" Corta un tilest y lo almacena en un array unidimensional. """
image = cargar_imagen(filename, True)
rect = image.get_rect()
col = rect.w / w
fil = rect.h / h
sprite = []
if con_None:
sprite = [None]

for f in range(fil):
for c in range(col):
sprite.append(image.subsurface((rect.left, rect.top, w, h)))
rect.left += w
rect.top += h
rect.left = 0

return sprite


Como podeis ver el metodo cargar_imagen lo que hace es cargar una imagen del disco simple y llanamente.
Ahora el cortar_tileset lo que hace es obtener recortes de la imagen grande y almacenarlos en un array.

Lo interesante de este método es como obtiene el numero de de tiles que forman la imagen.
Para ello al metodo le tenemos que indicar que vamos a trabajar con tiles de 32x32 pixeles.

Y luego él obtiene el numero de filas y columnas que tiene esa imagen con una simple fórmula:

col = rect.w / w
fil = rect.h / h

En nuestro caso rect.w (es decir el width de la imagen) es de 288 y el rect.h (height) es de 608.
col = 288 / 32 = 9
fil = 608 / 32 = 19

jueves, 29 de septiembre de 2011

Control de los Frames

Para que nuestro juego funcione correctamente debemos establecer una tasa de Frames Por Segundo (FPS). Digamos que el número de veces que se dibuja, actualiza y recoge eventos por segundo.

Todo esto lo controlaremos con la clase FpsClock.

import pygame.time


class FpsClock:
   "class for managing FPS related stuff"
   def __init__(self, desired_fps=30, do_report=0):
       "create FpsClock instance, give desired running fps and enable report"
       self.do_report = do_report
       self.frame_count = 0
       self.frame_timer = pygame.time.get_ticks()
       self.frame_delay = 0
       self.last_tick = pygame.time.get_ticks()
       self.set_fps(desired_fps)
       self.current_fps = 0.0


   def set_fps(self, desired_fps):
       "set the desired frames per second"
       if desired_fps:
           self.fps_ticks = int((0.975/desired_fps) * 1000)
           #slight fudge, not quite 1000millis
       else:
           self.fps_ticks = 0
       self.desired_fps = desired_fps


   def tick(self):
       "call this once per frame"
       #delay until milliseconds per frame has passed
       if self.fps_ticks:
           now = pygame.time.get_ticks()
           wait = self.fps_ticks - (now - self.last_tick)
           pygame.time.delay(wait)
           self.frame_delay += wait
       self.last_tick = pygame.time.get_ticks()

       #update current_fps
       self.frame_count += 1
       time = self.last_tick - self.frame_timer
       if time > 1000:
           time -= self.frame_delay
           if not time: self.current_fps = 1.0
           else: self.current_fps = self.frame_count / (time / 1000.0)
           self.frame_count = 0
           self.frame_delay = 0
           self.frame_timer = self.last_tick
           if self.do_report: self.report()


   def report(self):
       "override this for fancier fps reporting"
       subst = (0,0)
       if (self.current_fps > 0):
           subst = 1.0/self.current_fps, self.current_fps
       return 'AVG TIME: %.3f   FPS: %.2f' % subst


Esta clase la crearemos dentro de un archivo llamado fpsclock.py dentro de la carpeta de gamemanager.

Ahora solo nos quedará añadir el siguiente código en el archivo juego.py.

import pygame
from pygame.locals import *
 
from gamemanager.gamemanager import GameManager
from gamemanager.states import menustate
from gamemanager.fpsclock import *
 
if __name__ == "__main__":
 
    game = GameManager('Estados Juego Python',(320,200),False)
    game.changeState(menustate.MenuState(game))
    fps = FpsClock(35,0)
 
    while game.running:
        game.handleEvents(pygame.event.get())
        game.update()
        game.draw()
        fps.tick()
 
    game.cleanUp()


Con la instruccion fps = FpsClock(35,0) establecemos una tasa de FPS de 35.

El metodo fps.tick() espera a que se cumpla la tasa de FPS establecida.

martes, 15 de febrero de 2011

Estados del juego

Un juego se divide en distintos estados por los que puede pasar.
Por ejemplo:
  • Menú Principal
  • Opciones del Juego
  • Créditos
  • Pantalla del juego
  • etc ...
Todos estos estados comparten unos comportamientos como son:
  • Captura de eventos (teclado, ratón, etc..)
  • Dibujar (mostrar las opciones del menú, mostrar la pantalla del juego ...)
  • Actualizar estados (cambiar la opcion del menu seleccionada, actualizar la posicion del jugador ...)
  • Y otros como pueden ser, ¿que pasa cuando se inicializa el estado?, ¿que pasa cuando se finaliza?, ¿que pasa si se hace un pause?
Para controlar todos estos estados de una forma muy sencilla, existe un método que descubrí hace muchos años que es el siguiente.

Tendremos una clase llamada GameManager que controlará el paso de un estado a otro y sus eventos, y una clase abstracta llamada GameState, de la que heredarán todos los estados por los que pueda pasar nuestro juego.

Para que veais como funciona todo esto, vamos a hacer un pequeño ejemplo que luego nos servirá para nuestro juego.

Necesitamos crear inicialmente un fichero llamada juego.py y dos carpetas una llamada gamemanager y otra dentro de esta llamada states.

Dentro de la carpeta "states" crearemos un archivo llamado gamestate.py con el siguiente codigo:


class GameState(object):

    def __init__(self,parent):
        pass

    def start(self):
        pass

    def cleanUp(self):
        pass

    def pause(self):
        pass

    def resume(self):
        pass

    def handleEvents(self, events, parent):
        pass

    def update(self):
        pass

    def draw(self):
        pass