When my AABB physics engine resolves an intersection, it does so by finding the axis where the penetration is smaller, then "push out" the entity on that axis.
Considering the "jumping moving left" example:
If velocityX is bigger than velocityY, AABB pushes the entity out on the Y axis, effectively stopping the jump (result: the player stops in mid-air).
If velocityX is smaller than velocitY (not shown in diagram), the program works as intended, because AABB pushes the entity out on the X axis.
How can I solve this problem?
Source code:
public void Update()
{
Position += Velocity;
Velocity += World.Gravity;
List<SSSPBody> toCheck = World.SpatialHash.GetNearbyItems(this);
for (int i = 0; i < toCheck.Count; i++)
{
SSSPBody body = toCheck[i];
body.Test.Color = Color.White;
if (body != this && body.Static)
{
float left = (body.CornerMin.X - CornerMax.X);
float right = (body.CornerMax.X - CornerMin.X);
float top = (body.CornerMin.Y - CornerMax.Y);
float bottom = (body.CornerMax.Y - CornerMin.Y);
if (SSSPUtils.AABBIsOverlapping(this, body))
{
body.Test.Color = Color.Yellow;
Vector2 overlapVector = SSSPUtils.AABBGetOverlapVector(left, right, top, bottom);
Position += overlapVector;
}
if (SSSPUtils.AABBIsCollidingTop(this, body))
{
if ((Position.X >= body.CornerMin.X && Position.X <= body.CornerMax.X) &&
(Position.Y + Height/2f == body.Position.Y - body.Height/2f))
{
body.Test.Color = Color.Red;
Velocity = new Vector2(Velocity.X, 0);
}
}
}
}
}
public static bool AABBIsOverlapping(SSSPBody mBody1, SSSPBody mBody2)
{
if(mBody1.CornerMax.X <= mBody2.CornerMin.X || mBody1.CornerMin.X >= mBody2.CornerMax.X)
return false;
if (mBody1.CornerMax.Y <= mBody2.CornerMin.Y || mBody1.CornerMin.Y >= mBody2.CornerMax.Y)
return false;
return true;
}
public static bool AABBIsColliding(SSSPBody mBody1, SSSPBody mBody2)
{
if (mBody1.CornerMax.X < mBody2.CornerMin.X || mBody1.CornerMin.X > mBody2.CornerMax.X)
return false;
if (mBody1.CornerMax.Y < mBody2.CornerMin.Y || mBody1.CornerMin.Y > mBody2.CornerMax.Y)
return false;
return true;
}
public static bool AABBIsCollidingTop(SSSPBody mBody1, SSSPBody mBody2)
{
if (mBody1.CornerMax.X < mBody2.CornerMin.X || mBody1.CornerMin.X > mBody2.CornerMax.X)
return false;
if (mBody1.CornerMax.Y < mBody2.CornerMin.Y || mBody1.CornerMin.Y > mBody2.CornerMax.Y)
return false;
if(mBody1.CornerMax.Y == mBody2.CornerMin.Y)
return true;
return false;
}
public static Vector2 AABBGetOverlapVector(float mLeft, float mRight, float mTop, float mBottom)
{
Vector2 result = new Vector2(0, 0);
if ((mLeft > 0 || mRight < 0) || (mTop > 0 || mBottom < 0))
return result;
if (Math.Abs(mLeft) < mRight)
result.X = mLeft;
else
result.X = mRight;
if (Math.Abs(mTop) < mBottom)
result.Y = mTop;
else
result.Y = mBottom;
if (Math.Abs(result.X) < Math.Abs(result.Y))
result.Y = 0;
else
result.X = 0;
return result;
}
It's hard to read code of other people, but I think this is one possible (purely brainstormed) workaround, although of course I'm not able to test it:
Before the collision detection happens, save the players velocity to some temporary variable.
After you've done your collision response, check if the players X or Y position has been corrected
If X position has been changed, manually reset (as a kind of "safety reset") the players Y velocity to the one that he had before the response.
By the way, what happens with your current code when your players hits the roof while jumping?
I had that same problem. Even the Microsoft platformer starterskit seems to have that bug.
The solution I found so far is to either use multisampling (argh) or to use the movedirection of the object: only move the distance between those 2 objects and no further when a collision is detected (and do that for each axis).
One possible solution I found is sorting the objects before resolving based on the velocity of the player.
Related
I'm creating a MonoGame 2D engine framework for a platforming game, and I'm having trouble creating the collision response system. Although I've gotten SAT detection to work, the response travels across the actual direction of the static body's edge rather than its normal. Reversing the axes of the normal has not worked for me and does nothing, it has only created glitches involving the body going off screen.
Since I'm trying to make a platformer, I only want the normals of the static body to be considered as directions to respond. For example, if the static body is a box, I only want the moving body to travel on 90 degree normals.
Here is a video of the problem in action: https://www.youtube.com/watch?v=-wyXfZkxis0
And the source for the "Collision" module, which has all of the relevant geometric calculations inside (translation vector algorithm at the bottom):
using System;
using Microsoft.Xna.Framework;
namespace Crossfrog.Ferrum.Engine.Modules
{
public static class Collision
{
public static bool RectsCollide(Rectangle rect1, Rectangle rect2)
{
return
rect1.X <= rect2.X + rect2.Width &&
rect1.Y <= rect2.Y + rect2.Height &&
rect1.X + rect1.Width >= rect2.X &&
rect1.Y + rect1.Height >= rect2.Y;
}
private static float DotProduct(Vector2 v1, Vector2 v2)
{
return (v1.X * v2.X) + (v1.Y * v2.Y);
}
private static Vector2 NormalBetween(Vector2 v1, Vector2 v2)
{
return new Vector2(-(v1.Y - v2.Y), v1.X - v2.X);
}
private struct ProjectionLine
{
public float Start;
public float End;
}
private static ProjectionLine ProjectLine(Vector2[] points, Vector2 normal)
{
var projectionLine = new ProjectionLine() { Start = float.MaxValue, End = float.MinValue };
foreach (var p in points)
{
var projectionScale = DotProduct(p, normal);
projectionLine.Start = Math.Min(projectionScale, projectionLine.Start);
projectionLine.End = Math.Max(projectionScale, projectionLine.End);
}
return projectionLine;
}
private static bool CheckOverlapSAT(Vector2[] shape1, Vector2[] shape2)
{
for (int i = 0; i < shape1.Length; i++)
{
var vertex = shape1[i];
var nextVertex = shape1[(i + 1) % shape1.Length];
var edgeNormal = NormalBetween(vertex, nextVertex);
var firstProjection = ProjectLine(shape1, edgeNormal);
var secondProjection = ProjectLine(shape2, edgeNormal);
if (!(firstProjection.Start <= secondProjection.End && firstProjection.End >= secondProjection.Start))
return false;
}
return true;
}
public static bool ConvexPolysCollide(Vector2[] shape1, Vector2[] shape2)
{
return CheckOverlapSAT(shape1, shape2) && CheckOverlapSAT(shape2, shape1);
}
private static float? CollisionResponseAcrossLine(ProjectionLine line1, ProjectionLine line2)
{
if (line1.Start <= line2.Start && line1.End > line2.Start)
return line2.Start - line1.End;
else if (line2.Start <= line1.Start && line2.End > line1.Start)
return line2.End - line1.Start;
return null;
}
public static Vector2 MTVBetween(Vector2[] mover, Vector2[] collider)
{
if (!ConvexPolysCollide(mover, collider))
return Vector2.Zero;
float minResponseMagnitude = float.MaxValue;
var responseNormal = Vector2.Zero;
for (int c = 0; c < collider.Length; c++)
{
var cPoint = collider[c];
var cNextPoint = collider[(c + 1) % collider.Length];
var cEdgeNormal = NormalBetween(cPoint, cNextPoint);
var cProjected = ProjectLine(collider, cEdgeNormal);
var mProjected = ProjectLine(mover, cEdgeNormal);
var responseMagnitude = CollisionResponseAcrossLine(cProjected, mProjected);
if (responseMagnitude != null && responseMagnitude < minResponseMagnitude)
{
minResponseMagnitude = (float)responseMagnitude;
responseNormal = cEdgeNormal;
}
}
var normalLength = responseNormal.Length();
responseNormal /= normalLength;
minResponseMagnitude /= normalLength;
var mtv = responseNormal * minResponseMagnitude;
return mtv;
}
}
}
Your code is nearly right, just follow these steps and it should work.
Normalize your normal in NormalBetween(). Without this, your projected values are distorted and shouldn't be compared to get the right axis.
return Vector2.Normalize(new Vector2(-(v1.Y - v2.Y), v1.X - v2.X));
Use the same collision expression in CollisionResponseAcrossLine() as in CheckOverlapSAT(). Or just use one detection method for both.
if (line1.Start <= line2.Start && line1.End >= line2.Start) // use the >= operator
return line2.Start - line1.End;
else if (line2.Start <= line1.Start && line2.End >= line1.Start) // use the >= operator
return line2.End - line1.Start;
return null;
Compare the absolute magnitudes inside MTVBetween(). The calculated magnitudes can be negative, when they are pointing to the other direction of the normal.
if (responseMagnitude != null && Math.Abs(responseMagnitude.Value) < Math.Abs(minResponseMagnitude))
The following code is no longer needed, because we already normalized the vector in 1.
//var normalLength = responseNormal.Length();
//responseNormal /= normalLength;
//minResponseMagnitude /= normalLength;
This should get your example to work. But when you try it with two polygons, that have different seperation axes, it won't work, because in the collision response code, you only check for the axes of the static collider. The axes from the mover should also be checked, like you did in the collision detection method CheckOverlapSAT().
Calling the CheckOverlapSAT() method inside MTVBetween() seems to be redundant, you could also interrupt the MTVBetween() method, when any responseMagnitude is null.
And last but not least, consider replacing your CollisionResponseAcrossLine() code with the following:
private static float? CollisionResponseAcrossLine(ProjectionLine line1, ProjectionLine line2)
{
float distToStartOfLine2 = line2.Start - line1.End;
if (distToStartOfLine2 > 0)
return null;
float distToEndOfLine2 = line2.End - line1.Start;
if (distToEndOfLine2 < 0)
return null;
if (-distToStartOfLine2 < distToEndOfLine2) // negate distToStartOfLine2, cause it's always negative
return distToStartOfLine2;
else
return distToEndOfLine2;
}
This also accounts for the scenario of the player being inside an obstacle. It compares the distances to both sides and chooses the smaller one. Previously the player would always go to the same edge in this scenario.
If you want code that only supports AABBs, then you could go a simpler route without relying on the SAT. But I guess you want to support polygons too.
I'm programming a starship-game and have just finished the part which manages the rotating. I used a simple if/else-statement to check if the ship must rotate positive or negative in order to face the target as fast as possible. But I saw that the Rotation-Value can get negative, and then the ship does rotate to the wrong direction (It still faces the target-point in the end, but it takes longer). Please tell me what I did wrong :(
The function:
public bool RotateOrMove(Vector2 position)
{
if (IsRotationg == null) //todo
{
Vector2 direction = Position - position;
direction.Normalize();
FinalRotation = (float)Math.Atan2(-direction.X, direction.Y);
IsRotationg = true;
}
if (Equals(FinalRotation, Rotation))
IsRotationg = false;
if (IsRotationg == false)
{
Position = position;
return true;
}
else
{
if (FinalRotation >= Rotation)
{
Rotation += RotationVelocity;
if (FinalRotation - Rotation < RotationVelocity)
{
Rotation = FinalRotation;
IsRotationg = false;
}
}
if (FinalRotation < Rotation)
{
Rotation -= RotationVelocity;
if (FinalRotation - Rotation > -RotationVelocity)
{
Rotation = FinalRotation;
IsRotationg = false;
}
}
return false;
}
}
The Player-Class owns the Ship. When the player press the right mouse-button, this method will be called once per frame until the ship reaches the position where the cursor is pointing at.
if (!Ship.RotateOrMove(Position))
Position -= Velocity;
So if the ship had to rotate and couldn't move, it will remove the velocity it added just before to ensure that the ship won't move.
Hope you understand my problem^^
Math.Atan2 return values from -pi to pi.
to get a smooth rotation you can use this code got from here
private float CurveAngle(float from, float to, float step)
{
if (step == 0) return from;
if (from == to || step == 1) return to;
Vector2 fromVector = new Vector2((float)Math.Cos(from), (float)Math.Sin(from));
Vector2 toVector = new Vector2((float)Math.Cos(to), (float)Math.Sin(to));
Vector2 currentVector = Slerp(fromVector, toVector, step);
return (float)Math.Atan2(currentVector.Y, currentVector.X);
}
private Vector2 Slerp(Vector2 from, Vector2 to, float step)
{
if (step == 0) return from;
if (from == to || step == 1) return to;
double theta = Math.Acos(Vector2.Dot(from, to));
if (theta == 0) return to;
double sinTheta = Math.Sin(theta);
return (float)(Math.Sin((1 - step) * theta) / sinTheta) * from + (float)(Math.Sin(step * theta) / sinTheta) * to;
}
Basically I have been given a project to develop a game in unity using c# and the device Leap Motion. The problem I am having is that I have a basic power bar that goes up and down through a range of 0 - 100, when the user releases his fist I want to shoot using the selected power. the problem I am having is I cannot figure out a way to set the bool "shoot" to true on release of the fists.
Controller m_leapController;
public GameObject CannonBarrel;
[Range(0, 100)] public float ballPower = 0f;
public bool increasing = true;
public bool shoot = false;
if (frame.Hands.Count >= 2)
{
Hand leftHand = GetLeftMostHand(frame);
Hand rightHand = GetRightMostHand(frame);
Vector3 handDiff = leftHand.PalmPosition.ToUnityScaled() - rightHand.PalmPosition.ToUnityScaled();
Vector3 newRot = CannonBarrel.transform.localRotation.eulerAngles;
float leftRightSpeed = handDiff.y * 5.0f;
float handPitch = leftHand.Direction.Pitch + rightHand.Direction.Pitch * 0.5f;
newRot.x = 0;
newRot.y = 90;
newRot.z = 70 + -handPitch * 20.0f;
shoot = true;
// if closed fist...
if (frame.Fingers.Count < 3)
{
leftRightSpeed = 0;
shoot = false;
if (increasing == true)
{
ballPower++;
if (ballPower >= 100)
{
increasing = false;
}
}
else if (increasing == false)
{
ballPower--;
if (ballPower <= 0)
{
increasing = true;
}
}
}
else
{
//Move left or right depending on hands height difference.
transform.parent.rigidbody.velocity = transform.parent.right * leftRightSpeed;
//Rotate the barrel
CannonBarrel.transform.localRotation = Quaternion.Slerp(CannonBarrel.transform.localRotation, Quaternion.Euler(newRot), 0.1f);
}
if (shoot == true)
{
Debug.Log("fired");
//add code here to spawn projectile
}
}
I hope that this code makes sense to you. All that is happening is if you're holding your fists above the Leap sensor the ball power will increase till you hit 100, then it will decrease back down to 0 and so on until you release your fists. I need a way to set the "shoot" bool to true once a power has been selected as the way I currently do it I can only set it to true either during the power selection or it gets set to true before you clench your fists.
Thanks in advance.
A somewhat simple way you could do this is by setting a bool to check if the player has clenched his fist at least once, thus setting the power. Let's call it hasClenchedFist:
if (frame.Hands.Count >= 2)
{
Hand leftHand = GetLeftMostHand(frame);
Hand rightHand = GetRightMostHand(frame);
Vector3 handDiff = leftHand.PalmPosition.ToUnityScaled() - rightHand.PalmPosition.ToUnityScaled();
Vector3 newRot = CannonBarrel.transform.localRotation.eulerAngles;
float leftRightSpeed = handDiff.y * 5.0f;
float handPitch = leftHand.Direction.Pitch + rightHand.Direction.Pitch * 0.5f;
newRot.x = 0;
newRot.y = 90;
newRot.z = 70 + -handPitch * 20.0f;
//shoot = true;
// if closed fist...
if (frame.Fingers.Count < 3)
{
leftRightSpeed = 0;
hasClenchedFist = true;
shoot = false;
if (increasing == true)
{
ballPower++;
if (ballPower >= 100)
{
increasing = false;
}
}
else if (increasing == false)
{
ballPower--;
if (ballPower <= 0)
{
increasing = true;
}
}
}
else if(hasClenchedFist)
{
shoot = true;
}
else
{
//Move left or right depending on hands height difference.
transform.parent.rigidbody.velocity = transform.parent.right * leftRightSpeed;
//Rotate the barrel
CannonBarrel.transform.localRotation = Quaternion.Slerp(CannonBarrel.transform.localRotation, Quaternion.Euler(newRot), 0.1f);
}
if (shoot == true)
{
Debug.Log("fired");
//add code here to spawn projectile
}
}
This should work, unless I've misunderstood the code/requirements.
i have problem with blocking movement i already read question on Blocking Movement On Collision
but i dont have any idea for my own problem.
if you can give me the logic i will try to solve my own problem.
i hope you can help me
this my player class update
:EDIT: thanks for Kai Hartmann for reply
my problem is for 2D graphic and i want to know how to stop movement when object1 collision with object2
public void Update(GameTime gameTime)
{
// Ani Test
up_ani.Update(gameTime);
down_ani.Update(gameTime);
left_ani.Update(gameTime);
right_ani.Update(gameTime);
position += velocity;
if (Keyboard.GetState().IsKeyDown(key.MoveUp) && prevState.IsKeyUp(key.MoveDown) && prevState.IsKeyUp(key.MoveRight) && prevState.IsKeyUp(key.MoveLeft))
{
currentFace = FacePosition.Up;
velocity.Y = -3;
}
else if (Keyboard.GetState().IsKeyDown(key.MoveDown) && prevState.IsKeyUp(key.MoveUp) && prevState.IsKeyUp(key.MoveRight) && prevState.IsKeyUp(key.MoveLeft))
{
currentFace = FacePosition.Down;
velocity.Y = 3;
}
else if (Keyboard.GetState().IsKeyDown(key.MoveRight) && prevState.IsKeyUp(key.MoveDown) && prevState.IsKeyUp(key.MoveUp) && prevState.IsKeyUp(key.MoveLeft))
{
currentFace = FacePosition.Right;
velocity.X = 3;
}
else if (Keyboard.GetState().IsKeyDown(key.MoveLeft) && prevState.IsKeyUp(key.MoveDown) && prevState.IsKeyUp(key.MoveRight) && prevState.IsKeyUp(key.MoveUp))
{
currentFace = FacePosition.Left;
velocity.X = -3;
}
else
{
//currentFace = FacePosition.Down;
velocity = Vector2.Zero;
}
prevState = Keyboard.GetState();
}
and this my collision
public void IntersectWithScore(Vector2 vector1, Vector2 vector2, Texture2D object1, Texture2D object2, int doSomethingToScore, bool isIncrease)
{
if (vector1.X + object1.Width < vector2.X || vector1.X > vector2.X + object2.Width ||
vector1.Y + object1.Height < vector2.Y || vector1.Y > vector2.Y + object2.Height)
{
}
else
{
player1.Velocity = Vector2.Zero;
}
}
No need for a collision check Method as far as I'm concerned :) I've written this code really quickly but should work okay?(ish). I've assumed you want to check if something is in the player's way and also assumed that there are more than one objects that could be in your players way so to do this I suggest making a class for the 'Solid' object and giving it a position, width and height. Then make a list of this class to contain the multiple 'solid' objects.
// collidable object's Class name = SolidBlock
SolidBlock newSolidBlock;
List<SolidBlock> solidBlocksList = new List<SolidBlock>();
// This will create 10 'solid' objects and adds them all to a list
// This would preferably go in the Load() function so you can set the positions of
// the blocks after creating them (after the loop)
int i = 0;
while (i < 10)
{
newSolidBlock = new SolidBlock(Vector2, position) // Assuming width and height
// are set within class
solidBlocksList.Add(newSolidBlock);
i++;
}
// It doesn't matter how you create them, the important lines here are the creation of a
// newSolidBlock and then the line adding it to the list of blocks.
solidBlocksList[0].position = new Vector (20, 50); // < for example this is how you would set
// a block's position.
Then in your players update function you refer to this list of objects.
// Input a list of the objects you want your player to collide with
// this is a list in case you have multiple object2s you want to check
// for collision.
// An Object2 should have a position, height, and width, yes?
public void Update(GameTime gameTime, List<Object2> object2List)
{
// Ani Test
up_ani.Update(gameTime);
down_ani.Update(gameTime);
left_ani.Update(gameTime);
right_ani.Update(gameTime);
position += velocity;
if (Keyboard.GetState().IsKeyDown(key.MoveUp) && prevState.IsKeyUp(key.MoveDown) && prevState.IsKeyUp(key.MoveRight) && prevState.IsKeyUp(key.MoveLeft))
{
foreach (Object2 o in object2List)
{
if (position.Y <= o.position.Y + o.height)
{
velocity.Y = 0;
}
else
{
velocity.Y = -3;
}
}
currentFace = FacePosition.Up;
}
else if (Keyboard.GetState().IsKeyDown(key.MoveDown) && prevState.IsKeyUp(key.MoveUp) && prevState.IsKeyUp(key.MoveRight) && prevState.IsKeyUp(key.MoveLeft))
{
foreach (Object2 o in object2List)
{
if (position.Y + playerWidth >= o.position.Y)
{
velocity.Y = 0;
}
else
{
velocity.Y = 3;
}
}
currentFace = FacePosition.Down;
}
else if (Keyboard.GetState().IsKeyDown(key.MoveRight) && prevState.IsKeyUp(key.MoveDown) && prevState.IsKeyUp(key.MoveUp) && prevState.IsKeyUp(key.MoveLeft))
{
foreach (Object2 o in object2List)
{
if (position.X + playerWidth >= o.position.X)
{
velocity.X = 0;
}
else
{
velocity.X = 3;
}
}
currentFace = FacePosition.Right;
}
else if (Keyboard.GetState().IsKeyDown(key.MoveLeft) && prevState.IsKeyUp(key.MoveDown) && prevState.IsKeyUp(key.MoveRight) && prevState.IsKeyUp(key.MoveUp))
{
foreach (Object2 o in object2List)
{
if (position.X <= o.position.X + o.width)
{
velocity.X = 0;
}
else
{
velocity.X = -3;
}
}
currentFace = FacePosition.Left;
}
else
{
velocity = Vector2.Zero;
}
prevState = Keyboard.GetState();
}
Sorry if this isn't much help, especially the first block of code, that was very much an example of how you could create multiple object2s.
I've been racking my mind on this for days. When updating entities, I check for collision, and if they collide according to the code below, their appropriate motion value is set to 0. Except for some reason, this code doesn't work for checking with collision to its right. If there is a 2 tall wall in front of the entity, it will disregard it. However, it seems to work right if I check below it, but then it will stop completely.
Collision code:
public static bool CollidesRight(Level level, Vector2 position, Vector2 motion, int width, int height)
{
Vector2 result = position + motion;
int x1 = (int)(result.X / (float)Tile.TILE_WIDTH);
int x2 = (int)((result.X + (float)width) / (float)Tile.TILE_WIDTH) + 1;
int y1 = (int)(result.Y / (float)Tile.TILE_HEIGHT);
int y2 = (int)((result.Y + (float)height) / (float)Tile.TILE_HEIGHT);
AABB resultBB = new AABB(result, width, height);
for (int i = x1; i < x2; i++)
{
for (int j = y1; j < y2; j++)
{
if (level.GetTileAt(i, j) != 0 && (Tile.TileList[level.GetTileAt(i, j)].IsSolid() || Tile.TileList[level.GetTileAt(i, j)].HasSolidTop()))
{
AABB tile = new AABB(new Vector2(i * 64f, j * 64f), 64, 64);
Console.WriteLine(tile);
return resultBB.Intersects(tile);
}
}
}
return false;
}
Here's my entity's updating code:
public virtual void Tick()
{
lastMotion = motion;
lastPosition = position;
if (Collision.CollidesBottom(level, position, motion, width, height))
{
onGround = true;
}
else if(!Collision.CollidesBottom(level, position, motion, width, height))
{
onGround = false;
}
if (onGround && !isJumping)
{
motion.Y = 0f;
fallDistance = 0;
}
else if(!onGround)
{
fallDistance++;
motion.Y += gravity * (fallDistance * 0.005f);
}
if (isJumping)
{
timeJumping++;
if (timeJumping > 32)
{
timeJumping = 0;
isJumping = false;
}
else
{
motion.Y = -2.25f;
}
}
position.Y += motion.Y;
if (motion.X != 0)
{
if (motion.X < 0)
{
motion.X += friction;
}
if (motion.X > 0)
{
motion.X -= friction;
}
}
if ((motion.X > 0 && Collision.CollidesRight(level, position, motion, width, height)) || (motion.X < 0 && Collision.CollidesLeft(level, position, motion, width, height)))
{
motion.X = 0f;
}
position.X += motion.X;
//MoveEntity(motion);
}
I have 3 options for you.
First, Edit - nevermind about this one, i misread your code I think you should add your X motion before doing the collision check, since the collision check uses it to work. If you don't want to add it to the actual player motion, at least pass the potential motion into the function. That alone might fix everything. But there's another problem.
Second, and I'm pretty sure it's this one, I suspect that your position.Y is frequently truncated to an Int when your OnGround bool is set. Which means it's minimum will be a whole unit and the next unit up will be a whole unit. And in your AABB code from pastebin:
if (bb.max.Y <= min.Y || bb.min.Y >= max.Y)
{
return false;
}
This code will return false even if the X-coordinate related code detects a collision. You should modify the logic in here so that the function returns true if there is a collision, instead of returning false on two independent checks.
Third, assuming your width is 1 unit and your tiles are also 1 unit wide, with a block at x=2 and position.x = 0.5, and a very small xMotion (or one that hasn't been added in yet, see answer 1):
(occupying some of block 0 and some of block 1, like so) [.][ ][X]
You want to stop when you hit x = 1, because then you'll be up against your obstruction. But:
x1 = (int)(0.5) = 0
x2 = (int)(0.5 + 1) + 1 = 2
so your loop:
for (int i = x1; i < x2; i++)
checks block 0 (which is empty, then checks block 1 (which is empty) and then stops (because i == x2) and never checks block 2. So the block you care about never gets its collision check. I think if you change it to
for (int i = x1; i <= x2; i++)
then everything will work better at detecting an oncoming collision. You should probably do something similar for your future CollidesTop function.
P.S. Try these answers one at a time, of course. :)