Collision detection has been a huge issue for me lately, been breaking my mind over this particular problem.
After I got a reasonable collision detection for my Super Mario World remake in C# (XNA) I have the following problem: I keep getting sort of stuck in blocks when I jump against them...
Example: https://gyazo.com/0f1ac6f4894f41aa4bcbdc73e572e36d
This is my current code than handles the collision: http://pastebin.com/iWsnffWQ
If anyone knows anything that could help my problem, I have been searching high and low for the solution but I to no avail...
EDIT:
This problem has been fixed though a new one arised with object collision on the infamous mario "Mystery Blocks". Whenever I stand on them (not moving) either mario or the world starts to vibrate up and down by about one pixel.
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace PlatFormer
{
public abstract class Entity
{
protected ContentManager _Content;
protected Texture2D _Image;
protected Texture2D _Outline;
protected SpriteSheetAnimation _MoveAnimation;
protected FileManager _FileManager;
protected List<List<string>> _Attributes;
protected List<List<string>> _Contents;
protected Vector2 _Velocity;
protected Vector2 _PrevPosition;
protected Vector2 _Frames;
protected Rectangle _Collbox;
private Rectangle _TileBounds;
protected int _Health;
protected float _MoveSpeed;
protected float _Gravity;
protected float _PreviousBottom;
protected bool _ActivateGravity;
protected bool _TilePositionSync;
protected bool _FacingRight;
protected const float _Friction = 0.9f;
protected const float _Grav = 10f;
protected const float _TerminalVelocity = 10f;
protected Vector2 _Acceleration;
public bool _OnGround;
protected bool _IsJumping;
protected int collisiondeny;
public Vector2 Position;
public SpriteSheetAnimation Animation
{
get { return _MoveAnimation; }
}
public virtual void LoadContent(ContentManager content)
{
_Content = new ContentManager(content.ServiceProvider, "Content");
_Attributes = new List<List<string>>();
_Contents = new List<List<string>>();
}
public virtual void LoadContent(ContentManager content, InputManager input)
{
_Content = new ContentManager(content.ServiceProvider, "Content");
_Attributes = new List<List<string>>();
_Contents = new List<List<string>>();
}
public virtual void UnloadContent()
{
_Content.Unload();
}
public virtual void Update(GameTime gameTime, List<List<WorldTile>> TileMap, List<Enemy> EntList)
{
_PrevPosition = Position;
Position.X = _FacingRight ? Position.X + _MoveSpeed : Position.X - _MoveSpeed;
_Velocity.Y += _Gravity;
if (!_OnGround) { }
else
_Velocity.Y = 0;
UpdatePhysics(gameTime, TileMap, EntList);
_MoveAnimation.Position = Position;
_MoveAnimation.Update(gameTime);
}
public virtual void Update(GameTime gameTime, InputManager input, List<List<WorldTile>> TileMap)
{
}
public virtual void Draw(SpriteBatch spriteBatch)
{
_MoveAnimation.Draw(spriteBatch);
}
protected virtual void UpdatePhysics(GameTime gameTime, List<List<WorldTile>> TileMap, List<Enemy> EntList)
{
_Acceleration *= _Friction;
_Velocity *= _Friction;
_Velocity += _Acceleration;
Position.X = _FacingRight ? Position.X + _Velocity.X : Position.X - _Velocity.X;
Position.Y += _Velocity.Y;
if (Math.Abs(_Acceleration.X) < 0.001f)
{
_MoveAnimation.IsActive = false;
}
UpdateCollBox();
CollisionHandle(TileMap);
EntCollisionHandle(EntList);
if (Position.X == _PrevPosition.X)
_Velocity.X = 0;
if (Position.Y == _PrevPosition.Y)
_Velocity.Y = 0;
}
protected virtual void UpdatePhysics(GameTime gameTime, InputManager input, List<List<WorldTile>> TileMap)
{
float totalSecElapsed = gameTime.ElapsedGameTime.Milliseconds / 1000f;
_Acceleration.X *= _Friction;
_Velocity.X *= _Friction;
_Acceleration.Y = _Grav;
_Velocity.Y += _Acceleration.Y * totalSecElapsed;
_Velocity.X += _Acceleration.X;
if (_Velocity.Y >= _TerminalVelocity)
{
_Velocity.Y = _TerminalVelocity;
}
Position += _Velocity;
if (Math.Abs(_Acceleration.X) < 0.001f)
{
_MoveAnimation.IsActive = false;
}
UpdateCollBox();
CollisionHandle(TileMap); //replace with horizontal collision first then vertical collision
//TestCollisionHandle(TileMap);
if (Position.X == _PrevPosition.X)
_Velocity.X = 0;
if (Position.Y == _PrevPosition.Y)
_Velocity.Y = 0;
}
public void ObjectCollision(List<LevelObject> obj)
{
//OnThisNiceMysteryBox = false;
for (int i = 0; i < obj.Count; i++)
{
if (_Collbox.Intersects(obj[i].Bounds))
{
if (obj[i].Collision != TileCollision.Empty)
{
Vector2 depth = IntersectDepth(_Collbox, obj[i].Bounds);
if (depth != Vector2.Zero)
{
float absDepthX = Math.Abs(depth.X);
float absDepthY = Math.Abs(depth.Y);
if (absDepthY < absDepthX)
{
if (_Collbox.Top <= obj[i].Bounds.Bottom && _Collbox.Top >= obj[i].Bounds.Top)
{
Vector2 tempPos = obj[i].Position;
if (obj[i] is MysteryBox)
{
obj.Remove(obj[i]);
obj.Insert(i, new MysteryBox(tempPos));
}
}
if (_PreviousBottom <= obj[i].Bounds.Top)
{
_OnGround = true;
}
if (obj[i].Collision == TileCollision.Solid || _OnGround)
{
Position = new Vector2((float)Math.Round(Position.X), (float)Math.Round(Position.Y + depth.Y));
_Velocity.Y = 0;
UpdateCollBox();
}
}
else if (obj[i].Collision == TileCollision.Solid)
{
Position = new Vector2((float)Math.Round(Position.X + depth.X), (float)Math.Round(Position.Y));
UpdateCollBox();
}
}
}
}
_PreviousBottom = _Collbox.Bottom;
}
}
protected void EntCollisionHandle(List<Enemy> EntList)
{
for (int i = 0; i < EntList.Count; i++)
{
if (!(EntList[i] == this))
{
Vector2 intersection = IntersectDepth(this._Collbox, EntList[i]._Collbox);
if (intersection != Vector2.Zero)
{
if (collisiondeny == 0)
{
_FacingRight = !_FacingRight;
Position.X = _FacingRight ? Position.X - intersection.X : Position.X + intersection.X;
collisiondeny = 1;
}
else
{
collisiondeny--;
}
//if intersection has occured call both collision handles in colliding classes
}
}
}
}
protected void CollisionHandle(List<List<WorldTile>> TileMap)
{
int leftTile = (int)Math.Floor((float)_Collbox.Left / WorldTile.Width);
int rightTile = (int)Math.Ceiling(((float)_Collbox.Right / WorldTile.Width)) - 1;
int topTile = (int)Math.Floor((float)_Collbox.Top / WorldTile.Height);
int bottomTile = (int)Math.Ceiling(((float)_Collbox.Bottom / WorldTile.Height)) - 1;
_OnGround = false;
for (int y = topTile; y <= bottomTile; y++)
{
for (int x = leftTile; x <= rightTile; x++)
{
TileCollision collision = TileCollision.Empty;
if (y >= 0)
{
if (x >= 0)
{
if (y < TileMap.Count && x < TileMap[y].Count)
collision = TileMap[y][x].Collision;
}
else
{
collision = TileCollision.Solid;
}
}
if (collision != TileCollision.Empty)
{
_TileBounds = new Rectangle(x * WorldTile.Width, y * WorldTile.Height, WorldTile.Width, WorldTile.Height);
Vector2 depth = IntersectDepth(_Collbox, _TileBounds);
if (depth != Vector2.Zero)
{
float absDepthX = Math.Abs(depth.X);
float absDepthY = Math.Abs(depth.Y);
if (absDepthY <= absDepthX || collision == TileCollision.OneWay)
{
if (_PreviousBottom <= _TileBounds.Top)
_OnGround = true;
if ((collision == TileCollision.Solid) || _OnGround)
{
Position = new Vector2((int)Math.Round(Position.X), (int)Math.Round(Position.Y + depth.Y));
UpdateCollBox();
}
}
else if (collision == TileCollision.Solid)
{
Position = new Vector2((int)Math.Round(Position.X + depth.X), (int)Math.Round(Position.Y));
_FacingRight = !_FacingRight;
//_Velocity.Y = 0;
UpdateCollBox();
}
}
}
}
}
_PreviousBottom = _Collbox.Bottom;
}
protected void TestCollisionHandle(List<List<WorldTile>> TileMap)
{
int leftTile = (int)Math.Floor((float)_Collbox.Left / WorldTile.Width);
int rightTile = (int)Math.Ceiling(((float)_Collbox.Right / WorldTile.Width)) - 1;
int topTile = (int)Math.Floor((float)_Collbox.Top / WorldTile.Height);
int bottomTile = (int)Math.Ceiling(((float)_Collbox.Bottom / WorldTile.Height)) - 1;
_OnGround = false;
for (int y = topTile; y <= bottomTile; y++)
{
for (int x = leftTile; x <= rightTile; x++)
{
TileCollision collision = TileCollision.Empty;
if (y >= 0)
{
if (x >= 0)
{
if (y < TileMap.Count && x < TileMap[y].Count)
collision = TileMap[y][x].Collision;
}
}
if(collision != TileCollision.Empty)
{
//if collision can occor, get tilecollisionbox for horizontal
_TileBounds = new Rectangle(x * WorldTile.Width, y * WorldTile.Height, WorldTile.Width, WorldTile.Height);
//get the horizontal collision depth, will return zero if none is found
GetHorizontalIntersectionDepth(_Collbox, _TileBounds);
}
}
}
_PreviousBottom = _Collbox.Bottom;
}
private void UpdateCollBox()
{
_Collbox = new Rectangle((int)Math.Round(Position.X), (int)Math.Round(Position.Y), Animation.FrameWidth, Animation.FrameHeight);
}
private Vector2 IntersectDepth(Rectangle rectangleA, Rectangle rectangleB)
{
float halfWidthA = rectangleA.Width / 2.0f;
float halfHeightA = rectangleA.Height / 2.0f;
float halfWidthB = rectangleB.Width / 2.0f;
float halfHeightB = rectangleB.Height / 2.0f;
Vector2 centerA = new Vector2(rectangleA.Left + halfWidthA, rectangleA.Top + halfHeightA);
Vector2 centerB = new Vector2(rectangleB.Left + halfWidthB, rectangleB.Top + halfHeightB);
float distanceX = centerA.X - centerB.X;
float distanceY = centerA.Y - centerB.Y;
float minDistanceX = halfWidthA + halfWidthB;
float minDistanceY = halfHeightA + halfHeightB;
// If no intersection is happening, return Vector2.Zero
if (Math.Abs(distanceX) >= minDistanceX || Math.Abs(distanceY) >= minDistanceY)
return Vector2.Zero;
// Calculate instersection depth
float depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
float depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
return new Vector2(depthX, depthY);
}
private float GetHorizontalIntersectionDepth(Rectangle rectA, Rectangle rectB)
{
// Calculate half sizes.
float halfWidthA = rectA.Width / 2.0f;
float halfWidthB = rectB.Width / 2.0f;
// Calculate centers.
float centerA = rectA.Left + halfWidthA;
float centerB = rectB.Left + halfWidthB;
// Calculate current and minimum-non-intersecting distances between centers.
float distanceX = centerA - centerB;
float minDistanceX = halfWidthA + halfWidthB;
// If we are not intersecting at all, return (0, 0).
if (Math.Abs(distanceX) >= minDistanceX)
return 0f;
// Calculate and return intersection depths.
return distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
}
private float GetVerticalIntersectionDepth(Rectangle rectA, Rectangle rectB)
{
// Calculate half sizes.
float halfHeightA = rectA.Height / 2.0f;
float halfHeightB = rectB.Height / 2.0f;
// Calculate centers.
float centerA = rectA.Top + halfHeightA;
float centerB = rectB.Top + halfHeightB;
// Calculate current and minimum-non-intersecting distances between centers.
float distanceY = centerA - centerB;
float minDistanceY = halfHeightA + halfHeightB;
// If we are not intersecting at all, return (0, 0).
if (Math.Abs(distanceY) >= minDistanceY)
return 0f;
// Calculate and return intersection depths.
return distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
}
public enum Direction
{
Horizontal,
Vertical
}
private bool TileIntersectsPlayer(Rectangle player, Rectangle block, Direction direction, out Vector2 depth)
{
depth = direction == Direction.Vertical ? new Vector2(0, GetVerticalIntersectionDepth(player, block)) : new Vector2(GetHorizontalIntersectionDepth(player, block), 0);
return depth.Y != 0 || depth.X != 0;
}
}
}