4
\$\begingroup\$

I'm having trouble with my game that is based off the XNA Platformer starter kit. My game uses smaller tiles (16x16) then the original (32x40) which I'm thinking may be having an effect on collision (Being it needs to be more precise).

Standing on the edge of a tile and jumping causes the player to move off the the tile when he lands. And 80% of the time, when the player lands, he goes flying though SOLID tiles in a diagonal fashion. This is very annoying as it is almost impossible to test other features, when spawning and jumping will result in the player landing in another part of the level or falling off the edge completely.

The code is as follows:

/// <summary>
        /// Updates the player's velocity and position based on input, gravity, etc.
        /// </summary>
        public void ApplyPhysics(GameTime gameTime)
        {
            float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds;

            Vector2 previousPosition = Position;

            // Base velocity is a combination of horizontal movement control and
            // acceleration downward due to gravity.
            velocity.X += movement * MoveAcceleration * elapsed;
            velocity.Y = MathHelper.Clamp(velocity.Y + GravityAcceleration * elapsed, -MaxFallSpeed, MaxFallSpeed);

            velocity.Y = DoJump(velocity.Y, gameTime);

            // Apply pseudo-drag horizontally.
            if (IsOnGround)
                velocity.X *= GroundDragFactor;
            else
                velocity.X *= AirDragFactor;

            // Prevent the player from running faster than his top speed.            
            velocity.X = MathHelper.Clamp(velocity.X, -MaxMoveSpeed, MaxMoveSpeed);

            // Apply velocity.
            Position += velocity * elapsed;
            Position = new Vector2((float)Math.Round(Position.X), (float)Math.Round(Position.Y));

            // If the player is now colliding with the level, separate them.
            HandleCollisions();

            // If the collision stopped us from moving, reset the velocity to zero.
            if (Position.X == previousPosition.X)
                velocity.X = 0;

            if (Position.Y == previousPosition.Y)
                velocity.Y = 0;
        }


        /// <summary>
        /// Detects and resolves all collisions between the player and his neighboring
        /// tiles. When a collision is detected, the player is pushed away along one
        /// axis to prevent overlapping. There is some special logic for the Y axis to
        /// handle platforms which behave differently depending on direction of movement.


        /// </summary>
        private void HandleCollisions()
        {
            // Get the player's bounding rectangle and find neighboring tiles.
            Rectangle bounds = BoundingRectangle;
            int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
            int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
            int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
            int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;

            // Reset flag to search for ground collision.
            isOnGround = false;

            // For each potentially colliding tile,
            for (int y = topTile; y <= bottomTile; ++y)
            {
                for (int x = leftTile; x <= rightTile; ++x)
                {
                    // If this tile is collidable,
                    ItemCollision collision = Level.GetCollision(x, y);
                    if (collision != ItemCollision.Passable)
                    {
                        // Determine collision depth (with direction) and magnitude.
                        Rectangle tileBounds = Level.GetBounds(x, y);
                        Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileBounds);
                        if (depth != Vector2.Zero)
                        {
                            float absDepthX = Math.Abs(depth.X);
                            float absDepthY = Math.Abs(depth.Y);

                            // Resolve the collision along the shallow axis.
                            if (absDepthY < absDepthX || collision == ItemCollision.Platform)
                            {
                                // If we crossed the top of a tile, we are on the ground.
                                if (previousBottom <= tileBounds.Top)
                                    isOnGround = true;

                                // Ignore platforms, unless we are on the ground.
                                if (collision == ItemCollision.Impassable || IsOnGround)
                                {
                                    // Resolve the collision along the Y axis.
                                    Position = new Vector2(Position.X, Position.Y + depth.Y);

                                    // Perform further collisions with the new bounds.
                                    bounds = BoundingRectangle;
                                }
                            }
                            else if (collision == ItemCollision.Impassable) // Ignore platforms.
                            {
                                // Resolve the collision along the X axis.
                                Position = new Vector2(Position.X + depth.X, Position.Y);

                                // Perform further collisions with the new bounds.
                                bounds = BoundingRectangle;
                            }
                        }
                    }
                }
            }

            // Save the new bounds bottom.
            previousBottom = bounds.Bottom;
        }

It also tends to jitter a little bit sometimes, I'm solved some of this with some fixes I found here on stackexchange, But Ive only seen one other case of the flying though blocks problem. This question seems to have a similar problem in the video, but mine is more crazy.

Again this is a very annoying bug! Any help would be greatly appreciated!

EDIT: Speed stuff

    // Constants for controling horizontal movement
    private const float MoveAcceleration = 13000.0f;
    private const float MaxMoveSpeed = 1750.0f;
    private const float GroundDragFactor = 0.48f;
    private const float AirDragFactor = 0.58f;

    // Constants for controlling vertical movement
    private const float MaxJumpTime = 0.35f;
    private const float JumpLaunchVelocity = -3500.0f;
    private const float GravityAcceleration = 3400.0f;
    private const float MaxFallSpeed = 550.0f;
    private const float JumpControlPower = 0.14f; 
\$\endgroup\$
2
  • \$\begingroup\$ What is the maximum vertical speed? Put another way, how quickly can the player fall? \$\endgroup\$
    – Mike Cluck
    Commented Jun 11, 2012 at 19:46
  • \$\begingroup\$ Edited, I was just playing around, A lower speed seems to help, but dosent completely solve it. (half size = half speed I was thinking) That is the original code that is giving me the problems \$\endgroup\$
    – Cyral
    Commented Jun 11, 2012 at 19:57

1 Answer 1

7
\$\begingroup\$

I believe you're on the right track. Take a look at my amazing artistic skills here ;) Platform collision visualization

Let's say that the player is the red block with the solid being their current position and the open square being their previous position. Now say that the blue tile is the platform you expect to land on and the green one is some other "impassable" tile beneath it.

The collision resolution assumes the you can only intersect one solid tile at a time and checks against it. Specifically, these lines

if (previousBottom <= tileBounds.Top)
    isOnGround = true;

So if we look at the right side of the image we can see that technically your previous bottom position would be less than the green tiles top. Since we've determined that, we assume it's a piece of ground and try to move on top of it. Then on the next go around since we've "corrected" the vertical collision, any other collisions will be treated as horizontal collisions and attempt to "push you away" from the other block, thus giving you a diagonal descent in to the pits of no-clip zone.

You've got a couple different solutions you could try:

  • Change the maximum falling speed to be no greater than the height of your tiles.
  • Use a more advanced collision structure and try to predict collisions before they occur.
  • Check not only tiles you're directly colliding with but also one (or more) in a given direction. For example, if you're colliding with an "Impassable" check the tile just above it to see if it's a platform. It'd be pretty safe bet to say you passed through it if that's the case.
\$\endgroup\$
3
  • \$\begingroup\$ Awesome explanation! (Hey my drawing skills are no better, thats why Im coding the game) I set the max fall speed to 256(16 per frame) it works great! I knew I was close. Thanks! \$\endgroup\$
    – Cyral
    Commented Jun 11, 2012 at 20:31
  • \$\begingroup\$ After more debugging, What about fps changes? How could I change the maxSpeed to work with fps (ie. If fps is 30, and Tile.Height is 16, what should maxspeed be?) \$\endgroup\$
    – Cyral
    Commented Jun 14, 2012 at 23:02
  • \$\begingroup\$ That's where things get tricky. This particular system of collision resolution doesn't do well with FPS spikes but you can work around it for most cases by clamping your change in position. Vector2 change = velocity * elapsed; change.Y = MathHelper.Clamp(change.Y, -MaxFallSpeed, MaxFallSpeed); Position += change; \$\endgroup\$
    – Mike Cluck
    Commented Jun 14, 2012 at 23:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .