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

Así pues tendremos un array unidimensional de 171 elementos (9 col x 19 fil), que corresponden con el numero de tiles que tiene la imagen.

Ahora vamos a hacer una prueba con un nuevo estado del juego llamado pantalla.py . Si no sabes como crear un nuevo estado del juego mirar el artículo del otro dia.




import pygame
from pygame.locals import *
from gamemanager.states import gamestate
from utilidades.imagen import *

class PantallaState(gamestate.GameState):


def __init__(self, parent):
self.parent = parent
self.background = pygame.Surface(self.parent.screen.get_size())
self.background = self.background.convert()
self.background.fill((100, 100, 45))
self.tileset = cortar_tileset("data/imagenes/Angband/Ground/dg_grounds32.png",(32,32),False)
self.tile = 90

def start(self):
print "GameState Opciones Started"

def cleanUp(self):
print "GameState Opciones Cleaned"
pass

def pause(self):
print "GameState Opciones Paused"
pass

def resume(self):
print "GameState Opciones Resumed"
pass

def handleEvents(self, events):
for event in events:
if event.type == pygame.KEYDOWN :
if event.key == pygame.K_ESCAPE :
self.parent.popState()
elif event.key == pygame.K_UP:
self.tile += 1
elif event.key == pygame.K_DOWN:
self.tile -= 1
if (self.tile < 1):
self.tile = 1

def update(self):
pass

def draw(self):
self.parent.screen.blit(self.background, (0,0))
self.parent.screen.blit(self.tileset[self.tile], (0,0))



Lo importante de este archivo es:


self.tileset = cortar_tileset("data/imagenes/Angband/Ground/dg_grounds32.png",(32,32),False)
self.tile = 90


Donde le decimos que nos cree el array de la imagen data/imagenes/Angband/Ground/dg_grounds32.png.

Y lo de self.tile = 90 es para luego imprimir el elemento 90 de ese array que acabamos de crear.


self.parent.screen.blit(self.tileset[self.tile], (0,0))



Y para probar un poco hemos añadido que si pulsamos la tecla de subir muestre el siguiente elemento del array y si pulsamos la tecla bajar, nos muestre el anterior.


elif event.key == pygame.K_UP:
self.tile += 1
elif event.key == pygame.K_DOWN:
self.tile -= 1
if (self.tile < 1):
self.tile = 1



Por hoy ya hemos acabado, probar a cargar otros tilesets de otros tamaños a ver que tal se os da.

Nos vemos.

P.D.: Se me olvido una cosa, para poder acceder al nuevo estado acordaros de poner en el menustate.py lo siguiente:

import pantallastate

# Resto de codigo ...
# .....

elif event.key == pygame.K_p:
self.parent.pushState(pantallastate.PantallaState(self.parent))

2 comentarios:

  1. El método de cortar tileset está totalmente obsoleto, pues lo que hace es almecenar trozos de imagen en memoria lo que es muy costoso, lo ideal es solo almacenar posiciones que son numeros enteros. Proximamente publicaré un tutorial sobre como hacerlo correctamente. Por cierto soy el autor de la función cortar_tileset el autor de razonartificial.com

    ResponderEliminar
  2. ¡Hombre! gracias por comentar sobre el artículo :D. Supongo que habrás llegado a este blog por el link que puse a tu gran página sobre programación de juegos.

    Hombre lo que guardo es un array con trocitos de surfaces de la imagen de los tiles.

    Luego el mapa es una array bidimensional de numeros que representan el indice del array de tiles que hay que dibujar.

    La otra opcion es definir un método que te devuelva un trozo de la surface de los Tiles.
    Entonces cuando vayas a dibujar, le pasas el indice del tile que quieres dibujar y el método te devuelve un trozo de la surface de los tiles.

    ¿Seria mejor eso?

    ResponderEliminar