How can I detect wall collisions using a spritesheet for tiles

109 Views Asked by At

It seems like this has been asked lot, but I'm not sure any of the answers work for me. I'm fairly new to programming (I've been doing as a hobby of and on for quite a few years).

Here is my question;

I'm using a 2d array of integers and a list of source rectangles to create my tile map. I'm using the 2 for loops in the draw method to draw the tile map(if there is a better way to get the map drawn using a spritesheet I'm listening). The game is a top down style game, not a platformer.

What's the best way of detecting collision for certain tiles. I've thought about placing a new rectangle on the tiles that are impassable, but I'm not sure how to implement that. I would really like to use the intersects method, is there any using it with top, bottom, sides method? Is there a better way?

I'm happy to share all my code if that will help.

Thanks for the help.

2

There are 2 best solutions below

0
On

So I have somewhat figured this out. I created a 2d int array collision map, basically 0's and 1's, ones are the passable tiles, but it really doesn't matter which numbers you use. I then run through the array and create a list of rectangles for the non passable tiles. I then check for collision using a foreach loop and checking the Rectangle.Intersects method with the player sprite rectangle (which I've made a smaller collision rectangle for it)

I may move the collision detection from the map class I created to the character class I created for more control over the player position.

I will post all the code for this later today.

Main game1 class

 public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    // Class support 
    Map map;
    Character hero;
    List<Rectangle> collisionData;
    Texture2D mapSpriteSheet;
    Texture2D characterSpriteSheet;
    int currentLevel;

    //window adjustment
    const int WINDOW_WIDTH = 832;
    const int WINDOW_HEIGHT = 640;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";

        graphics.PreferredBackBufferWidth = WINDOW_WIDTH;
        graphics.PreferredBackBufferHeight = WINDOW_HEIGHT;
    }

    /// <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()
    {



        base.Initialize();
    }

    /// <summary>
    /// LoadContent will be called once per game and is the place to load
    /// all of your content.
    /// </summary>
    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);

        // load content and initialize class objects
        mapSpriteSheet = Content.Load<Texture2D>("tileSet");
        characterSpriteSheet = Content.Load<Texture2D>("professor_walk");
        map = new Map(mapSpriteSheet);
        hero = new Character(characterSpriteSheet, WINDOW_WIDTH, WINDOW_HEIGHT);


    }

    /// <summary>
    /// UnloadContent will be called once per game and is the place to unload
    /// all 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)
    {
        // Allows the game to exit
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();

        if (currentLevel == 100) this.Exit();

        currentLevel = hero.setCurrentLevel;
        collisionData = map.getCollisionData(currentLevel);
        hero.setCollisionData = collisionData;

        KeyboardState keyState = Keyboard.GetState();

        if (keyState.IsKeyDown(Keys.Up) && keyState.IsKeyUp(Keys.Down) && keyState.IsKeyUp(Keys.Right) && keyState.IsKeyUp(Keys.Left))
        {


            hero.moveUp(gameTime);

        }
        if (keyState.IsKeyUp(Keys.Up) && keyState.IsKeyDown(Keys.Down) && keyState.IsKeyUp(Keys.Right) && keyState.IsKeyUp(Keys.Left))
        {


            hero.moveDown(gameTime);
        }
        if (keyState.IsKeyUp(Keys.Up) && keyState.IsKeyUp(Keys.Down) && keyState.IsKeyDown(Keys.Right) && keyState.IsKeyUp(Keys.Left))
        {

            hero.moveRight(gameTime);
        }
        if (keyState.IsKeyUp(Keys.Up) && keyState.IsKeyUp(Keys.Down) && keyState.IsKeyUp(Keys.Right) && keyState.IsKeyDown(Keys.Left))
        {

            hero.moveLeft(gameTime);
        }

        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();

        map.Draw(spriteBatch, currentLevel);
        hero.Draw(spriteBatch);


        spriteBatch.End();

        base.Draw(gameTime);
    }
}

Character class

class Character
{
    #region feilds
    //other data
    int windowWidth;
    int windowHieght;

    // drawing support
    Texture2D spriteSheet;
    Rectangle destRect;
    Rectangle sourceRect;
    int destRectSize = 64;
    int sourceRectSize = 64;
    int currentLevel = 4;

    //movement support
    Vector2 position;
    Vector2 newPosition;
    int speed = 2;
    List<Rectangle> collisionData;
    Rectangle playerCollisionRect;
    bool collision;
    const int PLAYER_COLLISION_RECT_OFFSET = 16;
    LevelHelper levelHelper;

    //animation support
    int animationDelay = 100;
    float elapsedGameTime = 0;  
    int frames;
    const int TOTAL_FRAMES = 8;


    #endregion

    #region Constructor
    /// <summary>
    /// Constructor, takes the spritesheet for the character animations
    /// </summary>
    /// <param name="spriteSheet">character animation spritesheet</param>
    public Character(Texture2D spriteSheet, int windowWidth, int windowHieght)
    {
        //initialize new objects
        this.windowWidth = windowWidth;
        this.windowHieght = windowHieght;
        this.spriteSheet = spriteSheet;
        destRect = new Rectangle(windowWidth / 2 - destRectSize / 2, windowHieght / 2 - destRectSize / 2, destRectSize, destRectSize);
        sourceRect = new Rectangle(0, 0, sourceRectSize, sourceRectSize);
        position = new Vector2(destRect.X, destRect.Y);
        newPosition = new Vector2(destRect.X, destRect.Y);
        playerCollisionRect = new Rectangle((int)position.X, (int)position.Y, destRect.Width / 2, destRect.Height - 16);
        levelHelper = new LevelHelper();

    }
    #endregion

    #region Move Methods

    /// <summary>
    /// animates and moves sprite up, also checks for collision
    /// </summary>
    /// <param name="timer">game time</param>
    public void moveUp(GameTime timer)
    {
        elapsedGameTime += (float)timer.ElapsedGameTime.Milliseconds;
        sourceRect.Y = 0;

        if (elapsedGameTime >= animationDelay)
        {
            if (frames >= TOTAL_FRAMES)
            {
                frames = 0;
            }
            else
            {
                frames++;
            }

            elapsedGameTime = 0;

            sourceRect.X = frames * sourceRect.Width;
        }

            newPosition.Y = position.Y - speed;
            collision = checkCollision(newPosition);

            if (!collision)
            {
                position = newPosition;
                destRect.X = (int)position.X;
                destRect.Y = (int)position.Y;
            }

            if (collision)
            {
                position.Y += speed + 1;
            }

            if (position.Y <= 0)
            {
                position.Y = windowHieght - (destRect.Height - PLAYER_COLLISION_RECT_OFFSET);
                currentLevel = levelHelper.Up(currentLevel);
            }

    }

    /// <summary>
    /// animates and moves character down, also checks for collision
    /// </summary>
    /// <param name="timer">game time</param>
    public void moveDown(GameTime timer)
    {
        elapsedGameTime += (float)timer.ElapsedGameTime.Milliseconds;
        sourceRect.Y = 2 * sourceRect.Height;

        if (elapsedGameTime >= animationDelay)
        {
            if (frames >= TOTAL_FRAMES)
            {
                frames = 0;
            }
            else
            {
                frames++;
            }

            elapsedGameTime = 0;

            sourceRect.X = frames * sourceRect.Width;
        }

            newPosition.Y = position.Y + speed;
            collision = checkCollision(newPosition);

            if (!collision)
            {
                position = newPosition;
                destRect.X = (int)position.X;
                destRect.Y = (int)position.Y;
            }

            if (position.Y >= windowHieght - destRect.Height / 2)
            {
                position.Y = 0 + PLAYER_COLLISION_RECT_OFFSET;
                currentLevel = levelHelper.Down(currentLevel);
            }


    }

    /// <summary>
    /// animates and moves sprite right, also checks for collision
    /// </summary>
    /// <param name="timer">game time</param>
    public void moveRight(GameTime timer)
    {
        elapsedGameTime += (float)timer.ElapsedGameTime.Milliseconds;
        sourceRect.Y = 3 * sourceRect.Height;

        if (elapsedGameTime >= animationDelay)
        {
            if (frames >= TOTAL_FRAMES)
            {
                frames = 0;
            }
            else
            {
                frames++;
            }

            elapsedGameTime = 0;

            sourceRect.X = frames * sourceRect.Width;
        }

            newPosition.X = position.X + speed;
            collision = checkCollision(newPosition);

            if (!collision)
            {
                position = newPosition;
                destRect.X = (int)position.X;
                destRect.Y = (int)position.Y;
            }
            if (position.X >= windowWidth - destRect.Width / 2)
            {
                position.X = 0 + PLAYER_COLLISION_RECT_OFFSET;
                currentLevel = levelHelper.Right(currentLevel);
            }


    }

    /// <summary>
    /// animates and moves sprite left and checks for collision
    /// </summary>
    /// <param name="timer">game time</param>
    public void moveLeft(GameTime timer)
    {
        elapsedGameTime += (float)timer.ElapsedGameTime.Milliseconds;
        sourceRect.Y = sourceRect.Height;

        if (elapsedGameTime >= animationDelay)
        {
            if (frames >= TOTAL_FRAMES)
            {
                frames = 0;
            }
            else
            {
                frames++;
            }

            elapsedGameTime = 0;

            sourceRect.X = frames * sourceRect.Width;
        }

            newPosition.X = position.X - speed;
            collision = checkCollision(newPosition);

            if (!collision)
            {
                position = newPosition;
                destRect.X = (int)position.X;
                destRect.Y = (int)position.Y;
            }

            if (position.X <= 0)
            {
                position.X = windowWidth - destRect.Width - PLAYER_COLLISION_RECT_OFFSET;
                currentLevel = levelHelper.Left(currentLevel);
            }
    }
    #endregion

    #region Draw Method

    /// <summary>
    /// Draw the sprite
    /// </summary>
    /// <param name="spritebatch">spritebatch</param>
    public void Draw(SpriteBatch spritebatch)
    {
        spritebatch.Draw(spriteSheet, destRect, sourceRect, Color.White);
    }

    #endregion

    #region Properties

    /// <summary>
    /// input for list of collision rectangles generated from the map
    /// </summary>
    public List<Rectangle> setCollisionData
    {
        set { collisionData = value; }
    }

    public int setCurrentLevel
    {
        get { return currentLevel; }
        set { currentLevel = value; }
    }

    #endregion

    #region Private check collision method

    /// <summary>
    /// in class method for checking environment collisions
    /// </summary>
    /// <param name="checkPosition">takes list of collision rectangles</param>
    /// <returns></returns>
    private bool checkCollision(Vector2 checkPosition)
    {
        //set the player collision rectangle to the position of the player
        playerCollisionRect.Y = (int)checkPosition.Y + PLAYER_COLLISION_RECT_OFFSET;
        playerCollisionRect.X = (int)checkPosition.X + PLAYER_COLLISION_RECT_OFFSET;

        // run through the list and check each tile for a collision
        foreach (Rectangle rect in collisionData)
        {
            if (rect.Intersects(playerCollisionRect))
            {
                return true;
            }
        }

        return false;
    }

    #endregion
}

Map class

class Map
{
    #region Feilds
    // declare variables used in this class
    Texture2D spriteSheet;

    int destRectSize = 64;
    int sourceRectSize = 32;
    //int currentLevel;
    List<Rectangle> sourceRect;
    List<Rectangle> collisionRect;

    //Level support
    int[,] levelZero;
    int[,] levelOne;
    int[,] levelTwo;
    int[,] levelThree;
    int[,] levelFour;
    int[,] levelFive;
    int[,] levelSix;
    int[,] levelSeven;
    int[,] levelEight;
    List<int[,]> levels;

    #endregion

    #region Constructor
    /// <summary>
    /// Constructor, takes the ContentMAnager as a parameter. Adds source rectangles to the list for drawing and sets both 2d
    /// arrays for the tile draw layer and the collision layer
    /// </summary>
    /// <param name="Content">ContentManager</param>
    public Map(Texture2D spriteSheet)
    {
        //load the texture and create new list for source rectangles
        this.spriteSheet = spriteSheet;
        sourceRect = new List<Rectangle>();
        collisionRect = new List<Rectangle>();
        levels = new List<int[,]>();

        //add source rectangles to the list
        sourceRect.Add(new Rectangle(0, sourceRectSize, sourceRectSize, sourceRectSize));
        sourceRect.Add(new Rectangle(sourceRectSize * 2, sourceRectSize * 2, sourceRectSize, sourceRectSize));
        sourceRect.Add(new Rectangle(sourceRectSize * 4, sourceRectSize * 2, sourceRectSize, sourceRectSize));
        sourceRect.Add(new Rectangle(sourceRectSize * 6, sourceRectSize, sourceRectSize, sourceRectSize));
        sourceRect.Add(new Rectangle(sourceRectSize * 5, sourceRectSize * 2, sourceRectSize, sourceRectSize));
        sourceRect.Add(new Rectangle(0, 0, sourceRectSize, sourceRectSize));


        mapData();

    }
    #endregion

    #region Draw Method
    /// <summary>
    /// draws the tile map to the screen
    /// </summary>
    /// <param name="spritebatch">SpriteBatch</param>
    public void Draw(SpriteBatch spritebatch, int currentLevel)
    {
        //nested for loops run through the 2d array of ints to draw the tiles in the right spot.
        for (int y = 0; y < levels[currentLevel].GetLength(0); y++)
        {
            for (int x = 0; x < levels[currentLevel].GetLength(1); x++)
            {
                int index = levels[currentLevel][y, x];

                spritebatch.Draw(spriteSheet, new Rectangle(x * destRectSize, y * destRectSize, destRectSize, destRectSize), 
                    sourceRect[index], Color.White); 
            }
        }
    }
    #endregion

    //public int setCurrentLevel
    //{
    //    set { currentLevel = value; }
    //}


    public List<Rectangle> getCollisionData(int currentLevel)
    {
        collisionRect.Clear();

        // create a rectangle on the non passable tiles
        for (int y = 0; y < levels[currentLevel].GetLength(0); y++)
        {
            for (int x = 0; x < levels[currentLevel].GetLength(1); x++)
            {
                int index = levels[currentLevel][y, x];

                if (index == 0 || index == 3)
                {
                    collisionRect.Add(new Rectangle(x * destRectSize, y * destRectSize, destRectSize, destRectSize));
                }
            }
        }

        return collisionRect;
    }

    #region map method

    private void mapData()
    {
        //create tile maps
        levelZero = new int[,]{
            {0,0,0,0,0,0,0,0,0,0,0,0,0},
            {0,2,2,2,2,2,3,2,2,2,2,2,0},
            {0,2,2,2,2,2,3,2,2,2,2,2,0},
            {0,2,2,2,2,2,3,2,2,2,2,2,0},
            {0,2,2,2,2,2,3,2,2,2,2,2,2},
            {0,2,2,2,2,2,3,2,2,2,2,2,2},
            {0,2,2,2,2,2,3,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
        };

        levelOne = new int[,]{
            {0,0,0,0,0,0,0,0,0,0,0,0,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {2,2,2,2,2,3,3,3,2,2,2,2,0},
            {2,2,2,2,2,3,3,3,2,2,2,2,0},
            {0,2,2,2,2,3,3,3,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
        };

        levelTwo = new int[,]{
            {0,0,0,0,0,0,0,0,0,0,0,0,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,3,3,3,3,3,3,3,2,2,0},
            {0,2,2,3,2,2,2,2,2,3,2,2,0},
            {0,2,2,3,2,2,2,2,2,3,2,2,0},
            {0,2,2,3,3,3,2,3,3,3,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
        };

        levelThree = new int[,]{
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,3,3,3,3,3,3,3,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,2},
            {0,2,2,2,2,2,2,2,2,2,2,2,2},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,3,3,3,3,3,3,3,2,2,0},
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
        };

        levelFour = new int[,]{
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,3,2,2,2,3,2,2,2,0},
            {0,2,3,3,3,2,2,2,3,3,3,2,0},
            {2,2,2,2,2,2,2,2,2,2,2,2,2},
            {2,2,2,2,2,2,2,2,2,2,2,2,2},
            {0,2,3,3,3,2,2,2,3,3,3,2,0},
            {0,2,2,2,3,2,2,2,3,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
        };

        levelFive = new int[,]{
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,3,3,2,2,0},
            {0,2,2,2,2,2,2,2,2,3,2,2,0},
            {2,2,2,2,2,2,2,2,2,3,2,2,0},
            {2,2,2,2,2,2,2,2,2,3,2,2,0},
            {0,2,2,2,2,2,2,2,2,3,2,2,0},
            {0,2,2,2,2,2,2,2,3,3,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
        };

        levelSix = new int[,]{
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
            {0,2,3,3,3,3,3,3,3,3,3,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,2},
            {0,2,2,2,2,2,2,2,2,2,2,2,2},
            {0,2,2,3,3,3,3,3,3,3,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,0,0,0,0,0,0,0,0,0,0,0,0},
        };

        levelSeven = new int[,]{
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {2,2,2,2,2,2,2,2,2,2,2,2,2},
            {2,2,2,2,2,2,2,2,2,2,2,2,2},
            {0,2,2,3,3,2,2,3,3,2,2,2,0},
            {0,2,2,3,3,2,2,3,3,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,0,0,0,0,0,0,0,0,0,0,0,0},
        };

        levelEight = new int[,]{
            {0,0,0,0,0,2,2,2,0,0,0,0,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,3,3,3,3,3,3,3,2,2,0},
            {2,2,2,3,4,4,4,4,4,3,2,2,0},
            {2,2,2,3,4,4,4,4,4,3,2,2,0},
            {0,2,2,3,3,3,3,3,3,3,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,2,2,2,2,2,2,2,2,2,2,2,0},
            {0,0,0,0,0,0,0,0,0,0,0,0,0},
        };


        levels.Add(levelZero);
        levels.Add(levelOne);
        levels.Add(levelTwo);
        levels.Add(levelThree);
        levels.Add(levelFour);
        levels.Add(levelFive);
        levels.Add(levelSix);
        levels.Add(levelSeven);
        levels.Add(levelEight);

    }
    #endregion


}
3
On

Instead of checking collision between a sprite and a list of collision rectangles you can, and should, calculate the tile position of the player and then check if the new position will collide with a collision tile.

t.ex Collision 2d int array:

[1][1][1][1][1]
[1][X][0][0][1]
[1][0][0][0][1]
[1][0][0][0][1]
[1][0][0][0][1]
[1][1][1][1][1]

Player position: 1,1 (X on the "map")

Player wants to move up: new position will be 1,0 but since 1,0 is blocked (1 in collision array) the player can't move.