miércoles, 5 de octubre de 2011

Cargar mapas en el juego

Bueno llegó el momento de cargar el mapa creado en el articulo "Crear mapas con Tiled Map Editor".

Para ello primero crearemos el archivo mapa.py en la raiz del proyecto.

# -*- coding: utf-8 -*-
from xml.dom import minidom
import base64
import gzip
import StringIO

from utilidades.imagen import *
from utilidades.archivo import *

class Mapa:

    LAYER_PISABLE = 0
    LAYER_SUELO = 1
    LAYER_OBJETOS = 2
    LAYER_OBJETOS_SUPERPUESTOS = 3
    LAYER_CIELO = 4

    def __init__(self, nombre):
        self.nombre = nombre
        self.capas = []

        # Cargamos el mapa en self.capas.
        self._cargar_mapa() # Inicializa los valores desde el xml.
        
        # Obtenemos un array unidimensional con todos los tilesets del mapa.
        self.tileset = cortar_tileset("data/imagenes/" + self.tileset, self.tam_tiles, True)
        
        #self.crear_mapa()

    # Extrae valores mapa desde XML.
    def _cargar_mapa(self):
        xmlMap = minidom.parse("data/mapas/" + self.nombre)
        nPrincipal = xmlMap.childNodes[0]

        # Tamaño mapa
        self.width = int(nPrincipal.attributes.get("width").value)
        self.height = int(nPrincipal.attributes.get("height").value)

        for i in range(len(nPrincipal.childNodes)):
            if nPrincipal.childNodes[i].nodeType == 1:
                if nPrincipal.childNodes[i].nodeName == "tileset":
                    if nPrincipal.childNodes[i].attributes.get("name").value != "config":
                        width = nPrincipal.childNodes[i].attributes.get("tilewidth").value
                        height = nPrincipal.childNodes[i].attributes.get("tileheight").value
                        nombre = nPrincipal.childNodes[i].childNodes[1].attributes.get("source").value
                        nombre = extraer_nombre(nombre)
                        if nPrincipal.childNodes[i].attributes.get("name").value == "tileset":
                            self.tileset = nombre                            
                    self.tam_tiles = (int(width), int(height))
                if nPrincipal.childNodes[i].nodeName == "layer":
                    layer = nPrincipal.childNodes[i].childNodes[1].childNodes[0].data.replace("\n", "").replace(" ", "")
                    layer = self._decodificar(layer) # Decodifica la lista
                    layer = self.convertir(layer, self.width) # Convierta en array bidimensional                        
                    self.capas.append(layer)
    
    def _decodificar(self, cadena):
        ''' Decodifica y descomprime los datos de un mapa creado con el TileMap Editor '''
        # Decodificar.
        cadena = base64.decodestring(cadena)

        # Descomprimir.
        copmressed_stream = StringIO.StringIO(cadena)
        gzipper = gzip.GzipFile(fileobj=copmressed_stream)
        cadena = gzipper.read()

        # Convertir.
        salida = []
        for idx in xrange(0, len(cadena), 4):
            val = ord(str(cadena[idx])) | (ord(str(cadena[idx + 1])) << 8) | \
            (ord(str(cadena[idx + 2])) << 16) | (ord(str(cadena[idx + 3])) << 24)
            salida.append(val)

        return salida

    # Convierte un array unidimensional en uno bidimensional.
    def convertir(self, lista, col):
        nueva = []
        for i in range(0, len(lista), col):
            nueva.append(lista[i:i + col])
        return nueva


    def dibujar(self, capa, dest, x, y):
        ''' Dibuja una capa del mapa en una surface '''
        x_aux = x
        y_aux = y

        for f in range(self.height):
            for c in range(self.width):
                if self.capas[capa][f][c]:
                    dest.blit(self.tileset[self.capas[capa][f][c]],(x_aux,y_aux))
                x_aux = x_aux + self.tam_tiles[0]
            y_aux = y_aux + self.tam_tiles[1]
            x_aux = x
        

Si observáis el código tendréis bastante claro lo que hace, pero os indico más o menos su funcionamiento.

En self.capas guardamos un array [capa][fila][columna] con los datos leídos del archivo que nos generó el Tiled Map Editor.

En self.tileset tendremos un array unidimensional de las imagenes del tileset que usamos para crear el mapa.

Y en self.height y self.width tendremos el tamaño del mapa. En nuestro caso de 10 filas x 10 columnas.

El método _cargar_mapa hace todo lo que he comentado anteriormente. Va leyendo el archivo XML del mapa y obtiene los datos del mismo. Si editáis con un editor de textos el archivo del mapa mapa1.tmx veréis la estructura del mismo.

Por último el metodo dibujar, imprime una capa del mapa en la surface que se le indique.

La capa que se desea dibujar se especifica mediante estas "constantes":

LAYER_SUELO = 1
LAYER_OBJETOS = 2
LAYER_OBJETOS_SUPERPUESTOS = 3
LAYER_CIELO = 4

Como veréis al principio del archivo está la instrucción "from utilidades.archivo import *", pues bien deberemos crear ese archivo (archivo.py) en la ruta indicada "utilidades/".

import StringIO

def extraer_nombre(ruta):
    ''' Extra el nombre de un archivo de una ruta. '''
    a = -1
    for i in range(len(ruta)):
        if ruta[i] == "/" or ruta[i] == "\\":
            a = i
    if a == -1:
        return ruta
    return ruta[a + 1:]

Acordaros de pones la entrada de los archivos en los __init__.py.

Ahora solo queda añadir lo necesario para mostrar nuestro mapa en el estado de juego que creamos llamado pantallastate.py.
El archivo quedará de la siguiente forma:

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

from mapa 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((0, 0, 0))        
        self.mapa = Mapa('mapa1.tmx')

    def start(self):
        print "GameState Pantalla Started"
 
    def cleanUp(self):
        print "GameState Pantalla Cleaned"
        pass
 
    def pause(self):
        print "GameState Pantalla Paused"
        pass
 
    def resume(self):
        print "GameState Pantalla Resumed"
        pass

    def handleEvents(self, events):
        for event in events:
            if event.type == pygame.KEYDOWN :
                if event.key == pygame.K_ESCAPE :
                    self.parent.popState()

    def update(self):
        pass
 
    def draw(self):
        self.parent.screen.blit(self.background, (0,0))
        self.mapa.dibujar(Mapa.LAYER_SUELO, self.parent.screen,0,0)
        self.mapa.dibujar(Mapa.LAYER_OBJETOS, self.parent.screen,0,0)
        self.mapa.dibujar(Mapa.LAYER_OBJETOS_SUPERPUESTOS, self.parent.screen,0,0)
        self.mapa.dibujar(Mapa.LAYER_CIELO, self.parent.screen,0,0)


Hemos añadido en el __init__ la instrucción:
self.mapa = Mapa('mapa1.tmx')

y posteriromente en el draw:
self.mapa.dibujar(Mapa.LAYER_SUELO, self.parent.screen,0,0)
self.mapa.dibujar(Mapa.LAYER_OBJETOS, self.parent.screen,0,0)
self.mapa.dibujar(Mapa.LAYER_OBJETOS_SUPERPUESTOS, self.parent.screen,0,0)
self.mapa.dibujar(Mapa.LAYER_CIELO, self.parent.screen,0,0)

Una vez ejecutado el programa debería darnos este resultado:

No hay comentarios:

Publicar un comentario