1

我正在为大学作业创建一个项目,但遇到了一些障碍。这个问题很小,很可能会被忽视,但它仍然困扰着我,作为一个完成工作的程序性人,在解决之前我不能继续其他任何事情。

我的桨和球代码工作正常,以及基于球撞击桨的位置的简单角速度,但我的问题是球何时撞击砖块。该代码大部分都有效,并且看起来完美无瑕,但是当球执行以下两件事之一时,情况会发生变化:

  • 击中砖块的一个角可能会错误地反转球速度的错误轴
  • 同时击中两块砖(在它们的角落)会使球穿过它们并将它们移开;即使他们完全健康。

我使用的代码在这里(Ball.cs):

编辑(更新代码):

public bool Touching(Brick brick)
{
    var position = Position + Velocity;
    return position.Y + Size.Y >= brick.Position.Y &&
              position.Y <= brick.Position.Y + brick.Size.Y &&
              position.X + Size.X >= brick.Position.X &&
              position.X <= brick.Position.X + brick.Size.X;
}

public bool Collide(Brick brick)
{
    //Secondary precaution check
    if (!Touching(brick)) return false;

    //Find out where the ball will be
    var position = Position + Velocity;

    //Get the bounds of the ball based on where it will be
    var bounds = new Rectangle((int)position.X, (int)position.Y, Texture.Width, Texture.Height);

    //Stop the ball from moving here, so that changes to velocity will occur afterwards.
    Position = Position - Velocity;

    //If the ball hits the top or bottom of the brick
    if (bounds.Intersects(brick.Top) || bounds.Intersects(brick.Bottom))
    {
        Velocity = new Vector2(Velocity.X, -Velocity.Y); //Reverse the Y axe of the Velocity
    }

    //If the ball hits the left or right of the brick
    if (bounds.Intersects(brick.Left) || bounds.Intersects(brick.Right))
    {
        Velocity = new Vector2(-Velocity.X, Velocity.Y); //Reverse the X axe of the Velocity
    }

    return true;
}

这些方法是从 Level.cs 的 Update 方法中调用的:

public override void Update(GameTime gameTime)
{
    player.Update(gameTime);
    balls.ForEach(ball => ball.Update(gameTime));
    foreach (var brick in bricks)
    {
        foreach (var ball in balls)
        {
            if (ball.Touching(brick))
            {
                if (!collidingBricks.Contains(brick)) collidingBricks.Add(brick);
            }
        }
        brick.Update(gameTime);
    }

    if (collidingBricks.Count > 0)
    {
        foreach (var ball in balls)
        {
            Brick closestBrick = null;
            foreach (var brick in collidingBricks)
            {
                if (closestBrick == null)
                {
                    closestBrick = brick;
                    continue;
                }
                if (Vector2.Distance(ball.GetCenterpoint(brick), ball.GetCenterpoint()) < Vector2.Distance(ball.GetCenterpoint(closestBrick), ball.GetCenterpoint()))
                {
                    closestBrick = brick;
                }else
                {
                    brick.Health--;
                    if (brick.Health > 0) brick.Texture = Assets.GetBrick(brick.TextureName, brick.Health);
                }
            }

            if (ball.Collide(closestBrick))
            {
                closestBrick.Health--;
                if (closestBrick.Health > 0) closestBrick.Texture = Assets.GetBrick(closestBrick.TextureName, closestBrick.Health);
            }
        }
        collidingBricks = new List<Brick>();
    }

    bricks.RemoveAll(brick => brick.Health <= 0);
    base.Update(gameTime);
}

球的碰撞方法决定了球的新速度值。如您所见,它完全取决于球的位置,而不是球的位置。此外,我对球正在碰撞的最近的砖块运行碰撞检查,但我降低了球接触的所有砖块的生命值(因此,速度只会根据最近的砖块更新而不是所有碰撞的砖块)。

我理解为什么它会给我带来问题,这可能是因为球同时击中两侧(当它击中角落时)。

就像我说的,这是一个小问题,但仍然会影响游戏体验,尤其是当您期望球在逻辑上朝一个方向移动时,它只会朝相反的方向移动。

4

2 回答 2

1

我假设每次调用您的Update方法时,您都会通过添加按帧时间缩放的速度矢量将球移动到新位置,然后测试新位置。这个对吗?

在这个模型中,球占据了一系列点位置,而没有穿过中间空间。由于称为“隧道效应”的效应,这种类型的运动会导致各种问题。当一个物体移动得足够快以至于它似乎部分或完全穿过物体时,就会发生这种情况。

想象一下,您的球以每帧 3 个像素的速度垂直移动,并且当前距离块边缘 1 个像素。下一帧,球将移动到块2 个像素的位置。由于球和块不能重叠,这是一个错误。也许不是一个巨大的错误,但仍然不对。

正如您所发现的,当您到达拐角处时,错误确实会咬住您。如果球的新位置与积木的角重叠,则确定正确的动作变得更加困难,因为积木的最近边缘可能不是您通过(概念上)到达最终位置的边缘。

解决这个问题的方法是调用连续碰撞检测。每次移动球时,都​​必须检查整个运动路径以确定是否发生碰撞。

最简单的方法——虽然肯定不是最快的——可能是使用像Bresenham 算法这样的东西来绘制你的球沿着运动线占据的位置。Bresenham's 会给你一个合理的运动线,整数步长——每像素一步。这些步骤转换为可用于检测碰撞的坐标。

您可以通过预先计算是否可能发生碰撞以及如果发生碰撞,可以稍微加快速度。如果屏幕上有 100 个块,您不想检查每一步的每一个块。您可以通过计算总移动(当前位置到结束位置)的矩形边界框来缩小范围,并构建一个包含该框内的所有块和其他对象的列表。没有结果就意味着没有冲突,因此这是一种缩短昂贵操作的好方法,同时最终也降低了它们的成本。

要让它完美,还有很多工作要做,而且要让它足够接近,还有相当多的工作要做。谷歌continuous collision detectiongame physics tunneling有关这些主题的更多信息。

如果你能摆脱它,像 [Box2D] 或 Farseer 这样的东西会给你一些有趣的选项来处理这个问题。当然,您可能会花费与解决原始问题一样多的时间围绕物理引擎重新构建游戏:P

于 2013-11-20T00:49:28.293 回答
0

我认为这两个问题都可以通过解决沿其浅轴的碰撞来解决。考虑这种情况:

在您当前的代码中,这种碰撞将沿着 Y 轴解决,因为它有一个偏好,尽管碰撞显然发生在 X 轴上。

碰撞的一般方法是首先获取碰撞深度(这可以通过使用Rectangle.Intersect静态方法创建一个新矩形并使用它的宽度和高度来轻松完成)。然后,您必须检查两个轴的交点有多深,在您的情况下,还要检查球的速度矢量的浅轴分量。最后一步是检查两个相交对象的相对位置并沿浅轴移动其中一个 - 在给定的示例中,球在砖块的右侧,因此它必须向右移动相交深度。

于 2013-11-20T14:33:16.603 回答