From: = <=> Date: Tue, 29 Sep 2026 13:01:16 +0000 (+0200) Subject: More game impl stuff yey X-Git-Url: https://git.uwuaxy.net/?a=commitdiff_plain;h=29bdca2ff5fa1c97153896c4cf7d75366570a4af;p=axy%2Fft%2Fpacman.git More game impl stuff yey --- diff --git a/src/pacman/ecs/graphics.py b/src/pacman/ecs/graphics.py index 2c4dc99..1eb2bf8 100644 --- a/src/pacman/ecs/graphics.py +++ b/src/pacman/ecs/graphics.py @@ -6,7 +6,7 @@ import pygame from pacman.ecs.assets import Asset from pacman.ecs.coord import CoordAbs2D, PropagateTransform -from pacman.ecs.schedule import StartupSchedule, UpdateSchedule +from pacman.ecs.schedule import StartupSchedule, SystemSet, UpdateSchedule from pacman.ecs.update import PostUpdate, PreUpdate from pacman.ecs.world import ( Component, @@ -92,9 +92,14 @@ def render_sprite2d( pygame.display.flip() +GraphicsSystems = SystemSet("graphics-systems") + + def graphics_plugins(world: World) -> None: world.with_systems(StartupSchedule, init_window).with_systems( UpdateSchedule, - Systems(render_sprite2d).in_set(PostUpdate).after(PropagateTransform), + Systems(render_sprite2d) + .in_set(PostUpdate, GraphicsSystems) + .after(PropagateTransform), Systems(poll_events, close_on_close).chain().in_set(PreUpdate), ) diff --git a/src/pacman/game/__init__.py b/src/pacman/game/__init__.py index 48a75f7..4b5af6d 100644 --- a/src/pacman/game/__init__.py +++ b/src/pacman/game/__init__.py @@ -1,21 +1,48 @@ import math from collections.abc import Callable +from dataclasses import dataclass from pathlib import Path +from random import random import pygame from mazegenerator import MazeGenerator -from pygame import Vector2, Vector3 +from pygame import K_a, K_d, K_s, K_w, Vector2, Vector3 from pacman.ecs import minimal_plugins from pacman.ecs.assets import Assets from pacman.ecs.coord import Coord2D, CoordAbs2D -from pacman.ecs.graphics import ShouldRender, Sprite2D, graphics_plugins +from pacman.ecs.graphics import ( + GraphicsSystems, + ShouldRender, + Sprite2D, + WindowEvents, + graphics_plugins, +) from pacman.ecs.hierarchy import ChildOf from pacman.ecs.schedule import StartupSchedule, UpdateSchedule -from pacman.ecs.world import Component, Entity, Query, World +from pacman.ecs.update import PostUpdate, PreUpdate +from pacman.ecs.world import Component, Entity, Not, Query, Systems, World +from pacman.utils.panic import panic class Maze(Component): + def __init__(self, x: int, y: int) -> None: + self._cells: dict[tuple[int, int], Entity] = {} + self._x = x + self._y = y + + +@dataclass +class Cell(Component): + n: int + pass + + +class PacGum(Component): + pass + + +class SuperPacGum(Component): pass @@ -23,45 +50,240 @@ def create_maze( world: World, assets: Assets[Sprite2D] ) -> Callable[[MazeGenerator], Entity]: def inner(maze: MazeGenerator) -> Entity: + root_maze = Maze(len(maze.maze[0]), len(maze.maze)) root = world.spawn( - tuple[Maze, Coord2D, CoordAbs2D], (Maze(), Coord2D(), CoordAbs2D()) + tuple[Maze, Coord2D, CoordAbs2D], + (root_maze, Coord2D(), CoordAbs2D()), ) - cell_paths = [ - [f"./asset/walls/{cell}.png" for cell in line] - for line in maze.maze - ] cell_dims = Vector3(64, 64, 0) - cells = [ - ( + cells = { + (x, y): ( ChildOf(root), Coord2D(cell_dims.elementwise() * Vector3(x, y, 0)), - CoordAbs2D(), - assets.load(cell), + Cell(cell), ShouldRender(), + CoordAbs2D(), + assets.load(f"./asset/walls/{cell}.png"), ) - for y, line in enumerate(cell_paths) + for y, line in enumerate(maze.maze) for x, cell in enumerate(line) - ] - world.spawn_many( - tuple[ChildOf, Coord2D, CoordAbs2D, Sprite2D, ShouldRender], cells + } + spawned_cells = world.spawn_many( + tuple[ChildOf, Coord2D, Cell, ShouldRender, CoordAbs2D, Sprite2D], + list(cells.values()), ) + root_maze._cells = dict(zip(cells.keys(), spawned_cells, strict=True)) return root return inner +def spawn_superpacgums( + world: World, mazes: Query[Maze], assets: Assets[Sprite2D] +) -> None: + pacgums = [] + for maze in mazes: + for pos in [ + (0, 0), + (0, maze._y - 1), + (maze._x - 1, 0), + (maze._x - 1, maze._y - 1), + ]: + cell = maze._cells[pos] + pacgums.append( + ( + ChildOf(cell), + Coord2D(0, 0, -1), + CoordAbs2D(), + assets.load("./asset/misc/superpacgum.png"), + ShouldRender(), + SuperPacGum(), + ) + ) + world.spawn_many( + tuple[ + ChildOf, Coord2D, CoordAbs2D, Sprite2D, ShouldRender, SuperPacGum + ], + pacgums, + ) + + +def spawn_pacgums( + world: World, + cells: Query[tuple[Entity, Cell]], + superpacgums: Query[ChildOf, SuperPacGum], + assets: Assets[Sprite2D], +) -> None: + ignore = {e.parent for e in superpacgums} + pacgums = [] + for ent, cell in cells: + if ent in ignore: + continue + if cell.n == 15: + continue + if random() > 0.7: + continue + pacgums.append( + ( + ChildOf(ent), + Coord2D(0, 0, -1), + CoordAbs2D(), + assets.load("./asset/misc/pacgum.png"), + ShouldRender(), + PacGum(), + ) + ) + world.spawn_many( + tuple[ChildOf, Coord2D, CoordAbs2D, Sprite2D, ShouldRender, PacGum], + pacgums, + ) + + def setup_maze(world: World) -> None: world.add_system(create_maze)(MazeGenerator()) -def move_maze(mazes: Query[Coord2D, Maze]) -> None: - tick = pygame.time.get_ticks() - for coord in mazes: - n = 400 - coord.y = n + n * math.sin(tick / 100) +class PlayerControlled(Component): + def __init__(self) -> None: + self._dir: int = 0 + + @property + def direction(self) -> Vector2: + match self._dir: + case 0: + return Vector2(0, -1) + case 1: + return Vector2(1, 0) + case 2: + return Vector2(0, 1) + case 3: + return Vector2(-1, 0) + case _: + raise Exception() + + +def update_player_controlled( + entities: Query[PlayerControlled], events: WindowEvents +) -> None: + vec = None + for key in events.keys(): + if key == K_w: + vec = 0 + if key == K_d: + vec = 1 + if key == K_s: + vec = 2 + if key == K_a: + vec = 3 + if vec is not None: + for ctl in entities: + ctl._dir = vec + + +def move_player_controlled( + entities: Query[tuple[Coord2D, PlayerControlled]], +) -> None: + for pos, ctl in entities: + direction = ctl.direction * 4 + pos += Vector3(direction.x, direction.y, 0) + + +@dataclass +class AnimationSet(Component): + anims: list[list[Sprite2D]] + ticks_per_frame: int + idx: int = 0 + frame: int = 0 + curr_tick: int = 0 + + @property + def curr_frame(self) -> Sprite2D: + return self.anims[self.idx][self.frame] + + def tick(self) -> None: + self.curr_tick += 1 + self.frame += self.curr_tick // self.ticks_per_frame + self.curr_tick %= self.ticks_per_frame + self.frame %= len(self.anims[self.idx]) + + +def insert_animation_sprites( + world: World, query: Query[tuple[Entity, AnimationSet], Not[Sprite2D]] +) -> None: + inserts = [] + for ent, anim in query: + inserts.append((ent, anim.curr_frame)) + world.insert_many(Sprite2D, inserts) + + +def update_animations( + query: Query[tuple[Sprite2D, AnimationSet], ShouldRender], +) -> None: + for sprite, anim in query: + sprite.surface = anim.curr_frame.surface + anim.tick() + + +class Pacman(Component): + pass + + +def spawn_pacman(world: World, assets: Assets[Sprite2D]) -> None: + world.spawn( + tuple[ + Coord2D, + CoordAbs2D, + AnimationSet, + Pacman, + PlayerControlled, + ShouldRender, + ], + ( + Coord2D(0, 0, -10), + CoordAbs2D(), + AnimationSet( + [ + [ + assets.load( + f"./asset/player/player_{direction}{frame}.png" + ) + for frame in "ABCB" + ] + for direction in "URDL" + ], + 4, + ), + Pacman(), + PlayerControlled(), + ShouldRender(), + ), + ) + + +def update_pacman_direction( + query: Query[tuple[AnimationSet, PlayerControlled], Pacman], +) -> None: + for anims, ctl in query: + anims.idx = ctl._dir def run_game() -> None: World().with_plugins(graphics_plugins, minimal_plugins).with_systems( - StartupSchedule, setup_maze + StartupSchedule, + Systems( + setup_maze, spawn_superpacgums, spawn_pacgums, spawn_pacman + ).chain(), + ).with_systems( + UpdateSchedule, + Systems(update_player_controlled, move_player_controlled) + .chain() + .in_set(PreUpdate), + Systems( + insert_animation_sprites, + update_pacman_direction, + update_animations, + ) + .chain() + .before(GraphicsSystems) + .in_set(PostUpdate), ).run_main()