Reputation: 1
I'm trying to create a 2D game for my college project using MonoGame (which uses XNA's framework) and have had a lot of trouble regarding collision between two overlapping Rectangles and a Player 'Hitbox' Rectangle. The Player is stopped (which is not the desired result) if they are moving diagonally along one Wall Rectangle when another perpendicular Wall Rectangle that isn't in the way is encountered.
When the player walks into a Wall, the CollisionHandler method for the Player class is called, and the colliding Rectangle, along with the enum Side (essentially which way the Wall checks for collisions) are fed through the parameters. The method has some conditionals and then changes the Position of the player. This is the code for that:
public void CollisionHandler(Rectangle Wall, Side TestSide) // Assuming Hitbox and Wall are Intersecting
{
if ((TestSide == Side.Up && Direction.Y > 0) // If the Wall is testing for collision Upwards and the Player is moving Downwards through it
|| (TestSide == Side.Down && Direction.Y < 0)) // or if the Wall is testing for collision Downwards and vice versa
{
Position.Y -= Direction.Y * MovementSpeed; // Y movement is reversed
}
if ((TestSide == Side.Left && Direction.X > 0) // If the Wall is testing for collision to it's Left and the Player is moving to the Right through it
|| (TestSide == Side.Right && Direction.X < 0)) // or if the Wall is testing for collision to it's Right and vice versa
{
Position.X -= Direction.X * MovementSpeed; // X movement is reversed
}
}
(Direction is a normalised Vector2 of the Player's new Position minus their previous Position)
The problem is when the Player moves to the corner of two overlapping Walls. For example, here are two of the Walls I have on the map:
Walls.Add(new Rectangle(360, 240, 1, 120)); // Side = Side.Left
Walls.Add(new Rectangle(360, 240, 120, 1)); // Side = Side.Up
When the Player is travelling diagonally down and to the right (Direction would be roughly (0.707, 0.707)), the 'Left' oriented Wall would intersect the Player's Hitbox, despite the Player's Hitbox being behind and above the Wall, reducing the Player to a standstill.
I've tried many times to combat this, usually by changing how the Walls are made, and this is actually the most recent iteration of how the Walls are presented. Prior to this, they were just large Rectangles over each in-game Tile with a 'CanEnter' attribute of false.
I've racked my brain on-and-off for a few weeks now trying to figure out how I can prevent this but nothing has actually worked, and now my project is at a complete standstill due to the issue. I would really appreciate some help or pointers on how to solve this.
Upvotes: 0
Views: 1226
Reputation: 2860
There's a lot of things you haven't specified but I think I can make an educated guess: You made the position change once you got the input, and then did nothing more than "undo" the position change on detection of a collision, which brought the position Vector exactly where it was. You can solve this easily by doing "undo" part at least twice.
So in place of where you did
Position.Y -= Direction.Y * MovementSpeed;
You should do something like
Position.Y -= Direction.Y * MovementSpeed * 2;
You will further need to clip your HitBox at the exact edge of the Wall. Not doing so might make your Hitbox rectangle protruding outside your wall, and you probably don't want that.
Now an even better approach would be to only set the Position Vector once, which is only proper to do after setting the Direction vector from the input and checking for the collision. Also, I hope you are using an acceleration-based motion, as using only a constant velocity for moving can feel rough, whereas using acceleration will make your game smooth.
That being said, I can suggest a lot of improvements in your code
I think you are using a List Walls to store a rectangle for each side of the wall, and I think you are needlessly complicating the problem there. A "Room" having 4 walls should be what you need.
Let's say you have a Wall as follows
Rectangle Wall;
Which you initialized it as something like
Wall=new Rectangle(30,30,400,400);
This means you have Top wall from (30,30) to (430,30), Left Wall from (30,30) to (30,430), Right Wall from (430,30) to (430,430), and the Bottom Wall as (30,430) to (430,430)
Now I believe you have a Player HitBox which is again a Rectangle
Rectangle HitBox;
Now in the Update()
method you need to test if the HitBox and the Wall intersect, which you can get quite easily with
if(HitBox.Intersects(Wall)
{
//Handle the bouncing logic here
}
Since I have a lot of time, I'll offer you a complete working example of this.
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace test
{
public class Game1: Game
{
private readonly GraphicsDeviceManager _graphics;
private SpriteBatch _spriteBatch;
private Texture2D HitboxTexture, WallTexture;
private Rectangle HitBox, Wall;
private Vector2 Position, Direction, Acceleration;
float Speed;
public Game1()
{
_graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
IsMouseVisible = true;
}
protected override void Initialize(){base.Initialize();}
protected override void LoadContent()
{
_spriteBatch = new SpriteBatch(GraphicsDevice);
// TODO: use this.Content to load your game content here
HitboxTexture = Content.Load<Texture2D>("dirt");
WallTexture = Content.Load<Texture2D>("wall");
Speed=2;
HitBox=new Rectangle(0,0,40,40);
Wall=new Rectangle(30,30,400,400);
Position=Wall.Center.ToVector2();
}
protected override void Update(GameTime gameTime)
{
var ks=Keyboard.GetState();
if (ks.IsKeyDown(Keys.Escape))Exit();
//Get the Direction Vector from the keyboard
Acceleration=Vector2.Zero;
if(ks.IsKeyDown(Keys.Left))Acceleration.X=-1;
else if(ks.IsKeyDown(Keys.Right))Acceleration.X=1;
if(ks.IsKeyDown(Keys.Up))Acceleration.Y=-1;
else if(ks.IsKeyDown(Keys.Down))Acceleration.Y=1;
Acceleration*=Speed;
Acceleration=Acceleration - Direction*0.54f;//Velocity = Acceleration - Friction
Direction+=Acceleration;
Position+=Direction; //Postition
//Check for Intersectrion
if(Wall.Intersects(HitBox))
{
//Intersection is true. Now add the bouncing logic
//First, clip the box so that it is inside the wall
if(HitBox.Left<Wall.Left)Position.X=Wall.X+1;
if(HitBox.Top<Wall.Top)Position.Y=Wall.Y+1;
if(HitBox.Right>Wall.Right)Position.X=Wall.Right-HitBox.Width -1;
if(HitBox.Bottom>Wall.Bottom)Position.Y=Wall.Bottom-HitBox.Height-1;
//Now, make the direction change
if(HitBox.Left<Wall.Left || HitBox.Right>Wall.Right) Direction.X*=-1;
if(HitBox.Top<Wall.Top || HitBox.Bottom>Wall.Bottom) Direction.Y*=-1;
Direction*=2;//For True rebound
}
HitBox.X=(int)Position.X;
HitBox.Y=(int)Position.Y;
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
Color color=gameTime.IsRunningSlowly?Color.Red:Color.CornflowerBlue;
GraphicsDevice.Clear(color);
// TODO: Add your drawing code here
_spriteBatch.Begin();
_spriteBatch.Draw(WallTexture, Wall, Color.White);
_spriteBatch.Draw(HitboxTexture, HitBox, Color.White);
_spriteBatch.End();
base.Draw(gameTime);
}
}
}
Upvotes: 0