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.