You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
            }
        }
    }
}

现有代码修复方案

问题根源

  1. 变量命名混淆:x1是Pacman的Top值,y是Pacman的Left值,容易导致方向判断逻辑混乱。
  2. 方向切换逻辑不严谨:主方向被阻挡时,备选方向的选择没有优先靠近Pacman,且存在条件判断漏洞,导致幽灵在墙边来回切换方向。
  3. 缺少“禁止直接回头”的规则(原版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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 07:40:29