OpenGL로 2D 플랫폼 게임만들기 - 3. 충돌체크2

장명훈·2023년 6월 7일
0

OpenGL 2D platform

목록 보기
3/4

저번에는 AABB로 했다면 이번엔 영역 침범시 밀어내는 힘을 만들어 충돌체크를 하는 방식을 만들어 보자.

원리는 다음과 같다.

  • 두 벡터의 교차점을 이용해서 교차점과 교차점 사이 벡터만큼 밀어내어 영역침범이 일어나지 않게 한다.
  • player충돌지점 ~ Ground충돌지점 거리 및 방향만큼 밀어내야한다는 것이 방법이다.

글로만 보면 이해가 안되니 그림으로 설명하겠다.

c1 : player 중심
c2 : ground 중심

c1c2 선분과 player의 둘레와의 교차점을 PCross(노란점)
c1c2 선분과 ground의 둘레와의 교차점을 GCross(파란점) 이라 하자.
검정색으로 색칠된 ground에 player가 침범하면 PCross -> GCross 벡터만큼 player의 중심에 더해 밀어내는 것이 방법이다.

먼저 player기준에서 Collider를 감지해서 밀어내는 방식을 생각해서 player 클래스에 Collide함수를 만들었다.

bool Player::Collide(Sprite& other)
{
	if ((Right >= other.Left)
		&& (Left <= other.Right)
		&& (Bottom >= other.Top)
		&& (Top <= other.Bottom))
	{
		return true;
	}
	return false; // 충돌 하지 않음.
}

교차점을 구하는 공식을 이용해 player와 ground의 중심끼리 이은 선과 player의 둘레, ground의 둘레와의 교차점을 구하는 함수를 만들었다.

bool Player::CollidebyVector(Sprite& other)
{
	// 두 선 
	float t = 0;
	float p = 0;
	float num1, den1;
	float num2, den2;

	Vector2D g1 = other.vLT;
	Vector2D g2 = other.vRT;
	Vector2D c1 = mPos;
	Vector2D c2 = other.mPos;
	Vector2D p1 = vLB;
	Vector2D p2 = vRB;
	Vector2D Gcrosspoint;
	Vector2D Pcrosspoint;

	num1 = ((c1.x - g1.x) * (c2.y - c1.y)) - ((c2.x - c1.x) * (c1.y - g1.y));
	den1 = ((g2.x - g1.x) * (c2.y - c1.y)) - ((c2.x - c1.x) * (g2.y - g1.y));
	 
	num2 = ((c1.x - p1.x) * (c2.y - c1.y)) - ((c2.x - c1.x) * (c1.y - p1.y));
	den2 = ((p2.x - p1.x) * (c2.y - c1.y)) - ((c2.x - c1.x) * (p2.y - p1.y));

	//std::cout << num1 << "  " << den1 << '\n';
	t = num1 / den1;
	p = num2 / den2;

	Gcrosspoint = g1 * (1 - t) + g2 * t;
	Pcrosspoint = p1 * (1 - p) + p2 * p;

	// 예상값 g1 : (250,250), g2 : (350,250)
	std::cout << "g1" << " : "; g1.print(); std::cout << '\n';
	std::cout << "g2" << " : "; g2.print(); std::cout << '\n';
	Gcrosspoint.print(); std::cout << '\n';
	Pcrosspoint.print(); std::cout << '\n';
	(Gcrosspoint - Pcrosspoint).print(); std::cout << '\n';

	// 방향 = ohter의 선분에 수직
	// 크기 = 침범한 점 ~ 충돌지점
	// 근데 침범했는지 안했는지 어캐 아나...
	
	if (OnGround)
	{
		// mPos.y -= Bottom - crosspoint.y;
		mPos += Gcrosspoint - Pcrosspoint;
		gravity = 0;
		// 방향 : (crosspoint - p4); 위로 방향
		// 크기 : (crosspoint - bottom)
		return true;
	}
	return false; // 충돌 하지 않음.
}

수많은 시행착오 끝에 화면 밖에 나가는 현상을 해결하고 땅에 붙는 코드를 만들었다.
특히 Ground의 glTranslatef가 player와 달라서 Top과 Bottom이 반대로 된 현상도 있었다.
원점이 좌측 상단에 있는 OpenGL좌표계로 설정하려면 다음과 같다.

glTranslatef(mPos.x, g_Extern.WINDOWSIZE_HEIGHT - mPos.y, 0);

그런데 Ground는 그렇지 않았다.

glTranslatef(mPos.x, mPos.y, 0);

그래서 바닥과 Top이 반대로 되었던거 같다. 해서 glTranslatef를 player처럼 바꿔주니 예상했던 좌표가 출력되었다.

g1 : (250, 250)      // ground의 좌측 상단
g2 : (350, 250)      // ground의 우측 상단
(300, 250)           // Gcrosspoint 
(300, 50)            // Pcrosspoint
(0, 200)             // Gcrosspoint - Pcrosspoint

해서 여러가지 수정한 코드는 다음과 같다.

bool Player::Collide(Sprite& other)
{
	// 축 검사해서 겹치면 
	// bottom > other.top (일반 좌표)
	// 실시간이라 변수로 계산 X
	if ((Right >= other.Left)
		&& (Left <= other.Right)
		&& (Bottom >= other.Top)
		&& (Top <= other.Bottom))
	{
		return true;
	}
	return false; // 충돌 하지 않음.
}

bool Player::CollidebyVector(Sprite& other)
{
	float t = 0;
	float p = 0;
	float num1, den1;
	float num2, den2;

	Vector2D g1 = other.vLT;
	Vector2D g2 = other.vRT;
	Vector2D c1 = mPos;
	Vector2D c2 = other.mPos;
	Vector2D p1 = vLB;
	Vector2D p2 = vRB;
	Vector2D Gcrosspoint;
	Vector2D Pcrosspoint;

	num1 = ((c1.x - g1.x) * (c2.y - c1.y)) - ((c2.x - c1.x) * (c1.y - g1.y));
	den1 = ((g2.x - g1.x) * (c2.y - c1.y)) - ((c2.x - c1.x) * (g2.y - g1.y));
	 
	num2 = ((c1.x - p1.x) * (c2.y - c1.y)) - ((c2.x - c1.x) * (c1.y - p1.y));
	den2 = ((p2.x - p1.x) * (c2.y - c1.y)) - ((c2.x - c1.x) * (p2.y - p1.y));

	t = num1 / den1;
	p = num2 / den2;

	Gcrosspoint = g1 * (1 - t) + g2 * t;
	Pcrosspoint = p1 * (1 - p) + p2 * p;

	Gcrosspoint.print(); std::cout << '\n';
	Pcrosspoint.print(); std::cout << '\n';
	(Gcrosspoint - Pcrosspoint).print(); std::cout << '\n';

	
	if (OnGround)
	{
		mPos += Gcrosspoint - Pcrosspoint;
		return true;
	}
	return false; // 충돌 하지 않음.
}

이제 또다른 문제가 있다. 바로 x축방향으로 미끄러지는 현상이다. 아무래도 땅에 안착해 있는 건 x방향으로 영향을 받으면 안되는데 그림상에서 보면 검은화살표에는 x축방향의 벡터가 아주 근소하게 있다. 매프레임마다 이를 더해주게 된다면 player가 미끄러지듯이 ground옆으로 밀려나갈 것이다.

해서 y축 성분만 남겨두면 되지 않을까..? 생각하고 시도했더니 됐다.

mPos.y += (Gcrosspoint - Pcrosspoint).y;

0개의 댓글