0
\$\begingroup\$

I've started using XNA and am working on a simple 2D platformer (single level). However, I am stuck as to where the collision detection should be handled. Here is my current project structure:

  • The level is a child class of GameScene which is a base class for different screens (menus and such)
  • The game is based off of a structure where everything is tile-based
    • Therefore, everything inherits from Tile which in turn inherites from DrawableGameComponent

Currently I have a character (Character) and a block tile (Block), but there will be different other tiles as well. There are several types of blocks that the player cannot walk through, and should stop moving (in the necessary dimension) upon collision.

I know how to detect collisions; my problem is determining where the Collision detection code should go for player movement. I was told that all collisions should be handled by a separate collision manager which would be passed the (list) of objects to check and then resolve all collisions.

However, the way I was implementing this was to check for collisions, and if they occurred, I would have to check on which side the collision occurred, and then backtrack the character to a position directly flanking the other tile. Here is a (very brief - not all code) example:

Character_Block_CollisionManager.cs

public class Character_Block_CollisionManager : GameComponent
{
    private Character character;
    private List<Block> tileList;

    public Character_Block_CollisionManager(Game game, Character character, List<Tile> tileList)
        : base(game)
    {
        this.character = character;
        this.tileList = tileList;
    }

    public override void Update(GameTime gameTime)
    {
        //Checks for collisions and updates the objects accordingly
        foreach (Tile tile in tileList)
        {
            //Get intersection bounds of character and current tile
            Rectangle characterBounds = character.CollisionBounds;
            Rectangle tileBounds = tile.CollisionBounds;

            //Get horizontal and vertical differences in position between character and current tile
            float xDiff = characterBounds.X - tileBounds.X;
            float yDiff = characterBounds.Y - tileBounds.Y;

            //Track the post-collision location of the character (character cannot be displayed inside a wall)
            Vector2 postCollisionLocation = character.Position;

            //Check for a collision
            if (tileBounds.Intersects(characterBounds))
            {
                //Find the intersection rectangle between the two tiles
                Rectangle intersectionRectangle = Rectangle.Intersect(tileBounds, characterBounds);

                if (intersectionRectangle.Width > intersectionRectangle.Height)
                {
                    //A collision has occurred on either the top or bottom of the current tile
                    if (character.Position.Y + characterBounds.Height / 2 > tile.Position.Y + tileBounds.Height / 2)
                    {
                        //A collision has occurred on the bottom of the current tile
                        postCollisionLocation = new Vector2(character.Position.X, tileBounds.Y + tileBounds.Height + characterBounds.Height / 2);
                    }
                    else
                    {
                        //A collision has occurred on the top of the current tile
                        postCollisionLocation = new Vector2(character.Position.X, tileBounds.Y - characterBounds.Height / 2);
                    }
                }
                else
                {
                    //A collision has occurred on either the left or right of the current tile
                    if (character.Position.X + characterBounds.Width / 2 > tile.Position.X + tileBounds.Width / 2)
                    {
                        //A collision has occurred on the right of the current tile
                        postCollisionLocation = new Vector2(tileBounds.X + tileBounds.Width + characterBounds.Width / 2, character.Position.Y);
                    }
                    else
                    {
                        //A collision has occurred on the right of the current tile
                        postCollisionLocation = new Vector2(tileBounds.X - characterBounds.Width / 2, character.Position.Y);
                    }
                }

                //Update the position if the character to reflect the collision
                character.Position = postCollisionLocation;
            }
        }

        base.Update(gameTime);
    }
}

Level.cs

public class Level : GameScene
{
    private Tile[,] map = new Tile[32, 18];

    private Character character;
    private List<Tile>tileList;

    private CharacterCollisionManager characterCollisionManager;

    public Level1(Game game, SpriteBatch spriteBatch)
        : base(game, spriteBatch)
    {
        //Create character here...

        this.Components.Add(character);

        tileList = new List<Tile>();

        //Create blocks here...

        tileList.Add(block1);
        tileList.Add(block2);

        foreach (Tile tile in tileList)
        {
            this.Components.Add(tile);
        }

        characterCollisionManager = new CharacterCollisionManager(game, character, tileList);
        this.Components.Add(characterCollisionManager);
    }
}

This does work, but also means that I need to know which side the collision occurred on in order to reposition the character. One approach I have heard a lot about is the approach of creating a temporary player location based on movement and then checking said temporary location for collisions. If collisions will occur, calculate the distance to a collision on each axis and move the player that distance. Otherwise, proceed as normal.

This seems all very good in theory, but I am now stuck with the problem of not knowing where to implement this logic... Here are the possibilities I see:

  • Player class
    • Here is where it seems to need to be in order to have access to player position and therefore be able to calculate a temporary position and check it for collisions (this would be in Character Update())
    • However, it wouldn't have access to any of the other tiles to check against, so this seems pointless
  • CollisionManager class
    • Here is where it seems to need to be in order to have access to all the other tiles and calculate collisions.
    • Clarifying Edit: It seems that this will mean I can't create a tempory player position (based on input) and check it for collisions, as the position will have already been completely calculated in the Player Update method, which will thus have already been moved (possibly into a collision situation).
      Rather than this, I want to test the temporary position for a collision and if one occurs, change the temporary position to the closest it can get without a collision. Finally, I would update the Player's position to be the temporary position. However, as I see it this can only be done from the Player class (as the collision manager does not have access to the Player Update method).

That's basically all I've been able to come up with, which doesn't seem like much. I'm sure other people have done similar things, but have not been able to find a tutorial in this manner. Trust me, I have looked (I wouldn't have posted otherwise). Other tutorials/questions will often have some necessary code/pseudocode, but I've not found one that tells me where to put the code and why. If someone could please do this for me I would greatly appreciate it!

Let me know if I should add anything else!

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

You should definitely do this in the CollisionManager class. However, your question seems to allude to something inhibiting you from doing this, although I'm not sure what. If you're worried about good code design, then it's fine to use Get and Set methods to retrieve and set the positions you need when detecting and resolving collisions. This doesn't violate encapsulation.

A CollisionManager would also be extensible, meaning when you go to write other collision stuff, you can modify and generalise your CollisionManager rather than cluttering lots of other classes with collision logic. If you find an issue down the line with some collision code, it's much easier to change it once in the CollisionManager rather than lots of times in each individual class which implements collision detection/handling.

Edit: Ok, I see what you mean now. You're describing continuous collision detection. I had discrete collision detection in mind! I'm not used to this type of collision detection, so I'm not going to be of much help. I guess you could work the character's position at the next update based on current velocity, etc. and pass that to the CollisionManager, but I'm sure there are better ways of doing it.

You do have the option of using discrete collision detection, which is when you update the world, then check to see if anything has collided. If they have, you separate them, then render the scene. It's very easy to implement, and given that your game's a simple platformer, I can't see any major downsides to implementing it this way. It's up to you though!

\$\endgroup\$
  • \$\begingroup\$ Does the clarification I made help you to understand what I mean? A lot of people seem to recommend checking where the next position "would be" and then moving as close to it as possible. However, I can't do this from the CollisionManager as it doesn't have access to the Player's Update method. Correct me if I'm wrong... Thanks! \$\endgroup\$ – Kendall Nov 22 '15 at 1:56
  • \$\begingroup\$ @KendallRoth I understand what you mean. I've updated my answer. \$\endgroup\$ – KevLoughrey Nov 22 '15 at 2:27
  • \$\begingroup\$ So you're suggesting that I move the Player in the Player Update method, and then check for collisions in the CollisionManager class...if a collision occurs I then separate them so they appear to be touching?? Did I get that right? If so I think I also see what you mean about making it extensible...you're suggesting that I use one CollisionManager to check/resolve collisions between non-moving (brick, door, etc) and moving entities (player, enemy, etc), right? \$\endgroup\$ – Kendall Nov 22 '15 at 2:41
  • \$\begingroup\$ Yes, that's exactly what I'm suggesting. It's how I'd implement it anyway (and have done so in the past). \$\endgroup\$ – KevLoughrey Nov 22 '15 at 2:45
  • \$\begingroup\$ Final thing :). Say that a MoveableTile (character) falls onto a Tile (platform) and should play a noise. I'd use this CollisionManager to manage the post-collision positioning, but what about sound? I'd likely want another collision manager class for character specific collisions, right (for example)? And then in this other class not worry at all about positioning (covered in this CollisionManager), but only about other things (like sounds, animations, etc)??? Otherwise this CollisionManager would be filled with a lot of extra logic that would only happen for certain Tiles. \$\endgroup\$ – Kendall Nov 22 '15 at 2:57

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.