diff --git a/editor.py b/editor.py index 3adf043..a928e1b 100644 --- a/editor.py +++ b/editor.py @@ -1,14 +1,11 @@ import pygame import sys + from scripts.tilemap import Tilemap from scripts.utils import load_images +from scripts.models import Tile -RENDER_SCALE: float = 2.0 - - -class better_list(list): - def __init__(self, iterable): - super().__init__(iterable) +RENDER_SCALE = 2.0 class Editor: @@ -20,10 +17,6 @@ class Editor: self.display: pygame.Surface = pygame.Surface((320, 240)) self.clock: pygame.time.Clock = pygame.time.Clock() - self.running: bool = True - - self.collision_area: pygame.Rect = pygame.Rect(50, 50, 300, 50) - self.movement: list[bool] = [False, False, False, False] self.assets: dict[str, list[pygame.Surface]] = { "decor": load_images("tiles/decor"), @@ -32,54 +25,62 @@ class Editor: "stone": load_images("tiles/stone"), } - self.tilemap: Tilemap = Tilemap(self, 16) + self.movement: list[bool] = [False, False, False, False] + + self.tilemap: Tilemap = Tilemap(self, tile_size=16) try: self.tilemap.load("map.json") except FileNotFoundError: pass - self.isJumping: bool = False self.scroll: list[float] = [0, 0] - self.tile_list: better_list = better_list(self.assets) + + self.tile_list: list[str] = list(self.assets) self.tile_group: int = 0 self.tile_variant: int = 0 self.clicking: bool = False self.right_clicking: bool = False - self.shifting: bool = False + self.shift: bool = False + self.ongrid: bool = True + self.running = True - self.sekunde: int = 60 - self.zaehler: int = 0 - - self.on_grid: bool = True - - def run(self) -> None: + def run(self): while self.running: + # Hintergrund löschen self.display.fill((0, 0, 0)) - self.scroll[0] += (self.movement[1] - self.movement[0]) * 3 - self.scroll[1] += (self.movement[3] - self.movement[2]) * 3 + # Kamera-Steuerung + self.scroll[0] += (self.movement[1] - self.movement[0]) * 2 + self.scroll[1] += (self.movement[3] - self.movement[2]) * 2 - render_scroll = (int(self.scroll[0]), int(self.scroll[1])) + # Scroll-Offset fürs Rendern + render_scroll: tuple[int, int] = (int(self.scroll[0]), int(self.scroll[1])) + # + self.tilemap.render(self.display, offset=render_scroll) - self.tilemap.render(surface=self.display, offset=render_scroll) + # Aktuelle Kachel (Kopie) current_tile_img = self.assets[self.tile_list[self.tile_group]][ self.tile_variant ].copy() + # Halbtransparent current_tile_img.set_alpha(100) - mpos = pygame.mouse.get_pos() - mpos = (mpos[0] / RENDER_SCALE, mpos[1] / RENDER_SCALE) + # Mausposition (Screen) + mpos: tuple[int, int] = pygame.mouse.get_pos() + # Auf Display-Koordinaten skalieren + mpos: tuple[float, float] = (mpos[0] / RENDER_SCALE, mpos[1] / RENDER_SCALE) - tile_pos = ( + # Kachelkoordinate unter der Maus + tile_pos: tuple[int, int] = ( int((mpos[0] + self.scroll[0]) // self.tilemap.tile_size), int((mpos[1] + self.scroll[1]) // self.tilemap.tile_size), ) - # print(tile_pos[0] * self.tilemap.tile_size + self.scroll[0], tile_pos[1]*self.tilemap.tile_size + self.scroll[1]) - # print(mpos) - if self.on_grid: + # Wenn 'ongrid" aktiviert ist... + if self.ongrid: + # Zeige Tile-Vorschau an der aktuellen Grid-Position an self.display.blit( current_tile_img, ( @@ -87,48 +88,59 @@ class Editor: tile_pos[1] * self.tilemap.tile_size - self.scroll[1], ), ) - else: - self.display.blit(current_tile_img, (mpos[0], mpos[1])) + # Zeige Tile-Vorschau an der Mausposition an + self.display.blit(current_tile_img, mpos) + # Linksklick: Kachel platzieren if self.clicking: - if self.on_grid: - self.tilemap.tilemap[str(tile_pos[0]) + ";" + str(tile_pos[1])] = { - "type": self.tile_list[self.tile_group], - "variant": self.tile_variant, - "pos": tile_pos, - } - - if not self.on_grid: - self.tilemap.offgrid_tiles.append( - { - "type": self.tile_list[self.tile_group], - "variant": self.tile_variant, - "pos": (mpos[0] + self.scroll[0], mpos[1] + self.scroll[1]), - } + if self.ongrid: + # Tile in Tilemap setzen + key: str = f"{tile_pos[0]};{tile_pos[1]}" + self.tilemap.tilemap[key] = Tile( + type=self.tile_list[self.tile_group], + variant=self.tile_variant, + pos=list(tile_pos), ) - + else: + # Offgrid Tile direkt platzieren + world_pos = ( + int(mpos[0] + self.scroll[0]), + int(mpos[1] + self.scroll[1]), + ) + self.tilemap.offgrid_tiles.append( + Tile( + type=self.tile_list[self.tile_group], + variant=self.tile_variant, + pos=list(world_pos), + ) + ) + # Rechtsklick: Lösche Tile if self.right_clicking: - tile_loc = str(tile_pos[0]) + ";" + str(tile_pos[1]) + # Hole Position des anvisierten Tile-Objekts + tile_loc: str = str(tile_pos[0]) + ";" + str(tile_pos[1]) + # Wenn Tile in Tilemap existiert, lösche es. if tile_loc in self.tilemap.tilemap: del self.tilemap.tilemap[tile_loc] - - for offi_tile in self.tilemap.offgrid_tiles.copy(): - offi_tile_img = self.assets[offi_tile["type"]][offi_tile["variant"]] - offi_tile_r = pygame.Rect( - offi_tile["pos"][0] - self.scroll[0], - offi_tile["pos"][1] - self.scroll[1], - offi_tile_img.get_width(), - offi_tile_img.get_height(), + for tile in self.tilemap.offgrid_tiles.copy(): + tile_img: pygame.Surface = self.assets[tile.type][tile.variant] + tile_r: pygame.Rect = pygame.Rect( + tile.pos[0] - self.scroll[0], + tile.pos[1] - self.scroll[1], + tile_img.get_width(), + tile_img.get_height(), ) - if offi_tile_r.collidepoint(mpos): - self.tilemap.offgrid_tiles.remove(offi_tile) + if tile_r.collidepoint(mpos): + self.tilemap.offgrid_tiles.remove(tile) + self.display.blit(current_tile_img, (5, 5)) + # Events verarbeiten for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False + # Maus: platzieren/entfernen/auswählen if event.type == pygame.MOUSEBUTTONUP: if event.button == 1: self.clicking = False @@ -136,51 +148,6 @@ class Editor: if event.button == 3: self.right_clicking = False - if event.type == pygame.KEYDOWN: - if event.key == pygame.K_ESCAPE: - self.running = False - - if event.key == pygame.K_a: - self.movement[0] = True - - if event.key == pygame.K_d: - self.movement[1] = True - - if event.key == pygame.K_w: - self.movement[2] = True - - if event.key == pygame.K_s: - self.movement[3] = True - - if event.key == pygame.K_LSHIFT: - self.shifting = True - - if event.key == pygame.K_g: - self.on_grid = not self.on_grid - - if event.key == pygame.K_o: - self.tilemap.save("map.json") - print("Map gespeichert") - - if event.key == pygame.K_t: - self.tilemap.autotile() - - if event.type == pygame.KEYUP: - if event.key == pygame.K_a: - self.movement[0] = False - - if event.key == pygame.K_d: - self.movement[1] = False - - if event.key == pygame.K_s: - self.movement[3] = False - - if event.key == pygame.K_w: - self.movement[2] = False - - if event.key == pygame.K_LSHIFT: - self.shifting = False - if event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: self.clicking = True @@ -188,7 +155,7 @@ class Editor: self.right_clicking = True if event.button == 4: - if self.shifting: + if self.shift: if ( self.tile_variant >= len(self.assets[self.tile_list[self.tile_group]]) - 1 @@ -206,7 +173,7 @@ class Editor: self.tile_variant = 0 if event.button == 5: - if self.shifting: + if self.shift: if self.tile_variant <= 0: self.tile_variant = ( len(self.assets[self.tile_list[self.tile_group]]) @@ -225,19 +192,54 @@ class Editor: self.tile_group -= 1 self.tile_variant = 0 - self.zaehler += 1 - if self.zaehler >= self.sekunde: - self.zaehler = 0 - # print(f"{self.tile_group} + {self.tile_variant}") + if event.type == pygame.MOUSEBUTTONUP: + if event.button == 1: + self.clicking = False + if event.button == 3: + self.right_clicking = False + + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + self.running = False + if event.key == pygame.K_a: + self.movement[0] = True + if event.key == pygame.K_d: + self.movement[1] = True + if event.key == pygame.K_w: + self.movement[2] = True + if event.key == pygame.K_s: + self.movement[3] = True + if event.key == pygame.K_g: + self.ongrid: bool = not self.ongrid + if event.key == pygame.K_t: + self.tilemap.autotile() + if event.key == pygame.K_o: + print("Map saved.") + self.tilemap.save("map.json") + if event.key in (pygame.K_LSHIFT, pygame.K_RSHIFT): + self.shift: bool = True + if event.type == pygame.KEYUP: + if event.key == pygame.K_a: + self.movement[0] = False + if event.key == pygame.K_d: + self.movement[1] = False + if event.key == pygame.K_w: + self.movement[2] = False + if event.key == pygame.K_s: + self.movement[3] = False + if event.key in (pygame.K_LSHIFT, pygame.K_RSHIFT): + self.shift: bool = False + + # Display auf Screen skalieren self.screen.blit( pygame.transform.scale(self.display, self.screen.get_size()), (0, 0) ) + # Frame anzeigen pygame.display.update() + # FPS limitieren self.clock.tick(60) pygame.quit() sys.exit() - -editor = Editor() -editor.run() +Editor().run() diff --git a/game.py b/game.py index 92d6fa8..b50572f 100644 --- a/game.py +++ b/game.py @@ -54,7 +54,6 @@ class Game: self.clouds: Clouds = Clouds(self.assets["clouds"], count=16) self.isJumping: bool = False self.scroll: list[float] = [0, 0] - def run(self) -> None: while self.running: self.display.blit(self.assets["background"], (0, 0)) diff --git a/scripts/tilemap.py b/scripts/tilemap.py index 9b35f9d..05fa01e 100644 --- a/scripts/tilemap.py +++ b/scripts/tilemap.py @@ -1,20 +1,8 @@ -import pygame as pg import json +import pygame +from scripts.models import Tile -NEIGHBOR_OFFSETS = [ - (0, 0), - (-1, 0), - (-1, -1), - (-1, 1), - (0, 1), - (0, -1), - (1, 1), - (1, 0), - (1, -1), -] -PHYSICS_TILES = {"grass", "stone"} -AUTOTILE_TYPES = {"grass", "stone"} -AUTOTILE_MAP = { +AUTOTILE_MAP: dict[tuple[tuple[int, int], ...], int] = { tuple(sorted([(1, 0), (0, 1)])): 0, tuple(sorted([(1, 0), (0, 1), (-1, 0)])): 1, tuple(sorted([(-1, 0), (0, 1)])): 2, @@ -26,129 +14,113 @@ AUTOTILE_MAP = { tuple(sorted([(1, 0), (-1, 0), (0, 1), (0, -1)])): 8, } +NEIGHBOR_OFFSETS: list[tuple[int, int]] = [(-1, 0), (-1, -1), (0, -1), (1, -1), (1, 0), (0, 0), (-1, 1), (0, 1), (1, 1)] +PHYSICS_TILES: set[str] = {'grass', 'stone'} +AUTOTILE_TYPES: set[str] = {'grass', 'stone'} class Tilemap: - """Eine Map aus Tiles.""" - - def __init__(self, game, tile_size: int = 16): + def __init__(self, game, tile_size: int = 16) -> None: self.game = game self.tile_size: int = tile_size - self.tilemap: dict[str, dict] = {} - self.offgrid_tiles: list[dict] = [] - for i in range(10): - self.tilemap[str(3 + i) + ";10"] = { - "type": "grass", - "variant": 1, - "pos": (3 + i, 10), - } - self.tilemap["10;" + str(5 + i)] = { - "type": "stone", - "variant": 1, - "pos": (10, i + 5), - } + self.tilemap: dict[str, Tile] = {} + self.offgrid_tiles: list[Tile] = [] + + def extract(self, id_pairs: list[tuple[str, int]], keep: bool = False) -> list[dict]: + matches: list[dict] = [] + for tile in self.offgrid_tiles.copy(): + if (tile.type, tile.variant) in id_pairs: + matches.append(tile.to_dict()) + if not keep: + self.offgrid_tiles.remove(tile) - def render(self, surface: pg.Surface, offset: tuple[int, int] = (0, 0)) -> None: - for tile in self.offgrid_tiles: - surface.blit( - self.game.assets[tile["type"]][tile["variant"]], - (tile["pos"][0] - offset[0], tile["pos"][1] - offset[1]), - ) - for x in range( - offset[0] // self.tile_size, - (offset[0] + surface.get_width()) // self.tile_size + 1, - ): - for y in range( - offset[1] // self.tile_size, - (offset[1] + surface.get_height()) // self.tile_size + 1, - ): - location_key = f"{x};{y}" - if location_key in self.tilemap: - tile = self.tilemap[location_key] - surface.blit( - self.game.assets[tile["type"]][tile["variant"]], - ( - tile["pos"][0] * self.tile_size - offset[0], - tile["pos"][1] * self.tile_size - offset[1], - ), - ) + for loc in list(self.tilemap): + tilemap_tile = self.tilemap[loc] + if (tilemap_tile.type, tilemap_tile.variant) in id_pairs: + end_match = tilemap_tile.to_dict() + end_match["pos"] = [end_match['pos'][0] * self.tile_size, end_match['pos'][1] * self.tile_size] - def tiles_around(self, pos: list[float]) -> list[dict]: - tiles: list[dict] = [] - tile_location = (int(pos[0] // self.tile_size), int(pos[1] // self.tile_size)) + matches.append(end_match) + + if not keep: + del self.tilemap[loc] + return matches + + def tiles_around(self, pos: tuple[float, float]) -> list[Tile]: + tiles: list[Tile] = [] + tile_location: tuple[int, int] = (int(pos[0] // self.tile_size), int(pos[1] // self.tile_size)) for offset in NEIGHBOR_OFFSETS: - check_location = ( - str(tile_location[0] + offset[0]) - + ";" - + str(tile_location[1] + offset[1]) - ) + check_location: str = str(tile_location[0] + offset[0]) + ';' + str(tile_location[1] + offset[1]) if check_location in self.tilemap: tiles.append(self.tilemap[check_location]) - return tiles - - def physics_rects_around(self, pos: list[float]) -> list[pg.Rect]: - # Erzeuge eine leere Liste für die Rechtecke - rects: list[pg.Rect] = [] - # Durchlaufe alle Tiles aus der Umgebung (tiles_around) - for tile in self.tiles_around(pos): - # Prüfe, ob der Tile-Typ in PHYSICS_TILES enthalten ist - # (nur diese Tiles sollen kollidieren) - if tile["type"] in PHYSICS_TILES: - # Rechne die Tile-Position in Pixel-Koordinaten um - x_pixel = tile["pos"][0] * self.tile_size - y_pixel = tile["pos"][1] * self.tile_size - - # Erzeuge ein pygame.Rect mit: - # - x_pixel - # - y_pixel - # - Breite = self.tile_size - # - Höhe = self.tile_size - rect = pg.Rect(x_pixel, y_pixel, self.tile_size, self.tile_size) - # Füge das Rechteck der Liste rects hinzu - rects.append(rect) - - # Gib die Liste der Rechtecke zurück - return rects - - def autotile(self) -> None: - - for loc in self.tilemap: - tile = self.tilemap[loc] - - neighbors = set() - - for shift in [(1, 0), (-1, 0), (0, -1), (0, 1)]: - check_loc = ( - str(tile["pos"][0] + shift[0]) - + ";" - + str(tile["pos"][1] + shift[1]) - ) - - if check_loc in self.tilemap: - if self.tilemap[check_loc]["type"] == tile["type"]: - neighbors.add(shift) - - neighbors = tuple(sorted(neighbors)) - - if (tile["type"] in AUTOTILE_TYPES) and (neighbors in AUTOTILE_MAP): - tile["variant"] = AUTOTILE_MAP[neighbors] - + def save(self, path: str) -> None: - data = { - "tilemap": self.tilemap, - "tile_size": self.tile_size, - "offgrid": self.offgrid_tiles, + data: dict = { + 'tilemap': { + k: tile.to_dict() for k, tile in self.tilemap.items() + }, + 'tile_size': self.tile_size, + 'offgrid': [ + tile.to_dict() for tile in self.offgrid_tiles + ], } - - with open(path, "w") as f: + with open(path, 'w') as f: json.dump(data, f) - + + def load(self, path: str) -> None: - """Lädt die gespeicherte Tilemap.""" + with open(path, 'r') as f: + map_data = json.load(f) + + self.tile_size: int = map_data['tile_size'] - with open(path, "r") as f: - map_data: dict = json.load(f) + # Grid-Tiles: Dict -> Tile-Objekte + self.tilemap: dict[str, Tile] = {} + for key, tile_data in map_data['tilemap'].items(): + self.tilemap[key] = Tile.from_dict(tile_data) + + # Offgrid-Tiles: Dict -> Tile-Objekte + self.offgrid_tiles: list[Tile] = [ + Tile.from_dict(t) for t in map_data['offgrid'] + ] - self.tilemap: dict[str, dict] = map_data["tilemap"] - self.tile_size: int = map_data["tile_size"] - self.offgrid_tiles: list[dict] = map_data["offgrid"] + def physics_rects_around(self, pos: tuple[float, float]) -> list[pygame.Rect]: + rects: list[pygame.Rect] = [] + for tile in self.tiles_around(pos): + if tile.type in PHYSICS_TILES: + rects.append(pygame.Rect(tile.pos[0] * self.tile_size, tile.pos[1] * self.tile_size, self.tile_size, self.tile_size)) + return rects + + def autotile(self): + for loc in self.tilemap: + tile: Tile = self.tilemap[loc] + neighbors: set[tuple[int, int]] = set() + for shift in [(1, 0), (-1, 0), (0, -1), (0, 1)]: + check_loc: str = str(tile.pos[0] + shift[0]) + ';' + str(tile.pos[1] + shift[1]) + if check_loc in self.tilemap: + if self.tilemap[check_loc].type == tile.type: + neighbors.add(shift) + neighbors: tuple[tuple[int, int], ...] = tuple(sorted(neighbors)) + if (tile.type in AUTOTILE_TYPES) and (neighbors in AUTOTILE_MAP): + tile.variant = AUTOTILE_MAP[neighbors] + + def render(self, surface: pygame.Surface, offset: tuple[float, float] = (0, 0)) -> None: + for tile in self.offgrid_tiles: + surface.blit( + self.game.assets[tile.type][tile.variant], + (tile.pos[0] - offset[0], tile.pos[1] - offset[1]) + ) + + for x in range(offset[0] // self.tile_size, + (offset[0] + surface.get_width()) // self.tile_size + 1): + for y in range(offset[1] // self.tile_size, + (offset[1] + surface.get_height()) // self.tile_size + 1): + location_key: str = str(x) + ';' + str(y) + if location_key in self.tilemap: + tile: Tile = self.tilemap[location_key] + surface.blit( + self.game.assets[tile.type][tile.variant], + ( + tile.pos[0] * self.tile_size - offset[0], + tile.pos[1] * self.tile_size - offset[1]) + )