]> Untitled Git - axy/ft/pacman.git/commitdiff
Ghost chase animation and per ghost status
authorThomas Rimelen <trimelen@k0r1p10.42mulhouse.fr>
Thu, 8 Oct 2026 09:51:14 +0000 (11:51 +0200)
committerThomas Rimelen <trimelen@k0r1p10.42mulhouse.fr>
Thu, 8 Oct 2026 09:51:14 +0000 (11:51 +0200)
src/pacman/game/__init__.py

index 830c98d6a910073b9a14e621e517126ef8fd5259..82ffc14548fcd93e537f90b42d1d4ec898ace766 100644 (file)
@@ -66,15 +66,6 @@ class Score(Resource):
     score: int = 0
 
 
-@dataclass
-class GameStatus(Resource):
-    tick: int = 0
-    cycle_chase: int = 60 * 10
-    cycle_wander: int = 60 * 10
-    chase: bool = False
-    flee: bool = False
-
-
 @dataclass
 class Inputs(Resource):
     inputs: dict[int, float]
@@ -104,6 +95,15 @@ class Pacman(Component):
     pass
 
 
+@dataclass
+class GhostStatus(Component):
+    tick: int = 0
+    cycle_chase: int = 60 * 10
+    cycle_wander: int = 60 * 10
+    chase: bool = False
+    flee: bool = False
+
+
 @dataclass
 class Ghost(Component):
     variant: GhostVariant
@@ -246,7 +246,7 @@ def spawn_pacgums(
 
 
 def setup_maze(world: World) -> None:
-    world.add_system(create_maze)(MazeGenerator((12, 12)))
+    world.add_system(create_maze)(MazeGenerator((14, 14)))
 
 
 def move_in_motion(
@@ -276,14 +276,13 @@ def update_tile_pos(
 
 
 def move_to_target(
-    entities: Query[tuple[TargetTile, InMotion, Coord2D]],
+    entities: Query[tuple[TargetTile, InMotion, Coord2D, GhostStatus]],
     cells: Query[tuple[Cell, Coord2D]],
     maze_q: Query[Maze],
-    status: GameStatus,
 ) -> None:
     if (maze := maze_q.single()) is None:
         return
-    for target, motion, pos in entities:
+    for target, motion, pos, status in entities:
         cell, cell_pos = cells[motion.pos]
         if cell_pos.xy.distance_squared_to(pos.xy) > 0.01:
             continue
@@ -369,6 +368,7 @@ def spawn_ghosts(
             TilePos,
             Ghost,
             TargetTile,
+            GhostStatus,
         ],
         [
             (
@@ -383,7 +383,7 @@ def spawn_ghosts(
                             )
                         ]
                         for direction in "URDL"
-                    ],
+                    ] + [[assets.load("./asset/enemy/ghost_blue.png")]],
                     6,
                 ),
                 ShouldRender(),
@@ -392,6 +392,7 @@ def spawn_ghosts(
                 TilePos(maze._cells[pos]),
                 Ghost(variant, pos),
                 TargetTile(pos),
+                GhostStatus(),
             )
             for variant, pos in [
                 (GhostVariant.A, (0, 0)),
@@ -450,9 +451,8 @@ def collect_superpacgum(
     world: World,
     pacgums: Query[tuple[CoordAbs2D, Entity], SuperPacGum],
     pacmans: Query[CoordAbs2D, Pacman],
-    ghosts: Query[InMotion, Ghost],
+    ghosts: Query[tuple[InMotion, GhostStatus], Ghost],
     score: Score,
-    status: GameStatus,
 ) -> None:
     collided = [
         pacgum
@@ -462,12 +462,12 @@ def collect_superpacgum(
     ]
     score.score += len(collided * 10)
     if len(collided) > 0:
-        status.flee = True
-        status.tick = 60 * 8
-        for ghost in ghosts:
+        for ghost, status in ghosts:
             tmp = ghost.src
             ghost.src = ghost.pos
             ghost.pos = tmp
+            status.flee = True
+            status.tick = 60 * 8
     world.despawn_many(collided)
 
 
@@ -488,20 +488,21 @@ def level_is_finished(pacgums: Query[SuperPacGum | PacGum]) -> bool:
 
 
 def update_ghost_targets(
-    ghosts: Query[tuple[Ghost, TilePos, TargetTile]],
+    ghosts: Query[tuple[Ghost, TilePos, TargetTile, GhostStatus]],
     pacman_q: Query[tuple[InMotion, TilePos], Pacman],
     cells: Query[Cell],
-    chase: GameStatus,
 ) -> None:
     player = pacman_q.single()
     if player is None:
         return
     player_motion, player_pos = player
     blinky_pos = next(
-        pos.pos for ghost, pos, _ in ghosts if ghost.variant == GhostVariant.A
+        pos.pos
+        for ghost, pos, _, _ in ghosts
+        if ghost.variant == GhostVariant.A
     )
-    for ghost, pos, target in ghosts:
-        if not chase.chase:
+    for ghost, pos, target, status in ghosts:
+        if not status.chase:
             target.pos = ghost.home
             continue
         target.pos = ghost.variant.compute_target_tile(
@@ -513,23 +514,32 @@ def update_ghost_targets(
         )
 
 
-def update_chase_status(status: GameStatus) -> None:
-    if status.flee:
-        if status.tick == 0:
-            status.flee = False
-            return
-        status.tick -= 1
-    else:
-        status.tick += 1
-        status.chase = status.tick > status.cycle_wander
-        if status.tick < (status.cycle_wander + status.cycle_chase):
-            return
+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
+
+            status.tick = 0
+            if status.cycle_wander > 60 * 4:
+                status.cycle_wander -= 60
+            if status.cycle_chase > 60 * 7:
+                status.cycle_chase -= 60
+
+
+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
 
-        status.tick = 0
-        if status.cycle_wander > 60 * 4:
-            status.cycle_wander -= 60
-        if status.cycle_chase > 60 * 7:
-            status.cycle_chase -= 60
 
 
 def update_pacman_anim(
@@ -540,15 +550,14 @@ def update_pacman_anim(
 
 
 def update_ghost_anim(
-    ghost_q: Query[tuple[InMotion, AnimationSet], Ghost],
+    ghost_q: Query[tuple[InMotion, AnimationSet, GhostStatus], Ghost],
 ) -> None:
-    for motion, anim in ghost_q:
-        anim.idx = motion.direction
+    for motion, anim, status in ghost_q:
+        anim.idx = motion.direction if not status.flee else 4
 
 
 def setup_resources(world: World) -> None:
     world.spawn_resource(Inputs({}))
-    world.spawn_resource(GameStatus())
     world.spawn_resource(Score())
 
 
@@ -584,6 +593,7 @@ def run_game() -> None:
             move_to_target,
             move_in_motion,
             update_chase_status,
+            update_ghost_speed,
         )
         .chain()
         .in_set(Update),