简体   繁体   English

C#XNA-突破式球物理

[英]C# XNA - Breakout-style ball physics

I am creating a project for a university assignment, and I have hit a bit of a snag. 我正在为大学任务创建一个项目,但遇到了一些麻烦。 The problem is minor and can most likely be passed off as unnoticed, but it still bothers me and, being a procedural person in terms of completing work, I can't move on to anything else until it is solved. 这个问题很小,很容易被忽视,但是它仍然困扰着我,作为完成工作的程序人,在解决之前,我无法继续进行其他任何事情。

I have the paddle and ball code working fine, as well as a simple angular velocity based on where the ball hits the paddle, however my issue is when the ball hits the bricks. 我的桨和球代码工作得很好,并且基于球击中桨的位置的简单角速度,但是我的问题是球何时击中砖头。 The code mostly works and it seems flawless, however this changes when the ball does one of these two things: 该代码通常可以正常工作,而且看起来也很完美,但是当球执行以下两项操作之一时,这种情况就会改变:

  • Hitting a brick on one of its corners may incorrectly reverse the wrong axis of the ball's velocity 在砖块的一个角上击打砖块可能会错误地反转球速度的错误轴
  • Hitting two bricks at the same time (in their corners) will cause the ball to pass through them and remove them; 同时击中两块砖(在它们的角落)会导致球穿过并移走; even if they have full health. 即使他们身体健康

The code I am using is here (Ball.cs): 我正在使用的代码在这里(Ball.cs):

EDIT (Updated Code): 编辑(更新代码):

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

These methods are being called from Level.cs' Update method: 这些方法是从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);
}

The ball's Collide method is what determines the new velocity values of the ball. 球的“碰撞”方法决定了球的新速度值。 As you can see, it is entirely based on where the ball will be, not where the ball is. 正如你所看到的,它完全是基于在球 ,而不是球的位置。 Additionally, I run the collision check for the closest brick that the ball is colliding with, but I reduce the health of all bricks that the ball is touching (so, the velocity will only update based on just the closest brick, not all colliding bricks). 此外,我跑了那个球与碰撞最接近砖碰撞检查,但我减少所有砖,球被触碰(因此,速度只会更新基于最近的砖,并不是所有的碰撞砖健康)。

I understand why it is causing me problems, and that it may be because of the fact that the ball is hitting two sides at the same time (as it hits the corner.) 我了解为什么这会给我带来麻烦,这可能是因为球同时击中了两侧(击中了角落)。

Like I said, it's a minor issue, but can even still impact on the game experience, especially when you expect the ball to logically go in one direction only for it to travel in the opposite. 就像我说的那样,这是一个小问题,但它甚至仍然会影响游戏体验,尤其是当您期望球逻辑上只能朝一个方向移动而相反时。

I'm assuming that each call to your Update method you move the ball to a new position by adding the velocity vector scaled by the frame time, then test the new position. 我假设每次调用Update方法时,您都需要通过将按帧时间缩放的速度矢量相加来将球移动到新位置,然后测试新位置。 Is this correct? 这个对吗?

In this model the ball is occupying a series of point locations without passing through the intervening space. 在此模型中,球占据了一系列的点位置,而没有经过中间空间。 This type of motion causes a variety of problems due to an effect called 'Tunneling'. 由于所谓的“隧道效应”,这种运动会引起各种问题。 This happens when an object moves fast enough that it appears to pass through objects partially or completely. 当对象移动得足够快以致似乎部分或全部通过对象时,就会发生这种情况。

Imagine that your ball is moving vertically at 3 pixels per frame and is currently 1 pixel away from the edge of a block. 想象一下,您的球以每帧3像素的垂直方向移动,并且当前距离块的边缘1像素。 Next frame, the ball will move to a position that is 2 pixels inside the block. 下一帧,球将移动到块 2个像素的位置。 Since the ball and the block can't overlap, this is an error. 由于球和块不能重叠,所以这是一个错误。 Perhaps not a huge error, but still not right. 也许不是很大的错误,但仍然不正确。

As you have discovered, the error really bites you when you get to the corners. 正如您所发现的那样,当您走到角落时,错误确实会咬住您。 If the ball's new position is overlapping the corner of the block it becomes more difficult to determine the correct action since the closest edge of the block may not be the one that you passed through (conceptually) to get to where you ended up. 如果球的新位置与积木的拐角重叠,则确定正确的动作将变得更加困难,因为积木的最接近的边缘可能不是您(从概念上)经过的那一条直至到达最终位置。

The solution to this is call Continuous Collision Detection. 解决方案称为连续碰撞检测。 Each time you move the ball, you have to check the entire path of motion to determine if a collision occurs. 每次移动球时,都​​必须检查整个运动路径,以确定是否发生碰撞。

The simplest method - although certainly not the fastest - might be to use something like Bresenham's Algorithm to plot the positions your ball occupies along the line of motion. 最简单的方法(虽然肯定不是最快的方法)可能是使用布雷森汉姆算法之类的方法来绘制球沿运动线的位置。 Bresenham's will give you a reasonable line of motion with integer steps - one step per pixel. 布雷森汉姆(Bresenham)会以整数步长为您提供合理的运动路线-每个像素一步。 These steps translate to coordinates that you can use to detect collisions. 这些步骤将转换为可用于检测碰撞的坐标。

You can speed things up slightly by pre-calculating whether it is possible for a collision to occur, and if so with what. 您可以通过预先计算是否可能发生碰撞以及如果发生碰撞来稍微加快速度。 If you have 100 blocks on the screen you don't want to check every one of the for every step. 如果屏幕上有100个块,则您不想检查每一步的每一块。 You can narrow the range by calculating a rectangular bounding box for the total movement (current position to end position) and build a list of all blocks and other objects that fall within the box. 您可以通过为整个移动(当前位置到结束位置)计算一个矩形边界框来缩小范围,并建立一个所有位于该框内的块和其他对象的列表。 No results means no collisions, so this is a good way to short-circuit the expensive operations while also making them less expensive in the end. 没有结果意味着没有碰撞,因此这是使昂贵的操作短路的好方法,同时最终也使它们的成本降低。

There's a lot of work to do to get it perfect, and a reasonably large amount to get it close enough. 要使它完美,需要做很多工作,而要使其足够接近则需要做大量工作。 Google continuous collision detection and game physics tunneling for more information on the topics. Google continuous collision detectiongame physics tunneling可获取有关该主题的更多信息。

If you can get away with it, something like [Box2D] or Farseer will give you some interesting options for handling this. 如果您可以解决这个问题,可以使用[Box2D]或Farseer之类的东西给您一些有趣的选择。 Of course you'll probably spend about as much time re-tooling your game around a physics engine as you would have solving the original problem :P 当然,您可能会花费大约与解决原始问题有关的时间花在围绕物理引擎重新设计游戏上:P

I think that both problems can be solved by resolving the collision along its shallow axis. 我认为可以通过解决沿其浅轴的碰撞来解决这两个问题。 Consider this situation: 考虑这种情况:

In your current code, this collision would get resolved along the Y axis, because there's a preference for it, though collision has clearly happened on the X axis. 在您当前的代码中,此冲突将沿Y轴得到解决,因为有一个优先选择,尽管冲突显然发生在X轴上。

The general approach to collision is to first get the collision depth (this can be easily accomplished by making a new rectangle with Rectangle.Intersect static method and using its width and height). 进行碰撞的一般方法是首先获得碰撞深度(这可以通过使用Rectangle.Intersect静态方法制作一个新矩形并使用其宽度和高度来轻松实现)。 Then you have to check how deep the intersection is for both axes and, in your case, reverse shallow axis component of ball's velocity vector. 然后,您必须检查两个轴的交点有多深,在这种情况下,请检查球速度矢量的浅轴反向分量。 The last step is to check relative positions of two intersecting objects and move one of them along the shallow axis - in a given example, ball is to the right of brick, so it has to be moved to the right by the intersection depth. 最后一步是检查两个相交对象的相对位置,然后沿浅轴移动其中一个-在给定的示例中,球在砖块的右侧,因此必须在相交深度处向右移动。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM