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

장명훈·2023년 6월 7일
0

OpenGL 2D platform

목록 보기
4/4
post-thumbnail

4가지 면 충돌체크를 만들 생각을 하다보니 콜라이더는 ground에 있어야 한다고 생각했다. ground가 player를 밀어내야하기 때문이다.
그리고 Collider는 sprite에만 있으면 안되고 자체적인 class가 따로 있어야 한다고 생각한다. 이건 나중에 생각하고 먼저 player에 있는 Collider함수는 비활성화하고 ground에 똑같이 Collider를 만들었다.

그리고 4개의 변을 체크해야 하므로 교차점 구하는 식이 4번 들어가야 한다. 따라서 이를 함수화하였다. 그리고 교차점 crosspoint는 어느 변인지에 따라 달라질 것이므로 내분점 계산에 들어갈 점들도 달라진다. 따라서 이를 매개변수로 받아 crosspoint를 계산하는 식으로 생각했다.

void Ground::cal(Vector2D v1, Vector2D v2, Vector2D v3, Vector2D v4, Vector2D& cross)
{
	float num, den;
	float t;

	num = ((v3.x - v1.x) * (v4.y - v3.y)) - ((v4.x - v3.x) * (v3.y - v1.y));
	den = ((v2.x - v1.x) * (v4.y - v3.y)) - ((v4.x - v3.x) * (v2.y - v1.y));
	t = num / den;
	cross = v1 * (1 - t) + v2 * t;
}

그런데 우리가 사용하는 수식은 t가 0~1사이일때만이다. 따라서 이 조건을 이용해 crosspoint를 계산하는 방법을 적용했다.

void Ground::cal(Vector2D v1, Vector2D v2, Vector2D v3, Vector2D v4, Vector2D& cross)
{
	float num, den;
	float t;

	num = ((v3.x - v1.x) * (v4.y - v3.y)) - ((v4.x - v3.x) * (v3.y - v1.y));
	den = ((v2.x - v1.x) * (v4.y - v3.y)) - ((v4.x - v3.x) * (v2.y - v1.y));
	t = num / den;
	if (t <= 1 && t > 0 )
	{
		std::cout << t;
		cross = v1 * (1 - t) + v2 * t;
	}
}

그런데 문제가 생겼다. 단순히 t의 범위로 하게 되면 player의 교차점은 2개로 생각하게 된다. 이는 벡터를 직선으로 생각하여 플레이어의 교차점 Ocrosspoint 를 윗변과의 교차점으로 생각하는 에러가 생긴다.

해서 교차여부를 확인하기 위해 외적을 이용하였다. 2차원 벡터의 외적값은 실수값으로 부호를 통해 교차여부를 확인할 수 있다.

//원점에서 벡터 b가 벡터 a의 반시계 방향이면 양수, 시계방향이면 음수, 평행이면 0을 반환 한다.
float Ground::ccw(Vector2D a, Vector2D b) {
	return a.cross(b);
}

//점 p를 기준으로 벡터 b가 벡터 a의 반시계 방향이면 양수, 시계방향이면 음수, 평행이면 0을 반환 한다.
float Ground::ccw(Vector2D p, Vector2D a, Vector2D b) {
	return ccw(a - p, b - p);
}

이렇게 4개 변과의 교차여부를 판단하여 하나의 crosspoint를 찾는 함수를 완성했다.

// 내분점 여부를 따지고 crosspoint 계산
void Ground::cal(Vector2D v1, Vector2D v2, Vector2D v3, Vector2D v4, Vector2D& cross)
{
	float num, den;
	float t;
	float ab = ccw(v1, v2, v3) * ccw(v1, v2, v4);
	float cd = ccw(v3, v4, v1) * ccw(v3, v4, v2);

	num = ((v3.x - v1.x) * (v4.y - v3.y)) - ((v4.x - v3.x) * (v3.y - v1.y));
	den = ((v2.x - v1.x) * (v4.y - v3.y)) - ((v4.x - v3.x) * (v2.y - v1.y));
	t = num / den;
	if (t <= 1 && t > 0 && ab <= 0 && cd <= 0)
	{
		std::cout << t;
		cross = v1 * (1 - t) + v2 * t;
	}
}

이를 호출하여 계산하는 CollidebyVector함수는 다음과 같다.

bool Ground::CollidebyVector(Sprite& other)
{
	OnCollide = Collide(other);

	Vector2D o1 = other.vLT;
	Vector2D o2 = other.vRT;
	Vector2D o3 = other.vRB;
	Vector2D o4 = other.vLB;

	Vector2D c1 = mPos;
	Vector2D c2 = other.mPos;

	Vector2D g1 = vLT;
	Vector2D g2 = vRT;
	Vector2D g3 = vRB;
	Vector2D g4 = vLB;

	Vector2D Ocrosspoint;
	Vector2D Gcrosspoint;
	Vector2D PushVector;

	// Ocrosspoint
	cal(o1, o2, c1, c2, Ocrosspoint);
	cal(o2, o3, c1, c2, Ocrosspoint);
	cal(o3, o4, c1, c2, Ocrosspoint);
	cal(o4, o1, c1, c2, Ocrosspoint);

	// Gcrosspoint
	cal(g1, g2, c1, c2, Gcrosspoint);
	cal(g2, g3, c1, c2, Gcrosspoint);
	cal(g3, g4, c1, c2, Gcrosspoint);
	cal(g4, g1, c1, c2, Gcrosspoint);

	PushVector = Gcrosspoint - Ocrosspoint;
	
	Gcrosspoint.print(); std::cout << '\n';
	Ocrosspoint.print(); std::cout << '\n';
	PushVector.print(); std::cout << '\n';

	if (OnCollide)
	{
    	other.mPos += PushVector;
		return true;
	}
	return false; // 충돌 하지 않음.
}

이제 잘 안착은 하는데 저번처럼 미끄러지는 현상을 해결해야 한다. 즉, 밀어내는 벡터의 '크기'는 해결했지만 '방향'이 문제이다. 부딪히는 면의 법선벡터로 힘이 적용되어야 하는데 여러 경우의 수를 그려보고 생각한 결과, PushVector 벡터의 x,y 성분 중 크기가 더 큰 성분이 법선벡터가 되면 될 거 같다. 이는 그냥 c1→c2 로 판단해도 될거 같다.

if (OnCollide)
{
	if(fabs(PushVector.x) > fabs(PushVector.y))
		other.mPos.x += PushVector.x;
	else
		other.mPos.y += PushVector.y;
}

해결!!

※ 만약 회전이 들어가게 된다면 이 방법은 쓰면 안된다. 제일 좋은 방법은 부딪히는 면의 법선벡터 방향이다.
이는 고민중...

참고자료
https://bowbowbow.tistory.com/17

0개의 댓글