From: = <=> Date: Thu, 8 Oct 2026 10:51:32 +0000 (+0200) Subject: Ghost status as enum and respawn mechanics X-Git-Url: https://git.uwuaxy.net/Project%20Status.png?a=commitdiff_plain;h=26d9eafae8067fcf88438b00d699318292937b57;p=axy%2Fft%2Fpacman.git Ghost status as enum and respawn mechanics --- diff --git a/src/pacman/game/__init__.py b/src/pacman/game/__init__.py index 82ffc14..a3e96a0 100644 --- a/src/pacman/game/__init__.py +++ b/src/pacman/game/__init__.py @@ -95,19 +95,26 @@ class Pacman(Component): pass +class GhostState(Enum): + chase = auto() + wander = auto() + flee = auto() + dead = auto() + respawning = auto() + + @dataclass class GhostStatus(Component): tick: int = 0 - cycle_chase: int = 60 * 10 cycle_wander: int = 60 * 10 - chase: bool = False - flee: bool = False + state: GhostState = GhostState.wander @dataclass class Ghost(Component): variant: GhostVariant home: tuple[int, int] + home_entity: Entity @dataclass @@ -283,6 +290,8 @@ def move_to_target( if (maze := maze_q.single()) is None: return for target, motion, pos, status in entities: + if status.state in (GhostState.dead, GhostState.respawning): + continue cell, cell_pos = cells[motion.pos] if cell_pos.xy.distance_squared_to(pos.xy) > 0.01: continue @@ -302,7 +311,7 @@ def move_to_target( ) ), ) - if status.flee: + if status.state is GhostState.flee: lst = candidates[:-1] shuffle(lst) candidates = lst + [candidates[-1]] @@ -383,14 +392,15 @@ def spawn_ghosts( ) ] for direction in "URDL" - ] + [[assets.load("./asset/enemy/ghost_blue.png")]], + ] + + [[assets.load("./asset/enemy/ghost_blue.png")]], 6, ), ShouldRender(), InMotion(maze._cells[pos], maze._cells[pos], 1), MoveSpeed(2.5), TilePos(maze._cells[pos]), - Ghost(variant, pos), + Ghost(variant, pos, maze._cells[pos]), TargetTile(pos), GhostStatus(), ) @@ -441,7 +451,7 @@ def collect_pacgum( pacgum for coord, pacgum in pacgums for pos in pacmans - if pos._vec.xy.distance_squared_to(coord._vec.xy) < 100 + if pos._vec.xy.distance_squared_to(coord._vec.xy) <= 256 ] score.score += len(collided) world.despawn_many(collided) @@ -458,29 +468,47 @@ def collect_superpacgum( pacgum for pacgum_pos, pacgum in pacgums for pacman_pos in pacmans - if pacman_pos._vec.xy.distance_squared_to(pacgum_pos._vec.xy) < 100 + if pacman_pos._vec.xy.distance_squared_to(pacgum_pos._vec.xy) <= 256 ] score.score += len(collided * 10) - if len(collided) > 0: - for ghost, status in ghosts: + if len(collided) == 0: + return + for ghost, status in ghosts: + if status.state in (GhostState.wander, GhostState.chase): tmp = ghost.src ghost.src = ghost.pos ghost.pos = tmp - status.flee = True - status.tick = 60 * 8 + status.state = GhostState.flee + if status.state is GhostState.flee: + status.tick = 0 world.despawn_many(collided) def collide_ghost( - pacmans: Query[CoordAbs2D, Pacman], ghosts: Query[CoordAbs2D, Ghost] + pacmans: Query[CoordAbs2D, Pacman], + ghosts: Query[ + tuple[CoordAbs2D, GhostStatus, Ghost, InMotion, Coord2D], Ghost + ], ) -> None: for pacman_pos in pacmans: - for ghost_pos in ghosts: + for ghost_pos, status, ghost, motion, pos in ghosts: if ( pacman_pos._vec.xy.distance_squared_to(ghost_pos._vec.xy) - < 3600 + > 3600 ): - pass + continue + match status.state: + case GhostState.wander | GhostState.chase: + # TODO: kill pacman + print("Pacman hit") + case GhostState.flee: + status.state = GhostState.dead + status.tick = 0 + motion.src = ghost.home_entity + motion.pos = ghost.home_entity + pos.xy = Vector2(*ghost.home) * 64 + case _: + pass def level_is_finished(pacgums: Query[SuperPacGum | PacGum]) -> bool: @@ -502,44 +530,59 @@ def update_ghost_targets( if ghost.variant == GhostVariant.A ) for ghost, pos, target, status in ghosts: - if not status.chase: - target.pos = ghost.home - continue - target.pos = ghost.variant.compute_target_tile( - cells[player_pos.pos].grid_pos, - player_motion.direction, - cells[pos.pos].grid_pos, - ghost, - cells[blinky_pos].grid_pos, - ) + match status.state: + case GhostState.wander: + target.pos = ghost.home + case GhostState.chase: + target.pos = ghost.variant.compute_target_tile( + cells[player_pos.pos].grid_pos, + player_motion.direction, + cells[pos.pos].grid_pos, + ghost, + cells[blinky_pos].grid_pos, + ) + case _: + pass def update_chase_status(status_q: Query[GhostStatus]) -> None: for status in status_q: - if status.flee: - if status.tick == 0: - status.flee = False - continue - status.tick -= 1 - else: - status.tick += 1 - status.chase = status.tick > status.cycle_wander - if status.tick < (status.cycle_wander + status.cycle_chase): - continue - + target_time: int + next_state: GhostState + cycle_wander_next: int = status.cycle_wander + match status.state: + case GhostState.flee: + target_time, next_state = (8 * 60, GhostState.wander) + case GhostState.chase: + if cycle_wander_next > 60 * 4: + cycle_wander_next -= 120 + target_time, next_state = ( + 60 * 10, + GhostState.wander, + ) + case GhostState.wander: + target_time, next_state = ( + status.cycle_wander, + GhostState.chase, + ) + case GhostState.dead: + target_time, next_state = (5 * 60, GhostState.respawning) + case GhostState.respawning: + target_time, next_state = (3 * 60, GhostState.wander) + status.tick += 1 + if target_time <= status.tick: + status.state = next_state status.tick = 0 - if status.cycle_wander > 60 * 4: - status.cycle_wander -= 60 - if status.cycle_chase > 60 * 7: - status.cycle_chase -= 60 + status.cycle_wander = cycle_wander_next def update_ghost_speed(ghost: Query[tuple[MoveSpeed, GhostStatus]]) -> None: for speed, status in ghost: - speed.pixels_per_frame = 2.5 - if status.flee: - speed.pixels_per_frame = 1.5 - + match status.state: + case GhostState.chase | GhostState.wander: + speed.pixels_per_frame = 2.5 + case GhostState.flee: + speed.pixels_per_frame = 1.5 def update_pacman_anim( @@ -553,7 +596,13 @@ def update_ghost_anim( ghost_q: Query[tuple[InMotion, AnimationSet, GhostStatus], Ghost], ) -> None: for motion, anim, status in ghost_q: - anim.idx = motion.direction if not status.flee else 4 + match status.state: + case GhostState.chase | GhostState.wander: + anim.idx = motion.direction + case GhostState.flee: + anim.idx = 4 + case _: + pass def setup_resources(world: World) -> None: @@ -601,8 +650,12 @@ def run_game() -> None: Systems(update_pacman_anim, update_ghost_anim) .before(GraphicsSystems) .in_set(PostUpdate), - Systems(collect_pacgum, collect_superpacgum, collide_ghost).after( - PropagateTransform - ), + Systems( + collect_pacgum, + collect_superpacgum, + Systems(collide_ghost).before(update_ghost_anim), + ) + .after(PropagateTransform) + .before(GraphicsSystems), Systems(victory).run_if(level_is_finished), ).run_main()