Jump to content

  • Log In with Google      Sign In   
  • Create Account

#ActualMekuri

Posted 08 June 2013 - 02:08 AM

Hey there.

I recently began on a new project, inspired by my Ludum Dare entry, and I decided to some more solid collision detection than in my other project.

 

I've finally reached a result that I am fairly happy with, but I experience jitters, that I don't fully understand. When I move right and collide with something, my sprite jitters.. But when going left, it works exactly as intended. Also there are a few corner cases where there's some small jitter.

 

I do the collision detection and seperation like this:

 

1. Do horizontal movement.

2. Check and resolve horizontal collisions.

3. Do vertical movement.

4. Check and resolve vertical movement.

 

For both the horizontal and vertical step, I first check for rectangular collision, and then pixel based within the rectangle.

Since I divide both movement and collision handling up in horizontal and vertical, I have a hard time understanding why it jitters.

 

The code I am using is the following:

 

Player class handles movement and velocity

private void Movement()
        {
            PreviousPosition = position;
            //TODO: Add custom controls, by saving keys in a list or something, and then compare pressed keys to that list, and act accordingly.
            if (InputController.CurrentKeyboardState.IsKeyDown(Keys.A) || InputController.CurrentKeyboardState.IsKeyDown(Keys.Left))
                movement = -1.0f;
            else if (InputController.CurrentKeyboardState.IsKeyDown(Keys.D) || InputController.CurrentKeyboardState.IsKeyDown(Keys.Right))
                movement = 1.0f;
            else
                movement = 0f;

            //For testing jump strength - Final version will have a fixed strength!
            if(InputController.IsKeyPressed(Keys.OemPlus))
                BASE_JUMP_POWER++;
            else if (InputController.IsKeyPressed(Keys.OemMinus))
                BASE_JUMP_POWER--;
            
            //Velocity
            velocity.X += movement * BASE_SPEED;
            velocity.X *= AIR_DRAG;

            velocity.Y += GRAVITY;
            velocity.X = MathHelper.Clamp((float)Math.Round(velocity.X, 4, MidpointRounding.ToEven), -MAX_MOVE_SPEED, MAX_MOVE_SPEED);

            if (isOnGround)
            {
                velocity.Y = MathHelper.Clamp(-velocity.Y * .8f, -MAX_JUMP_SPEED, -BASE_JUMP_POWER);
                isOnGround = false;
            }
            velocity.Y = MathHelper.Clamp((float)Math.Round(velocity.Y, 4, MidpointRounding.ToEven), -MAX_JUMP_SPEED, MAX_FALL_SPEED);
        }

        public override void ApplyHorizontalMovement()
        {
            position.X += velocity.X;
            position.X = (float)Math.Round(position.X);
        }

        public override void ApplyVerticalMovement()
        {
            position.Y += velocity.Y;
            position.Y = (float)Math.Round(position.Y);        
        }

Movement() is called every update cycle.

Notice the apply movement methods, those are called by the "playfield" class, during collision handling, as you can see in the following code:

blic void WorldCollision(Sprite sprite, bool verticalMovement)
        {
            if (verticalMovement)
                sprite.ApplyVerticalMovement();
            else
                sprite.ApplyHorizontalMovement();
            for (int x = sprite.Bounds.X / TILE_SIZE - 1; x < sprite.Bounds.X / TILE_SIZE + 2; x++)
            {
                for (int y = sprite.Bounds.Y / TILE_SIZE - 1; y < sprite.Bounds.Y / TILE_SIZE + 2; y++)
                {
                    Rectangle tmp = new Rectangle(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);                    
                    if (sprite.Bounds.Intersects(tmp))
                   {
                       if (StaticTileInfo.GetCollisionType(_world[x, y].Type) != Collision.Passable)
                       {
                           if (PixelbasedCollision(sprite.Bounds, sprite.GetTextureData, tmp, GetTileColorData(x, y)))
                           {
                               if (StaticTileInfo.GetCollisionType(_world[x, y].Type) == Collision.Spike)
                               {
                                   sprite.IsDying = true;
                                   return;
                               }
                               else
                               {
                                   if (verticalMovement)
                                   {
                                       float intersectionDepth = RectangleExtensions.GetVerticalIntersectionDepth(sprite.Bounds, tmp);
                                       sprite.Position += new Vector2(0, intersectionDepth);
                                       if (StaticTileInfo.GetCollisionType(_world[sprite.Bounds.Center.X / TILE_SIZE, (sprite.Bounds.Bottom + 1) / TILE_SIZE].Type) == Collision.Impassable ||
                                           StaticTileInfo.GetCollisionType(_world[sprite.Bounds.Left / TILE_SIZE, (sprite.Bounds.Bottom + 1) / TILE_SIZE].Type) == Collision.Impassable ||
                                           StaticTileInfo.GetCollisionType(_world[sprite.Bounds.Right / TILE_SIZE, (sprite.Bounds.Bottom + 1) / TILE_SIZE].Type) == Collision.Impassable)
                                           sprite.IsOnGround = true;
                                       else
                                           sprite.Velocity = new Vector2(sprite.Velocity.X, 0);
                                   }
                                   else
                                   {
                                       float intersectionDepth = RectangleExtensions.GetHorizontalIntersectionDepth(sprite.Bounds, tmp); 
                                       sprite.Position += new Vector2(intersectionDepth, 0);
                                       sprite.Velocity = new Vector2(0, sprite.Velocity.Y);
                                   }
                                   break;
                               }
                           }
                       }
                       else
                           continue;
                   }
                }
            }
            if(!verticalMovement)
                WorldCollision(sprite, true);
        }

private bool PixelbasedCollision(Rectangle rect1, Color[] data1, Rectangle rect2, Color[] data2)
        {
            int top = Math.Max(rect1.Top, rect2.Top);
            int bottom = Math.Min(rect1.Bottom, rect2.Bottom);
            int left = Math.Max(rect1.Left, rect2.Left);
            int right = Math.Min(rect1.Right, rect2.Right);

            for (int y = top; y < bottom; y++)
            {
                for (int x = left; x < right; x++)
                {
                    Color color1 = data1[(x - rect1.Left) + (y - rect1.Top) * rect1.Width];
                    Color color2 = data2[(x - rect2.Left) + (y - rect2.Top) * rect2.Width];

                    if (color1.A != 0 && color2.A != 0)
                        return true;
                }
            }
            
            return false;
        }

        private Color[] GetTileColorData(int x, int y)
        {
            Color[] tmp = new Color[TILE_SIZE * TILE_SIZE];
            _tileSheet.GetData<Color>(0 * TILE_SIZE, new Rectangle(_world[x,y].Variant * TILE_SIZE, (int)(_world[x,y].Type - 1) * TILE_SIZE, TILE_SIZE, TILE_SIZE), tmp, 0, tmp.Length);
            return tmp;
        }

 

I've also made a small video showing the issues I am having:

 

I hope I provided all the information necessary. I know there's a lot of code, but I was hoping someone with experience might immediately know the error just from seeing the movie :-)

 

Thanks for reading!


#1Mekuri

Posted 07 June 2013 - 04:19 PM

Hey there.

I recently began on a new project, inspired by my Ludum Dare entry, and I decided to some more solid collision detection than in my other project.

 

I've finally reached a result that I am fairly happy with, but I experience jitters, that I don't fully understand. When I move right and collide with something, my sprite jitters.. But when going left, it works exactly as intended. Also there are a few corner cases where there's some small jitter.

 

I do the collision detection and seperation like this:

 

1. Do horizontal movement.

2. Check and resolve horizontal collisions.

3. Do vertical movement.

4. Check and resolve vertical movement.

 

For both the horizontal and vertical step, I first check for rectangular collision, and then pixel based within the rectangle.

Since I divide both movement and collision handling up in horizontal and vertical, I have a hard time understanding why it jitters.

 

The code I am using is the following:

 

Player class handles movement and velocity

private void Movement()
        {
            PreviousPosition = position;
            //TODO: Add custom controls, by saving keys in a list or something, and then compare pressed keys to that list, and act accordingly.
            if (InputController.CurrentKeyboardState.IsKeyDown(Keys.A) || InputController.CurrentKeyboardState.IsKeyDown(Keys.Left))
                movement = -1.0f;
            else if (InputController.CurrentKeyboardState.IsKeyDown(Keys.D) || InputController.CurrentKeyboardState.IsKeyDown(Keys.Right))
                movement = 1.0f;
            else
                movement = 0f;

            //For testing jump strength - Final version will have a fixed strength!
            if(InputController.IsKeyPressed(Keys.OemPlus))
                BASE_JUMP_POWER++;
            else if (InputController.IsKeyPressed(Keys.OemMinus))
                BASE_JUMP_POWER--;
            
            //Velocity
            velocity.X += movement * BASE_SPEED;
            velocity.X *= AIR_DRAG;

            velocity.Y += GRAVITY;
            velocity.X = MathHelper.Clamp((float)Math.Round(velocity.X, 4, MidpointRounding.ToEven), -MAX_MOVE_SPEED, MAX_MOVE_SPEED);

            if (isOnGround)
            {
                velocity.Y = MathHelper.Clamp(-velocity.Y * .8f, -MAX_JUMP_SPEED, -BASE_JUMP_POWER);
                isOnGround = false;
            }
            velocity.Y = MathHelper.Clamp((float)Math.Round(velocity.Y, 4, MidpointRounding.ToEven), -MAX_JUMP_SPEED, MAX_FALL_SPEED);

            if (velocity.X > -.1f && velocity.X < .1f)
                velocity.X = 0;

        }

        public override void ApplyHorizontalMovement()
        {
            position.X += velocity.X;
            position.X = (float)Math.Round(position.X);
        }

        public override void ApplyVerticalMovement()
        {
            position.Y += velocity.Y;
            position.Y = (float)Math.Round(position.Y);        
        }

Movement() is called every update cycle.

Notice the apply movement methods, those are called by the "playfield" class, during collision handling, as you can see in the following code:

blic void WorldCollision(Sprite sprite, bool verticalMovement)
        {
            if (verticalMovement)
                sprite.ApplyVerticalMovement();
            else
                sprite.ApplyHorizontalMovement();
            for (int x = sprite.Bounds.X / TILE_SIZE - 1; x < sprite.Bounds.X / TILE_SIZE + 2; x++)
            {
                for (int y = sprite.Bounds.Y / TILE_SIZE - 1; y < sprite.Bounds.Y / TILE_SIZE + 2; y++)
                {
                    Rectangle tmp = new Rectangle(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);                    
                    if (sprite.Bounds.Intersects(tmp))
                   {
                       if (StaticTileInfo.GetCollisionType(_world[x, y].Type) != Collision.Passable)
                       {
                           if (PixelbasedCollision(sprite.Bounds, sprite.GetTextureData, tmp, GetTileColorData(x, y)))
                           {
                               if (StaticTileInfo.GetCollisionType(_world[x, y].Type) == Collision.Spike)
                               {
                                   sprite.IsDying = true;
                                   return;
                               }
                               else
                               {
                                   if (verticalMovement)
                                   {
                                       float intersectionDepth = RectangleExtensions.GetVerticalIntersectionDepth(sprite.Bounds, tmp);
                                       sprite.Position += new Vector2(0, intersectionDepth);
                                       if (StaticTileInfo.GetCollisionType(_world[sprite.Bounds.Center.X / TILE_SIZE, (sprite.Bounds.Bottom + 1) / TILE_SIZE].Type) == Collision.Impassable ||
                                           StaticTileInfo.GetCollisionType(_world[sprite.Bounds.Left / TILE_SIZE, (sprite.Bounds.Bottom + 1) / TILE_SIZE].Type) == Collision.Impassable ||
                                           StaticTileInfo.GetCollisionType(_world[sprite.Bounds.Right / TILE_SIZE, (sprite.Bounds.Bottom + 1) / TILE_SIZE].Type) == Collision.Impassable)
                                           sprite.IsOnGround = true;
                                       else
                                           sprite.Velocity = new Vector2(sprite.Velocity.X, 0);
                                   }
                                   else
                                   {
                                       float intersectionDepth = RectangleExtensions.GetHorizontalIntersectionDepth(sprite.Bounds, tmp); 
                                       sprite.Position += new Vector2(intersectionDepth, 0);
                                       sprite.Velocity = new Vector2(0, sprite.Velocity.Y);
                                   }
                                   break;
                               }
                           }
                       }
                       else
                           continue;
                   }
                }
            }
            if(!verticalMovement)
                WorldCollision(sprite, true);
        }

private bool PixelbasedCollision(Rectangle rect1, Color[] data1, Rectangle rect2, Color[] data2)
        {
            int top = Math.Max(rect1.Top, rect2.Top);
            int bottom = Math.Min(rect1.Bottom, rect2.Bottom);
            int left = Math.Max(rect1.Left, rect2.Left);
            int right = Math.Min(rect1.Right, rect2.Right);

            for (int y = top; y < bottom; y++)
            {
                for (int x = left; x < right; x++)
                {
                    Color color1 = data1[(x - rect1.Left) + (y - rect1.Top) * rect1.Width];
                    Color color2 = data2[(x - rect2.Left) + (y - rect2.Top) * rect2.Width];

                    if (color1.A != 0 && color2.A != 0)
                        return true;
                }
            }
            
            return false;
        }

        private Color[] GetTileColorData(int x, int y)
        {
            Color[] tmp = new Color[TILE_SIZE * TILE_SIZE];
            _tileSheet.GetData<Color>(0 * TILE_SIZE, new Rectangle(_world[x,y].Variant * TILE_SIZE, (int)(_world[x,y].Type - 1) * TILE_SIZE, TILE_SIZE, TILE_SIZE), tmp, 0, tmp.Length);
            return tmp;
        }

 

I've also made a small video showing the issues I am having:

 

I hope I provided all the information necessary. I know there's a lot of code, but I was hoping someone with experience might immediately know the error just from seeing the movie :-)

 

Thanks for reading!


PARTNERS