Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/game1/tiledtmxloader.py
diff options
context:
space:
mode:
authorMateu Batle <mateu.batle@collabora.co.uk>2010-10-27 17:40:28 (GMT)
committer Mateu Batle <mateu.batle@collabora.co.uk>2010-10-27 17:40:28 (GMT)
commit0018a9e211ebbe74d371339a70edc0d36c4fc99f (patch)
treeddfd93d0713c615ed70dbf869747b55b94791af8 /game1/tiledtmxloader.py
Initial import of game1 and game2 in math quwy
Diffstat (limited to 'game1/tiledtmxloader.py')
-rwxr-xr-xgame1/tiledtmxloader.py1058
1 files changed, 1058 insertions, 0 deletions
diff --git a/game1/tiledtmxloader.py b/game1/tiledtmxloader.py
new file mode 100755
index 0000000..4647484
--- /dev/null
+++ b/game1/tiledtmxloader.py
@@ -0,0 +1,1058 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+u"""
+TileMap loader for python for Tiled, a generic tile map editor
+from http://mapeditor.org/ .
+It loads the \*.tmx files produced by Tiled.
+"""
+
+__version__ = u'$Id$'
+__author__ = u'DR0ID_ @ 2009'
+
+if __debug__:
+ import sys
+ sys.stdout.write(u'%s loading ... \n' % (__name__))
+ import time
+ _start_time = time.time()
+
+#-------------------------------------------------------------------------------
+
+
+import sys
+from xml.dom import minidom, Node
+import base64
+import gzip
+import StringIO
+import os.path
+#import codecs
+
+# TODO: ideas: save indexed_tiles as {type:data} so no image loader is needed
+# user would have to write its own image loading
+# different types would be : {gid : ('img_parts', (margin, spacing, path, tile_w, tile_h, colorkey))}
+# {gid : ('img_path', ('C:/...', colorkey)}
+# {gid : ('file_like', (file_like_obj, colorkey))}
+#
+# maybe use cStringIO instead of StringIO
+
+#-------------------------------------------------------------------------------
+class IImageLoader(object):
+ u"""
+ Interface for image loading. Depending on the framework used the
+ images have to be loaded differently.
+ """
+
+ def load_image(self, filename, colorkey=None): # -> image
+ u"""
+ Load a single image.
+
+ :Parameters:
+ filename : string
+ Path to the file to be loaded.
+ colorkey : tuple
+ The (r, g, b) color that should be used as colorkey (or magic color).
+ Default: None
+
+ :rtype: image
+
+ """
+ raise NotImplementedError(u'This should be implemented in a inherited class')
+
+ def load_image_file_like(self, file_like_obj, colorkey=None): # -> image
+ u"""
+ Load a image from a file like object.
+
+ :Parameters:
+ file_like_obj : file
+ This is the file like object to load the image from.
+ colorkey : tuple
+ The (r, g, b) color that should be used as colorkey (or magic color).
+ Default: None
+
+ :rtype: image
+ """
+ raise NotImplementedError(u'This should be implemented in a inherited class')
+
+ def load_image_parts(self, filename, margin, spacing, tile_width, tile_height, colorkey=None): #-> [images]
+ u"""
+ Load different tile images from one source image.
+
+ :Parameters:
+ filename : string
+ Path to image to be loaded.
+ margin : int
+ The margin around the image.
+ spacing : int
+ The space between the tile images.
+ tile_width : int
+ The width of a single tile.
+ tile_height : int
+ The height of a single tile.
+ colorkey : tuple
+ The (r, g, b) color that should be used as colorkey (or magic color).
+ Default: None
+
+ Luckily that iteration is so easy in python::
+
+ ...
+ w, h = image_size
+ for y in xrange(margin, h, tile_height + spacing):
+ for x in xrange(margin, w, tile_width + spacing):
+ ...
+
+ :rtype: a list of images
+ """
+ raise NotImplementedError(u'This should be implemented in a inherited class')
+
+#-------------------------------------------------------------------------------
+class ImageLoaderPygame(IImageLoader):
+ u"""
+ Pygame image loader.
+
+ It uses an internal image cache. The methods return Surface.
+
+ :Undocumented:
+ pygame
+ """
+
+
+ def __init__(self):
+ self.pygame = __import__('pygame')
+ self._img_cache = {} # {name: surf}
+
+ def load_image(self, filename, colorkey=None):
+ img = self._img_cache.get(filename, None)
+ if img is None:
+ img = self.pygame.image.load(filename)
+ self._img_cache[filename] = img
+ if colorkey:
+ img.set_colorkey(colorkey)
+ return img
+
+ def load_image_part(self, filename, x, y, w, h, colorkey=None):
+ source_rect = self.pygame.Rect(x, y, w, h)
+ img = self._img_cache.get(filename, None)
+ if img is None:
+ img = self.pygame.image.load(filename)
+ self._img_cache[filename] = img
+ img_part = self.pygame.Surface((w, h), 0, img)
+ img_part.blit(img, (0, 0), source_rect)
+ if colorkey:
+ img_part.set_colorkey(colorkey)
+ return img_part
+
+ def load_image_parts(self, filename, margin, spacing, tile_width, tile_height, colorkey=None): #-> [images]
+ source_img = self._img_cache.get(filename, None)
+ if source_img is None:
+ source_img = self.pygame.image.load(filename)
+ self._img_cache[filename] = source_img
+ w, h = source_img.get_size()
+ images = []
+ for y in xrange(margin, h, tile_height + spacing):
+ for x in xrange(margin, w, tile_width + spacing):
+ img_part = self.pygame.Surface((tile_width, tile_height), 0, source_img)
+ img_part.blit(source_img, (0, 0), self.pygame.Rect(x, y, tile_width, tile_height))
+ if colorkey:
+ img_part.set_colorkey(colorkey)
+ images.append(img_part)
+ return images
+
+ def load_image_file_like(self, file_like_obj, colorkey=None): # -> image
+ # pygame.image.load can load from a path and from a file-like object
+ # that is why here it is redirected to the other method
+ return self.load_image(file_like_obj, colorkey)
+
+#-------------------------------------------------------------------------------
+class ImageLoaderPyglet(IImageLoader):
+ u"""
+ Pyglet image loader.
+
+ It uses an internal image cache. The methods return some form of
+ AbstractImage. The resource module is not used for loading the images.
+
+ Thanks to HydroKirby from #pyglet to contribute the ImageLoaderPyglet and the pyglet demo!
+
+ :Undocumented:
+ pyglet
+ """
+
+
+ def __init__(self):
+ self.pyglet = __import__('pyglet')
+ self._img_cache = {} # {name: image}
+
+ def load_image(self, filename, colorkey=None, fileobj=None):
+ img = self._img_cache.get(filename, None)
+ if img is None:
+ if fileobj:
+ img = self.pyglet.image.load(filename, fileobj, self.pyglet.image.codecs.get_decoders("*.png")[0])
+ else:
+ img = self.pyglet.image.load(filename)
+ self._img_cache[filename] = img
+ return img
+
+ def load_image_part(self, filename, x, y, w, h, colorkey=None):
+ img = self._img_cache.get(filename, None)
+ if img is None:
+ img = self.pyglet.image.load(filename)
+ self._img_cache[filename] = img
+ img_part = image.get_region(x, y, w, h)
+ return img_part
+
+
+ def load_image_parts(self, filename, margin, spacing, tile_width, tile_height, colorkey=None): #-> [images]
+ source_img = self._img_cache.get(filename, None)
+ if source_img is None:
+ source_img = self.pyglet.image.load(filename)
+ self._img_cache[filename] = source_img
+ images = []
+ # Reverse the map column reading to compensate for pyglet's y-origin.
+ for y in xrange(source_img.height - tile_height, margin - tile_height,
+ -tile_height - spacing):
+ for x in xrange(margin, source_img.width, tile_width + spacing):
+ #img_part = source_img.get_region(x, y, tile_width, tile_height)
+ img_part = source_img.get_region(x, y - spacing, tile_width, tile_height)
+ images.append(img_part)
+ return images
+
+ def load_image_file_like(self, file_like_obj, colorkey=None): # -> image
+ # pyglet.image.load can load from a path and from a file-like object
+ # that is why here it is redirected to the other method
+ return self.load_image(file_like_obj, colorkey, file_like_obj)
+
+#-------------------------------------------------------------------------------
+class TileMap(object):
+ u"""
+
+ The TileMap holds all the map data.
+
+ :Ivariables:
+ orientation : string
+ orthogonal or isometric or hexagonal or shifted
+ tilewidth : int
+ width of the tiles (for all layers)
+ tileheight : int
+ height of the tiles (for all layers)
+ width : int
+ width of the map (number of tiles)
+ height : int
+ height of the map (number of tiles)
+ version : string
+ version of the map format
+ tile_sets : list
+ list of TileSet
+ properties : dict
+ the propertis set in the editor, name-value pairs, strings
+ pixel_width : int
+ width of the map in pixels
+ pixel_height : int
+ height of the map in pixels
+ layers : list
+ list of TileLayer
+ map_file_name : dict
+ file name of the map
+ object_groups : list
+ list of :class:MapObjectGroup
+ indexed_tiles : dict
+ dict containing {gid : (offsetx, offsety, surface} if load() was called
+ when drawing just add the offset values to the draw point
+ indexed_tiles_tileset: dict
+ dict containing {gid : layer_name }
+ named_layers : dict of string:TledLayer
+ dict containing {name : TileLayer}
+ named_tile_sets : dict
+ dict containing {name : TileSet}
+
+ """
+
+
+ def __init__(self):
+# This is the top container for all data. The gid is the global id (for a image).
+# Before calling convert most of the values are strings. Some additional
+# values are also calculated, see convert() for details. After calling
+# convert, most values are integers or floats where appropriat.
+ u"""
+ The TileMap holds all the map data.
+ """
+ # set through parser
+ self.orientation = None
+ self.tileheight = 0
+ self.tilewidth = 0
+ self.width = 0
+ self.height = 0
+ self.version = 0
+ self.tile_sets = [] # TileSet
+ self.layers = [] # WorldTileLayer <- what order? back to front (guessed)
+ self.indexed_tiles = {} # {gid: (offsetx, offsety, image}
+ self.indexed_tiles_tileset = {} # {gid: tileset_name }
+ self.object_groups = []
+ self.properties = {} # {name: value}
+ # additional info
+ self.pixel_width = 0
+ self.pixel_height = 0
+ self.named_layers = {} # {name: layer}
+ self.named_tile_sets = {} # {name: tile_set}
+ self.map_file_name = ""
+ self._image_loader = None
+
+ def convert(self):
+ u"""
+ Converts numerical values from strings to numerical values.
+ It also calculates or set additional data:
+ pixel_width
+ pixel_height
+ named_layers
+ named_tile_sets
+ """
+ self.tilewidth = int(self.tilewidth)
+ self.tileheight = int(self.tileheight)
+ self.width = int(self.width)
+ self.height = int(self.height)
+ self.pixel_width = self.width * self.tilewidth
+ self.pixel_height = self.height * self.tileheight
+ for layer in self.layers:
+ self.named_layers[layer.name] = layer
+ layer.opacity = float(layer.opacity)
+ layer.x = int(layer.x)
+ layer.y = int(layer.y)
+ layer.width = int(layer.width)
+ layer.height = int(layer.height)
+ layer.pixel_width = layer.width * self.tilewidth
+ layer.pixel_height = layer.height * self.tileheight
+ layer.visible = bool(int(layer.visible))
+ for tile_set in self.tile_sets:
+ self.named_tile_sets[tile_set.name] = tile_set
+ tile_set.spacing = int(tile_set.spacing)
+ tile_set.margin = int(tile_set.margin)
+ for img in tile_set.images:
+ if img.trans:
+ img.trans = (int(img.trans[:2], 16), int(img.trans[2:4], 16), int(img.trans[4:], 16))
+ for obj_group in self.object_groups:
+ obj_group.x = int(obj_group.x)
+ obj_group.y = int(obj_group.y)
+ obj_group.width = int(obj_group.width)
+ obj_group.height = int(obj_group.height)
+ for map_obj in obj_group.objects:
+ map_obj.x = int(map_obj.x)
+ map_obj.y = int(map_obj.y)
+ map_obj.width = int(map_obj.width)
+ map_obj.height = int(map_obj.height)
+
+ def load(self, image_loader):
+ u"""
+ loads all images using a IImageLoadermage implementation and fills up
+ the indexed_tiles dictionary.
+ The image may have per pixel alpha or a colorkey set.
+ """
+ self._image_loader = image_loader
+ for tile_set in self.tile_sets:
+ # do images first, because tiles could reference it
+ for img in tile_set.images:
+ if img.source:
+ self._load_image_from_source(tile_set, img)
+ else:
+ tile_set.indexed_images[img.id] = self._load_image(img)
+ # tiles
+ for tile in tile_set.tiles:
+ for img in tile.images:
+ if not img.content and not img.source:
+ # only image id set
+ indexed_img = tile_set.indexed_images[img.id]
+ self.indexed_tiles[int(tile_set.firstgid) + int(tile.id)] = (0, 0, indexed_img)
+ self.indexed_tiles_tileset[int(tile_set.firstgid) + int(tile.id)] = tile_set.name
+ else:
+ if img.source:
+ self._load_image_from_source(tile_set, img)
+ else:
+ indexed_img = self._load_image(img)
+ self.indexed_tiles[int(tile_set.firstgid) + int(tile.id)] = (0, 0, indexed_img)
+ self.indexed_tiles_tileset[int(tile_set.firstgid) + int(tile.id)] = tile_set.name
+
+ def _load_image_from_source(self, tile_set, a_tile_image):
+ # relative path to file
+ img_path = os.path.join(os.path.dirname(self.map_file_name), a_tile_image.source)
+ tile_width = int(self.tilewidth)
+ tile_height = int(self.tileheight)
+ if tile_set.tileheight:
+ tile_width = int(tile_set.tilewidth)
+ if tile_set.tilewidth:
+ tile_height = int(tile_set.tileheight)
+ offsetx = 0
+ offsety = 0
+# if tile_width > self.tilewidth:
+# offsetx = tile_width
+ if tile_height > self.tileheight:
+ offsety = tile_height - self.tileheight
+ idx = 0
+ for image in self._image_loader.load_image_parts(img_path, \
+ tile_set.margin, tile_set.spacing, tile_width, tile_height, a_tile_image.trans):
+ self.indexed_tiles[int(tile_set.firstgid) + idx] = (offsetx, -offsety, image)
+ self.indexed_tiles_tileset[int(tile_set.firstgid) + idx] = tile_set.name
+ idx += 1
+
+ def _load_image(self, a_tile_image):
+ img_str = a_tile_image.content
+ if a_tile_image.encoding:
+ if a_tile_image.encoding == u'base64':
+ img_str = decode_base64(a_tile_image.content)
+ else:
+ raise Exception(u'unknown image encoding %s' % a_tile_image.encoding)
+ sio = StringIO.StringIO(img_str)
+ new_image = self._image_loader.load_image_file_like(sio, a_tile_image.trans)
+ return new_image
+
+ def decode(self):
+ u"""
+ Decodes the TileLayer encoded_content and saves it in decoded_content.
+ """
+ for layer in self.layers:
+ layer.decode()
+#-------------------------------------------------------------------------------
+
+
+class TileSet(object):
+ u"""
+ A tileset holds the tiles and its images.
+
+ :Ivariables:
+ firstgid : int
+ the first gid of this tileset
+ name : string
+ the name of this TileSet
+ images : list
+ list of TileImages
+ tiles : list
+ list of Tiles
+ indexed_images : dict
+ after calling load() it is dict containing id: image
+ indexed_tiles : dict
+ after calling load() it is a dict containing
+ gid: (offsetx, offsety, image) , the image corresponding to the gid
+ spacing : int
+ the spacing between tiles
+ marging : int
+ the marging of the tiles
+ properties : dict
+ the propertis set in the editor, name-value pairs
+ tilewidth : int
+ the actual width of the tile, can be different from the tilewidth of the map
+ tilehight : int
+ the actual hight of th etile, can be different from the tilehight of the map
+
+ """
+
+ def __init__(self):
+ self.firstgid = 0
+ self.name = None
+ self.images = [] # TileImage
+ self.tiles = [] # Tile
+ self.indexed_images = {} # {id:image}
+ self.indexed_tiles = {} # {gid: (offsetx, offsety, image} <- actually in map data
+ self.spacing = 0
+ self.margin = 0
+ self.properties = {}
+ self.tileheight = 0
+ self.tilewidth = 0
+
+#-------------------------------------------------------------------------------
+
+class TileImage(object):
+ u"""
+ An image of a tile or just an image.
+
+ :Ivariables:
+ id : int
+ id of this image (has nothing to do with gid)
+ format : string
+ the format as string, only 'png' at the moment
+ source : string
+ filename of the image. either this is set or the content
+ encoding : string
+ encoding of the content
+ trans : tuple of (r,g,b)
+ the colorkey color, raw as hex, after calling convert just a (r,g,b) tuple
+ properties : dict
+ the propertis set in the editor, name-value pairs
+ image : TileImage
+ after calling load the pygame surface
+ """
+
+ def __init__(self):
+ self.id = 0
+ self.format = None
+ self.source = None
+ self.encoding = None # from <data>...</data>
+ self.content = None # from <data>...</data>
+ self.image = None
+ self.trans = None
+ self.properties = {} # {name: value}
+
+#-------------------------------------------------------------------------------
+
+class Tile(object):
+ u"""
+ A single tile.
+
+ :Ivariables:
+ id : int
+ id of the tile gid = TileSet.firstgid + Tile.id
+ images : list of :class:TileImage
+ list of TileImage, either its 'id' or 'image data' will be set
+ properties : dict of name:value
+ the propertis set in the editor, name-value pairs
+ """
+
+ def __init__(self):
+ self.id = 0
+ self.images = [] # uses TileImage but either only id will be set or image data
+ self.properties = {} # {name: value}
+
+#-------------------------------------------------------------------------------
+
+class TileLayer(object):
+ u"""
+ A layer of the world.
+
+ :Ivariables:
+ x : int
+ position of layer in the world in number of tiles (not pixels)
+ y : int
+ position of layer in the world in number of tiles (not pixels)
+ width : int
+ number of tiles in x direction
+ height : int
+ number of tiles in y direction
+ pixel_width : int
+ width of layer in pixels
+ pixel_height : int
+ height of layer in pixels
+ name : string
+ name of this layer
+ opacity : float
+ float from 0 (full transparent) to 1.0 (opaque)
+ decoded_content : list
+ list of graphics id going through the map::
+
+ e.g [1, 1, 1, ]
+ where decoded_content[0] is (0,0)
+ decoded_content[1] is (1,0)
+ ...
+ decoded_content[1] is (width,0)
+ decoded_content[1] is (0,1)
+ ...
+ decoded_content[1] is (width,height)
+
+ usage: graphics id = decoded_content[tile_x + tile_y * width]
+ content2D : list
+ list of list, usage: graphics id = content2D[x][y]
+
+ """
+
+ def __init__(self):
+ self.width = 0
+ self.height = 0
+ self.x = 0
+ self.y = 0
+ self.pixel_width = 0
+ self.pixel_height = 0
+ self.name = None
+ self.opacity = -1
+ self.encoding = None
+ self.compression = None
+ self.encoded_content = None
+ self.decoded_content = []
+ self.visible = True
+ self.properties = {} # {name: value}
+ self.content2D = None
+
+ def decode(self):
+ u"""
+ Converts the contents in a list of integers which are the gid of the used
+ tiles. If necessairy it decodes and uncompresses the contents.
+ """
+ s = self.encoded_content
+ if self.encoded_content:
+ if self.encoding:
+ if self.encoding == u'base64':
+ s = decode_base64(s)
+ else:
+ raise Exception(u'unknown data encoding %s' % (self.encoding))
+ if self.compression:
+ if self.compression == u'gzip':
+ s = decompress_gzip(s)
+ else:
+ raise Exception(u'unknown data compression %s' %(self.compression))
+ else:
+ raise Exception(u'no encoded content to decode')
+ self.decoded_content = []
+ for idx in xrange(0, len(s), 4):
+ val = ord(str(s[idx])) | (ord(str(s[idx + 1])) << 8) | \
+ (ord(str(s[idx + 2])) << 16) | (ord(str(s[idx + 3])) << 24)
+ self.decoded_content.append(val)
+ # generate the 2D version
+ self._gen_2D()
+
+ def _gen_2D(self):
+ self.content2D = []
+ # generate the needed lists
+ for xpos in xrange(self.width):
+ self.content2D.append([])
+ # fill them
+ for xpos in xrange(self.width):
+ for ypos in xrange(self.height):
+ self.content2D[xpos].append(self.decoded_content[xpos + ypos * self.width])
+
+ def pretty_print(self):
+ num = 0
+ for y in range(int(self.height)):
+ s = u""
+ for x in range(int(self.width)):
+ s += str(self.decoded_content[num])
+ num += 1
+ print s
+#-------------------------------------------------------------------------------
+
+
+class MapObjectGroup(object):
+ u"""
+ Group of objects on the map.
+
+ :Ivariables:
+ x : int
+ the x position
+ y : int
+ the y position
+ width : int
+ width of the bounding box (usually 0, so no use)
+ height : int
+ height of the bounding box (usually 0, so no use)
+ name : string
+ name of the group
+ objects : list
+ list of the map objects
+
+ """
+
+ def __init__(self):
+ self.width = 0
+ self.height = 0
+ self.name = None
+ self.objects = []
+ self.x = 0
+ self.y = 0
+ self.properties = {} # {name: value}
+
+#-------------------------------------------------------------------------------
+
+class MapObject(object):
+ u"""
+ A single object on the map.
+
+ :Ivariables:
+ x : int
+ x position relative to group x position
+ y : int
+ y position relative to group y position
+ width : int
+ width of this object
+ height : int
+ height of this object
+ type : string
+ the type of this object
+ image_source : string
+ source path of the image for this object
+ image : :class:TileImage
+ after loading this is the pygame surface containing the image
+ """
+ def __init__(self):
+ self.name = None
+ self.x = 0
+ self.y = 0
+ self.width = 0
+ self.height = 0
+ self.type = None
+ self.image_source = None
+ self.image = None
+ self.properties = {} # {name: value}
+
+#-------------------------------------------------------------------------------
+def decode_base64(in_str):
+ u"""
+ Decodes a base64 string and returns it.
+
+ :Parameters:
+ in_str : string
+ base64 encoded string
+
+ :returns: decoded string
+ """
+ return base64.decodestring(in_str)
+
+#-------------------------------------------------------------------------------
+def decompress_gzip(in_str):
+ u"""
+ Uncompresses a gzip string and returns it.
+
+ :Parameters:
+ in_str : string
+ gzip compressed string
+
+ :returns: uncompressed string
+ """
+ # gzip can only handle file object therefore using StringIO
+ copmressed_stream = StringIO.StringIO(in_str)
+ gzipper = gzip.GzipFile(fileobj=copmressed_stream)
+ s = gzipper.read()
+ gzipper.close()
+ return s
+
+#-------------------------------------------------------------------------------
+def printer(obj, ident=''):
+ u"""
+ Helper function, prints a hirarchy of objects.
+ """
+ import inspect
+ print ident + obj.__class__.__name__.upper()
+ ident += ' '
+ lists = []
+ for name in dir(obj):
+ elem = getattr(obj, name)
+ if isinstance(elem, list) and name != u'decoded_content':
+ lists.append(elem)
+ elif not inspect.ismethod(elem):
+ if not name.startswith('__'):
+ if name == u'data' and elem:
+ print ident + u'data = '
+ printer(elem, ident + ' ')
+ else:
+ print ident + u'%s\t= %s' % (name, getattr(obj, name))
+ for l in lists:
+ for i in l:
+ printer(i, ident + ' ')
+
+#-------------------------------------------------------------------------------
+class TileMapParser(object):
+ u"""
+ Allows to parse and decode map files for 'Tiled', a open source map editor
+ written in java. It can be found here: http://mapeditor.org/
+ """
+
+ def _build_tile_set(self, tile_set_node, world_map):
+ tile_set = TileSet()
+ self._set_attributes(tile_set_node, tile_set)
+ for node in self._get_nodes(tile_set_node.childNodes, u'image'):
+ self._build_tile_set_image(node, tile_set)
+ for node in self._get_nodes(tile_set_node.childNodes, u'tile'):
+ self._build_tile_set_tile(node, tile_set)
+ self._set_attributes(tile_set_node, tile_set)
+ world_map.tile_sets.append(tile_set)
+
+ def _build_tile_set_image(self, image_node, tile_set):
+ image = TileImage()
+ self._set_attributes(image_node, image)
+ # id of TileImage has to be set!! -> Tile.TileImage will only have id set
+ for node in self._get_nodes(image_node.childNodes, u'data'):
+ self._set_attributes(node, image)
+ image.content = node.childNodes[0].nodeValue
+ tile_set.images.append(image)
+
+ def _build_tile_set_tile(self, tile_set_node, tile_set):
+ tile = Tile()
+ self._set_attributes(tile_set_node, tile)
+ for node in self._get_nodes(tile_set_node.childNodes, u'image'):
+ self._build_tile_set_tile_image(node, tile)
+ tile_set.tiles.append(tile)
+
+ def _build_tile_set_tile_image(self, tile_node, tile):
+ tile_image = TileImage()
+ self._set_attributes(tile_node, tile_image)
+ for node in self._get_nodes(tile_node.childNodes, u'data'):
+ self._set_attributes(node, tile_image)
+ tile_image.content = node.childNodes[0].nodeValue
+ tile.images.append(tile_image)
+
+ def _build_layer(self, layer_node, world_map):
+ layer = TileLayer()
+ self._set_attributes(layer_node, layer)
+ for node in self._get_nodes(layer_node.childNodes, u'data'):
+ self._set_attributes(node, layer)
+ layer.encoded_content = node.lastChild.nodeValue
+ world_map.layers.append(layer)
+
+ def _build_world_map(self, world_node):
+ world_map = TileMap()
+ self._set_attributes(world_node, world_map)
+ if world_map.version != u"1.0":
+ raise Exception(u'this parser was made for maps of version 1.0, found version %s' % world_map.version)
+ for node in self._get_nodes(world_node.childNodes, u'tileset'):
+ self._build_tile_set(node, world_map)
+ for node in self._get_nodes(world_node.childNodes, u'layer'):
+ self._build_layer(node, world_map)
+ for node in self._get_nodes(world_node.childNodes, u'objectgroup'):
+ self._build_object_groups(node, world_map)
+ return world_map
+
+ def _build_object_groups(self, object_group_node, world_map):
+ object_group = MapObjectGroup()
+ self._set_attributes(object_group_node, object_group)
+ for node in self._get_nodes(object_group_node.childNodes, u'object'):
+ tiled_object = MapObject()
+ self._set_attributes(node, tiled_object)
+ for img_node in self._get_nodes(node.childNodes, u'image'):
+ tiled_object.image_source = img_node.attributes[u'source'].nodeValue
+ object_group.objects.append(tiled_object)
+ world_map.object_groups.append(object_group)
+
+ #-- helpers --#
+ def _get_nodes(self, nodes, name):
+ for node in nodes:
+ if node.nodeType == Node.ELEMENT_NODE and node.nodeName == name:
+ yield node
+
+ def _set_attributes(self, node, obj):
+ attrs = node.attributes
+ for attr_name in attrs.keys():
+ setattr(obj, attr_name, attrs.get(attr_name).nodeValue)
+ self._get_properties(node, obj)
+
+
+ def _get_properties(self, node, obj):
+ props = {}
+ for properties_node in self._get_nodes(node.childNodes, u'properties'):
+ for property_node in self._get_nodes(properties_node.childNodes, u'property'):
+ try:
+ props[property_node.attributes[u'name'].nodeValue] = property_node.attributes[u'value'].nodeValue
+ except KeyError:
+ props[property_node.attributes[u'name'].nodeValue] = property_node.lastChild.nodeValue
+ obj.properties.update(props)
+
+
+ #-- parsers --#
+ def parse(self, file_name):
+ u"""
+ Parses the given map. Does no decoding nor loading the data.
+ :return: instance of TileMap
+ """
+ #dom = minidom.parseString(codecs.open(file_name, "r", "utf-8").read())
+ dom = minidom.parseString(open(file_name, "rb").read())
+ for node in self._get_nodes(dom.childNodes, 'map'):
+ world_map = self._build_world_map(node)
+ break
+ world_map.map_file_name = os.path.abspath(file_name)
+ world_map.convert()
+ return world_map
+
+ def parse_decode(self, file_name):
+ u"""
+ Parses the map but additionally decodes the data.
+ :return: instance of TileMap
+ """
+ world_map = TileMapParser().parse(file_name)
+ world_map.decode()
+ return world_map
+
+ def parse_decode_load(self, file_name, image_loader):
+ u"""
+ Parses the data, decodes them and loads the images using the image_loader.
+ :return: instance of TileMap
+ """
+ world_map = self.parse_decode(file_name)
+ world_map.load(image_loader)
+ return world_map
+
+#-------------------------------------------------------------------------------
+def demo_pygame(file_name):
+ pygame = __import__('pygame')
+
+ # parser the map
+ world_map = TileMapParser().parse_decode(file_name)
+ # init pygame and set up a screen
+ pygame.init()
+ pygame.display.set_caption("tiledtmxloader - " + file_name)
+ screen_width = min(1024, world_map.pixel_width)
+ screen_height = min(768, world_map.pixel_height)
+ screen = pygame.display.set_mode((screen_width, screen_height))
+
+ # load the images using pygame
+ world_map.load(ImageLoaderPygame())
+ #printer(world_map)
+
+ # an example on how to access the map data and draw an orthoganl map
+ # draw the map
+ assert world_map.orientation == "orthogonal"
+
+ running = True
+ dirty = True
+ # cam_offset is for scrolling
+ cam_offset_x = 0
+ cam_offset_y = 0
+ # mainloop
+ while running:
+ # eventhandling
+ events = [pygame.event.wait()]
+ events.extend(pygame.event.get())
+ for event in events:
+ dirty = True
+ if event.type == pygame.QUIT:
+ running = False
+ elif event.type == pygame.KEYDOWN:
+ if event.key == pygame.K_ESCAPE:
+ running = False
+ elif event.key == pygame.K_DOWN:
+ cam_offset_y -= world_map.tileheight
+ elif event.key == pygame.K_UP:
+ cam_offset_y += world_map.tileheight
+ elif event.key == pygame.K_LEFT:
+ cam_offset_x += world_map.tilewidth
+ elif event.key == pygame.K_RIGHT:
+ cam_offset_x -= world_map.tilewidth
+
+ # draw the map
+ if dirty:
+ dirty = False
+ for layer in world_map.layers[:]:
+ if layer.visible:
+ idx = 0
+ # loop over all tiles
+ for ypos in xrange(0, layer.height):
+ for xpos in xrange(0, layer.width):
+ # add offset in number of tiles
+ x = (xpos + layer.x) * world_map.tilewidth
+ y = (ypos + layer.y) * world_map.tileheight
+ # get the gid at this position
+ img_idx = layer.content2D[xpos][ypos]
+ idx += 1
+ if img_idx:
+ # get the actual image and its offset
+ offx, offy, screen_img = world_map.indexed_tiles[img_idx]
+ # only draw the tiles that are relly visible (speed up)
+ if x >= cam_offset_x - 3 * world_map.tilewidth and x + cam_offset_x <= screen_width + world_map.tilewidth\
+ and y >= cam_offset_y - 3 * world_map.tileheight and y + cam_offset_y <= screen_height + 3 * world_map.tileheight:
+ if screen_img.get_alpha():
+ screen_img = screen_img.convert_alpha()
+ else:
+ screen_img = screen_img.convert()
+ if layer.opacity > -1:
+ #print 'per surf alpha', layer.opacity
+ screen_img.set_alpha(None)
+ alpha_value = int(255. * float(layer.opacity))
+ screen_img.set_alpha(alpha_value)
+ screen_img = screen_img.convert_alpha()
+ # draw image at right position using its offset
+ screen.blit(screen_img, (x + cam_offset_x + offx, y + cam_offset_y + offy))
+ # map objects
+ for obj_group in world_map.object_groups:
+ goffx = obj_group.x
+ goffy = obj_group.y
+ if goffx >= cam_offset_x - 3 * world_map.tilewidth and goffx + cam_offset_x <= screen_width + world_map.tilewidth \
+ and goffy >= cam_offset_y - 3 * world_map.tileheight and goffy + cam_offset_y <= screen_height + 3 * world_map.tileheight:
+ for map_obj in obj_group.objects:
+ size = (map_obj.width, map_obj.height)
+ if map_obj.image_source:
+ surf = pygame.image.load(map_obj.image_source)
+ surf = pygame.transform.scale(surf, size)
+ screen.blit(surf, (goffx + map_obj.x + cam_offset_x, goffy + map_obj.y + cam_offset_y))
+ else:
+ r = pygame.Rect((goffx + map_obj.x + cam_offset_x, goffy + map_obj.y + cam_offset_y), size)
+ pygame.draw.rect(screen, (255, 255, 0), r, 1)
+ # simple pygame
+ pygame.display.flip()
+
+#-------------------------------------------------------------------------------
+
+def demo_pyglet(file_name):
+ """Loads and views a map using pyglet.
+
+ Holding the arrow keys will scroll along the map.
+ Holding the left shift key will make you scroll faster.
+ Pressing the escape key ends the application.
+ """
+
+ import pyglet
+ from pyglet.gl import glTranslatef, glLoadIdentity
+
+ world_map = TileMapParser().parse_decode(file_name)
+ # delta is the x/y position of the map view.
+ # delta is a list because the scoping is different for immutable types.
+ # This list can be used within the update method.
+ delta = [0.0, 0.0]
+ window = pyglet.window.Window()
+
+ @window.event
+ def on_draw():
+ window.clear()
+ # Reset the "eye" back to the default location.
+ glLoadIdentity()
+ # Move the "eye" to the current location on the map.
+ glTranslatef(delta[0], delta[1], 0.0)
+ batch.draw()
+
+ keys = pyglet.window.key.KeyStateHandler()
+ window.push_handlers(keys)
+ world_map.load(ImageLoaderPyglet())
+
+ def update(dt):
+ speed = 3.0 + keys[pyglet.window.key.LSHIFT] * 6.0
+ if keys[pyglet.window.key.LEFT]:
+ delta[0] += speed
+ if keys[pyglet.window.key.RIGHT]:
+ delta[0] -= speed
+ if keys[pyglet.window.key.UP]:
+ delta[1] -= speed
+ if keys[pyglet.window.key.DOWN]:
+ delta[1] += speed
+
+ # Generate the graphics for every visible tile.
+ batch = pyglet.graphics.Batch()
+ groups = []
+ sprites = []
+ for group_num, layer in enumerate(world_map.layers[:]):
+ if layer.visible is False:
+ continue
+ groups.append(pyglet.graphics.OrderedGroup(group_num))
+ for xtile in range(layer.width):
+ for ytile in range(layer.height):
+ image_id = layer.content2D[xtile][ytile]
+ if image_id:
+ # o_x and o_y are offsets. They are not helpful here.
+ o_x, o_y, image_file = world_map.indexed_tiles[image_id]
+ # To compensate for pyglet's upside-down y-axis, the
+ # Sprites are placed in rows that are backwards compared
+ # to what was loaded into the map. The "max - current"
+ # formula does this reversal.
+ sprites.append(pyglet.sprite.Sprite(image_file,
+ xtile * world_map.tilewidth,
+ layer.pixel_height - (ytile+1) * world_map.tileheight,
+ batch=batch, group=groups[group_num]))
+
+ pyglet.clock.schedule_interval(update, 1.0 / 60.0)
+ pyglet.app.run()
+
+
+#-------------------------------------------------------------------------------
+def main():
+
+ args = sys.argv[1:]
+ if len(args) != 2:
+ #print 'usage: python test.py mapfile.tmx [pygame|pyglet]'
+ print('usage: python %s your_map.tmx [pygame|pyglet]' % \
+ os.path.basename(__file__))
+ return
+
+ if args[1] == 'pygame':
+ demo_pygame(args[0])
+ elif args[1] == 'pyglet':
+ demo_pyglet(args[0])
+ else:
+ print 'missing framework, usage: python test.py mapfile.tmx [pygame|pyglet]'
+ sys.exit(-1)
+
+#-------------------------------------------------------------------------------
+
+if __name__ == '__main__':
+ main()
+
+
+if __debug__:
+ _dt = time.time() - _start_time
+ sys.stdout.write(u'%s loaded: %fs \n' % (__name__, _dt))