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!