I have recently been experimenting with Monogame and XNA (moving from Gamemaker) and decided to work on a simple collision system. Problem is shown in the GIF.
Main Collision
Each entity (Quick name for anything with properties) has some parameters. Those are:
public Vector2 Position;
public Vector2 Origin;
public Vector2 Velocity;
public Vector2 Speed;
public Color Color = Color.White;
public Rectangle Rectangle
{
get
{
return new Rectangle((int)Position.X, (int)Position.Y, _texture.Width, _texture.Height);
}
}
I used blocks just to check how collision works on multiple objects next to each other. This is not about optimisation right now. As you can see, there is an invisible wall coming to the right of each vertical block. I can add multiples just to demonstrate:
Now let's get to the collision code. I wanted to do something similar like in GMS2 and check if the box of sprite is going to overlap any other entity in the game at set postition. Since I need to know in the future, which side will be touched, I wrote my code in Entity class like this:
protected bool IsTouchingLeft(Vector2 pos, Entity entity)
{
return pos.X + _texture.Width >= entity.Rectangle.Left &&
pos.X <= entity.Rectangle.Left &&
pos.Y + _texture.Height >= entity.Rectangle.Top &&
pos.Y <= entity.Rectangle.Bottom;
}
protected bool IsTouchingRight(Vector2 pos, Entity entity)
{
return pos.X >= entity.Rectangle.Right &&
pos.X + _texture.Width >= entity.Rectangle.Right &&
pos.Y + _texture.Height >= entity.Rectangle.Top &&
pos.Y <= entity.Rectangle.Bottom;
}
protected bool IsTouchingTop(Vector2 pos, Entity entity)
{
return pos.X <= entity.Rectangle.Right &&
pos.X + _texture.Width >= entity.Rectangle.Left &&
pos.Y + _texture.Height >= entity.Rectangle.Top &&
pos.Y <= entity.Rectangle.Top;
}
protected bool IsTouchingBottom(Vector2 pos, Entity entity)
{
return pos.X <= entity.Rectangle.Right &&
pos.X + _texture.Width >= entity.Rectangle.Left &&
pos.Y <= entity.Rectangle.Bottom &&
pos.Y + _texture.Height >= entity.Rectangle.Bottom;
}
For these functions I use two variables, the position at which collision is going to happen and Entity that it should collide with.
And to make everything simple when I just need to know, if it's going to collide at all, I added a PlaceMeeting function.
protected bool PlaceMeeting(Vector2 pos, Entity entity)
{
return IsTouchingLeft(pos, entity) ||
IsTouchingRight(pos, entity) ||
IsTouchingTop(pos, entity) ||
IsTouchingBottom(pos, entity);
}
It just checks if there wil be any collision at a set point with a set Entity. ( if you've used GMS, you know what I want to do )
Now Let's go to the movement script. Player is a child of Entity class and has few new variables.
private Vector2 move;
private float grav = 0.3f;
private Int32 input_left;
private Int32 input_right;
private bool input_up;
Input is taken like this:
input_left = Convert.ToInt32(Keyboard.GetState().IsKeyDown(Keys.A));
input_right = Convert.ToInt32(Keyboard.GetState().IsKeyDown(Keys.D));
input_up = Keyboard.GetState().IsKeyDown(Keys.Space);
Horizontal input is set in such way because of simpler movement. Here is Move() Function.
private void Move()
{
move.X = (input_right - input_left);
Velocity.X = move.X * speed.X;
Velocity.Y += grav;
if (input_up)
Velocity.Y = speed.Y;
}
And the the code Player Collision code:
foreach (var entity in entities) //Looks at all entities created
{
if (entity == this) //If it is player, don't check collision
continue;
//Horizontal Collision
if (PlaceMeeting(new Vector2(Position.X + Velocity.X, Position.Y), entity))
{
while (!PlaceMeeting(new Vector2(Position.X + Math.Sign(Velocity.X), Position.Y), entity))
{
Position.X += Math.Sign(Velocity.X);
}
Velocity.X = 0;
}
//Vertical Collision
if (PlaceMeeting(new Vector2(Position.X, Position.Y + Velocity.Y), entity)) // checking if it's going to collide in 1 step
{
while (!PlaceMeeting(new Vector2(Position.X, Position.Y + Math.Sign(Velocity.Y)), entity))// checking if it's not going to collide in 1 pixel
{
Position.Y += Math.Sign(Velocity.Y); // incrementing pixel By pixel until it's going to collide in 1 pixel
}
Velocity.Y = 0; //stoping the player
}
}
And then I Increment the position with Velocity.
Position += Velocity;
I tried different collision methods and this is how they work out and look like:
Other Collision One
In the Player Class I used this collision code instead:
if (this.Velocity.X > 0 && IsTouchingLeft(Position + Velocity, entity))
this.Velocity.X = 0;
if (this.Velocity.X < 0 && IsTouchingRight(Position + Velocity, entity))
this.Velocity.X = 0;
//Vertical Collision
if (this.Velocity.Y > 0 && IsTouchingTop(Position + Velocity, entity))
this.Velocity.Y = 0;
if (this.Velocity.Y < 0 && IsTouchingBottom(Position + Velocity, entity))
this.Velocity.Y = 0;
I basically couldn't move Left when in the right of vertical blocks.
Other Collision 2
I also tried using this strange code with Rectangle.Intersects()
//Horizontal Collision
var xRect = new Rectangle((int)Position.X+ (int)Velocity.X, (int)Position.Y, _texture.Width, _texture.Height);
if (xRect.Intersects(entity.Rectangle))
{
xRect = new Rectangle((int)Position.X + Math.Sign(Velocity.X), (int)Position.Y, _texture.Width, _texture.Height);
while (!xRect.Intersects(entity.Rectangle))
{
Position.X += Math.Sign(Velocity.X);
}
this.Velocity.X = 0;
}
//Vertical Collision
var yRect = new Rectangle((int)Position.X, (int)Position.Y + (int)Velocity.Y, _texture.Width, _texture.Height);
if (yRect.Intersects(entity.Rectangle))
{
yRect = new Rectangle((int)Position.X, (int)Position.Y + Math.Sign(Velocity.Y), _texture.Width, _texture.Height);
while (!yRect.Intersects(entity.Rectangle))
{
Position.Y += Math.Sign(Velocity.Y);
}
this.Velocity.Y = 0;
}
This made my program to unresponsive - why? What can I do to prevent it from becoming unresponsive?
I will post the full code of each class: Game1.cs Entity.cs Player.cs Block.cs