Where am I going wrong with this collision code?

Started by
13 comments, last by fastcall22 2 years, 5 months ago

So I am a little confused with my code, I have got left and right collision to work but when it comes to colliding with the top and bottom, the player is thrown to the side of the enemy instead of being pushed back (please see the gif). Any help would be greatly appreciated.

Here is the code that I am working with currently:

void Player::CollisionChecker(Entity* otherEntity)
{
	sf::FloatRect entityCol = otherEntity->collisionComponent()->getGlobalBounds();
	sf::FloatRect playerCol = collisionComponent()->getGlobalBounds();

	printf("%f, %f\n", oldBounds.width, oldBounds.left);

	if (playerCol.top + playerCol.height < entityCol.top ||
		playerCol.top > entityCol.top + entityCol.height ||
		playerCol.left + playerCol.width < entityCol.left ||
		playerCol.left > entityCol.left + entityCol.width)
	return;

	if (playerCol.left + playerCol.width >= entityCol.left && GetPosition().x < entityCol.left)
	{
		SetPosition(entityCol.left - entityCol.width - 0.1f, GetPosition().y);
		_velocity.x = 0.f;
	}
	else if (playerCol.left <= entityCol.left + entityCol.width && GetPosition().x > entityCol.width)
	{
		SetPosition(entityCol.left + entityCol.width + 0.1f, GetPosition().y);
		_velocity.x = 0.f;
	}
	else if (playerCol.top + playerCol.height >= entityCol.top && GetPosition().y < entityCol.top)
	{
		SetPosition(GetPosition().x, entityCol.top - entityCol.height - 0.1f);
		_velocity.y = 0.f;
	}
	else if (playerCol.top <= entityCol.top + entityCol.height && GetPosition().y > entityCol.height)
	{
		SetPosition(GetPosition().x, entityCol.top + entityCol.height + 0.1f);
		_velocity.y = 0.f;
	}
}
Advertisement

A wild guess: If it doesn't work on the Y-Axis but does work on the X-Axis, why do you believe that the mistake happens in the collision detection code rather than SetPosition?

Also, you shouldn't use magic numbers (like 0.1f) and why are you calling GetPosition when you have a rect already acquired on the top of the function?

Elit3d said:
but when it comes to colliding with the top and bottom, the player is thrown to the side of the enemy instead of being pushed back

It's because your code applies position correction in fixed order. If there is a vertical collision, there is a horizontal collision too, so the horizontal collision is resolved first and after that there is no more vertical collision.

A better way would be to calculate overlap and it's center, then displacement directions from object center to overlap center (blue lines).
Then you could store a mass for each object, or you calculate area of the boxes and use that for mass.
Use the masses to decide which object should move more. E.g. if red had mass of 2 and green had mass of 1, the red would resolve 1/(2+1) = 33% of the collision, the green would resolve 2/(2+1) = 67 %. (while drawing i assumed both have the same mass)
Then you displace them along their blue lines so they touch but don't overlap.

That's just a proposal i came up with right now. It's not physical as it does not cause the objects to rotate, but likely you don't want rotations.

Another way, simpler and maybe more like what you want:

If the absolute horizontal difference between the centers is smaller than the absolute vertical difference, use horizontal separation, otherwise vertical.

JoeJ said:

Elit3d said:
but when it comes to colliding with the top and bottom, the player is thrown to the side of the enemy instead of being pushed back

It's because your code applies position correction in fixed order. If there is a vertical collision, there is a horizontal collision too, so the horizontal collision is resolved first and after that there is no more vertical collision.

A better way would be to calculate overlap and it's center, then displacement directions from object center to overlap center (blue lines).
Then you could store a mass for each object, or you calculate area of the boxes and use that for mass.
Use the masses to decide which object should move more. E.g. if red had mass of 2 and green had mass of 1, the red would resolve 1/(2+1) = 33% of the collision, the green would resolve 2/(2+1) = 67 %. (while drawing i assumed both have the same mass)
Then you displace them along their blue lines so they touch but don't overlap.

That's just a proposal i came up with right now. It's not physical as it does not cause the objects to rotate, but likely you don't want rotations.

I don't really like to ask this, but could you give me a brief example of this please?

I tried to get the best of both worlds. Seems to work fairly well, but did not do much testing.

In case the contact is close to the corner, it does a diagonal resolve:

In case the whole height of the overlap fits into one box, it does strict horizontal resolve (same for the vertical case):

Otherwise some diagonal resolve fades in:

Feels pretty good. I would expect this behavior from something like auto positioning windows in a GUI.
And i hope it also works for rotation less game physics. But this depends. You can turn off the diagonal stuff with preventDiscontinuousSolution = 0.
Advantage of the diagonal stuff is: As objects move smoothly, solution changes smoothly as well and there is no discontinuity from switching between horizontal and vertical resolve, which could cause issues.
To make one object static, use masses 0 and 1.

		static bool visAABoxColl = 1; ImGui::Checkbox("visAABoxColl", &visAABoxColl);
		{
			static vec2 pos0 (-0.5f, 0); ImGui::DragFloat2("pos0", (float*)&pos0, 0.01f);
			static vec2 size0 (1, 1.5f); ImGui::DragFloat2("size0", (float*)&size0, 0.01f);

			static vec2 pos1 (0.3f, 1.5f); ImGui::DragFloat2("pos1", (float*)&pos1, 0.01f);
			static vec2 size1 (1, 2); ImGui::DragFloat2("size1", (float*)&size1, 0.01f);

			static bool preventDiscontinuousSolution = 1; ImGui::Checkbox("preventDiscontinuousSolution", &preventDiscontinuousSolution);


			struct AABox2
			{
				vec2 minmax[2];

				AABox2 () {}

				AABox2 (const vec2 &center, const vec2 &size)
				{
					minmax[0] = center - size*.5f;
					minmax[1] = center + size*.5f;
				}

				AABox2  (const AABox2 &a, const AABox2 &b) // intersection
				{
					minmax[0] = vec2(	max(a.minmax[0][0], b.minmax[0][0]), 
										max(a.minmax[0][1], b.minmax[0][1]) );
					minmax[1] = vec2(	min(a.minmax[1][0], b.minmax[1][0]), 
										min(a.minmax[1][1], b.minmax[1][1]) );
				}

				vec2 Size () const
				{
					return minmax[1] - minmax[0];
				}

				float Area ()
				{
					vec2 d = Size();
					return d[0] * d[1];
				}

				vec2 Center () const
				{
					return (minmax[0] + minmax[1]) * 0.5f;
				}

				void Translate (const vec2 &t)
				{
					minmax[0] += t;
					minmax[1] += t;
				}



				void Vis (float r, float g, float b)
				{
					vec2 temp0 (minmax[0][0], minmax[1][1]);
					vec2 temp1 (minmax[1][0], minmax[0][1]);
					RenderLine (minmax[0], temp0, r,g,b);
					RenderLine (temp0, minmax[1], r,g,b);
					RenderLine (minmax[1], temp1, r,g,b);
					RenderLine (temp1, minmax[0], r,g,b);
					RenderPoint (Center(), r,g,b);
				}
			};

			AABox2 box0 (pos0, size0);
			AABox2 box1 (pos1, size1);
			AABox2 boxI (box0, box1);

			float overlapArea = boxI.Area();

			box0.Vis(.5f,0,0);
			box1.Vis(0,.5f,0);

			if (overlapArea > 0) // intersecting
			{
				boxI.Vis(1,1,0);

				vec2 overlapSize = boxI.Size();
				int resolveDim = overlapSize[0] > overlapSize[1];

				float penetration = overlapSize[resolveDim];
				vec2 resolve (0);

				resolve[resolveDim] = penetration;
				if (box0.Center()[resolveDim] < box1.Center()[resolveDim])
					resolve[resolveDim] *= -1.f;

				if (preventDiscontinuousSolution)
				{
					float o0 = overlapSize[!resolveDim];
					float o1 = overlapSize[resolveDim];
					float m = min(box0.Size()[!resolveDim], box1.Size()[!resolveDim]);
					float k = 1.f - (o0 - o1) / (m - o1);
					k *= o1/o0;

					resolve[!resolveDim] = k * overlapSize[!resolveDim];
					if (box0.Center()[!resolveDim] < box1.Center()[!resolveDim])
						resolve[!resolveDim] *= -1.f;
				}
				
				float mass0 = box0.Area();
				float mass1 = box1.Area();
				float massSum = mass0 + mass1;
				float f0 = mass1 / massSum;
				float f1 = mass0 / massSum;

				AABox2 box0Res = box0; 
				box0Res.Translate(resolve * f0);
				AABox2 box1Res = box1; 
				box1Res.Translate(resolve * -f1);

				box0Res.Vis(1,0,0);
				box1Res.Vis(0,1,0);
			}
		}

I have some collision code. It detects faces which should help with pixel perfect collision code. I pulled it from my own game engine.

      var results = {
        left: false,
        top: false,
        right: false,
        bottom: false,
        center: false,
        left_corner: false,
        right_corner: false,
        x: 0,
        y: 0
      };
      var hmap_width = sprite.hit_map.right - sprite.hit_map.left + 1;
      var hmap_height = sprite.hit_map.bottom - sprite.hit_map.top + 1;
      var delta_x = Math.floor(hmap_width * (sprite.cdelta_x / 100));
      var delta_y = Math.floor(hmap_height * (sprite.cdelta_y / 100));
      // Create 12 collision points. The middle collision point is important.
      var t1 = { x: sprite.hit_map.left + delta_x, y: sprite.hit_map.top };
      var t2 = { x: sprite.hit_map.right - delta_x, y: sprite.hit_map.top };
      var tc = { x: sprite.hit_map.left + Math.floor(hmap_width / 2), y: sprite.hit_map.top };
      var l1 = { x: sprite.hit_map.left, y: sprite.hit_map.top + delta_y };
      var l2 = { x: sprite.hit_map.left, y: sprite.hit_map.bottom - delta_y };
      var lc = { x: sprite.hit_map.left, y: sprite.hit_map.top + Math.floor(hmap_height / 2) };
      var r1 = { x: sprite.hit_map.right, y: sprite.hit_map.top + delta_y };
      var r2 = { x: sprite.hit_map.right, y: sprite.hit_map.bottom - delta_y };
      var rc = { x: sprite.hit_map.right, y: sprite.hit_map.top + Math.floor(hmap_height / 2) };
      var b1 = { x: sprite.hit_map.left + delta_x, y: sprite.hit_map.bottom };
      var b2 = { x: sprite.hit_map.right - delta_x, y: sprite.hit_map.bottom };
      var bc = { x: sprite.hit_map.left + Math.floor(hmap_width / 2), y: sprite.hit_map.bottom };
      var bl = { x: sprite.hit_map.left, y: sprite.hit_map.bottom };
      var br = { x: sprite.hit_map.right, y: sprite.hit_map.bottom };
      // Determine which face was hit.
      if (self.Point_In_Box(t1, other.hit_map) || self.Point_In_Box(t2, other.hit_map) || self.Point_In_Box(tc, other.hit_map)) {
        results.top = true;
        results.center = self.Point_In_Box(tc, other.hit_map);
        results.y = other.y + Math.floor(other.height * other.size_y * other.scale);
      }
      if (self.Point_In_Box(l1, other.hit_map) || self.Point_In_Box(l2, other.hit_map) || self.Point_In_Box(lc, other.hit_map)) {
        results.left = true;
        results.center = self.Point_In_Box(lc, other.hit_map);
        results.x = other.x + Math.floor(other.width * other.size_x * other.scale);
      }
      if (self.Point_In_Box(r1, other.hit_map) || self.Point_In_Box(r2, other.hit_map) || self.Point_In_Box(rc, other.hit_map)) {
        results.right = true;
        results.center = self.Point_In_Box(rc, other.hit_map);
        results.x = other.x - Math.floor(sprite.width * sprite.size_x * sprite.scale);
      }
      if (self.Point_In_Box(b1, other.hit_map) || self.Point_In_Box(b2, other.hit_map) || self.Point_In_Box(bc, other.hit_map)) {
        results.bottom = true;
        results.center = self.Point_In_Box(bc, other.hit_map);
        results.y = other.y - Math.floor(sprite.height * sprite.size_y * sprite.scale);
        // Also detect bottom right and bottom left hit.
        results.left_corner = self.Point_In_Box(bl, other.hit_map);
        results.right_corner = self.Point_In_Box(br, other.hit_map);
      }
      return results;

Codeloader - Free games, stories, and articles!
If you stare at a computer for 5 minutes you might be a nerdneck!
https://www.codeloader.dev

I guess I have a question in terms of 2D programming but I kind of anticipate the answer already… Is it better to handle collision from the center of the sprite collision or from the edges of the sprite or the points/corners of the sprite?

The edges. I guarantee a good collision detection. I have an article on my site explaining the whole process and why it is better. The reason is that you can detect faces and know which one was it, for example, if you want some faces of the sprite to react differently to collisions.

Codeloader - Free games, stories, and articles!
If you stare at a computer for 5 minutes you might be a nerdneck!
https://www.codeloader.dev

Francois_Software said:

The edges. I guarantee a good collision detection. I have an article on my site explaining the whole process and why it is better. The reason is that you can detect faces and know which one was it, for example, if you want some faces of the sprite to react differently to collisions.

Awesome, I will check that out.

This topic is closed to new replies.

Advertisement