Search code examples
pythonpygametiledpytmxtmxtiledmap

Animated objects launch at a precise time with pytmx, pygame


I am in troubles, I am trying to make doors opening in my game. I am using pygame, and pytmx, I have built a Level made with Rooms, and in each Room I have a Renderer using pytmx, what I want to achieve is for exemple on level 0 the player has to move to a door to open it and enter level 1, my goal is to launch and draw the animation when player hit the door.

I tried to make my Door object (his parent is pygame Sprite class) update, it has kind of worked but of course the object was modified and displayed, but the tiled map object also and was staying in the back of the map, so I tried to just not initialize my objects in the tile map but if so the door doesn't bit at all. Then I tried to modify the tile object image and to reload my rendering but still doesn't work, does anyone of you has any idea ?

Here is some parts of the code to let you have better understanding


"""
This is a test of using the pytmx library with Tiled.
"""
import pygame
import pytmx


class Renderer(object):
    """
    This object renders tile maps from Tiled
    """
    def __init__(self, filename):
        tm = pytmx.load_pygame(filename, pixelalpha=True)
        self.object_images = []
        self.size = tm.width * tm.tilewidth, tm.height * tm.tileheight
        self.tmx_data = tm
        self.map_surface = self.make_map()
        self.current_frame = 0

    def render(self, surface):

        tw = self.tmx_data.tilewidth
        th = self.tmx_data.tileheight
        
        print(self.tmx_data.tile_properties.items())

        if self.tmx_data.background_color:
            surface.fill(self.tmx_data.background_color)

        for layer in self.tmx_data.layers:
            if isinstance(layer, pytmx.TiledTileLayer):
                for x, y, image in layer.tiles():
                    if image:
                        surface.blit(image.convert_alpha() , (x * tw, y * th))

            elif isinstance(layer, pytmx.TiledObjectGroup):
                for object in layer:
                    if object.image:
                        surface.blit(object.image.convert_alpha() , (object.x, object.y))

            elif isinstance(layer, pytmx.TiledImageLayer):
                if image:
                    surface.blit(image , (0, 0))

    def make_map(self):
        temp_surface = pygame.Surface(self.size)
        self.render(temp_surface)
        return temp_surface
    
    def reload(self):
        self.map_surface = self.make_map()
    
    def update_object_image(self, object_id, surface):
            pass
    
    def get_layer(self, layer_name):
        return self.tmx_data.get_layer_by_name(layer_name)

class Room:
    def __init__(self, room_image, level_options):
        self.renderer = Renderer(room_image)
        self.surface = self.renderer.map_surface
        self.rect = self.surface.get_rect()
        self.level_options = level_options
        
        self.obstacles = pygame.sprite.Group()
        self.doors = pygame.sprite.Group()
        
        self.load_obstacles()
        self.load_doors()
        
    def load_obstacles(self):
        obstacles = self.level_options['obstacle_layers']
        for obstacle in obstacles:
            try:
                items = self.renderer.get_layer(obstacle)
                for x, y, image in items.tiles():
                    self.obstacles.add(Obstacle(x, y, image, SPRITE_SIZE))
            except ValueError:
                return
            
    def load_doors(self):
        doors = self.renderer.get_layer(self.level_options['door_layer'])
        
        if isinstance(doors, pytmx.TiledTileLayer):
            for x, y, image in doors.tiles():
                if image:
                    self.doors.add(Door(self, x, y, image, SPRITE_SIZE, self.level_options['open_door_actions']))

        elif isinstance(doors, pytmx.TiledObjectGroup):
            for object in doors:
                self.doors.add(Door(self, object.x, object.y, object.image, SPRITE_SIZE, self.level_options['open_door_actions'], object))
            
    def update_doors(self):
        for door in self.doors:
            if door.object:
                door.update()
                #self.renderer.update_object_image(door.object.id, door.image)
            
class Wall(pygame.sprite.Sprite):
    def __init__(self, x, y, image, sprite_size):
        pygame.sprite.Sprite.__init__(self)
        self.rect = pygame.Rect(x * sprite_size,y * sprite_size, sprite_size, sprite_size)
        self.image = image 
    
class Obstacle(pygame.sprite.Sprite):
    def __init__(self, x, y, image, sprite_size):
        pygame.sprite.Sprite.__init__(self)
        self.rect = pygame.Rect(x * sprite_size,y * sprite_size, sprite_size, sprite_size)
        self.image = image 
        
class Door(pygame.sprite.Sprite):
    def __init__(self, parent, x, y, image, sprite_size, open_actions, object = None):
        self.parent = parent
        pygame.sprite.Sprite.__init__(self)
        
        self.object = object
        
        if self.object:
            self.rect = pygame.Rect(x ,y, self.object.width, self.object.height)
            self.current_index = 0
            self.load_animations()
        else:
            self.rect = pygame.Rect(x * sprite_size,y * sprite_size, sprite_size, sprite_size)
            
        self.image = image 
        self.open_actions = open_actions
        self.last_updated = 0
        self.is_open = False
        self.is_activated = False
        
    def load_animations(self):
        self.animations = []
        filename = os.path.join(os.getcwd(), IMAGES_FOLDER, 'maps', 'animations', self.object.animated_image)
        spritesheet = Spritesheet(filename, self.object.width, self.object.height)
        
        for i in range(spritesheet.data['columns']):
            self.animations.append(spritesheet.parse_sprite(i)) 
            
        self.image = self.animations[self.current_index]
        
    def open_animation(self):
        if self.is_open:
            return
        
        if not self.object or not self.is_activated or not self.animations:
            return
        
        self.image = self.animations[self.current_index]
        
        if self.current_index == len(self.animations) - 1:
            self.is_open = True
        
                
    def update(self):
        if self.is_open:
            if self.object:
                self.image = self.animations[-1]
                
        if self.is_activated:
            if self.object:
                now = pygame.time.get_ticks()
                
                if now - self.last_updated > 200:
                    self.last_updated = now
                    self.current_index = (self.current_index + 1) % len(self.animations)
                    self.image = self.animations[self.current_index]
            
        if not self.is_open:
            self.image = self.animations[0]
        
    def check_doors_state(self, player):
        if 'any' in self.open_actions:
            self.is_activated = True
        
        for action in player.actions:
            if action in self.open_actions:
                self.is_activated = True

So right now what I am trying to do is to use an animated tile object and launch the animation from tmx data, but I can't even understand how to launch the animation at the first rendering.

Thank you in advance for you answers.


Solution

  • So I found a solution, I am not trying to change and reload the object tiles, I simply made my game sprites in groups, modified my generating and rendering of tiles.

    Classes modified :

        
    class Room:
        def __init__(self, level, room_image, level_options):
            self.level = level
            self.renderer = Renderer(room_image)
            self.surface = self.renderer.map_surface
            self.tmx_data = self.renderer.tmx_data
            self.rect = self.surface.get_rect()
            self.level_options = level_options
            
            self.walls = pygame.sprite.Group()
            self.doors = pygame.sprite.Group()
            
            #self.load_walls()
            #self.load_doors()
            
        def load_walls(self):
            walls = self.renderer.get_layer(self.level_options['wall_layer'])
            
            if isinstance(walls, pytmx.TiledObjectGroup):
                for object in walls:
                    self.walls.add(Wall(object.x, object.y, object, SPRITE_SIZE))
        
                
        def load_doors(self):
            doors = self.renderer.get_layer(self.level_options['door_layer'])
            
            if isinstance(doors, pytmx.TiledTileLayer):
                for x, y, image in doors.tiles():
                    if image:
                        self.doors.add(Door(self, x, y, image, SPRITE_SIZE, self.level_options['open_door_actions']))
    
            elif isinstance(doors, pytmx.TiledObjectGroup):
                for object in doors:
                    self.doors.add(Door(self, object.x, object.y, object.image, SPRITE_SIZE, self.level_options['open_door_actions'], object))
        
        def update(self):
            self.doors.update()
            
        def draw(self, display, camera = None):
            self.doors.draw(self.surface)
            
            if not camera:
                display.blit(self.surface, self.rect)
            else:
                display.blit(self.surface, (self.rect.x - camera.offset.x, self.rect.y - camera.offset.y))
        
        def get_opening_actions(self):
            return self.level_options['open_door_actions']
                
            
                
    class Wall(pygame.sprite.Sprite):
        def __init__(self, game, x, y):
            self.groups = game.all_sprites, game.walls
            pygame.sprite.Sprite.__init__(self, self.groups)
            
            self.game = game
            self.image = game.wall_img
            self.rect = self.image.get_rect()
            self.x = x
            self.y = y
            self.rect.x = x * SPRITE_SIZE
            self.rect.y = y * SPRITE_SIZE
            
    class Obstacle(pygame.sprite.Sprite):
        def __init__(self, game, x, y, w, h):
            self.groups = game.walls
            pygame.sprite.Sprite.__init__(self, self.groups)
            self.game = game
            self.rect = pygame.Rect(x, y, w, h)
            self.hit_rect = self.rect
            self.x = x
            self.y = y
            self.rect.x = x
            self.rect.y = y
            
    class Door(pygame.sprite.Sprite):
        def __init__(self, game, x, y, w, h, open_actions, animated_image):
            self.groups = game.all_sprites, game.doors
            pygame.sprite.Sprite.__init__(self, self.groups)
            
            
            self.game = game
            self.rect = pygame.Rect(x ,y, w, h)
            self.current_index = 0
            self.animated_image = animated_image
            
            self.load_animations()
            
            self.open_actions = open_actions
            self.last_updated = 0
            self.is_open = False
            self.is_activated = False
            
        def load_animations(self):
            self.animations = []
            filename = os.path.join(os.getcwd(), IMAGES_FOLDER, 'maps', 'animations', self.animated_image)
            spritesheet = Spritesheet(filename, self.rect.width, self.rect.height)
            
            for i in range(spritesheet.data['columns']):
                self.animations.append(spritesheet.parse_sprite(i)) 
                
            self.image = self.animations[self.current_index]
            
        def open_animation(self):
            if self.is_open:
                return
            
            if not self.is_activated or not self.animations:
                return
            
            now = pygame.time.get_ticks()
            if now - self.last_updated > 15:
                self.last_updated = now
                self.current_index = (self.current_index + 1) % len(self.animations)
                
            self.image = self.animations[self.current_index]
            
            if self.current_index == len(self.animations) - 1:
                self.current_index = -1
                self.is_open = True
            
                    
        def update(self):
            if not self.is_open:
                self.image = self.animations[0]
                
            if self.is_open:
                self.image = self.animations[-1]
                    
                
            
        def check_doors_state(self, player):
            if 'any' in self.open_actions:
                self.is_activated = True
            
            for action in player.actions:
                if action in self.open_actions:
                    self.is_activated = True
    
    

    Sprites groups generating, updating and rendering added (in my game class) :

         
        def new(self, level = 0):
            # initialize all variables and do all the setup for a new game
            self.all_sprites = pygame.sprite.Group()
            self.walls = pygame.sprite.Group()
            self.doors = pygame.sprite.Group()
            self.mobs = pygame.sprite.Group()
    
            ## LOAD LEVEL
            self.level = Level(level)
            self.room = self.level.current_room
            
            for tile_object in self.room.tmx_data.objects:
                if tile_object.name == 'player':
                    self.player = Player(self, tile_object.x, tile_object.y)
                
                if tile_object.name == 'obstacle':
                    Obstacle(self, tile_object.x, tile_object.y, tile_object.width, tile_object.height)
                
                if tile_object.name == 'wall':
                    Wall(self, tile_object.x, tile_object.y, tile_object.width, tile_object.height)
                    
                if tile_object.name == 'door':
                    Door(self, tile_object.x, tile_object.y, tile_object.width, tile_object.height, self.room.get_opening_actions(), tile_object.animated_image)
    
    
        def update(self):
            self.all_sprites.update()
    
        def draw(self):
            
            self.window.fill((0, 0, 0))
            
            ## DISPLAY MAP
            self.level.draw_room(self.window, self.camera)
            
            ## DISPLAY SPRITES
            for sprite in self.all_sprites:
                self.window.blit(sprite.image, (sprite.rect.x - self.camera.offset.x, sprite.rect.y - self.camera.offset.y))
    
            ## REFRESH SCREEN
            pygame.display.flip()
    

    And here is the result (I am working on better hitbox for the player ^^) door opening when player getting close to it