0
\$\begingroup\$

I'm currently working on a project in XNA and I'm trying to implement collision detection but can't wrap my head around how exactly i'm going to do it.

I'm using the Tiled application to create my maps, exporting to a .tmx file and then reading in and drawing through my program.

Here is my main game1.cs code:

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System;
using System.Xml.Linq;

namespace tutorial
{
    /// <summary>
    /// This is the main type for your game.
    /// </summary>
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        private SpriteFont font;
        private int frames;
        Tile[,] tileset;

        private Texture2D background;
        Character player;
        bool playOnce;
        KeyboardState state = Keyboard.GetState();
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            ScreenManager.Instance.Initialize();
            ScreenManager.Instance.Dimensions = new Vector2(800, 480);
            graphics.PreferredBackBufferWidth = (int)ScreenManager.Instance.Dimensions.X;
            graphics.PreferredBackBufferHeight = (int)ScreenManager.Instance.Dimensions.Y;
            graphics.ApplyChanges();
            playOnce = true;
            // TODO: Add your initialization logic here
            base.Initialize();
            tileset = getTileset();
        }

        public Tile[,] getTileset()
        {
            XDocument xml = XDocument.Load("Content/map3.tmx");
            int mapWidth = int.Parse(xml.Root.Attribute("width").Value);
            int mapHeight = int.Parse(xml.Root.Attribute("height").Value);
            int tilecount = int.Parse(xml.Root.Element("tileset").Attribute("tilecount").Value);
            int columns = int.Parse(xml.Root.Element("tileset").Attribute("columns").Value);

            string IDarray = xml.Root.Element("layer").Element("data").Value;
            string[] splitArray = IDarray.Split(',');

            int[,] intID = new int[mapWidth, mapHeight];

            for (int x = 0; x < mapWidth; x++)
            {
                for (int y = 0; y < mapHeight; y++)
                {
                    intID[x, y] = int.Parse(splitArray[x + y * mapWidth]);
                }
            }

            int key = 0;
            Vector2[] sourcePosition = new Vector2[tilecount];
            for (int x = 0; x < tilecount / columns; x++)
            {
                for(int y = 0 ; y < columns; y++)
                {
                    sourcePosition[key] = new Vector2(y * 32, x * 32);
                    key++;

                }
            }

            Texture2D sourceTex = Content.Load<Texture2D>("level/Tiles_32x32");

            Tile[,] tiles = new Tile[mapWidth, mapHeight];
            for (int x = 0; x < mapWidth; x++)
            {
                for (int y = 0; y < mapHeight; y++)
                {
                    if (intID[x, y] != 0)
                    {
                        tiles[x, y] = new Tile(new Vector2(x * 32, y * 32),
                                            sourceTex,
                                            new Rectangle((int)sourcePosition[intID[x, y] -1].X, (int)sourcePosition[intID[x, y] -1].Y, 32, 32)
                                            );
                    }


                }
            }
                return tiles;
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            ScreenManager.Instance.LoadContent(Content);
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("Score");
            background = Content.Load<Texture2D>("Sprites/background");

            player = new Character(new Vector2(300, 100));
            player.LoadContent(Content);
            int numberOfSongs = 14;
            Song[] bgMusicList = new Song[14];
            bgMusicList[0] = Content.Load<Song>("music/FirstStepMaster");
            bgMusicList[1] = Content.Load<Song>("music/FloralLife");
            bgMusicList[2] = Content.Load<Song>("music/CavaBien");
            bgMusicList[3] = Content.Load<Song>("music/AboveTheTreetops");
            bgMusicList[4] = Content.Load<Song>("music/amoria");
            bgMusicList[5] = Content.Load<Song>("music/BlueWorld");
            bgMusicList[6] = Content.Load<Song>("music/GoPicnic");
            bgMusicList[7] = Content.Load<Song>("music/HighlandStar");
            bgMusicList[8] = Content.Load<Song>("music/LetsMarch");
            bgMusicList[9] = Content.Load<Song>("music/Nightmare");
            bgMusicList[10] = Content.Load<Song>("music/SleepyWood");
            bgMusicList[11] = Content.Load<Song>("music/Subway");
            bgMusicList[12] = Content.Load<Song>("music/UponTheSky");
            bgMusicList[13] = Content.Load<Song>("music/WhenTheMorningComes");
            Random r = new Random();
            int rInt = r.Next(0, 3);
            int currentSong = rInt;
            if(MediaPlayer.State != MediaState.Playing) 
            {  
                if(playOnce) 
                {  
                    playOnce = false;  
                    currentSong++;  
                    if(currentSong >= numberOfSongs)  
                        currentSong = 0;
                 }  
            MediaPlayer.Play(bgMusicList[currentSong]);  
            }  

            if(MediaPlayer.State == MediaState.Playing) 
            {  
                playOnce = true;  
            }




            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// game-specific content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here

        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            frames++;
            state = Keyboard.GetState();
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();
            ScreenManager.Instance.Update(gameTime);
            player.Update(gameTime);

            //------ATTEMPT AT BASIC COLLISION DETECTION-----------
            foreach(Tile t in tileset)
            {
                if(t != null)
                {
                    //Rectangle tile = t.getRect();
                    if (player.getPos().Y < t.getPos().Y)
                    {
                        float x = player.getPos().X;
                        float y = player.getPos().Y;
                        y += 2;
                        Vector2 newPos = new Vector2(x, y);
                        player.setPos(newPos);
                    }
                }

            }


                base.Update(gameTime);

        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();
            foreach (Tile t in tileset)
            {
                if(t != null)
                    t.Draw(spriteBatch);
            }
            //spriteBatch.Draw(background, new Rectangle(0, 0, 800, 480), Color.White);
            spriteBatch.DrawString(font, "Frame: " + frames, new Vector2(10, 10), Color.White);
            ScreenManager.Instance.Draw(spriteBatch);
            player.Draw(spriteBatch);
            spriteBatch.End();
            // TODO: Add your drawing code here

            base.Draw(gameTime);
        }
    }
}

As you can see by my awful attempt at collision detection, i'm a bit lost:

//------ATTEMPT AT BASIC COLLISION DETECTION-----------
        foreach(Tile t in tileset)
        {
            if(t != null)
            {
                //Rectangle tile = t.getRect();
                if (player.getPos().Y < t.getPos().Y)
                {
                    float x = player.getPos().X;
                    float y = player.getPos().Y;
                    y += 2;
                    Vector2 newPos = new Vector2(x, y);
                    player.setPos(newPos);
                }
            }

        }

I understand the concept, and that both X and Y have to overlap to trigger a collision, but right now i'm basing everything off the position of the player and the position of the tile when I feel like I should be doing it from the dimensions of the rectangle encumbering the tile/player and whether those corners overlap.

I figure there has to be some calculations regarding the dimensions of the tile and dimensions of the player in respect to their current position and whether they intersect however my brain is turning to mush trying to figure this out. Any help would be appreciated.

\$\endgroup\$
2
\$\begingroup\$

Here is some code that I pulled up for you from my XNA coding days. This is how I would handle collisions with XNA. This method works strictly with Rectangle colliders.

The first function I will show you is to get the intersection depth between two Rectangles:

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

Since the vertical intersection calculation is slightly different, here is the function to grab that depth as well:

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

With these functions, you can determine how deep an intersection between two rectangles are. This is important as we need to know this information in order to correct our position in our collision response.

The next method, is a "helper" function that will calculate the depth between two rectangles based on the direction you are looking to check:

private bool TileIntersectsPlayer(Rectangle player, Rectangle block, Direction direction, out Vector2 depth)
{
    if (direction == Direction.Vertical) {
        depth = new Vector2(0, GetVerticalIntersectionDepth(player, block));
    } else if (direction == Direction.Horizontal) {
        depth = new Vector2(GetHorizontalIntersectionDepth(player, block), 0);
    }

    return depth.Y != 0 || depth.X != 0;
}

With this helper method, we will now be able to handle the collision handling and response with this function:

private bool HandleCollisions(Direction direction)
{
    bool collides = false;

    // Whatever method you use to get a list of tiles to check against
    // A quad tree structure here would be beneficial to query
    List<Land> land = GetListOfLandTilesToCheck();

    // process tiles and check if we are colliding with them
    foreach (Land l in land)
    {
        // Calculate how deep the intersection is
        Vector2 depth;      
        if (l.Rectangle.Intersects(Rectangle))
        {
            if (TileIntersectsPlayer(Rectangle, l.Rectangle, direction, out depth))
            {
                // Adjust position for collision response
                Position += depth;
                collides = true;
            }
        }   
    }
    return collides;
}

And then finally, you need to handle your movement and collision checks one-axis at a time in order for this method to work. In your update method you would have something like this:

// Process Y axis
Velocity.Y += Gravity.Y * elapsed * Mass;
Position.Y += Velocity.Y * elapsed;

// Handle vertical collision that may have occured
HandleCollisions(Direction.Vertical);


// Process X Axis
Velocity.X += Speed * elapsed;
Position.X += Velocity.X * elapsed;

HandleCollisions(Direction.Horizontal);
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.