1
\$\begingroup\$

I have a tile based map with a top down camera. My sprite stops moving when he collides with a wall in any of the four directions however I am trying to get the sprite to slide along the wall if more than one directional key is pressed after being stopped. Tiles are set to 32 x 32.

Here is my code;

        // Gets Tile Player Is Standing On
        var splatterTileX = (int)player.Position.X / Engine.TileWidth;
        var splatterTileY = (int)player.Position.Y / Engine.TileHeight;

        // Foreach Layer In World Splatter Map Layers
        foreach (var layer in WorldSplatterTileMapLayers)
        {
            // If Sprite Is Not On Any Edges
            if (splatterTileX < layer.Width - 1 && splatterTileX > 0 && splatterTileY < layer.Height - 1 && splatterTileY > 0)
            {
                tileN = layer.GetTile(splatterTileX, splatterTileY - 1);      // North
                tileNE = layer.GetTile(splatterTileX + 1, splatterTileY - 1); // North-East
                tileE = layer.GetTile(splatterTileX + 1, splatterTileY);      // East
                tileSE = layer.GetTile(splatterTileX + 1, splatterTileY + 1); // South-East
                tileS = layer.GetTile(splatterTileX, splatterTileY + 1);      // South
                tileSW = layer.GetTile(splatterTileX - 1, splatterTileY + 1); // South-West                    
                tileW = layer.GetTile(splatterTileX - 1, splatterTileY);      // West
                tileNW = layer.GetTile(splatterTileX - 1, splatterTileY - 1); // North-West
            }

            // If Sprite Is Not On Any X Edges And Is On -Y Edge
            if (splatterTileX < layer.Width - 1 && splatterTileX > 0 && splatterTileY == 0)
            {
                tileE = layer.GetTile(splatterTileX + 1, splatterTileY);      // East
                tileSE = layer.GetTile(splatterTileX + 1, splatterTileY + 1); // South-East
                tileS = layer.GetTile(splatterTileX, splatterTileY + 1);      // South
                tileSW = layer.GetTile(splatterTileX - 1, splatterTileY + 1); // South-West                    
                tileW = layer.GetTile(splatterTileX - 1, splatterTileY);      // West
            }
            // If Sprite Is On +X And -Y Edges
            if (splatterTileX == layer.Width - 1 && splatterTileY == 0)
            {
                tileS = layer.GetTile(splatterTileX, splatterTileY + 1);      // South
                tileSW = layer.GetTile(splatterTileX - 1, splatterTileY + 1); // South-West                    
                tileW = layer.GetTile(splatterTileX - 1, splatterTileY);      // West
            }
            // If Sprite Is On +X Edge And Y Is Not On Any Edge
            if (splatterTileX == layer.Width - 1 && splatterTileY < layer.Height - 1 && splatterTileY > 0)
            {
                tileS = layer.GetTile(splatterTileX, splatterTileY + 1);      // South
                tileSW = layer.GetTile(splatterTileX - 1, splatterTileY + 1); // South-West                    
                tileW = layer.GetTile(splatterTileX - 1, splatterTileY);      // West
                tileNW = layer.GetTile(splatterTileX - 1, splatterTileY - 1); // North-West
                tileN = layer.GetTile(splatterTileX, splatterTileY - 1);      // North
            }
            // If Sprite Is On +X And +Y Edges
            if (splatterTileX == layer.Width - 1 && splatterTileY == layer.Height - 1)
            {
                tileW = layer.GetTile(splatterTileX - 1, splatterTileY);      // West
                tileNW = layer.GetTile(splatterTileX - 1, splatterTileY - 1); // North-West
                tileN = layer.GetTile(splatterTileX, splatterTileY - 1);      // North
            }
            // If Sprite Is Not On Any X Edges And Is On +Y Edge
            if (splatterTileX < (layer.Width - 1) && splatterTileX > 0 && splatterTileY == layer.Height - 1)
            {
                tileW = layer.GetTile(splatterTileX - 1, splatterTileY);      // West
                tileNW = layer.GetTile(splatterTileX - 1, splatterTileY - 1); // North-West
                tileN = layer.GetTile(splatterTileX, splatterTileY - 1);      // North
                tileNE = layer.GetTile(splatterTileX + 1, splatterTileY - 1); // North-East
                tileE = layer.GetTile(splatterTileX + 1, splatterTileY);      // East
            }
            // If Sprite Is On -X And +Y Edges
            if (splatterTileX == 0 && splatterTileY == layer.Height - 1)
            {
                tileN = layer.GetTile(splatterTileX, splatterTileY - 1);      // North
                tileNE = layer.GetTile(splatterTileX + 1, splatterTileY - 1); // North-East
                tileE = layer.GetTile(splatterTileX + 1, splatterTileY);      // East
            }
            // If Sprite Is On -X Edge And Y Is Not On Any Edges
            if (splatterTileX == 0 && splatterTileY < (layer.Height - 1) && splatterTileY > 0)
            {
                tileN = layer.GetTile(splatterTileX, splatterTileY - 1);      // North
                tileNE = layer.GetTile(splatterTileX + 1, splatterTileY - 1); // North-East
                tileE = layer.GetTile(splatterTileX + 1, splatterTileY);      // East
                tileSE = layer.GetTile(splatterTileX + 1, splatterTileY + 1); // South-East
                tileS = layer.GetTile(splatterTileX, splatterTileY + 1);      // South
            }
            // If Sprite Is In The Top Left Corner
            if (splatterTileX == 0 && splatterTileY == 0)
            {
                tileE = layer.GetTile(splatterTileX + 1, splatterTileY);      // East
                tileSE = layer.GetTile(splatterTileX + 1, splatterTileY + 1); // South-East
                tileS = layer.GetTile(splatterTileX, splatterTileY + 1);      // South
            }
            // Creates A New Rectangle For TileN
            tileN.TileRectangle = new Rectangle(splatterTileX * Engine.TileWidth,
                                              (splatterTileY - 1) * Engine.TileHeight,
                                              Engine.TileWidth,
                                              Engine.TileHeight);

            // Tile Collision Detection Between Player Rectangle And N Tile
            var tileNCollision = player.Rectangle.Intersects(tileN.TileRectangle);


            // Creates A New Rectangle For TileNE
            tileNE.TileRectangle = new Rectangle((splatterTileX + 1) * Engine.TileWidth,
                                              (splatterTileY - 1) * Engine.TileHeight,
                                              Engine.TileWidth,
                                              Engine.TileHeight);

            // Tile Collision Detection Between Player Rectangle And NE Tile
            var tileNECollision = player.Rectangle.Intersects(tileNE.TileRectangle);

            // Creates A New Rectangle For TileE
            tileE.TileRectangle = new Rectangle((splatterTileX + 1) * Engine.TileWidth,
                                              splatterTileY * Engine.TileHeight,
                                              Engine.TileWidth,
                                              Engine.TileHeight);

            // Tile Collision Detection Between Player Rectangle And E Tile
            var tileECollision = player.Rectangle.Intersects(tileE.TileRectangle);

            // Creates A New Rectangle For TileSE
            tileSE.TileRectangle = new Rectangle((splatterTileX + 1) * Engine.TileWidth,
                                              (splatterTileY + 1) * Engine.TileHeight,
                                              Engine.TileWidth,
                                              Engine.TileHeight);

            // Tile Collision Detection Between Player Rectangle And SE Tile
            var tileSECollision = player.Rectangle.Intersects(tileSE.TileRectangle);

            // Creates A New Rectangle For TileS
            tileS.TileRectangle = new Rectangle(splatterTileX * Engine.TileWidth,
                                              (splatterTileY + 1) * Engine.TileHeight,
                                              Engine.TileWidth,
                                              Engine.TileHeight);

            // Tile Collision Detection Between Player Rectangle And S Tile
            var tileSCollision = player.Rectangle.Intersects(tileS.TileRectangle);

            // Creates A New Rectangle For TileSW
            tileSW.TileRectangle = new Rectangle((splatterTileX - 1) * Engine.TileWidth,
                                              (splatterTileY + 1) * Engine.TileHeight,
                                              Engine.TileWidth,
                                              Engine.TileHeight);

            // Tile Collision Detection Between Player Rectangle And SW Tile
            var tileSWCollision = player.Rectangle.Intersects(tileSW.TileRectangle);

            // Creates A New Rectangle For TileW
            tileW.TileRectangle = new Rectangle((splatterTileX - 1) * Engine.TileWidth,
                                              splatterTileY * Engine.TileHeight,
                                              Engine.TileWidth,
                                              Engine.TileHeight);

            // Tile Collision Detection Between Player Rectangle And Current Tile
            var tileWCollision = player.Rectangle.Intersects(tileW.TileRectangle);

            // Creates A New Rectangle For TileNW
            tileNW.TileRectangle = new Rectangle((splatterTileX - 1) * Engine.TileWidth,
                                              (splatterTileY - 1) * Engine.TileHeight,
                                              Engine.TileWidth,
                                              Engine.TileHeight);

            // Tile Collision Detection Between Player Rectangle And Current Tile
            var tileNWCollision = player.Rectangle.Intersects(tileNW.TileRectangle);

            // Allow Sprite To Occupy More Than One Tile
            if (tileNCollision && tileN.TileBlocked == false)
            {
                tileN.TileOccupied = true;
            }
            if (tileECollision && tileE.TileBlocked == false)
            {
                tileE.TileOccupied = true;
            }
            if (tileSCollision && tileS.TileBlocked == false)
            {
                tileS.TileOccupied = true;
            }
            if (tileWCollision && tileW.TileBlocked == false)
            {
                tileW.TileOccupied = true;
            }

            // Player Up
            if (keyState.IsKeyDown(Keys.W) ||
                (gamePadOneState.DPad.Up == ButtonState.Pressed))
            {
                player.CurrentAnimation = AnimationKey.Up;

                if (tileN.TileOccupied == false)
                {
                    if (tileNWCollision && tileNW.TileBlocked || tileNCollision && tileN.TileBlocked || tileNECollision && tileNE.TileBlocked)
                    {
                        playerMotion.Y = 0;
                    }
                    else
                        playerMotion.Y = -1;
                }
                else if (tileN.TileOccupied)
                {
                    if (tileNWCollision && tileNW.TileBlocked || tileNECollision && tileNE.TileBlocked)
                    {
                        playerMotion.Y = 0;
                    }
                    else
                        playerMotion.Y = -1;
                }
            }

            // Player Down
            if (keyState.IsKeyDown(Keys.S) ||
                (gamePadOneState.DPad.Down == ButtonState.Pressed))
            {
                player.CurrentAnimation = AnimationKey.Down;

                // Check Collision With Tiles
                if (tileS.TileOccupied == false)
                {
                    if (tileSWCollision && tileSW.TileBlocked || tileSCollision && tileS.TileBlocked || tileSECollision && tileSE.TileBlocked)
                    {
                        playerMotion.Y = 0;
                    }
                    else
                        playerMotion.Y = 1;
                }
                else if (tileS.TileOccupied)
                {
                    if (tileSWCollision && tileSW.TileBlocked || tileSECollision && tileSE.TileBlocked)
                    {
                        playerMotion.Y = 0;
                    }
                    else
                        playerMotion.Y = 1;
                }
            }

            // Player Left
            if (keyState.IsKeyDown(Keys.A) ||
                (gamePadOneState.DPad.Left == ButtonState.Pressed))
            {
                player.CurrentAnimation = AnimationKey.Left;

                if (tileW.TileOccupied == false)
                {
                    if (tileNWCollision && tileNW.TileBlocked || tileWCollision && tileW.TileBlocked || tileSWCollision && tileSW.TileBlocked)
                    {
                        playerMotion.X = 0;
                    }
                    else
                        playerMotion.X = -1;
                }
                else if (tileW.TileOccupied)
                {
                    if (tileNWCollision && tileNW.TileBlocked || tileSWCollision && tileSW.TileBlocked)
                    {
                        playerMotion.X = 0;
                    }
                    else
                        playerMotion.X = -1;
                }
            }

            // Player Right
            if (keyState.IsKeyDown(Keys.D) ||
                (gamePadOneState.DPad.Right == ButtonState.Pressed))
            {
                player.CurrentAnimation = AnimationKey.Right;

                if (tileE.TileOccupied == false)
                {
                    if (tileNECollision && tileNE.TileBlocked || tileECollision && tileE.TileBlocked || tileSECollision && tileSE.TileBlocked)
                    {
                        playerMotion.X = 0;
                    }
                    else
                        playerMotion.X = 1;
                }
                else if (tileE.TileOccupied)
                {
                    if (tileNECollision && tileNE.TileBlocked || tileSECollision && tileSE.TileBlocked)
                    {
                        playerMotion.X = 0;
                    }
                    else
                        playerMotion.X = 1;
                }
            }

I have my tile detection setup so the 8 tiles around the sprite are the only ones detected. The collision variable is true if the sprites rectangle intersects with one of the detected tiles. The sprites origin is centered at 16, 16 on the image so whenever this point goes over to the next tile it calls the surrounding tiles.

I am trying to have collision detection like in the game Secret of Mana.

If I remove the diagonal checks the sprite will pass through thoses tiles because whichever tile the sprites origin is on will be the detection center. So if the sprite is near the edge of the tile and then goes up it looks like half the sprite is walking through the wall.

Is there a way for the detection to occur for each tile the sprite's rectangle touches?

\$\endgroup\$
0
\$\begingroup\$

You're determining whether each N/W/S/E direction is possible using the NW/NE/SE/SW checks, which keeps you from moving along a wall.

So, in your "up and left being held down" hypothetical, if extended to have a horizontal wall directly above the player, your code would have tileNWCollision set to true. I'm assuming that tileNW.TileBlocked is set somewhere else and basically means "this tile can't be occupied by the player".

If you look in your Keys.A handling, you'll see that having tileNWCollision and tileNW.TileBlocked both true means playerMotion.X is set to 0. However, assuming the player is moving within the grid, there's no reason that a wall to the northwest should prevent them from moving west.

EDIT:

So: I think you might get more traction on your problem here by instead constructing "next position" rectangles based on the input and checking those for collision with blocking tiles.

E.g., when moving north, create the rectangle representing what your player would occupy if you let them move. If it's valid (no collision), let them move (set playerMotion.Y as appropriate).

For multi-input, try a diagonal/combined rectangle first, and if it fails just tie-break however you want for the individual inputs.

This approach should prevent any off-by-one/directionality errors, and should also prevent the "walking through the wall" issue.

Sorry that this still isn't directly answering your question. :) Hope it helps.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Your assumption is correct regarding the TileBlocked variable. If I remove the checks for NW/NE/SE/SW then the sprite will enter tiles diagonally if two directional keys are used. \$\endgroup\$ – Devin Rawlek Jul 28 '13 at 1:01
  • \$\begingroup\$ Sorry, I misunderstood. Was thinking same tile/sprite size. I'll edit my answer. \$\endgroup\$ – Chris Mills-Price Jul 29 '13 at 1:57
  • \$\begingroup\$ I was able to resolve the issue by checking the directions individually. \$\endgroup\$ – Devin Rawlek Dec 29 '13 at 4:32
0
\$\begingroup\$

If your diagonal movement will put your player inside a wall then test for the diagonal and vertical part of the movement individually. This allows for sliding along walls. Tips here: http://xnafan.net/2013/04/simple-platformer-game-in-xna-tutorial-part-four-simple-collision-detection/

Kind regards - Jakob

| improve this answer | |
\$\endgroup\$
-2
\$\begingroup\$

Maybe this thread could helps you: How can I use Rectangle.Intersect() to resolve collisions in XNA?

It describes how to resolve collisions in XNA. Thre is a video and source code. It helped me, when I had the same problem.

Direct Link to Source: http://pastie.org/3152377#103 Direct Link to Video: http://www.youtube.com/watch?v=5-D0PGdoDDY

| improve this answer | |
\$\endgroup\$
  • 3
    \$\begingroup\$ Please don't just link to answers. We prefer to have the answer content inline. \$\endgroup\$ – Sean Middleditch Jul 29 '13 at 17:11

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.