#!/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 ... self.content = None # from ... 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))