WPF C# Canvas实现Pacman幽灵追踪玩家移动的问题排查
Pacman幽灵追踪与移动逻辑修复方案
我用WPF C#的Canvas组件开发原版Pacman游戏时,幽灵的追踪移动逻辑出了问题:原本想预判幽灵下一步是否穿墙并阻止,但现在幽灵会走到墙边后退一步,接着再次向墙移动,往复循环。以下是问题代码、修复方案及其他实现思路:
问题代码
// flags用于判断幽灵下一步是否会穿墙 double x1 = Canvas.GetTop(pacman); double y = Canvas.GetLeft(pacman); if (Math.Abs(Canvas.GetTop(redGuy) - x1) > Math.Abs(Canvas.GetLeft(redGuy) - y) && (flagUp == false || flagDown == false)) { if (Canvas.GetTop(redGuy) < x1) { if (flagDown == false) { Canvas.SetTop(redGuy, Canvas.GetTop(redGuy) + ghostSpeed); } else if (flagRight == false || flagLeft == false) { if (Canvas.GetLeft(redGuy) < y && flagLeft == false) { Canvas.SetLeft(redGuy, Canvas.GetLeft(redGuy) + ghostSpeed); } else if (flagRight == false) { Canvas.SetLeft(redGuy, Canvas.GetLeft(redGuy) - ghostSpeed); } } } else { if (flagUp == false) { Canvas.SetTop(redGuy, Canvas.GetTop(redGuy) - ghostSpeed); } else if (flagRight == false || flagLeft == false) { if (Canvas.GetLeft(redGuy) < y && flagLeft == false) { Canvas.SetLeft(redGuy, Canvas.GetLeft(redGuy) + ghostSpeed); } else if (flagRight == false) { Canvas.SetLeft(redGuy, Canvas.GetLeft(redGuy) - ghostSpeed); } } } } else if (flagRight == false || flagLeft == false) { if (Canvas.GetLeft(redGuy) < y) { if (flagLeft == false) { Canvas.SetLeft(redGuy, Canvas.GetLeft(redGuy) + ghostSpeed); } else if (flagUp == false || flagDown == false) { if (Canvas.GetTop(redGuy) > x1 && flagUp == false) { Canvas.SetTop(redGuy, Canvas.GetTop(redGuy) - ghostSpeed); } else if (flagDown == false) { Canvas.SetTop(redGuy, Canvas.GetTop(redGuy) + ghostSpeed); } } } else { if (flagRight == false) { Canvas.SetLeft(redGuy, Canvas.GetLeft(redGuy) - ghostSpeed); } else if (flagUp == false || flagDown == false) { if (Canvas.GetTop(redGuy) > x1 && flagDown == false) { Canvas.SetTop(redGuy, Canvas.GetTop(redGuy) - ghostSpeed); } else if (flagRight == false) { Canvas.SetTop(redGuy, Canvas.GetTop(redGuy) + ghostSpeed); } } } }
现有代码修复方案
问题根源
- 变量命名混淆:
x1是Pacman的Top值,y是Pacman的Left值,容易导致方向判断逻辑混乱。 - 方向切换逻辑不严谨:主方向被阻挡时,备选方向的选择没有优先靠近Pacman,且存在条件判断漏洞,导致幽灵在墙边来回切换方向。
- 缺少“禁止直接回头”的规则(原版Pacman幽灵默认不会立刻回头),加剧了往复移动的问题。
修复后的代码
// 修正变量命名,避免混淆 double pacmanTop = Canvas.GetTop(pacman); double pacmanLeft = Canvas.GetLeft(pacman); double ghostTop = Canvas.GetTop(redGuy); double ghostLeft = Canvas.GetLeft(redGuy); // 计算幽灵与Pacman在上下、左右方向的距离差 double verticalDiff = Math.Abs(ghostTop - pacmanTop); double horizontalDiff = Math.Abs(ghostLeft - pacmanLeft); // 优先选择距离更大的方向(上下或左右) bool prioritizeVertical = verticalDiff > horizontalDiff; // 记录当前幽灵的移动方向(需提前维护,比如用枚举) Direction lastDirection = GetGhostLastDirection(redGuy); // 优先尝试主方向 bool moved = false; if (prioritizeVertical) { if (ghostTop < pacmanTop) { // 目标:向下移动,排除回头方向 if (flagDown == false && lastDirection != Direction.Up) { Canvas.SetTop(redGuy, ghostTop + ghostSpeed); moved = true; } } else { // 目标:向上移动,排除回头方向 if (flagUp == false && lastDirection != Direction.Down) { Canvas.SetTop(redGuy, ghostTop - ghostSpeed); moved = true; } } // 主方向不可走,从可走方向选离Pacman最近的 if (!moved) { List<Direction> availableDirs = new List<Direction>(); if (flagUp == false && lastDirection != Direction.Down) availableDirs.Add(Direction.Up); if (flagDown == false && lastDirection != Direction.Up) availableDirs.Add(Direction.Down); if (flagLeft == false && lastDirection != Direction.Right) availableDirs.Add(Direction.Left); if (flagRight == false && lastDirection != Direction.Left) availableDirs.Add(Direction.Right); if (availableDirs.Count > 0) { Direction bestDir = availableDirs.OrderBy(dir => CalculateDistanceToPacman(redGuy, dir, pacmanTop, pacmanLeft, ghostSpeed)) .First(); MoveGhostInDirection(redGuy, bestDir, ghostSpeed); } } } else { if (ghostLeft < pacmanLeft) { // 目标:向右移动,排除回头方向 if (flagLeft == false && lastDirection != Direction.Right) { Canvas.SetLeft(redGuy, ghostLeft + ghostSpeed); moved = true; } } else { // 目标:向左移动,排除回头方向 if (flagRight == false && lastDirection != Direction.Left) { Canvas.SetLeft(redGuy, ghostLeft - ghostSpeed); moved = true; } } // 主方向不可走,选最优备选方向 if (!moved) { List<Direction> availableDirs = new List<Direction>(); if (flagUp == false && lastDirection != Direction.Down) availableDirs.Add(Direction.Up); if (flagDown == false && lastDirection != Direction.Up) availableDirs.Add(Direction.Down); if (flagLeft == false && lastDirection != Direction.Right) availableDirs.Add(Direction.Left); if (flagRight == false && lastDirection != Direction.Left) availableDirs.Add(Direction.Right); if (availableDirs.Count > 0) { Direction bestDir = availableDirs.OrderBy(dir => CalculateDistanceToPacman(redGuy, dir, pacmanTop, pacmanLeft, ghostSpeed)) .First(); MoveGhostInDirection(redGuy, bestDir, ghostSpeed); } } } // 辅助方法:计算幽灵向指定方向移动一步后到Pacman的距离 private double CalculateDistanceToPacman(UIElement ghost, Direction dir, double pacmanTop, double pacmanLeft, double speed) { double newTop = Canvas.GetTop(ghost); double newLeft = Canvas.GetLeft(ghost); switch (dir) { case Direction.Up: newTop -= speed; break; case Direction.Down: newTop += speed; break; case Direction.Left: newLeft += speed; break; case Direction.Right: newLeft -= speed; break; } return Math.Sqrt(Math.Pow(newTop - pacmanTop, 2) + Math.Pow(newLeft - pacmanLeft, 2)); } // 辅助方法:让幽灵向指定方向移动 private void MoveGhostInDirection(UIElement ghost, Direction dir, double speed) { switch (dir) { case Direction.Up: Canvas.SetTop(ghost, Canvas.GetTop(ghost) - speed); break; case Direction.Down: Canvas.SetTop(ghost, Canvas.GetTop(ghost) + speed); break; case Direction.Left: Canvas.SetLeft(ghost, Canvas.GetLeft(ghost) + speed); break; case Direction.Right: Canvas.SetLeft(ghost, Canvas.GetLeft(ghost) - speed); break; } } // 移动方向枚举 public enum Direction { Up, Down, Left, Right, None }
修复要点
- 新增
Direction枚举和lastDirection变量,避免幽灵直接回头。 - 主方向不可走时,收集所有可走方向(排除回头),选择移动后距离Pacman最近的方向。
- 修正变量命名,让逻辑更清晰。
- 拆分辅助方法,降低代码耦合度。
其他实现思路
1. 网格化路径规划(A*算法)
把游戏地图拆分为固定大小的网格,每个格子标记是否为墙。用A*算法计算幽灵到Pacman的最短路径,幽灵沿着路径节点移动。这种方式能彻底解决穿墙和往复移动问题,行为更贴合原版Pacman的幽灵逻辑。
2. 预判式碰撞检测
不要用flag标记可走方向,而是在每次移动前计算幽灵下一步的坐标,直接检查该坐标是否与墙碰撞:
double nextTop = Canvas.GetTop(redGuy) + ghostSpeed; if (!IsWallCollision(nextTop, Canvas.GetLeft(redGuy))) { Canvas.SetTop(redGuy, nextTop); } // 同理处理其他方向
其中IsWallCollision方法负责判断指定坐标是否处于墙区域内。
3. 状态机管理幽灵行为
给幽灵设计多种状态(追踪、四散、被困、逃跑),不同状态下使用不同的移动逻辑:
- 追踪状态:优先向Pacman移动。
- 四散状态:随机选择可走方向。
- 逃跑状态:远离Pacman移动。
通过状态切换,让幽灵行为更丰富,也能避免单一逻辑导致的往复问题。
内容的提问来源于stack exchange,提问作者MR. potato35
相关产品推荐
相关产品推荐

