"""Utility module - image loading, coordinate conversion, and sprite animation.""" import os import pygame as pg IMG_PATH='assets/images' WINDOW_SIZE=(800,600) def load_image(path, colorkey=(0,0,0)): """Load an image and set a colorkey for transparency. Args: path (str): Path to the image relative to ``IMG_PATH``. colorkey (tuple[int,int,int]): RGB colour treated as transparent. Defaults to black. Returns: pg.Surface: The loaded and converted image. """ img = pg.image.load(IMG_PATH + path).convert() img.set_colorkey(colorkey) return img def load_images(dir_path): """Load all images from a directory using colorkey transparency. Args: dir_path (str): Directory path relative to ``IMG_PATH``. Returns: list[pg.Surface]: All images found in the directory, in filesystem order. """ images = [] for img_name in os.listdir(IMG_PATH + dir_path): images.append(load_image(dir_path + '/' + img_name)) return images def load_image_alpha(path): """Load an image preserving its alpha channel. Args: path (str): Path to the image relative to ``IMG_PATH``. Returns: pg.Surface: The loaded image with per-pixel alpha. """ img = pg.image.load(IMG_PATH + path).convert_alpha() return img def load_images_alpha(dir_path): """Load all images from a directory, preserving alpha channels. Args: dir_path (str): Directory path relative to ``IMG_PATH``. Returns: list[pg.Surface]: All images found in the directory, in filesystem order. """ images = [] for img_name in os.listdir(IMG_PATH + dir_path): images.append(load_image_alpha(dir_path + '/' + img_name)) return images def tile_to_pixel(waypoint, tile_size, center=False): """Convert a tile grid position to world-space pixel coordinates. Args: tile_pos (tuple[int, int]): Grid position ``(x, y)``. tile_size (int): Width and height of a single tile in pixels. center (bool): If ``True``, return the centre of the tile. Defaults to ``False``. Returns: tuple[int, int]: Pixel position ``(x, y)``. """ if center: return ( waypoint[0] * tile_size + tile_size // 2, waypoint[1] * tile_size + tile_size // 2 ) return ( waypoint[0] * tile_size, waypoint[1] * tile_size ) def pixel_to_tile(waypoint, tile_size): """Convert a world-space pixel position to tile grid coordinates. Args: pixel_pos (tuple[float, float]): Pixel position ``(x, y)``. tile_size (int): Width and height of a single tile in pixels. Returns: tuple[float, float]: Fractional tile position ``(x, y)``. """ return ( waypoint[0] / tile_size, waypoint[1] / tile_size ) class Animation: """Frame-based sprite animation with optional looping and manual play control. Attributes: images (list[pg.Surface]): Ordered list of animation frames. loop (bool): Whether the animation loops continuously. img_duration (int): Number of update ticks each frame is shown for. done (bool): ``True`` once a non-looping animation has finished. frame (int): Current tick counter used to derive the active frame index. playing (bool): Whether the animation is currently advancing. """ def __init__(self, images, img_dur=5, loop=True): self.images = images self.loop = loop self.img_duration = img_dur self.done = False self.frame = 0 self.playing = False # not playing until told to def copy(self): """Return a fresh copy of this animation, reset to its initial state. Returns: Animation: A new Animation sharing the same images and settings. """ return Animation(self.images, self.img_duration, self.loop) def update(self): """Advance the animation by one tick if it is currently playing.""" if not self.playing: return if self.loop: self.frame = (self.frame + 1) % (self.img_duration * len(self.images)) else: self.frame = min(self.frame + 1, self.img_duration * len(self.images) - 1) if self.frame >= self.img_duration * len(self.images) - 1: self.done = True self.playing = False def play_animation(self): """Start the animation from the first frame.""" self.playing = True self.done = False self.frame = 0 def img(self): """Return the image for the current tick. Returns: pg.Surface: The active animation frame. """ return self.images[self.frame // self.img_duration]