碰撞检测错误和滞后运动

时间:2014-03-09 15:03:13

标签: c# xna-4.0 game-physics

这是我第一次这样做而且很抱歉,如果我这样做错了,但是在过去的几周里,我一直在用xna和c#做一个小项目,同时关注一本书 我制作了一个简单的平台游戏,包括敌人,收藏品和陷阱 但我碰到了一个问题...

你看我一直在努力寻找(并写出)一个好的碰撞检测功能。我最终使用了微软平台项目中的一个,除了书中找到的功能,我正在阅读一切都很好,但我注意到游戏角色在移动时滞后(有点像突然移动而不是平滑运动)。帧速率稳定且远高于400(我关闭了固定时间步长)。

所以我的问题是,如果有人可以查看我的代码并告诉我这是什么问题.. 我认为主要问题在于HandleCollisons功能或水平和垂直碰撞检测功能

ps:英语不是我的第一语言,因为你可能已经注意到了对于坏语法的抱歉:D /> 无论如何,提前谢谢

这是执行碰撞检测的类:

    public override void Update(GameTime gameTime)
    {
        float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

        Vector2 prevLocation = worldLocation;

        if (useGravity) { speed.Y += Gravity.Y * elapsed; }

        Vector2 moveAmount;
        moveAmount.X = speed.X * elapsed;
        moveAmount.Y = speed.Y * elapsed;

        if (isPlayer)
        {
            moveAmount = horizontalCollisionTest(moveAmount);
            moveAmount = verticalCollisionTest(moveAmount);
        }
        if (moveAmount == Vector2.Zero)
        {
            worldLocation = prevLocation;
        }
        else
        {
            worldLocation.X += moveAmount.X;
            worldLocation.Y += moveAmount.Y;
        }

        HandleCollisions();

        if (limitSpeed)
        {
            if (speed.X > maxSpeed.X) speed.X = maxSpeed.X;
            if (speed.X < -maxSpeed.X) speed.X = -maxSpeed.X;
            if (speed.Y > maxSpeed.Y) speed.Y = maxSpeed.Y;
            if (speed.Y < -maxSpeed.Y) speed.Y = -maxSpeed.Y;
        }

        if ((speed.Y > MaxSpeed.Y / 20))
        {
            onGround = false;
            jumping = true;
        }

        if (!beingMoved)
        {
            if (speed.X < 0) speed.X += deaccelerationX * elapsed;
            if (speed.X > 0) speed.X -= deaccelerationX * elapsed;

            if (speed.X > 0 && speed.X < (deaccelerationX * elapsed)) speed.X = 0;
            if (speed.X < 0 && speed.X > (-deaccelerationX * elapsed)) speed.X = 0;
        }

        if (worldLocation.X <= 0)
        {
            worldLocation.X = 0;
            speed.X = 0;
        }
        if (worldLocation.X >= (Camera.WorldRectangle.Width - FrameWidth))
        {
            worldLocation.X = Camera.WorldRectangle.Width - FrameWidth;
            speed.X = 0;
        }

        base.Update(gameTime);
    }

    private void HandleCollisions()
    {
        Rectangle bounds = CollisionRectangle;
        int left = (int)((bounds.Left / TileMap.TileWidth)) * TileMap.TileWidth;
        int right = (int)((bounds.Right / TileMap.TileWidth)) * TileMap.TileWidth;

        int top = (int)((bounds.Top / TileMap.TileHeight)) * TileMap.TileHeight;
        int bottom = (int)((bounds.Bottom / TileMap.TileHeight)) * TileMap.TileHeight;

        for (int i = left; i <= right; i += TileMap.TileWidth)
        {
            for (int j = top; j <= bottom; j += TileMap.TileHeight)
            {
                Rectangle tile = TileMap.CellWorldRectangle(i / TileMap.TileWidth, j / TileMap.TileHeight);

                if (!TileMap.CellIsPassable(TileMap.GetCellByPixel(new Vector2(i, j))))
                {
                    Rectangle tileRect = new Rectangle(i, j, TileMap.TileWidth, TileMap.TileHeight);
                    Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileRect);

                    if (depth != Vector2.Zero)
                    {
                        float absDepthX = Math.Abs(depth.X);
                        float absDepthY = Math.Abs(depth.Y);

                        if (absDepthY <= absDepthX)
                        {
                            if (preBottom <= tileRect.Top)
                            {
                                onGround = true;
                                jumping = false;
                            }
                            if (onGround)
                            {
                                worldLocation = new Vector2(worldLocation.X, worldLocation.Y + depth.Y + 1);
                                bounds = CollisionRectangle;
                                speed.Y = 0;
                            }
                        }
                        else
                        {
                            worldLocation = new Vector2(worldLocation.X + depth.X, worldLocation.Y);
                            bounds = CollisionRectangle;
                            speed.X = 0;
                        }
                    }
                }
            }
        }
        preBottom = bounds.Bottom;
    }

    private Vector2 horizontalCollisionTest(Vector2 moveAmount)
    {
        if (moveAmount.X == 0)
            return moveAmount;

        Rectangle afterMoveRect = CollisionRectangle;
        afterMoveRect.Offset((int)moveAmount.X, -5);
        Vector2 corner1, corner2;

        if (moveAmount.X < 0)
        {
            corner1 = new Vector2(afterMoveRect.Left,
                                  afterMoveRect.Top + 1);
            corner2 = new Vector2(afterMoveRect.Left,
                                  afterMoveRect.Bottom - 1);
        }
        else
        {
            corner1 = new Vector2(afterMoveRect.Right,
                                  afterMoveRect.Top + 1);
            corner2 = new Vector2(afterMoveRect.Right,
                                  afterMoveRect.Bottom - 1);
        }

        Vector2 mapCell1 = TileMap.GetCellByPixel(corner1);
        Vector2 mapCell2 = TileMap.GetCellByPixel(corner2);

        if (!TileMap.CellIsPassable(mapCell1) ||
            !TileMap.CellIsPassable(mapCell2))
        {
            moveAmount.X = 0;
            speed.X = 0;
        }

        return moveAmount;
    }

    private Vector2 verticalCollisionTest(Vector2 moveAmount)
    {
        if (moveAmount.Y == 0)
            return moveAmount;

        Rectangle afterMoveRect = CollisionRectangle;
        afterMoveRect.Offset((int)moveAmount.X, (int)moveAmount.Y + 1);
        Vector2 corner1, corner2;

        if (moveAmount.Y < 0)
        {
            corner1 = new Vector2(afterMoveRect.Left + 1,
                                  afterMoveRect.Top);
            corner2 = new Vector2(afterMoveRect.Right - 1,
                                  afterMoveRect.Top);
        }
        else
        {
            corner1 = new Vector2(afterMoveRect.Left + 1,
                                  afterMoveRect.Bottom);
            corner2 = new Vector2(afterMoveRect.Right - 1,
                                  afterMoveRect.Bottom);
        }

        Vector2 mapCell1 = TileMap.GetCellByPixel(corner1);
        Vector2 mapCell2 = TileMap.GetCellByPixel(corner2);

        if (!TileMap.CellIsPassable(mapCell1) ||
            !TileMap.CellIsPassable(mapCell2))
        {
            if (moveAmount.Y > 0)
            {
                onGround = true; 
            }
            moveAmount.Y = 0;
            speed.Y = 0;
        }            

        return moveAmount;
    }

    public override void Draw(SpriteBatch spriteBatch)
    {
        base.Draw(spriteBatch);
    }
}

}

并且矩形交叉深度的代码是:

public static Vector2 GetIntersectionDepth(this Rectangle rectA, Rectangle rectB)
    {
        // Calculate half sizes.
        float halfWidthA = rectA.Width / 2.0f;
        float halfHeightA = rectA.Height / 2.0f;
        float halfWidthB = rectB.Width / 2.0f;
        float halfHeightB = rectB.Height / 2.0f;

        // Calculate centers.
        Vector2 centerA = new Vector2(rectA.Left + halfWidthA, rectA.Top + halfHeightA);
        Vector2 centerB = new Vector2(rectB.Left + halfWidthB, rectB.Top + halfHeightB);

        // Calculate current and minimum-non-intersecting distances between centers.
        float distanceX = centerA.X - centerB.X;
        float distanceY = centerA.Y - centerB.Y;
        float minDistanceX = halfWidthA + halfWidthB;
        float minDistanceY = halfHeightA + halfHeightB;

        // If we are not intersecting at all, return (0, 0).
        if (Math.Abs(distanceX) >= minDistanceX || Math.Abs(distanceY) >= minDistanceY)
            return Vector2.Zero;

        // Calculate and return intersection depths.
        float depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
        float depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
        return new Vector2(depthX, depthY);
    }

2 个答案:

答案 0 :(得分:0)

您的碰撞检测非常棒。在我的2D游戏中,我使用简单的XNA矩形(纹理的边界)。世界有许多不同的块(带有矩形的64x64纹理),我使用简单的包裹纹理来增加块的大小然后通过调用character.rectangle.Intersects(world,我)来检查字符的矩形是否与任何世界矩形相交。矩形[i])进入世界矩形的循环。

似乎你的游戏Update()方法的循环费用很高:

        for (int i = left; i <= right; i += TileMap.TileWidth)
        {
           for (int j = top; j <= bottom; j += TileMap.TileHeight)
           {
              ...
           }
        }

要优化 LARGE循环,这可能导致 FPS丢弃,我使用 QuadTree

Read this about quadtree which optimize collision detection

您可以使用不同的算法,如Octree,BinaryTree,BSP等,但是quadtree是2d空间的最佳选择。

答案 1 :(得分:0)

我禁用了所有碰撞,只留下了移动玩家仍在抖动。所以!我已经开始深入研究运动,发现播放器的速度在最大速度下是稳定的,并且不会改变它意味着不会再次引起抖动。所以!接下来我的步骤是挖相机,我想我找到了你在找什么!似乎您在所有其他代码移动计算等之前计算在玩家输入处的相机重新定位,并且它会导致抖动。 我在base.Update()之后做了reposition()方法(在处理了所有运动代码之后)并且我认为它有效! 以下是Player.cs Update方法中代码的一些变化。注意repositionCamera();放置在代码处:

    public class Player : MovingGameObject
    {
    ...
    public override void Update(GameTime gameTime)
    {
        float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;
        this.beingMoved = false;

        if (canBeDameged)
        {
            if (!canTakeDamage)
            {
                timeSinceDamage += (float)gameTime.ElapsedGameTime.Milliseconds;
                if (timeSinceDamage >= damegeDelay)
                {
                    canTakeDamage = true;
                    timeSinceDamage = 0;
                }
            }
        }

        if (hasInput)
        {
            string newAnimation = "Stand";

            if (Input.KeyDown(leftKey0) ||
                Input.KeyDown(leftKey1))
            {
                speed.X -= accelerationX * elapsed;
                beingMoved = true;
                flipped = true;
                newAnimation = "Walk";
            }
            if (Input.KeyDown(rightKey0) ||
                Input.KeyDown(rightKey1))
            {
                speed.X += accelerationX * elapsed;
                beingMoved = true;
                flipped = false;
                newAnimation = "Walk";
            }
            if ((Input.KeyPressed(jumpKey0) ||
                Input.KeyPressed(jumpKey1) ||
                Input.KeyPressed(jumpKey2)))
            {
                Jump();
                newAnimation = "Jump";
            }
            if ((Input.KeyDown(downKey0) ||
                Input.KeyDown(downKey1)))
            {
                speed.Y += Gravity.Y * 3 * elapsed;
            }
            HandelAnimations(newAnimation);

            //repositionCamera(); --> replace it after base.Update(gameTime)
        }

        base.Update(gameTime);

        repositionCamera(); // place here!
    }
    ...
    }
相关问题