Mercurial > pygame-tutorial
changeset 7:2a0f43c87b80
Fourth part of the tutorial
| author | Radomir Dopieralski <sheep-devel@sheep.art.pl> |
|---|---|
| date | Mon, 18 Jun 2012 21:36:08 +0200 |
| parents | 96fffc932ba7 |
| children | ca34c14ea3a8 |
| files | 002/007.py 002/spritesheet.png 003/000.py 003/001.py 003/002.py 003/003.py 003/004.py 003/005.py 003/006.py 003/control.png 003/effects.png 003/level.tmx 003/pytmx/__init__.py 003/pytmx/constants.py 003/pytmx/pytmx.py 003/pytmx/tmxloader.py 003/pytmx/tmxloader3.py 003/pytmx/utils.py 003/spritesheet.png 003/tilesheet.png |
| diffstat | 20 files changed, 2889 insertions(+), 144 deletions(-) [+] |
line wrap: on
line diff
--- a/002/007.py Sun Jun 17 22:37:30 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,144 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import pygame -import pygame.locals as pg -from pytmx import tmxloader - - -def load_sheet(filename, width=32, height=None): - if height is None: - height = width - image = pygame.image.load(filename) - image = image.convert() - image.set_colorkey((0xff, 0, 0xff)) - w, h = image.get_size() - images = [] - for x in xrange(w // width): - for y in xrange(h // height): - rect = pygame.rect.Rect(x * width, y * height, width, height) - images.append(image.subsurface(rect)) - return images - - -class HeroSprite(pygame.sprite.Sprite): - def __init__(self, (x, y)): - super(HeroSprite, self).__init__() - self.spritesheet = load_sheet('spritesheet.png', 64) - self.image = self.spritesheet[2] - self.rect = self.image.get_rect() - self.rect.midbottom = (x * 32 + 16, y * 32 + 32) - self.animations = [] - - def move(self, (dx, dy)): - self.animations.append(self.animation_slide((dx, dy))) - - def animation_slide(self, (dx, dy)): - for step in xrange(8): - self.rect.move_ip(dx * 4, dy * 4) - yield - - def update(self): - if self.animations: - try: - self.animations[0].next() - except StopIteration: - self.animations.pop(0) - -class Level(object): - def __init__(self, filename): - self.tiled_map = tmxloader.load_pygame(filename) - self.hero_position = 1, 1 - - def render(self, screen): - w = self.tiled_map.tilewidth - h = self.tiled_map.tileheight - get_image = self.tiled_map.getTileImage - for l, layer in enumerate(self.tiled_map.tilelayers): - for y in xrange(self.tiled_map.height): - for x in xrange(self.tiled_map.width): - image = get_image(x, y, l) - if image: - screen.blit(image, (x * w, y * h)) - x, y = self.hero_position - - def get_properties(self, (x, y)): - properties = {} - for l, layer in enumerate(self.tiled_map.tilelayers): - properties.update(self.tiled_map.getTileProperties((x, y, l)) or {}) - return properties - - def is_blocked(self, (x, y)): - return self.get_properties((x, y)).get('block') - - def move_hero(self, (dx, dy)): - x, y = self.hero_position - if not self.is_blocked((x + dx, y + dy)): - self.hero_position = x + dx, y + dy - return True - return False - - -class Game(object): - def __init__(self): - pygame.display.init() - pygame.font.init() - self.screen = pygame.display.set_mode((800, 600)) - self.screen.fill((0x66, 0x66, 0x66)) - self.keys = { - pg.K_UP: (self.move_hero, (0, -1)), - pg.K_DOWN: (self.move_hero, (0, 1)), - pg.K_LEFT: (self.move_hero, (-1, 0)), - pg.K_RIGHT: (self.move_hero, (1, 0)), - pg.K_ESCAPE: (self.quit,), - } - self.sprites = pygame.sprite.RenderUpdates() - - def load_level(self, filename): - self.level = Level(filename) - self.hero_sprite = HeroSprite(self.level.hero_position) - self.sprites.add(self.hero_sprite) - self.background = self.screen.copy() - self.level.render(self.background) - self.screen.blit(self.background, (0, 0)) - self.sprites.draw(self.screen) - pygame.display.update() - - def quit(self): - self.game_over = True - - def move_hero(self, direction): - if self.level.move_hero(direction): - self.hero_sprite.move(direction) - - def control(self): - for event in pygame.event.get(): - if event.type == pg.QUIT: - self.quit() - elif event.type == pg.KEYDOWN: - try: - action = self.keys[event.key] - except KeyError: - pass - else: - action[0](*action[1:]) - - def display(self): - self.sprites.clear(self.screen, self.background) - self.sprites.update() - dirty = self.sprites.draw(self.screen) - pygame.display.update(dirty) - - def run(self): - self.clock = pygame.time.Clock() - self.game_over = False - while not self.game_over: - self.display() - self.control() - self.clock.tick(30) - - -if __name__ == '__main__': - game = Game() - game.load_level('level.tmx') - game.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/000.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,132 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pygame +import pygame.locals as pg +from pytmx import tmxloader + + +def load_sheet(filename, width=32, height=None): + if height is None: + height = width + image = pygame.image.load(filename) + image = image.convert() + image.set_colorkey((0xff, 0, 0xff)) + w, h = image.get_size() + images = [] + for x in xrange(w // width): + for y in xrange(h // height): + rect = pygame.rect.Rect(x * width, y * height, width, height) + images.append(image.subsurface(rect)) + return images + + +class HeroSprite(pygame.sprite.Sprite): + def __init__(self, (x, y)): + super(HeroSprite, self).__init__() + self.spritesheet = load_sheet('spritesheet.png', 64) + self.image = self.spritesheet[2] + self.rect = self.image.get_rect() + self.rect.midbottom = (x * 32 + 16, y * 32 + 32) + + def move(self, (dx, dy)): + self.rect.move_ip(dx * 32, dy * 32) + + +class Level(object): + def __init__(self, filename): + self.tiled_map = tmxloader.load_pygame(filename) + self.hero_position = 1, 1 + + def render(self, screen): + w = self.tiled_map.tilewidth + h = self.tiled_map.tileheight + get_image = self.tiled_map.getTileImage + for l, layer in enumerate(self.tiled_map.tilelayers): + for y in xrange(self.tiled_map.height): + for x in xrange(self.tiled_map.width): + image = get_image(x, y, l) + if image: + screen.blit(image, (x * w, y * h)) + x, y = self.hero_position + + def get_properties(self, (x, y)): + properties = {} + for l, layer in enumerate(self.tiled_map.tilelayers): + properties.update(self.tiled_map.getTileProperties((x, y, l)) or {}) + return properties + + def is_blocked(self, (x, y)): + return self.get_properties((x, y)).get('block') + + def move_hero(self, (dx, dy)): + x, y = self.hero_position + if not self.is_blocked((x + dx, y + dy)): + self.hero_position = x + dx, y + dy + return True + return False + + +class Game(object): + def __init__(self): + pygame.display.init() + pygame.font.init() + self.screen = pygame.display.set_mode((800, 600)) + self.screen.fill((0x66, 0x66, 0x66)) + self.keys = { + pg.K_UP: (self.move_hero, (0, -1)), + pg.K_DOWN: (self.move_hero, (0, 1)), + pg.K_LEFT: (self.move_hero, (-1, 0)), + pg.K_RIGHT: (self.move_hero, (1, 0)), + pg.K_ESCAPE: (self.quit,), + } + self.sprites = pygame.sprite.RenderUpdates() + + def load_level(self, filename): + self.level = Level(filename) + self.hero_sprite = HeroSprite(self.level.hero_position) + self.sprites.add(self.hero_sprite) + self.background = self.screen.copy() + self.level.render(self.background) + self.screen.blit(self.background, (0, 0)) + self.sprites.draw(self.screen) + pygame.display.update() + + def quit(self): + self.game_over = True + + def move_hero(self, direction): + if self.level.move_hero(direction): + self.hero_sprite.move(direction) + + def control(self): + for event in pygame.event.get(): + if event.type == pg.QUIT: + self.quit() + elif event.type == pg.KEYDOWN: + try: + action = self.keys[event.key] + except KeyError: + pass + else: + action[0](*action[1:]) + + def display(self): + self.sprites.clear(self.screen, self.background) + self.sprites.update() + dirty = self.sprites.draw(self.screen) + pygame.display.update(dirty) + + def run(self): + self.clock = pygame.time.Clock() + self.game_over = False + while not self.game_over: + self.display() + self.control() + self.clock.tick(30) + + +if __name__ == '__main__': + game = Game() + game.load_level('level.tmx') + game.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/001.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,144 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pygame +import pygame.locals as pg +from pytmx import tmxloader + + +def load_sheet(filename, width=32, height=None): + if height is None: + height = width + image = pygame.image.load(filename) + image = image.convert() + image.set_colorkey((0xff, 0, 0xff)) + w, h = image.get_size() + images = [] + for x in xrange(w // width): + for y in xrange(h // height): + rect = pygame.rect.Rect(x * width, y * height, width, height) + images.append(image.subsurface(rect)) + return images + + +class HeroSprite(pygame.sprite.Sprite): + def __init__(self, (x, y)): + super(HeroSprite, self).__init__() + self.spritesheet = load_sheet('spritesheet.png', 64) + self.image = self.spritesheet[2] + self.rect = self.image.get_rect() + self.rect.midbottom = (x * 32 + 16, y * 32 + 32) + self.animations = [] + + def move(self, (dx, dy)): + self.animations.append(self.animation_slide((dx, dy))) + + def animation_slide(self, (dx, dy)): + for step in xrange(8): + self.rect.move_ip(dx * 4, dy * 4) + yield + + def update(self): + if self.animations: + try: + self.animations[0].next() + except StopIteration: + self.animations.pop(0) + +class Level(object): + def __init__(self, filename): + self.tiled_map = tmxloader.load_pygame(filename) + self.hero_position = 1, 1 + + def render(self, screen): + w = self.tiled_map.tilewidth + h = self.tiled_map.tileheight + get_image = self.tiled_map.getTileImage + for l, layer in enumerate(self.tiled_map.tilelayers): + for y in xrange(self.tiled_map.height): + for x in xrange(self.tiled_map.width): + image = get_image(x, y, l) + if image: + screen.blit(image, (x * w, y * h)) + x, y = self.hero_position + + def get_properties(self, (x, y)): + properties = {} + for l, layer in enumerate(self.tiled_map.tilelayers): + properties.update(self.tiled_map.getTileProperties((x, y, l)) or {}) + return properties + + def is_blocked(self, (x, y)): + return self.get_properties((x, y)).get('block') + + def move_hero(self, (dx, dy)): + x, y = self.hero_position + if not self.is_blocked((x + dx, y + dy)): + self.hero_position = x + dx, y + dy + return True + return False + + +class Game(object): + def __init__(self): + pygame.display.init() + pygame.font.init() + self.screen = pygame.display.set_mode((800, 600)) + self.screen.fill((0x66, 0x66, 0x66)) + self.keys = { + pg.K_UP: (self.move_hero, (0, -1)), + pg.K_DOWN: (self.move_hero, (0, 1)), + pg.K_LEFT: (self.move_hero, (-1, 0)), + pg.K_RIGHT: (self.move_hero, (1, 0)), + pg.K_ESCAPE: (self.quit,), + } + self.sprites = pygame.sprite.RenderUpdates() + + def load_level(self, filename): + self.level = Level(filename) + self.hero_sprite = HeroSprite(self.level.hero_position) + self.sprites.add(self.hero_sprite) + self.background = self.screen.copy() + self.level.render(self.background) + self.screen.blit(self.background, (0, 0)) + self.sprites.draw(self.screen) + pygame.display.update() + + def quit(self): + self.game_over = True + + def move_hero(self, direction): + if self.level.move_hero(direction): + self.hero_sprite.move(direction) + + def control(self): + for event in pygame.event.get(): + if event.type == pg.QUIT: + self.quit() + elif event.type == pg.KEYDOWN: + try: + action = self.keys[event.key] + except KeyError: + pass + else: + action[0](*action[1:]) + + def display(self): + self.sprites.clear(self.screen, self.background) + self.sprites.update() + dirty = self.sprites.draw(self.screen) + pygame.display.update(dirty) + + def run(self): + self.clock = pygame.time.Clock() + self.game_over = False + while not self.game_over: + self.display() + self.control() + self.clock.tick(30) + + +if __name__ == '__main__': + game = Game() + game.load_level('level.tmx') + game.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/002.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,154 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pygame +import pygame.locals as pg +from pytmx import tmxloader + + +FACING = { + (0, -1): 0, + (-1, 0): 1, + (0, 1): 2, + (1, 0): 3, +} + + +def load_sheet(filename, width=32, height=None): + if height is None: + height = width + image = pygame.image.load(filename) + image = image.convert() + image.set_colorkey((0xff, 0, 0xff)) + w, h = image.get_size() + images = [] + for x in xrange(w // width): + for y in xrange(h // height): + rect = pygame.rect.Rect(x * width, y * height, width, height) + images.append(image.subsurface(rect)) + return images + + +class HeroSprite(pygame.sprite.Sprite): + def __init__(self, (x, y)): + super(HeroSprite, self).__init__() + self.spritesheet = load_sheet('spritesheet.png', 64) + self.image = self.spritesheet[2] + self.rect = self.image.get_rect() + self.rect.midbottom = (x * 32 + 16, y * 32 + 32) + self.animations = [] + + def move(self, (dx, dy)): + self.animations.append(self.animation_slide((dx, dy))) + + def animation_slide(self, (dx, dy)): + facing = FACING[dx, dy] + self.image = self.spritesheet[facing] + for step in xrange(8): + self.rect.move_ip(dx * 4, dy * 4) + yield + + def update(self): + if self.animations: + try: + self.animations[0].next() + except StopIteration: + self.animations.pop(0) + +class Level(object): + def __init__(self, filename): + self.tiled_map = tmxloader.load_pygame(filename) + self.hero_position = 1, 1 + + def render(self, screen): + w = self.tiled_map.tilewidth + h = self.tiled_map.tileheight + get_image = self.tiled_map.getTileImage + for l, layer in enumerate(self.tiled_map.tilelayers): + for y in xrange(self.tiled_map.height): + for x in xrange(self.tiled_map.width): + image = get_image(x, y, l) + if image: + screen.blit(image, (x * w, y * h)) + x, y = self.hero_position + + def get_properties(self, (x, y)): + properties = {} + for l, layer in enumerate(self.tiled_map.tilelayers): + properties.update(self.tiled_map.getTileProperties((x, y, l)) or {}) + return properties + + def is_blocked(self, (x, y)): + return self.get_properties((x, y)).get('block') + + def move_hero(self, (dx, dy)): + x, y = self.hero_position + if not self.is_blocked((x + dx, y + dy)): + self.hero_position = x + dx, y + dy + return True + return False + + +class Game(object): + def __init__(self): + pygame.display.init() + pygame.font.init() + self.screen = pygame.display.set_mode((800, 600)) + self.screen.fill((0x66, 0x66, 0x66)) + self.keys = { + pg.K_UP: (self.move_hero, (0, -1)), + pg.K_DOWN: (self.move_hero, (0, 1)), + pg.K_LEFT: (self.move_hero, (-1, 0)), + pg.K_RIGHT: (self.move_hero, (1, 0)), + pg.K_ESCAPE: (self.quit,), + } + self.sprites = pygame.sprite.RenderUpdates() + + def load_level(self, filename): + self.level = Level(filename) + self.hero_sprite = HeroSprite(self.level.hero_position) + self.sprites.add(self.hero_sprite) + self.background = self.screen.copy() + self.level.render(self.background) + self.screen.blit(self.background, (0, 0)) + self.sprites.draw(self.screen) + pygame.display.update() + + def quit(self): + self.game_over = True + + def move_hero(self, direction): + if self.level.move_hero(direction): + self.hero_sprite.move(direction) + + def control(self): + for event in pygame.event.get(): + if event.type == pg.QUIT: + self.quit() + elif event.type == pg.KEYDOWN: + try: + action = self.keys[event.key] + except KeyError: + pass + else: + action[0](*action[1:]) + + def display(self): + self.sprites.clear(self.screen, self.background) + self.sprites.update() + dirty = self.sprites.draw(self.screen) + pygame.display.update(dirty) + + def run(self): + self.clock = pygame.time.Clock() + self.game_over = False + while not self.game_over: + self.display() + self.control() + self.clock.tick(30) + + +if __name__ == '__main__': + game = Game() + game.load_level('level.tmx') + game.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/003.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,156 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pygame +import pygame.locals as pg +from pytmx import tmxloader + + +FACING = { + (0, -1): 0, + (-1, 0): 1, + (0, 1): 2, + (1, 0): 3, +} + + +def load_sheet(filename, width=32, height=None): + if height is None: + height = width + image = pygame.image.load(filename) + image = image.convert() + image.set_colorkey((0xff, 0, 0xff)) + w, h = image.get_size() + images = [] + for x in xrange(w // width): + for y in xrange(h // height): + rect = pygame.rect.Rect(x * width, y * height, width, height) + images.append(image.subsurface(rect)) + return images + + +class HeroSprite(pygame.sprite.Sprite): + def __init__(self, (x, y)): + super(HeroSprite, self).__init__() + self.spritesheet = load_sheet('spritesheet.png', 64) + self.image = self.spritesheet[2] + self.rect = self.image.get_rect() + self.rect.midbottom = (x * 32 + 16, y * 32 + 32) + self.animations = [] + + def move(self, (dx, dy)): + self.animations.append(self.animation_walk((dx, dy))) + + def animation_walk(self, (dx, dy)): + facing = FACING[dx, dy] + for step in xrange(8): + self.image = self.spritesheet[facing + step * 4 + 4] + self.rect.move_ip(dx * 4, dy * 4) + yield + self.image = self.spritesheet[facing] + + def update(self): + while self.animations: + try: + self.animations[0].next() + return + except StopIteration: + self.animations.pop(0) + +class Level(object): + def __init__(self, filename): + self.tiled_map = tmxloader.load_pygame(filename) + self.hero_position = 1, 1 + + def render(self, screen): + w = self.tiled_map.tilewidth + h = self.tiled_map.tileheight + get_image = self.tiled_map.getTileImage + for l, layer in enumerate(self.tiled_map.tilelayers): + for y in xrange(self.tiled_map.height): + for x in xrange(self.tiled_map.width): + image = get_image(x, y, l) + if image: + screen.blit(image, (x * w, y * h)) + x, y = self.hero_position + + def get_properties(self, (x, y)): + properties = {} + for l, layer in enumerate(self.tiled_map.tilelayers): + properties.update(self.tiled_map.getTileProperties((x, y, l)) or {}) + return properties + + def is_blocked(self, (x, y)): + return self.get_properties((x, y)).get('block') + + def move_hero(self, (dx, dy)): + x, y = self.hero_position + if not self.is_blocked((x + dx, y + dy)): + self.hero_position = x + dx, y + dy + return True + return False + + +class Game(object): + def __init__(self): + pygame.display.init() + pygame.font.init() + self.screen = pygame.display.set_mode((800, 600)) + self.screen.fill((0x66, 0x66, 0x66)) + self.keys = { + pg.K_UP: (self.move_hero, (0, -1)), + pg.K_DOWN: (self.move_hero, (0, 1)), + pg.K_LEFT: (self.move_hero, (-1, 0)), + pg.K_RIGHT: (self.move_hero, (1, 0)), + pg.K_ESCAPE: (self.quit,), + } + self.sprites = pygame.sprite.RenderUpdates() + + def load_level(self, filename): + self.level = Level(filename) + self.hero_sprite = HeroSprite(self.level.hero_position) + self.sprites.add(self.hero_sprite) + self.background = self.screen.copy() + self.level.render(self.background) + self.screen.blit(self.background, (0, 0)) + self.sprites.draw(self.screen) + pygame.display.update() + + def quit(self): + self.game_over = True + + def move_hero(self, direction): + if self.level.move_hero(direction): + self.hero_sprite.move(direction) + + def control(self): + for event in pygame.event.get(): + if event.type == pg.QUIT: + self.quit() + elif event.type == pg.KEYDOWN: + try: + action = self.keys[event.key] + except KeyError: + pass + else: + action[0](*action[1:]) + + def display(self): + self.sprites.clear(self.screen, self.background) + self.sprites.update() + dirty = self.sprites.draw(self.screen) + pygame.display.update(dirty) + + def run(self): + self.clock = pygame.time.Clock() + self.game_over = False + while not self.game_over: + self.display() + self.control() + self.clock.tick(30) + + +if __name__ == '__main__': + game = Game() + game.load_level('level.tmx') + game.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/004.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pygame +import pygame.locals as pg +from pytmx import tmxloader + + +FACING = { + (0, -1): 0, + (-1, 0): 1, + (0, 1): 2, + (1, 0): 3, +} + + +def load_sheet(filename, width=32, height=None): + if height is None: + height = width + image = pygame.image.load(filename) + image = image.convert() + image.set_colorkey((0xff, 0, 0xff)) + w, h = image.get_size() + images = [] + for x in xrange(w // width): + for y in xrange(h // height): + rect = pygame.rect.Rect(x * width, y * height, width, height) + images.append(image.subsurface(rect)) + return images + + +class HeroSprite(pygame.sprite.Sprite): + def __init__(self, (x, y)): + super(HeroSprite, self).__init__() + self.spritesheet = load_sheet('spritesheet.png', 64) + self.image = self.spritesheet[2] + self.rect = self.image.get_rect() + self.rect.midbottom = (x * 32 + 16, y * 32 + 32) + self.animations = [] + + def move(self, (dx, dy)): + self.animations.append(self.animation_walk((dx, dy))) + + def animation_walk(self, (dx, dy)): + facing = FACING[dx, dy] + for step in xrange(8): + self.image = self.spritesheet[facing + step * 4 + 4] + self.rect.move_ip(dx * 4, dy * 4) + yield + self.image = self.spritesheet[facing] + + def update(self): + while self.animations: + try: + self.animations[0].next() + return + except StopIteration: + self.animations.pop(0) + +class Level(object): + def __init__(self, filename): + self.tiled_map = tmxloader.load_pygame(filename) + self.hero_position = 1, 1 + + def render(self, screen): + w = self.tiled_map.tilewidth + h = self.tiled_map.tileheight + get_image = self.tiled_map.getTileImage + for l, layer in enumerate(self.tiled_map.tilelayers): + for y in xrange(self.tiled_map.height): + for x in xrange(self.tiled_map.width): + image = get_image(x, y, l) + if image: + screen.blit(image, (x * w, y * h)) + x, y = self.hero_position + + def get_properties(self, (x, y)): + properties = {} + for l, layer in enumerate(self.tiled_map.tilelayers): + properties.update(self.tiled_map.getTileProperties((x, y, l)) or {}) + return properties + + def is_blocked(self, (x, y)): + return self.get_properties((x, y)).get('block') + + def move_hero(self, (dx, dy)): + x, y = self.hero_position + if not self.is_blocked((x + dx, y + dy)): + self.hero_position = x + dx, y + dy + return True + return False + + +class Game(object): + def __init__(self): + pygame.display.init() + pygame.font.init() + self.screen = pygame.display.set_mode((800, 600)) + self.screen.fill((0x66, 0x66, 0x66)) + self.keys = { + pg.K_UP: (self.move_hero, (0, -1)), + pg.K_DOWN: (self.move_hero, (0, 1)), + pg.K_LEFT: (self.move_hero, (-1, 0)), + pg.K_RIGHT: (self.move_hero, (1, 0)), + pg.K_ESCAPE: (self.quit,), + } + self.sprites = pygame.sprite.RenderUpdates() + self.key = None + + def load_level(self, filename): + self.level = Level(filename) + self.hero_sprite = HeroSprite(self.level.hero_position) + self.sprites.add(self.hero_sprite) + self.background = self.screen.copy() + self.level.render(self.background) + self.screen.blit(self.background, (0, 0)) + self.sprites.draw(self.screen) + pygame.display.update() + + def quit(self): + self.game_over = True + + def move_hero(self, direction): + if self.level.move_hero(direction): + self.hero_sprite.move(direction) + + def control(self): + for event in pygame.event.get(): + if event.type == pg.QUIT: + self.quit() + elif event.type == pg.KEYDOWN: + self.key = event.key + if not self.hero_sprite.animations: + keys = pygame.key.get_pressed() + for key, action in self.keys.iteritems(): + if keys[key] or self.key == key: + action[0](*action[1:]) + break + self.key = None + + def display(self): + self.sprites.clear(self.screen, self.background) + self.sprites.update() + dirty = self.sprites.draw(self.screen) + pygame.display.update(dirty) + + def run(self): + self.clock = pygame.time.Clock() + self.game_over = False + while not self.game_over: + self.display() + self.control() + self.clock.tick(30) + + +if __name__ == '__main__': + game = Game() + game.load_level('level.tmx') + game.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/005.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,166 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pygame +import pygame.locals as pg +from pytmx import tmxloader + + +FACING = { + (0, -1): 0, + (-1, 0): 1, + (0, 1): 2, + (1, 0): 3, +} + + +def load_sheet(filename, width=32, height=None): + if height is None: + height = width + image = pygame.image.load(filename) + image = image.convert() + image.set_colorkey((0xff, 0, 0xff)) + w, h = image.get_size() + images = [] + for x in xrange(w // width): + for y in xrange(h // height): + rect = pygame.rect.Rect(x * width, y * height, width, height) + images.append(image.subsurface(rect)) + return images + + +class HeroSprite(pygame.sprite.Sprite): + def __init__(self, (x, y)): + super(HeroSprite, self).__init__() + self.spritesheet = load_sheet('spritesheet.png', 64) + self.facing = 2 + self.image = self.spritesheet[self.facing] + self.rect = self.image.get_rect() + self.rect.midbottom = (x * 32 + 16, y * 32 + 32) + self.animations = [] + + def move(self, (dx, dy)): + self.facing = FACING[dx, dy] + self.animations.append(self.animation_walk((dx, dy))) + + def animation_walk(self, (dx, dy)): + for step in xrange(8): + self.image = self.spritesheet[self.facing + step * 4 + 4] + self.rect.move_ip(dx * 4, dy * 4) + yield + + def animation_stand(self): + self.image = self.spritesheet[self.facing] + return + yield + + def update(self): + if not self.animations: + self.animations.append(self.animation_stand()) + while self.animations: + try: + self.animations[0].next() + return + except StopIteration: + self.animations.pop(0) + +class Level(object): + def __init__(self, filename): + self.tiled_map = tmxloader.load_pygame(filename) + self.hero_position = 1, 1 + + def render(self, screen): + w = self.tiled_map.tilewidth + h = self.tiled_map.tileheight + get_image = self.tiled_map.getTileImage + for l, layer in enumerate(self.tiled_map.tilelayers): + for y in xrange(self.tiled_map.height): + for x in xrange(self.tiled_map.width): + image = get_image(x, y, l) + if image: + screen.blit(image, (x * w, y * h)) + x, y = self.hero_position + + def get_properties(self, (x, y)): + properties = {} + for l, layer in enumerate(self.tiled_map.tilelayers): + properties.update(self.tiled_map.getTileProperties((x, y, l)) or {}) + return properties + + def is_blocked(self, (x, y)): + return self.get_properties((x, y)).get('block') + + def move_hero(self, (dx, dy)): + x, y = self.hero_position + if not self.is_blocked((x + dx, y + dy)): + self.hero_position = x + dx, y + dy + return True + return False + + +class Game(object): + def __init__(self): + pygame.display.init() + pygame.font.init() + self.screen = pygame.display.set_mode((800, 600)) + self.screen.fill((0x66, 0x66, 0x66)) + self.keys = { + pg.K_UP: (self.move_hero, (0, -1)), + pg.K_DOWN: (self.move_hero, (0, 1)), + pg.K_LEFT: (self.move_hero, (-1, 0)), + pg.K_RIGHT: (self.move_hero, (1, 0)), + pg.K_ESCAPE: (self.quit,), + } + self.sprites = pygame.sprite.RenderUpdates() + self.key = None + + def load_level(self, filename): + self.level = Level(filename) + self.hero_sprite = HeroSprite(self.level.hero_position) + self.sprites.add(self.hero_sprite) + self.background = self.screen.copy() + self.level.render(self.background) + self.screen.blit(self.background, (0, 0)) + self.sprites.draw(self.screen) + pygame.display.update() + + def quit(self): + self.game_over = True + + def move_hero(self, direction): + if self.level.move_hero(direction): + self.hero_sprite.move(direction) + + def control(self): + for event in pygame.event.get(): + if event.type == pg.QUIT: + self.quit() + elif event.type == pg.KEYDOWN: + self.key = event.key + if not self.hero_sprite.animations: + keys = pygame.key.get_pressed() + for key, action in self.keys.iteritems(): + if keys[key] or self.key == key: + action[0](*action[1:]) + break + self.key = None + + def display(self): + self.sprites.clear(self.screen, self.background) + self.sprites.update() + dirty = self.sprites.draw(self.screen) + pygame.display.update(dirty) + + def run(self): + self.clock = pygame.time.Clock() + self.game_over = False + while not self.game_over: + self.display() + self.control() + self.clock.tick(30) + + +if __name__ == '__main__': + game = Game() + game.load_level('level.tmx') + game.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/006.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,186 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pygame +import pygame.locals as pg +from pytmx import tmxloader + + +FACING = { + (0, -1): 0, + (-1, 0): 1, + (0, 1): 2, + (1, 0): 3, +} + + +def load_sheet(filename, width=32, height=None): + if height is None: + height = width + image = pygame.image.load(filename) + image = image.convert() + image.set_colorkey((0xff, 0, 0xff)) + w, h = image.get_size() + images = [] + for x in xrange(w // width): + for y in xrange(h // height): + rect = pygame.rect.Rect(x * width, y * height, width, height) + images.append(image.subsurface(rect)) + return images + + +class ShadowSprite(pygame.sprite.Sprite): + def __init__(self, sprite): + super(ShadowSprite, self).__init__() + self.spritesheet = load_sheet('effects.png', 32) + self.image = self.spritesheet[0] + self.rect = self.image.get_rect() + self.image.set_alpha(0.6 * 255) + self.sprite = sprite + self.rect.midbottom = self.sprite.rect.midbottom + + def update(self): + self.rect.midbottom = self.sprite.rect.midbottom + + +class HeroSprite(pygame.sprite.Sprite): + def __init__(self, (x, y)): + super(HeroSprite, self).__init__() + self.spritesheet = load_sheet('spritesheet.png', 64) + self.facing = 2 + self.image = self.spritesheet[self.facing] + self.rect = self.image.get_rect() + self.rect.midbottom = (x * 32 + 16, y * 32 + 32) + self.animations = [] + + def move(self, (dx, dy)): + self.facing = FACING[dx, dy] + self.animations.append(self.animation_walk((dx, dy))) + + def animation_walk(self, (dx, dy)): + for step in xrange(8): + self.image = self.spritesheet[self.facing + step * 4 + 4] + self.rect.move_ip(dx * 4, dy * 4) + yield + + def animation_stand(self): + self.image = self.spritesheet[self.facing] + return + yield + + def update(self): + if not self.animations: + self.animations.append(self.animation_stand()) + while self.animations: + try: + self.animations[0].next() + return + except StopIteration: + self.animations.pop(0) + +class Level(object): + def __init__(self, filename): + self.tiled_map = tmxloader.load_pygame(filename) + self.hero_position = 1, 1 + + def render(self, screen): + w = self.tiled_map.tilewidth + h = self.tiled_map.tileheight + get_image = self.tiled_map.getTileImage + for l, layer in enumerate(self.tiled_map.tilelayers): + for y in xrange(self.tiled_map.height): + for x in xrange(self.tiled_map.width): + image = get_image(x, y, l) + if image: + screen.blit(image, (x * w, y * h)) + x, y = self.hero_position + + def get_properties(self, (x, y)): + properties = {} + for l, layer in enumerate(self.tiled_map.tilelayers): + properties.update(self.tiled_map.getTileProperties((x, y, l)) or {}) + return properties + + def is_blocked(self, (x, y)): + return self.get_properties((x, y)).get('block') + + def move_hero(self, (dx, dy)): + x, y = self.hero_position + if not self.is_blocked((x + dx, y + dy)): + self.hero_position = x + dx, y + dy + return True + return False + + +class Game(object): + def __init__(self): + pygame.display.init() + pygame.font.init() + self.screen = pygame.display.set_mode((800, 600)) + self.screen.fill((0x66, 0x66, 0x66)) + self.keys = { + pg.K_UP: (self.move_hero, (0, -1)), + pg.K_DOWN: (self.move_hero, (0, 1)), + pg.K_LEFT: (self.move_hero, (-1, 0)), + pg.K_RIGHT: (self.move_hero, (1, 0)), + pg.K_ESCAPE: (self.quit,), + } + self.shadow_sprites = pygame.sprite.RenderUpdates() + self.sprites = pygame.sprite.RenderUpdates() + self.key = None + + def load_level(self, filename): + self.level = Level(filename) + self.hero_sprite = HeroSprite(self.level.hero_position) + self.sprites.add(self.hero_sprite) + self.shadow_sprites.add(ShadowSprite(self.hero_sprite)) + self.background = self.screen.copy() + self.level.render(self.background) + self.screen.blit(self.background, (0, 0)) + self.sprites.draw(self.screen) + pygame.display.update() + + def quit(self): + self.game_over = True + + def move_hero(self, direction): + if self.level.move_hero(direction): + self.hero_sprite.move(direction) + + def control(self): + for event in pygame.event.get(): + if event.type == pg.QUIT: + self.quit() + elif event.type == pg.KEYDOWN: + self.key = event.key + if not self.hero_sprite.animations: + keys = pygame.key.get_pressed() + for key, action in self.keys.iteritems(): + if keys[key] or self.key == key: + action[0](*action[1:]) + break + self.key = None + + def display(self): + self.sprites.clear(self.screen, self.background) + self.shadow_sprites.clear(self.screen, self.background) + self.sprites.update() + self.shadow_sprites.update() + dirty = [] + dirty.extend(self.shadow_sprites.draw(self.screen)) + dirty.extend(self.sprites.draw(self.screen)) + pygame.display.update(dirty) + + def run(self): + self.clock = pygame.time.Clock() + self.game_over = False + while not self.game_over: + self.display() + self.control() + self.clock.tick(30) + + +if __name__ == '__main__': + game = Game() + game.load_level('level.tmx') + game.run()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/level.tmx Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<map version="1.0" orientation="orthogonal" width="25" height="18" tilewidth="32" tileheight="32"> + <tileset firstgid="1" name="control" tilewidth="32" tileheight="32"> + <properties> + <property name="control" value="1"/> + </properties> + <image source="control.png" trans="ff00ff" width="64" height="32"/> + <tile id="0"> + <properties> + <property name="block" value="1"/> + </properties> + </tile> + <tile id="1"> + <properties> + <property name="block" value="1"/> + </properties> + </tile> + </tileset> + <tileset firstgid="3" name="tilesheet" tilewidth="32" tileheight="32"> + <image source="tilesheet.png" trans="ff00ff" width="288" height="192"/> + </tileset> + <layer name="Control" width="25" height="18"> + <data encoding="base64" compression="zlib"> + eJxjZGBgYKQDpiUY7nYwUYBH7Ri1Y9SOoWEHNcFws4PWGABJRwF/ + </data> + </layer> + <layer name="Background" width="25" height="18"> + <data encoding="base64" compression="zlib"> + eJyTZ2BgkKcDVqIhHu52UDPcR6Id+MJ61A762zFc0hW17aBHWTJU7aA1BgDmfzlJ + </data> + </layer> + <layer name="Corners" width="25" height="18"> + <data encoding="base64" compression="zlib"> + eJy1VdtqwzAMFWQbFHaBwNq1DyFhD92lD7Zr//+v1WY65ERTRyFMIOzKro6OJCtJRJL8SNA1qi3KLNG5x8L/xd1E+7uqH/+g94Q7OnF1tM/mrNBanDt91XPVQeZcjHT/tep2he4Ia9A1EI9i7EJ7rk3642yk/UB+rN1KvNEGX4+Or2YveoYcIta2f6u6d/RAPsDnVh5BMXA+mVizxvRu4mFfmeJFPc4OP+RjonPwFcXge168ydiBLbJ8g5P63hAOYzw4voABns3ey9xXQZb8OVdFOWXFsPMAGB3xQJ3whry++qr67ehJzzE/PB4Wm+0v9H87n7jO3BuoLXLqYXTyu+acMwj7tdjwhXeAvDOPo6ybg5+y7FGuB3qlSSa+ntgZCWk+evrN7+NJ1s3Ba/qsWODC3xbuRc47vjtB5tlh5yMk0noBeX8qEg== + </data> + </layer> +</map>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/pytmx/__init__.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,5 @@ +from tmxloader import load_tmx, load_pygame +from pytmx import TiledElement, TiledMap, TiledTileset, TiledLayer, \ + TiledObjectGroup, TiledObject +from utils import buildDistributionRects +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/pytmx/constants.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,11 @@ +# internal flags +TRANS_FLIPX = 1 +TRANS_FLIPY = 2 +TRANS_ROT = 4 + + +# Tiled gid flags +GID_TRANS_FLIPX = 1<<31 +GID_TRANS_FLIPY = 1<<30 +GID_TRANS_ROT = 1<<29 +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/pytmx/pytmx.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,572 @@ +from itertools import chain, product +from xml.etree import ElementTree +from collections import defaultdict +from utils import decode_gid, types, parse_properties, read_points +from constants import * + + + +""" +Class declarations and other important stuff +""" + + + +class TiledElement(object): + def set_properties(self, node): + """ + read the xml attributes and tiled "properties" from a xml node and fill + in the values into the object's dictionary. Names will be checked to + make sure that they do not conflict with reserved names. + """ + + # set the attributes reserved for tiled + [ setattr(self, k, types[str(k)](v)) for (k,v) in node.items() ] + + # set the attributes that are derived from tiled 'properties' + for k,v in parse_properties(node).items(): + if k in self.reserved: + msg = "{} \"{}\" has a property called \"{}\"" + print msg.format(self.__class__.__name__, self.name, k, self.__class__.__name__) + msg = "This name is reserved for {} objects and can cannot be used." + print msg.format(self.__class__.__name__) + print "Please change the name in Tiled and try again." + print v + raise ValueError + setattr(self, k, types[str(k)](v)) + + +class TiledMap(TiledElement): + """ + Contains the tile layers, tile images, object groups, and objects from a + Tiled TMX map. + """ + + reserved = "version orientation width height tilewidth tileheight properties tileset layer objectgroup".split() + + + def __init__(self, filename): + from collections import defaultdict + + TiledElement.__init__(self) + self.tilesets = [] # list of TiledTileset objects + self.tilelayers = [] # list of TiledLayer objects + self.objectgroups = [] # list of TiledObjectGroup objects + self.tile_properties = {} # dict of tiles that have metadata + self.filename = filename + + # only used tiles are actually loaded, so there will be a difference + # between the GID's in the Tile map data (tmx) and the data in this + # class and the layers. This dictionary keeps track of that difference. + self.gidmap = defaultdict(list) + + # should be filled in by a loader function + self.images = [] + + # defaults from the TMX specification + self.version = 0.0 + self.orientation = None + self.width = 0 + self.height = 0 + self.tilewidth = 0 + self.tileheight = 0 + + self.imagemap = {} # mapping of gid and trans flags to real gids + self.maxgid = 1 + + self.load() + + + def __repr__(self): + return "<{}: \"{}\">".format(self.__class__.__name__, self.filename) + + + def getTileImage(self, x, y, layer): + """ + return the tile image for this location + x and y must be integers and are in tile coordinates, not pixel + + return value will be 0 if there is no tile with that location. + """ + + try: + gid = self.tilelayers[int(layer)].data[int(y)][int(x)] + except (IndexError, ValueError): + msg = "Coords: ({0},{1}) in layer {2} is not valid." + raise Exception, msg.format(x, y, layer) + except TypeError: + msg = "Tiles must be specified in integers." + raise TypeError, msg + + else: + try: + return self.images[gid] + except (IndexError, ValueError): + msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}" + raise Exception, msg.format(x, y, layer, gid) + + + def getTileGID(self, x, y, layer): + """ + return GID of a tile in this location + x and y must be integers and are in tile coordinates, not pixel + """ + + try: + return self.tilelayers[int(layer)].data[int(y)][int(x)] + except (IndexError, ValueError): + msg = "Coords: ({0},{1}) in layer {2} is invalid" + raise Exception, msg.format(x, y, layer) + + + def getDrawOrder(self): + """ + return a list of objects in the order that they should be drawn + this will also exclude any layers that are not set to visible + + may be useful if you have objects and want to control rendering + from tiled + """ + + raise NotImplementedError + + + def getTileImages(self, r, layer): + """ + return a group of tiles in an area + expects a pygame rect or rect-like list/tuple + + useful if you don't want to repeatedly call getTileImage + """ + + raise NotImplementedError + + + def getObjects(self): + """ + Return iterator of all the objects associated with this map + """ + + return chain(*(i for i in self.objectgroups)) + + + def getTileProperties(self, (x, y, layer)): + """ + return the properties for the tile, if any + x and y must be integers and are in tile coordinates, not pixel + + returns a dict of there are properties, otherwise will be None + """ + + try: + gid = self.tilelayers[int(layer)].data[int(y)][int(x)] + except (IndexError, ValueError): + msg = "Coords: ({0},{1}) in layer {2} is invalid." + raise Exception, msg.format(x, y, layer) + + else: + try: + return self.tile_properties[gid] + except (IndexError, ValueError): + msg = "Coords: ({0},{1}) in layer {2} has invaid GID: {3}" + raise Exception, msg.format(x, y, layer, gid) + except KeyError: + return None + + + def getLayerData(self, layer): + """ + Return the data for a layer. + + Data is an array of arrays. + + >>> pos = data[y][x] + """ + + try: + return self.tilelayers[layer].data + except IndexError: + msg = "Layer {} does not exist." + raise ValueError, msg.format(layer) + + + def getTileLocation(self, gid): + # experimental way to find locations of a tile by the GID + + p = product(xrange(self.width), + xrange(self.height), + xrange(len(self.tilelayers))) + + return [ (x,y,l) for (x,y,l) in p + if self.tilelayers[l].data[y][x] == gid ] + + + def getTilePropertiesByGID(self, gid): + try: + return self.tile_properties[gid] + except KeyError: + return None + + + def setTileProperties(self, gid, d): + """ + set the properties of a tile by GID. + must use a standard python dict as d + """ + + try: + self.tile_properties[gid] = d + except KeyError: + msg = "GID #{} does not exist." + raise ValueError, msg.format(gid) + + + def getTilePropertiesByLayer(self, layer): + """ + Return a list of tile properties (dict) in use in this tile layer. + """ + + try: + layer = int(layer) + except: + msg = "Layer must be an integer. Got {} instead." + raise ValueError, msg.format(type(layer)) + + p = product(range(self.width),range(self.height)) + layergids = set( self.tilelayers[layer].data[y][x] for x, y in p ) + + props = [] + for gid in layergids: + try: + props.append((gid, self.tile_properties[gid])) + except: + continue + + return props + + + def registerGID(self, real_gid, flags=0): + """ + used to manage the mapping of GID between the tmx data and the internal + data. + + number returned is gid used internally + """ + + if real_gid: + try: + return self.imagemap[(real_gid, flags)][0] + except KeyError: + # this tile has not been encountered before, or it has been + # transformed in some way. make a new GID for it. + gid = self.maxgid + self.maxgid += 1 + self.imagemap[(real_gid, flags)] = (gid, flags) + self.gidmap[real_gid].append((gid, flags)) + return gid + + else: + return 0 + + + + def mapGID(self, real_gid): + """ + used to lookup a GID read from a TMX file's data + """ + + try: + return self.gidmap[int(real_gid)] + except KeyError: + return None + except TypeError: + msg = "GID's must be an integer" + raise TypeError, msg + + + def loadTileImages(self, filename): + raise NotImplementedError + + + def load(self): + """ + parse a map node from a tiled tmx file + """ + etree = ElementTree.parse(self.filename).getroot() + self.set_properties(etree) + + # initialize the gid mapping + self.imagemap[(0,0)] = 0 + + for node in etree.findall('layer'): + self.tilelayers.append(TiledLayer(self, node)) + + for node in etree.findall('objectgroup'): + self.objectgroups.append(TiledObjectGroup(self, node)) + + for node in etree.findall('tileset'): + self.tilesets.append(TiledTileset(self, node)) + + # "tile objects", objects with a GID, have need to have their + # attributes set after the tileset is loaded + for o in self.getObjects(): + p = self.getTilePropertiesByGID(o.gid) + if p: + o.name = "TileObject" + o.__dict__.update(p) + + + @property + def visibleTileLayers(self): + return [layer for layer in self.tilelayers if layer.visible] + + +class TiledTileset(TiledElement): + reserved = "firstgid source name tilewidth tileheight spacing margin image tile properties".split() + + def __init__(self, parent, node): + TiledElement.__init__(self) + self.parent = parent + + # defaults from the specification + self.firstgid = 0 + self.source = None + self.name = None + self.tilewidth = 0 + self.tileheight = 0 + self.spacing = 0 + self.margin = 0 + self.tiles = {} + + self.parse(node) + + def __repr__(self): + return "<{}: \"{}\">".format(self.__class__.__name__, self.name) + + + def parse(self, node): + """ + parse a tileset element and return a tileset object and properties for + tiles as a dict + + a bit of mangling is doew here so that tilesets that have external + TSX files appear the same as those that don't + """ + import os + + + # if true, then node references an external tileset + source = node.get('source', False) + if source: + if source[-4:].lower() == ".tsx": + + # external tilesets don't save this, store it for later + self.firstgid = int(node.get('firstgid')) + + # we need to mangle the path - tiled stores relative paths + dirname = os.path.dirname(self.parent.filename) + path = os.path.abspath(os.path.join(dirname, source)) + try: + node = ElementTree.parse(path).getroot() + except IOError: + msg = "Cannot load external tileset: {}" + raise Exception, msg.format(path) + + else: + msg = "Found external tileset, but cannot handle type: {}" + raise Exception, msg.format(self.source) + + self.set_properties(node) + + # since tile objects [probably] don't have a lot of metadata, + # we store it seperately in the parent (a TiledMap instance) + for child in node.iter('tile'): + real_gid = int(child.get("id")) + p = parse_properties(child) + p['width'] = self.tilewidth + p['height'] = self.tileheight + for gid, flags in self.parent.mapGID(real_gid + self.firstgid): + self.parent.setTileProperties(gid, p) + + + image_node = node.find('image') + self.source = image_node.get('source') + self.trans = image_node.get("trans", None) + + +class TiledLayer(TiledElement): + reserved = "name x y width height opacity properties data".split() + + def __init__(self, parent, node): + TiledElement.__init__(self) + self.parent = parent + self.data = [] + + # defaults from the specification + self.name = None + self.opacity = 1.0 + self.visible = True + + self.parse(node) + + + def __repr__(self): + return "<{}: \"{}\">".format(self.__class__.__name__, self.name) + + + def parse(self, node): + """ + parse a layer element + """ + from utils import group + from itertools import product, imap + from struct import unpack + import array + + self.set_properties(node) + + data = None + next_gid = None + + data_node = node.find('data') + + encoding = data_node.get("encoding", None) + if encoding == "base64": + from base64 import decodestring + data = decodestring(data_node.text.strip()) + + elif encoding == "csv": + next_gid = imap(int, "".join( + line.strip() for line in data_node.text.strip() + ).split(",")) + + elif encoding: + msg = "TMX encoding type: {} is not supported." + raise Exception, msg.format(encoding) + + compression = data_node.get("compression", None) + if compression == "gzip": + from StringIO import StringIO + import gzip + with gzip.GzipFile(fileobj=StringIO(data)) as fh: + data = fh.read() + + elif compression == "zlib": + import zlib + data = zlib.decompress(data) + + elif compression: + msg = "TMX compression type: {} is not supported." + raise Exception, msg.format(str(attr["compression"])) + + # if data is None, then it was not decoded or decompressed, so + # we assume here that it is going to be a bunch of tile elements + # TODO: this will probably raise an exception if there are no tiles + if encoding == next_gid == None: + def get_children(parent): + for child in parent.findall('tile'): + yield int(child.get('gid')) + + next_gid = get_children(data_node) + + elif data: + # data is a list of gid's. cast as 32-bit ints to format properly + # create iterator to effeciently parse data + next_gid=imap(lambda i:unpack("<L", "".join(i))[0], group(data, 4)) + + # using bytes here limits the layer to 256 unique tiles + # may be a limitation for very detailed maps, but most maps are not + # so detailed. + [ self.data.append(array.array("B")) for i in xrange(self.height) ] + + for (y, x) in product(xrange(self.height), xrange(self.width)): + self.data[y].append(self.parent.registerGID(*decode_gid(next(next_gid)))) + + +class TiledObjectGroup(TiledElement, list): + """ + Stores TiledObjects. Supports any operation of a normal list. + """ + reserved = "name color x y width height opacity object properties".split() + + def __init__(self, parent, node): + TiledElement.__init__(self) + self.parent = parent + + # defaults from the specification + self.name = None + + self.parse(node) + + + def __repr__(self): + return "<{}: \"{}\">".format(self.__class__.__name__, self.name) + + + def parse(self, node): + """ + parse a objectgroup element and return a object group + """ + + self.set_properties(node) + + for child in node.findall('object'): + o = TiledObject(self.parent, child) + self.append(o) + + +class TiledObject(TiledElement): + reserved = "name type x y width height gid properties polygon polyline image".split() + + def __init__(self, parent, node): + TiledElement.__init__(self) + self.parent = parent + + # defaults from the specification + self.name = None + self.type = None + self.x = 0 + self.y = 0 + self.width = 0 + self.height = 0 + self.gid = 0 + + self.parse(node) + + + def __repr__(self): + return "<{}: \"{}\">".format(self.__class__.__name__, self.name) + + + def parse(self, node): + self.set_properties(node) + + # correctly handle "tile objects" (object with gid set) + if self.gid: + self.gid = self.parent.registerGID(self.gid) + + polygon = node.find('polygon') + if polygon is not None: + x1 = x2 = y1 = y2 = 0 + self.points = read_points(polygon.get('points')) + self.closed = 1 + for x, y in self.points: + if x < x1: x1 = x + if x > x2: x2 = x + if y < y1: y1 = y + if y > y2: y2 = y + self.width = abs(x1) + abs(x2) + self.height = abs(y1) + abs(y2) + + polyline = node.find('polyline') + if polyline is not None: + x1 = x2 = y1 = y2 = 0 + self.points = read_points(polyline.get('points')) + self.closed = 0 + for x, y in self.points: + if x < x1: x1 = x + if x > x2: x2 = x + if y < y1: y1 = y + if y > y2: y2 = y + self.width = abs(x1) + abs(x2) + self.height = abs(y1) + abs(y2)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/pytmx/tmxloader.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,304 @@ +""" +Map loader for TMX Files +bitcraft (leif dot theden at gmail.com) +v.15 - for python 2.7 + +If you have any problems or suggestions, please contact me via email. +Tested with Tiled 0.8.1 for Mac. + +released under the LGPL v3 + +=============================================================================== + +This map loader can be used to load maps created in the Tiled map editor. It +provides a simple way to get tiles and associated metadata so that you can draw +a map onto the screen. It does not force you to draw your map in any +particular way. + +This is *not* a rendering engine. It will load the data that is necessary to +render a map onto the screen. All tiles will be loaded into in memory and +available to blit onto the screen. + + +Design Goals: + Simple api + Memory efficient and fast + +Features: + Loads data and "properties" metadata from Tile's TMX format + "Properties" for: maps, tilesets, layers, objectgroups, objects, and tiles + Point data for polygon and polyline objects + Automatic flipping and rotation of tiles + Supports base64, csv, gzip, zlib and uncompressed XML + Image loading with pygame + +New in .15: + loader: fixed issue where objects with tile gid did not load properties + loader: polygon and polyline objects + loader: new lookup methods use iterators + loader: loading function moved into classes + loader: data/images can be reloaded on the fly + loader: uses etree for faster xml parsing + +New in .14: + loader: Fixed gid lookup for "buildDistributionRects" + loader: Added useful output to a few classes "__repr__" + loader: Fixed a gid mapping issue that broke rotated tiles + pygame: fixed colorkey handling + pygame: correctly handles margins and spacing between tiles in tilesets + pygame: b/c of changes, now correctly renders tiled's example maps + added scrolling demo + +New in .13: + loader: Renamed "get_tile_image" to "getTileImage" + loader: Removed duplicates returned from getTilePropertiesByLayer + loader: Modified confusing messages for GID errors + loader: Fixed bug where transformed tile properties are not available + loader: No longer loads metadata for tiles that are not used + loader: Reduced tile cache to 256 unique tiles + loader: Removed 'visible' from list of reserved words + loader: Added 'buildDistributionRects' and maputils module + loader: Added some misc. functions for retrieving properties + pygame: Smarter tile management made tile loading cache useless; removed it + pygame: pygame.RLEACCEL flag added when appropriate + +New in .12: + loader: Fixed bug where tile properties could contain reserved words + loader: Reduced size of image index by only allocating space for used tiles + +New in .11: + loader: Added support for tileset properties + loader: Now checks for property names that are reserved for internal use + loader: Added support for rotated tiles + pygame: Only the tiles that are used in the map will be loaded into memory + pygame: Added support for rotated tiles + pygame: Added option to force a bitsize (depth) for surfaces + pygame: Added option to convert alpha transparency to colorkey transparency + pygame: Tilesets no longer load with per-pixel alphas by default + pygame: Colorkey transparency should be correctly handled now + + +NOTES: + +* The Tiled "properties" have reserved names. + +If you use "properties" for any of the following object types, you cannot use +any of theese words as a name for your property. A ValueError will be raised +if there are any conflicts. + +As of 0.8.1, these values are: + +map: version, orientation, width, height, tilewidth, tileheight + properties, tileset, layer, objectgroup + +tileset: firstgid, source, name, tilewidth, tileheight, spacing, margin, + image, tile, properties + +tile: id, image, properties + +layer: name, x, y, width, height, opacity, properties, data + +objectgroup: name, color, x, y, width, height, opacity, object, properties + +object: name, type, x, y, width, height, gid, properties, polygon, + polyline, image + + + +I have been intentionally not including a module rendering utility since +rendering a map will have different requirements for every situation. However, +I can appreciate that some poeple won't understand how it works unless they see +it, so I am including a sample map and viewer. It includes a scrolling/zooming +renderer. They are for demonstation purposes, and may not be suitable for all +projects. Use at your own risk. + +I've also included a copy of this loader that may work with python 3.x. I +personally do not think that python 3.x should be used with pygame, yet (and I +am not the only person). You can try it if you insist on using pygame with +python 3.x, but I don't update that often. + +=============================================================================== + +Basic usage sample: + + >>> from pytmx import tmxloader + >>> tmxdata = tmxloader.load_pygame("map.tmx") + + +When you want to draw tiles, you simply call "getTileImage": + + >>> image = tmxdata.getTileImage(x, y, layer) + >>> screen.blit(position, image) + + +Maps, tilesets, layers, objectgroups, and objects all have a simple way to +access metadata that was set inside tiled: they all become object attributes. + + >>> layer = tmxdata.layers[0] + >>> print layer.tilewidth + 32 + >>> print layer.weather + 'sunny' + + +Tiles properties are the exception here, and must be accessed through +"getTileProperties". The data is a regular Python dictionary: + + >>> tile = tmxdata.getTileProperties(x, y, layer) + >>> tile["name"] + 'CobbleStone' + + + +Please see the TiledMap class for some api information. +""" +from utils import types +from constants import * + + + +def load_tmx(filename, *args, **kwargs): + # for .14 compatibility + from pytmx import TiledMap + + tiledmap = TiledMap(filename) + return tiledmap + + +def load_images_pygame(tmxdata, mapping, *args, **kwargs): + """ + due to the way the tiles are loaded, they will be in the same pixel format + as the display when it is loaded. take this into consideration if you + intend to support different screen pixel formats. + + by default, the images will not have per-pixel alphas. this can be + changed by including "pixelalpha=True" in the keywords. this will result + in much slower blitting speeds. + + if the tileset's image has colorkey transparency set in Tiled, the loader + will return images that have their transparency already set. using a + tileset with colorkey transparency will greatly increase the speed of + rendering the map. + + optionally, you can force the loader to strip the alpha channel of the + tileset image and to fill in the missing areas with a color, then use that + new color as a colorkey. the resulting tiles will render much faster, but + will not preserve the transparency of the tile if it uses partial + transparency (which you shouldn't be doing anyway, this is SDL). + """ + from itertools import product + from pygame import Surface, mask + import pygame, os + + + def handle_transformation(tile, flags): + if flags: + fx = flags & TRANS_FLIPX == TRANS_FLIPX + fy = flags & TRANS_FLIPY == TRANS_FLIPY + r = flags & TRANS_ROT == TRANS_ROT + + if r: + # not sure why the flip is required...but it is. + newtile = pygame.transform.rotate(tile, 270) + newtile = pygame.transform.flip(newtile, 1, 0) + + if fx or fy: + newtile = pygame.transform.flip(newtile, fx, fy) + + elif fx or fy: + newtile = pygame.transform.flip(tile, fx, fy) + + # preserve any flags that may have been lost after the transformation + return newtile.convert(tile) + + else: + return tile + + + pixelalpha = kwargs.get("pixelalpha", False) + force_colorkey = kwargs.get("force_colorkey", False) + force_bitdepth = kwargs.get("depth", False) + + if force_colorkey: + try: + force_colorkey = pygame.Color(*force_colorkey) + except: + msg = "Cannot understand color: {}" + raise Exception, msg.format(force_colorkey) + + tmxdata.images = [0] * tmxdata.maxgid + + for firstgid, t in sorted((t.firstgid, t) for t in tmxdata.tilesets): + path = os.path.join(os.path.dirname(tmxdata.filename), t.source) + + image = pygame.image.load(path) + + w, h = image.get_size() + tile_size = (t.tilewidth, t.tileheight) + real_gid = t.firstgid - 1 + + if t.trans: + tileset_colorkey = pygame.Color("#{}".format(t.trans)) + + # i dont agree with margins and spacing, but i'll support it anyway + # such is life. okay.jpg + tilewidth = t.tilewidth + t.spacing + tileheight = t.tileheight + t.spacing + + # some tileset images may be slightly larger than the tile area + # ie: may include a banner, copyright, ect. this compensates for that + width = ((int((w-t.margin*2) + t.spacing) / tilewidth) * tilewidth) - t.spacing + height = ((int((h-t.margin*2) + t.spacing) / tileheight) * tileheight) - t.spacing + + # using product avoids the overhead of nested loops + p = product(xrange(t.margin, height+t.margin, tileheight), + xrange(t.margin, width+t.margin, tilewidth)) + + for (y, x) in p: + real_gid += 1 + gids = tmxdata.mapGID(real_gid) + if gids == []: continue + + # we do some tests to correctly handle the tile and set the right + # blitting flags. just grab a section of it. + temp = image.subsurface(((x,y), tile_size)) + + # count the number of pixels in the tile that are not transparent + px = mask.from_surface(temp).count() + + # there are no transparent pixels in the image + if px == tile_size[0] * tile_size[1]: + tile = temp.convert() + + # there are transparent pixels, and set to force a colorkey + elif force_colorkey: + tile = Surface(tile_size) + tile.fill(force_colorkey) + tile.blit(temp, (0,0)) + tile.set_colorkey(force_colorkey, pygame.RLEACCEL) + + # there are transparent pixels, and tiled set a colorkey + elif t.trans: + tile = temp.convert() + tile.set_colorkey(tileset_colorkey, pygame.RLEACCEL) + + # there are transparent pixels, and set for perpixel alpha + elif pixelalpha: + tile = temp.convert_alpha() + + # there are transparent pixels, and we won't handle them + else: + tile = temp.convert() + + for gid, flags in gids: + tmxdata.images[gid] = handle_transformation(tile, flags) + + +def load_pygame(filename, *args, **kwargs): + tmxdata = load_tmx(filename, *args, **kwargs) + load_images_pygame(tmxdata, None, *args, **kwargs) + + return tmxdata + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/pytmx/tmxloader3.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,612 @@ +""" +Map loader for TMX Files +bitcraft (leif.theden at gmail.com) +v.7 -- for python 3.x + +If you have any problems, please contact me via email. +Tested with Tiled 0.7.1 for Mac. + +released under the LGPL v3 + +====================================================================== +Design Goals: + Simple api + Memory efficient and fast + Quick access to tiles, attributes, and properties + +Non-Goals: + Rendering + +Works: + Pygame image loading + Map loading with all required types + Properties for all types: maps, layers, objects, tiles + Automatic flipping of tiles + +Todo: + Pygame: test colorkey transparency + + Optimized for maps that do not make heavy use of tile + properties. If i find that it is used a lot then i can rework + it for better performance. + +====================================================================== + +Basic usage sample: + + >>> import tmxloader + >>> tiledmap = tmxloader.load_pygame("map.tmx") + + +When you want to draw tiles, you simply call "get_tile_image": + + >>> image = tiledmap.get_tile_image(x, y, layer) + >>> screen.blit(position, image) + + +Layers, objectgroups, tilesets, and maps all have a simple way to access +metadata that was set inside tiled: they all become class attributes. + + >>> print(layer.tilewidth) + 32 + >>> print(layer.weather) + 'sunny' + + +Tiles are the exception here, and must be accessed through "getTileProperties" +and are regular Python dictionaries: + + >>> tile = tiledmap.getTileProperties(x, y, layer) + >>> tile["name"] + 'CobbleStone' + +""" + +from itertools import chain + + +# internal flags +FLIP_X = 1 +FLIP_Y = 2 + + +# Tiled gid flags +GID_FLIP_X = 1<<31 +GID_FLIP_Y = 1<<30 + + +class TiledElement(object): + pass + +class TiledMap(TiledElement): + """ + not really useful unless "loaded" ie: don't instance directly. + see the pygame loader for inspiration + """ + + def __init__(self): + TiledElement.__init__(self) + self.layers = [] # list of all layer types (tile layers + object layers) + self.tilesets = [] # list of TiledTileset objects + self.tilelayers = [] # list of TiledLayer objects + self.objectgroups = [] # list of TiledObjectGroup objects + self.tile_properties = {} # dict of tiles that have additional metadata (properties) + self.filename = None + + # this is a work around to tiled's strange way of storing gid's + self.images = [0] + + # defaults from the TMX specification + self.version = 0.0 + self.orientation = None + self.width = 0 + self.height = 0 + self.tilewidth = 0 + self.tileheight = 0 + + def get_tile_image(self, x, y, layer): + """ + return the tile image for this location + x and y must be integers and are in tile coordinates, not pixel + + return value will be 0 if there is no tile with that location. + """ + + try: + gid = self.tilelayers[layer].data[y][x] + except (IndexError, ValueError): + msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer) + raise Exception(msg) + + else: + try: + return self.images[gid] + except (IndexError, ValueError): + msg = "Coords: ({0},{1}) in layer {2} has invalid GID: {3}/{4}.".format(x, y, layer, gid, len(self.images)) + raise Exception(msg) + + def getTileGID(self, x, y, layer): + """ + return GID of a tile in this location + x and y must be integers and are in tile coordinates, not pixel + """ + + try: + return self.tilelayers[layer].data[y][x] + except (IndexError, ValueError): + msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer) + raise Exception(msg) + + def getDrawOrder(self): + """ + return a list of objects in the order that they should be drawn + this will also exclude any layers that are not set to visible + + may be useful if you have objects and want to control rendering + from tiled + """ + + raise NotImplementedError + + def getTileImages(self, r, layer): + """ + return a group of tiles in an area + expects a pygame rect or rect-like list/tuple + + usefull if you don't want to repeatedly call get_tile_image + probably not the most effecient way of doing this, but oh well. + """ + + raise NotImplementedError + + def getObjects(self): + """ + Return iterator all of the objects associated with this map + """ + + return chain(*[ i.objects for i in self.objectgroups ]) + + def getTileProperties(self, x, y, layer): + """ + return the properties for the tile, if any + x and y must be integers and are in tile coordinates, not pixel + + returns a dict of there are properties, otherwise will be None + """ + + try: + gid = self.tilelayers[layer].data[y][x] + except (IndexError, ValueError): + msg = "Coords: ({0},{1}) in layer {2} is invalid.".format(x, y, layer) + raise Exception(msg) + + else: + try: + return self.tile_properties[gid] + except (IndexError, ValueError): + msg = "Coords: ({0},{1}) in layer {2} has invaid GID: {3}/{4}.".format(x, y, layer, gid, len(self.images)) + raise Exception(msg) + + def getTilePropertiesByGID(self, gid): + try: + return self.tile_properties[gid] + except KeyError: + return None + +# the following classes get their attributes filled in with the loader + +class TiledTileset(TiledElement): + def __init__(self): + TiledElement.__init__(self) + + # defaults from the specification + self.firstgid = 0 + self.lastgid = 0 + self.name = None + self.tilewidth = 0 + self.tileheight = 0 + self.spacing = 0 + self.margin = 0 + +class TiledLayer(TiledElement): + def __init__(self): + TiledElement.__init__(self) + self.data = None + + # defaults from the specification + self.name = None + self.opacity = 1.0 + self.visible = 1 + +class TiledObjectGroup(TiledElement): + def __init__(self): + TiledElement.__init__(self) + self.objects = [] + + # defaults from the specification + self.name = None + +class TiledObject(TiledElement): + __slots__ = ['name', 'type', 'x', 'y', 'width', 'height', 'gid'] + + def __init__(self): + TiledElement.__init__(self) + + # defaults from the specification + self.name = None + self.type = None + self.x = 0 + self.y = 0 + self.width = 0 + self.height = 0 + self.gid = 0 + + +def load_tmx(filename): + """ + Utility function to parse a Tiled TMX and return a usable object. + Images will not be loaded, so probably not useful to call this directly + + See the load_pygame func for an idea of what to do + """ + + from xml.dom.minidom import parse + from itertools import tee, islice, chain + from collections import defaultdict + from struct import unpack + import array, os + + # used to change the unicode string returned from minidom to + # proper python variable types. + types = { + "version": float, + "orientation": str, + "width": int, + "height": int, + "tilewidth": int, + "tileheight": int, + "firstgid": int, + "source": str, + "name": str, + "spacing": int, + "margin": int, + "source": str, + "trans": str, + "id": int, + "opacity": float, + "visible": bool, + "encoding": str, + "compression": str, + "gid": int, + "type": str, + "x": int, + "y": int, + "value": str, + } + + def pairwise(iterable): + # return a list as a sequence of pairs + a, b = tee(iterable) + next(b, None) + return zip(a, b) + + def group(l, n): + # return a list as a sequence of n tuples + return zip(*[islice(l, i, None, n) for i in range(n)]) + + def parse_properties(node): + """ + parse a node and return a dict that represents a tiled "property" + """ + + d = {} + + for child in node.childNodes: + if child.nodeName == "properties": + for subnode in child.getElementsByTagName("property"): + # the "properties" from tiled's tmx have an annoying + # quality that "name" and "value" is included as part of it. + # so we mangle it to get that stuff out. + d.update(dict(pairwise([ str(i.value) for i in list(subnode.attributes.values()) ]))) + + return d + + def get_properties(node): + """ + parses a node and returns a dict that contains the data from the node's + attributes and any data from "property" elements as well. + """ + + d = {} + + # get tag attributes + d.update(get_attributes(node)) + + # get vlues of the properties element, if any + d.update(parse_properties(node)) + + return d + + def set_properties(obj, node): + """ + read the xml attributes and tiled "properties" from a xml node and fill in + the values into an object's dictionary + """ + + [ setattr(obj, k, v) for k,v in list(get_properties(node).items()) ] + + def get_attributes(node): + """ + get the attributes from a node and fix them to the correct type + """ + + d = defaultdict(lambda:None) + + for k, v in list(node.attributes.items()): + k = str(k) + d[k] = types[k](v) + + return d + + def decode_gid(raw_gid): + # gid's are encoded with extra information + # as of 0.7.0 it determines if the tile should be flipped when rendered + + flags = 0 + if raw_gid & GID_FLIP_X == GID_FLIP_X: flags += FLIP_X + if raw_gid & GID_FLIP_Y == GID_FLIP_Y: flags += FLIP_Y + gid = raw_gid & ~(GID_FLIP_X | GID_FLIP_Y) + + return gid, flags + + + def parse_map(node): + """ + parse a map node from a tiled tmx file + return a tiledmap + """ + + tiledmap = TiledMap() + tiledmap.filename = filename + set_properties(tiledmap, map_node) + + for node in map_node.getElementsByTagName("tileset"): + t, tiles = parse_tileset(node) + tiledmap.tilesets.append(t) + tiledmap.tile_properties.update(tiles) + + for node in dom.getElementsByTagName("layer"): + l = parse_layer(tiledmap.tilesets, node) + tiledmap.tilelayers.append(l) + tiledmap.layers.append(l) + + for node in dom.getElementsByTagName("objectgroup"): + o = parse_objectgroup(node) + tiledmap.objectgroups.append(o) + tiledmap.layers.append(o) + + return tiledmap + + + def parse_tileset(node, firstgid=None): + """ + parse a tileset element and return a tileset object and properties for tiles as a dict + """ + + tileset = TiledTileset() + set_properties(tileset, node) + tiles = {} + + if firstgid != None: + tileset.firstgid = firstgid + + # since tile objects probably don't have a lot of metadata, + # we store it seperately from the class itself + for child in node.childNodes: + if child.nodeName == "tile": + p = get_properties(child) + gid = p["id"] + tileset.firstgid + del p["id"] + tiles[gid] = p + + # check for tiled "external tilesets" + if hasattr(tileset, "source"): + if tileset.source[-4:].lower() == ".tsx": + try: + # we need to mangle the path some because tiled stores relative paths + path = os.path.join(os.path.dirname(filename), tileset.source) + tsx = parse(path) + except IOError: + raise IOError("Cannot load external tileset: " + path) + + tileset_node = tsx.getElementsByTagName("tileset")[0] + tileset, tiles = parse_tileset(tileset_node, tileset.firstgid) + else: + raise Exception("Found external tileset, but cannot handle type: " + tileset.source) + + # if we have an "image" tag, process it here + try: + image_node = node.getElementsByTagName("image")[0] + except IndexError: + pass + else: + attr = get_attributes(image_node) + tileset.source = attr["source"] + tileset.trans = attr["trans"] + + # calculate the number of tiles in this tileset + x, r = divmod(attr["width"], tileset.tilewidth) + y, r = divmod(attr["height"], tileset.tileheight) + + tileset.lastgid = tileset.firstgid + x + y + + return tileset, tiles + + + def parse_layer(tilesets, node): + """ + parse a layer element and return a layer object + + tilesets is required since we need to mangle gid's here + """ + + layer = TiledLayer() + layer.data = [] + layer.flipped_tiles = [] + set_properties(layer, node) + + data = None + next_gid = None + + data_node = node.getElementsByTagName("data")[0] + attr = get_attributes(data_node) + + if attr["encoding"] == "base64": + from base64 import b64decode + data = b64decode(bytes(data_node.lastChild.nodeValue, 'ascii')) + + elif attr["encoding"] == "csv": + next_gid = map(int, "".join([line.strip() for line in data_node.lastChild.nodeValue]).split(",")) + + elif not attr["encoding"] == None: + raise Exception("TMX encoding type: " + str(attr["encoding"]) + " is not supported.") + + if attr["compression"] == "gzip": + from io import BytesIO + import gzip + with gzip.GzipFile(fileobj=BytesIO(data)) as fh: + data = fh.read() + + elif not attr["compression"] == None: + raise Exception("TMX compression type: " + str(attr["compression"]) + " is not supported.") + + # if data is None, then it was not decoded or decompressed, so + # we assume here that it is going to be a bunch of tile elements + if attr["encoding"] == next_gid == None: + def get_children(parent): + for child in parent.getElementsByTagName("tile"): + yield int(child.getAttribute("gid")) + + next_gid = get_children(data_node) + + elif not data == None: + # cast the data as a list of 32-bit integers + def u(i): return unpack("<L", bytes(i))[0] + next_gid = map(u, group(data, 4)) + + gids = [ i.firstgid for i in tilesets ] + + # fill up our 2D array of gid's. + for y in range(layer.height): + + # store as 16-bit ints, since we will never use enough tiles to fill a 32-bit int + layer.data.append(array.array("H")) + + for x in range(layer.width): + gid, flags = decode_gid(next(next_gid)) + if not flags == 0: layer.flipped_tiles.append((x, y, gid, flags)) + layer.data[y].append(gid) + + return layer + + + def parse_objectgroup(node): + """ + parse a objectgroup element and return a object group + """ + + objgroup = TiledObjectGroup() + set_properties(objgroup, node) + + for subnode in node.getElementsByTagName("object"): + obj = TiledObject() + set_properties(obj, subnode) + objgroup.objects.append(obj) + + return objgroup + + + # open and read our TMX (which is really just xml) + dom = parse(filename) + map_node = dom.getElementsByTagName("map")[0] + return parse_map(map_node) + + +def load_pygame(filename): + """ + load a tiled TMX map for use with pygame + """ + + from pygame import Surface + import pygame, os + + tiledmap = load_tmx(filename) + + # cache will find duplicate tiles to reduce memory usage + # mostly this is a problem in the blank areas of a tilemap + cache = {} + + # just a precaution to make sure tileset images are added in the correct order + for firstgid, t in sorted([ (t.firstgid, t) for t in tiledmap.tilesets ]): + path = os.path.join(os.path.dirname(tiledmap.filename), t.source) + + image = pygame.image.load(path) + + w, h = image.get_rect().size + + tile_size = (t.tilewidth, t.tileheight) + + # some tileset images may be slightly larger than the tiles area + # ie: may include a banner, copyright, ect. this compensates for that + for y in range(0, int(h / t.tileheight) * t.tileheight, t.tileheight): + for x in range(0, int(w / t.tilewidth) * t.tilewidth, t.tilewidth): + + # somewhat handle transparency, though colorkey handling is not tested + if t.trans == None: + tile = Surface(tile_size, pygame.SRCALPHA) + else: + tile = Surface(tile_size) + + # blit the smaller portion onto a new image from the larger one + tile.blit(image, (0,0), ((x, y), tile_size)) + + # make a unique id for this image, not sure if this is the best way, but it works + key = pygame.image.tostring(tile, "RGBA") + + # make sure we don't have a duplicate tile + try: + tile = cache[key] + except KeyError: + if t.trans == None: + tile = tile.convert_alpha() + else: + tile = tile.convert() + tile.set_colorkey(t.trans) + + # update the cache + cache[key] = tile + + tiledmap.images.append(tile) + + # correctly handle transformed tiles. currently flipped tiles + # work by creating a new gid for the flipped tile and changing the gid + # in the layer to the new gid. + for layer in tiledmap.tilelayers: + for x, y, gid, trans in layer.flipped_tiles: + fx = trans & FLIP_X == FLIP_X + fy = trans & FLIP_Y == FLIP_Y + + tile = pygame.transform.flip(tiledmap.images[gid], fx, fy) + tiledmap.images.append(tile) + + # change the original gid in the layer data to the new gid + layer.data[y][x] = len(tiledmap.images) - 1 + + del layer.flipped_tiles + del cache + + return tiledmap + +if __name__ == '__main__': + print('[tmxloader] starting built-in test') + load_tmx("../resources/maps/village.tmx")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/003/pytmx/utils.py Mon Jun 18 21:36:08 2012 +0200 @@ -0,0 +1,251 @@ +from pygame import Rect +from itertools import tee, islice, izip, product +from collections import defaultdict +from constants import * + + + +""" +Various code that doesn't really fit anywhere else +""" + + +def read_points(text): + return [ tuple(map(lambda x: int(x), i.split(','))) + for i in text.split() ] + + + +def parse_properties(node): + """ + parse a node and return a dict that represents a tiled "property" + """ + + # the "properties" from tiled's tmx have an annoying quality that "name" + # and "value" is included. here we mangle it to get that junk out. + + d = {} + + for child in node.findall('properties'): + for subnode in child.findall('property'): + d[subnode.get('name')] = subnode.get('value') + + return d + + +def decode_gid(raw_gid): + # gid's are encoded with extra information + # as of 0.7.0 it determines if the tile should be flipped when rendered + # as of 0.8.0 bit 30 determines if GID is rotated + + flags = 0 + if raw_gid & GID_TRANS_FLIPX == GID_TRANS_FLIPX: flags += TRANS_FLIPX + if raw_gid & GID_TRANS_FLIPY == GID_TRANS_FLIPY: flags += TRANS_FLIPY + if raw_gid & GID_TRANS_ROT == GID_TRANS_ROT: flags += TRANS_ROT + gid = raw_gid & ~(GID_TRANS_FLIPX | GID_TRANS_FLIPY | GID_TRANS_ROT) + + return gid, flags + + +def handle_bool(text): + # properly convert strings to a bool + try: + return bool(int(text)) + except: + pass + + try: + text = str(text).lower() + if text == "true": return True + if text == "yes": return True + if text == "false": return False + if text == "no": return False + except: + pass + + raise ValueError + + +# used to change the unicode string returned from xml to proper python +# variable types. +types = defaultdict(lambda: str) +types.update({ + "version": float, + "orientation": str, + "width": int, + "height": int, + "tilewidth": int, + "tileheight": int, + "firstgid": int, + "source": str, + "name": str, + "spacing": int, + "margin": int, + "source": str, + "trans": str, + "id": int, + "opacity": float, + "visible": handle_bool, + "encoding": str, + "compression": str, + "gid": int, + "type": str, + "x": int, + "y": int, + "value": str, +}) + + +def pairwise(iterable): + # return a list as a sequence of pairs + a, b = tee(iterable) + next(b, None) + return izip(a, b) + + +def group(l, n): + # return a list as a sequence of n tuples + return izip(*(islice(l, i, None, n) for i in xrange(n))) + + +def buildDistributionRects(tmxmap, layer, tileset=None, real_gid=None): + """ + generate a set of non-overlapping rects that represents the distribution + of the specfied gid. + + useful for generating rects for use in collision detection + """ + + if isinstance(tileset, int): + try: + tileset = tmxmap.tilesets[tileset] + except IndexError: + msg = "Tileset #{} not found in map {}." + raise IndexError, msg.format(tileset, tmxmap) + + elif isinstance(tileset, str): + try: + tileset = [ t for t in tmxmap.tilesets if t.name == tileset ].pop() + except IndexError: + msg = "Tileset \"{}\" not found in map {}." + raise ValueError, msg.format(tileset, tmxmap) + + elif tileset: + msg = "Tileset must be either a int or string. got: {}" + raise ValueError, msg.format(type(tileset)) + + gid = None + if real_gid: + try: + gid, flags = tmxmap.mapGID(real_gid)[0] + except IndexError: + msg = "GID #{} not found" + raise ValueError, msg.format(real_gid) + + + if isinstance(layer, int): + layer_data = tmxmap.getLayerData(layer).data + elif isinstance(layer, str): + try: + layer = [ l for l in tmxmap.tilelayers if l.name == layer ].pop() + layer_data = layer.data + except IndexError: + msg = "Layer \"{}\" not found in map {}." + raise ValueError, msg.format(layer, tmxmap) + + p = product(xrange(tmxmap.width), xrange(tmxmap.height)) + if gid: + points = [ (x,y) for (x,y) in p if layer_data[y][x] == gid ] + else: + points = [ (x,y) for (x,y) in p if layer_data[y][x] ] + + rects = simplify(points, tmxmap.tilewidth, tmxmap.tileheight) + return rects + + +def simplify(all_points, tilewidth, tileheight): + """ + klugde: + + "A kludge (or kluge) is a workaround, a quick-and-dirty solution, + a clumsy or inelegant, yet effective, solution to a problem, typically + using parts that are cobbled together." + + -- wikipedia + + turn a list of points into a rects + adjacent rects will be combined. + + plain english: + the input list must be a a list of tuples that represent + the areas to be combined into rects + the rects will be blended together over solid groups + + so if data is something like: + + 0 1 1 1 0 0 0 + 0 1 1 0 0 0 0 + 0 0 0 0 0 4 0 + 0 0 0 0 0 4 0 + 0 0 0 0 0 0 0 + 0 0 1 1 1 1 1 + + you'll have the 4 rects that mask the area like this: + + ..######...... + ..####........ + ..........##.. + ..........##.. + .............. + ....########## + + pretty cool, right? + + there may be cases where the number of rectangles is not as low as possible, + but i haven't found that it is excessively bad. certainly much better than + making a list of rects, one for each tile on the map! + + """ + + def pick_rect(points, rects): + ox, oy = sorted([ (sum(p), p) for p in points ])[0][1] + x = ox + y = oy + ex = None + + while 1: + x += 1 + if not (x, y) in points: + if ex == None: + ex = x - 1 + + if ((ox, y+1) in points): + if x == ex + 1 : + y += 1 + x = ox + + else: + y -= 1 + break + else: + if x <= ex: y-= 1 + break + + c_rect = Rect(ox*tilewidth,oy*tileheight,\ + (ex-ox+1)*tilewidth,(y-oy+1)*tileheight) + + rects.append(c_rect) + + rect = Rect(ox,oy,ex-ox+1,y-oy+1) + kill = [ p for p in points if rect.collidepoint(p) ] + [ points.remove(i) for i in kill ] + + if points: + pick_rect(points, rects) + + rect_list = [] + while all_points: + pick_rect(all_points, rect_list) + + return rect_list +
