简体   繁体   中英

Collision Detection in C# XNA

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;
        }
    }
}

Consider using or just compare your code to the libraries that exist already: see GeoLib which is very simple to use and/or clipper lib . 'Do not reinvent...'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM