Brian Leigh
Brian Leigh

Reputation: 47

XNA Tile based Game: Checking collision between 2 objects using their grid position inside a 2D array

I was wondering if anyone could help me figure out how to check collisions between 2 objects inside a 2d array.

I'm trying to make a simple 2D top down game. The map is made up of a 20x20 2D array of squares. The player and the enemies are all squares and each take up one square inside the grid. The grid is made up of multiple squares of different types. So far I have floor squares(which the player and enemies will be allowed to move along), Wall squares(Which the player and enemies cannot move past or along) and then the enemy and players squares. Here is a screenshot of it:

enter image description here

I create and initialise the 2D array in the Game Class. Using numbers from 0-3 I can select what each square will contain: 0- Floor square 1- Wall square 2- Enemy Square 3- player Square.

So firstly I have the player colliding with the wall squares by simply using a nested for loop and checking the grid positions Above, Below, Left and Right of the player to see if the the value within the 2D array is 1. If it isn't then that means it's not a wall which means the player is allowed to move.

However I can't use this method when checking collision between the player and enemy as I only use the value inside the 2D array(2 and 3) to choose where to originally draw the enemy and player squares. This means that the grid position that they are both currently on at any time contains a value of 0.

I thought I would go about it by storing the the "grid Position" of each player and enemy object. That way I could just check each grid block around the player to see if it was equal to the grid position of an enemy. If it was equal then I would set the movement vector that I use to move the player in that specific direction to (0,0) preventing them from moving in that direction. When the enemy is no longer within one block of the player then the movement vector is given back it's original value.

I've had some success with this but it only seems to work with one enemy object. With one enemy I can't pass through it from any angle but with the others I can pass right through them.

Note: It seems that the enemy furthest down the screen is the only enemy that the player square can collide with.

I've used break points and I can see that when I get close to any of the enemies I can pass through, it does actually run the check to see if the square next to the player is an enemy but it doesn't actually prevent the player from walking through the enemy.

So my question is, am I going about doing the collision wrong, is there a simpler way to do it? Or if perhaps there may be a mistake in my code which is stopping it from working?

Below is some of my code:

This first sample is for the player/enemy Collision. This is contained in the enemy class. As I stated before, I use the player and enemies grid position to check to see if they are within one square of each other, if they are then I set the players movement vector to 0,0. When they are no longer within one square of each other I reset the movement vector back to it's original value.

public void CheckCollision(Player playerObject)
    {
        //Check above player
        if (playerObject.PlayerGridPosition.Y - 1 == gridPosition.Y && playerObject.PlayerGridPosition.X == gridPosition.X)
        {
            //north is a vector2 variable that I use to add to the players position in order to move them about the screen. I use it to update both the players position
            //and the grid position. North, South, East and West are all similar except the values contained in each are for a specific direction
            playerObject.north.X = 0;
            playerObject.north.Y = 0;
            //This bool is used to check for when an enemy is beside and no longer beside the player.
            besidePlayer = true;
        }
        //Check below player
        if (playerObject.PlayerGridPosition.Y + 1 == gridPosition.Y && playerObject.PlayerGridPosition.X == gridPosition.X)
        {
            playerObject.south.X = 0;
            playerObject.south.Y = 0;
            besidePlayer = true;
        }
        //Check to right of player
        if (playerObject.PlayerGridPosition.Y == gridPosition.Y && playerObject.PlayerGridPosition.X + 1 == gridPosition.X)
        {
            playerObject.east.X = 0;
            playerObject.east.Y = 0;
            besidePlayer = true;
        }
        //This if statement just checks to see if any of the enemies are within a squares space of the player, if they are not then the besidePlayer bool is set to false
        else if (playerObject.PlayerGridPosition.Y != gridPosition.Y && playerObject.PlayerGridPosition.X + 1 != gridPosition.X && playerObject.PlayerGridPosition.Y - 1 != gridPosition.Y && playerObject.PlayerGridPosition.X != gridPosition.X && playerObject.PlayerGridPosition.Y + 1 != gridPosition.Y && playerObject.PlayerGridPosition.X != gridPosition.X)
        {
            besidePlayer = false;
        }
        //When an enemy is no longer beside the player then we can reset all the North, South, East and West vector velues back to their original values.
        if (besidePlayer == false)
        {
            playerObject.north.X = 0;
            playerObject.north.Y = -1;

            playerObject.south.X = 0;
            playerObject.south.Y = 1;

            playerObject.east.X = 1;
            playerObject.east.Y = 0;
        }
    }

This next piece of code is for where I set the values inside the 2D array and create the layout of the level. It's also where I create the enemy objects and set the player objects position and grid position, using the row,col of their specific "number" on the grid in order to choose where to draw them.

Note: The rows and columns are flipped as otherwise the level gets drawn sidewards.

public void LoadLevels(int level)
    {
        //More levels will be added in later.
        if(level == 1)
        {
            //Here I set the values inside the 2D array "grid". It is a 20x20 array.
            //0 = Floor Square, 1 = Wall square, 2 = Enemy Square, 3 = Player Square
            grid = new int[maxRows, maxCols]
            {
                {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
                {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
                {1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1},
                {1,0,1,0,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
                {1,0,1,0,1,1,0,1,0,1,1,0,1,1,1,1,1,1,0,1},
                {1,0,1,2,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
                {1,0,1,0,1,1,0,1,0,0,0,0,1,1,1,0,1,1,1,1},
                {1,0,1,0,1,1,0,1,0,1,1,0,0,0,0,0,0,0,0,1},
                {1,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,1,1},
                {1,1,1,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,1},
                {1,0,0,0,0,0,0,0,0,1,1,1,0,1,1,1,1,1,0,1},
                {1,1,1,1,1,1,1,1,0,0,0,3,0,0,0,2,0,0,0,1},
                {1,0,1,0,0,0,0,1,0,1,0,1,0,1,0,1,1,1,0,1},
                {1,2,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
                {1,0,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
                {1,0,1,2,1,1,0,1,0,1,0,1,0,1,0,0,0,0,0,1},
                {1,0,1,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1},
                {1,0,1,0,1,1,0,1,0,0,0,1,0,1,0,1,0,1,0,1},
                {1,0,0,0,0,0,0,0,0,1,0,1,0,1,0,1,1,1,0,1},
                {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
            };
            //Cycle through the array with a nested if
            for (int i = 0; i < maxCols; i++)
            {
                for (int j = 0; j < maxRows; j++)
                {
                    //If the the value at grid[i, j] is a 2 then create an enemy at that position
                    if (grid[i, j] == 2)
                    { 
                        enemyList.Add(new Enemies(Content.Load<Texture2D>("redSquare"), new Vector2(j * squareWidth, i * squareHeight), new Vector2(j, i)));
                        //Reset the value at this position to 0 so that when the player/enemy moves from this spot, a floor tile will be drawn instead of a blank space.
                        grid[i, j] = 0;  
                    }
                    //If the value is 3 then set the players position and grid position to the value based of [i, j] values.
                    if (grid[i, j] == 3)
                    {
                        playerObject.PlayerPosition = new Vector2(i * squareWidth, j * squareHeight);
                        playerObject.PlayerGridPosition = new Vector2(i, j);
                        grid[i, j] = 0;
                    }
                }
            }

        }
        if (level == 2)
        {
        }
    }

Upvotes: 1

Views: 1216

Answers (3)

Asik
Asik

Reputation: 22133

However I can't use this method when checking collision between the player and enemy as I only use the value inside the 2D array(2 and 3) to choose where to originally draw the enemy and player squares.

Perhaps this exposes a flaw in your design that you should fix rather than work around. I don't have enough of the code to tell exactly, but it seems like you are duplicating information inside the Player class or perhaps another grid. Based on your description, a 2D grid containing values 0-1-2-3 for each square fully describes the state of the game. Why not just use that Grid as the single source of truth for everything?

Upvotes: 2

Thariq Nugrohotomo
Thariq Nugrohotomo

Reputation: 753

It's hard to find where the actual bug without seeing the complete code & debug it directly, but I feel that your approach is too complicated.

Personally, I will follow the following logic/pseudocode:

loading () {
    load level to array, no need to perform additional modification
}
game_update () {
    for each tile within the array {
        if ( tile is player ) {
            read user's input
            temp <- compute the next position of the player
            if ( temp is floor ) then { current player position <- temp } // move allowed, update its position
            else { the player must stay on its current position } // move rejected
        }
        if ( tile is enemy ) {
            temp <- compute the next position of this enemy
            if ( temp is floor ) then { this enemy position <- temp } // move allowed, update its position
            else { this enemy must stay on its current position } // move rejected
        }
    }
}
game_draw () {
    for each tile within the array {
        draw the current tile
    }
}

This way, any ilegal move will be blocked before it occurs.

Upvotes: 2

sowrd299
sowrd299

Reputation: 149

Side note: You seem to be missing code for west. If that is unintentional, it should be an easy fix.

I think what may be happening is as soon it one enemy isn't next to the player, it allows the player to walk in all directions again, regardless of restrictions put on by earlier monsters.

If I am correct what is happening is it checks every enemy in descending screen position and blocks the player if then needed. However, if any of the remaining enemies aren't next to the player, the change is undone when the check for that enemy is preformed and the player is allowed to walk through again. This doesn't happen for the last enemy, because their are no more non-adjacent enemies to undo its blocking.

Try changing it so that it resets the allowed movements at the beginning of every frame, as opposed to every time it finds a monster not next to the player.

Upvotes: 1

Related Questions