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 fromDrawableGameComponent
- Therefore, everything inherits from
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
- 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
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 thePlayer
class (as the collision manager does not have access to thePlayer
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!