그래픽스/OpenGL

2D 운석 피하기 게임 속 수학

뽀또치즈맛 2024. 10. 20. 19:41

 

운석에 6번 맞으면 플레이어 사망,

레이저와 플레이어가 운석이 직접 충돌하는 것으로 운석 부술 수 있음

 

벡터와 물리

 

벡터는 방향 + 스칼라입니다.

 

벡터는 방향을 나타낼 수 있으므로 게임에서는 오브젝트의 방향을 표현하고자

벡터를 종종 사용합니다.

 

오브젝트의 전방 벡터는 오브젝트가 나아가는 직선 방향을 나타내는 벡터입니다.

예를 들어 x축을 따라 쭉 이동하는 오브젝트는 전방 벡터 <1,0>을 가집니다.

 

개발자는 게임상에서 다양한 벡터 연산을 수행하여 기능 구현을 합니다.

따라서 일반적으로 게임 프로그래머는 이런 다양한 벡터 연산을 수행하기 위해 라이브러리를 사용합니다.

이로인해 벡터 관련 방정식을 기억하는 것 보다는,

어떤 벡터 연산으로 어떤 문제를 해결할 수 있는지를 아는 것이 더 좋습니다.

 

 

2D 좌표 평면에서는 x,y축을 이용하기 때문에

Vector2 구조체를 이용하여 좌표값을 설정합니다.

 

벡터의 크기는 절대값으로 나오며,

크기를 구하는 방식은 유클리드 거리 공식과 비슷해보입니다.

또한 유클리드 거리 공식이 매우 간소화된 것처럼 보이기도 합니다.

 

 

 

이 길이 공식에서는 제곱근을 계산하면

상대적으로 계산 비용이 크게 들게 됩니다.

 

반드시 길이를 알아야 한다면 이 제곱근을 피할 방법은 없지만,

 

특정한 상황에서는 굳이 제곱근 계산을 하지 않을 수 있습니다.

예를 들어,

단순히 상대적 비교만을 필요로 하는 경우에는 길이 대신에

길이의 제곱값을 사용하면 됩니다.

 

이러한 경우 Length()멤버 함수를 호출해서 두 벡터의 길이를 비교하는 것이

논리적으로 각 벡터의 길이 제곱값과 비교하는 것과 똑같습니다.

 

단위 벡터의 정규화

 

단위 벡터는 1의 길이를 가진 벡터입니다.

정규화를 거치면 단위 벡터가 아닌 벡터를

단위 벡터로 변환하는 것이 가능합니다.

벡터를 정규화하려면 벡터의 각 요소를 벡터의 길이로 나누면 됩니다.

 

어떤 경우에는 단위 벡터를 사용하면 계산이 단순해질 수 있습니다.

하지만, 벡터를 정규화하는 것은 벡터 원래의 크기 정보를 잃는 결과를 초래합니다.

그렇기 때문에 벡터를 정규화할 때는 항상 주의를 기울여야 합니다.

 

좋은 습관은 오직 방향만 필요로 할 경우에 정규화하는 것입니다.

화살표가 가리키는 방향이나 엑터의 전방 벡터가 좋은 예에 해당합니다.

하지만 오브젝트 간 거리를 보여주는 레이더같이 거리 또한 중요한 벡터에

정규화를 수행하면 이런 정보를 잃게 될 것입니다.

 

일반적으로 물체가 어느 방향으로 향하고 있는지를 나타내는 전방 벡터나

어느 방향이 위쪽인지를 나타내는 상향 벡터를 정규화합니다.

 

그러나 다른 벡터들의 정규화는 원치 않는 결과를 낳을 수 있습니다.

예를 들어 중력 벡터를 정규화해버리면 중력의 크기를 잃어버리게 됩니다.

 

 

각도로부터 전방 벡터 변환

 

 

이러한 각도를 세타로 표현한 단위 원의 방정식은 다음과 같습니다.

따라서 x = cosθ y = sinθ 가 됩니다.

 

이 방정식은 액터의 각도를 전방 벡터로 변환하는 데 바로 사용할 수 있습니다.

 

// 함수 뒤에 const는 읽기 전용 함수라는 뜻이며,
// Get은 주로 private 멤버의 값을 읽어올 함수를 만들어 줄 때 씁니다.
Vector2 GetForward() const { return Vector2(Math::Cos(mRotation), -Math::Sin(mRotation)); }

 

 

전방 벡터를 각도로 변환 : 아크탄젠트

 

전방 벡터가 주어졌을 때 이 전방 벡터를 각도로 변환하려 합니다.

탄젠트 함수가 각도를 인자로 받고 삼각형의 밑변과 높이의 비율값을 반환하는 것을

떠올리면 접근할 수 있습니다.

 

이제 액터의 새로운 전방 벡터로부터 회전 멤버 변수에

해당하는 각도를 구한다고 가정해봅니다.

새로운 전방 벡터 v와 x축으로 직각 삼각형을 구성하면 된다.

이 삼강형에서 전방 벡터의 x 요소는 삼격형의 밑변의 길이다.

그리고 전방 벡터의 y 요소는 삼각형 높이입니다.

이 요소들의 비율값을 이용하면 아크탄젠트 함수를 사용해서

각도 세타를 계산하는 것이 가능합니다.

 

프로그래밍에서 선호되는 아크탄젠트 함수는 atan2 함수입니다.

이 함수는 파라미터로 2개의 인자를 받아오고,(그 인자는 각각 높이와 밑변의 길이)

그리고 [-π,π] 범위의 각도를 리턴합니다.

 

양의 각도는 삼각형이 1사분면이나 2사분면(양의 y값)에 있고,

음의 각도는 삼격형이 3사분면이나 4사분면에 있다는 것을 뜻합니다.

 

예를 들어 우주선이 운석을 바라본다고 가정하면,

먼저 우주선에서 운석으로 향하는 벡터를 만들고 이 벡터를 정규화 합니다.

다음으로 atan2 함수를 사용해서 새로운 전방 벡터를 각도로 변환한다.

마지막으로 우주선 액터의 회전값을 이 새로운 각도로 설정하면 됩니다.

 

 

두 벡터 사이의 각도 구하기 : 내적

 

두 벡터 사이의 내적 결과는 단일 스칼라 값입니다.

게임에서 내적을 사용하는 가장 일반적인 경우는 두 벡터 사이의 각도를 찾는 것입니다.

 

내적은 각의 코사인과 관계가 있습니다.

두 벡터의 내적 식은 다음과 같습니다.

코사인 법칙을 토대로 한 것이기 때문에 이를 이용하여 세타를 구할 수 있습니다.

 

θ = arccos(a의 단위벡터, b의 단위벡터)

 

단위 벡터를 사용했으므로 식이 단순해졌습니다.

방향만 중요하다면 미리 벡터를 정규화해두는 것이 좋습니다.

 

즉, 두 벡터 사이의 각도를 계산할 시에는

내적에서 발생할 수 있는 몇 가지 특별한 경우를 기억해두면 좋습니다.

두 단위 벡터 사이의 내적이 0이면

두 벡터가 수직한다는 것을 뜻합니다.

 

각도를 계산하기 위해 내적을 사용할 경우,

한가지 결점은 아크코사인의 범위입니다.

아크코사인은 두 벡터 사이의 최소 범위에서 각도를 반환한다는 것입니다.

이 때문에 아크코사인은 두 벡터 사이의 최소 회전값을 주지만

이 회전값이 시계 방향인지 아니면 반시계 방향인지는 알 수 없습니다.

 

법선 벡터 계산하기 : 외적

법선 벡터는 표면에 수직한 벡터다.

표면의 법선 벡터를 계산하면 3D 게임에서 매우 도움이 됩니다.

예를 들어 3D 그래픽스에서 설명하는 광원 모델은 법선 벡터의 계산이 필요합니다.

 

평행하지 않은 2개의 3D 벡터가 주어지면 두 벡터를 포함하는 평면은 반드시 존재합니다.

이러한 것을 그 평면에 수직한 벡터를 구한다고 합니다.

 

외적은 2D 벡터에서 동작하지 않습니다.

그러나 2D 벡터를 3D벡터로 변환하면 사용할 수 있습니다.

2D벡터를 3D벡터로 변환하려먼 z요소값 0을 2D벡터에 추가하면 됩니다.

 

OpenGL은 오른손 좌표계를 씁니다.

외적을 계산한 결과 벡터의 값 자체

좌표계가 왼손 좌표계인지, 오른손 좌표계인지에 따라 방향이 달라집니다.

외적의 크기(길이)는 동일하지만, 방향이 반대인 벡터가 나옵니다.

 

물체의 가속도 선형 역학 개요

 

질향은 물체에 내포된 물질의 양을 나타내는 스칼라입니다.

무게와 질량을 혼동하기 쉽지만 질량은 중력과 관계없는 독립적인 값이며,

반면 무게는 중력과 관계가 있습니다.

물체의 질량이 크면 클수록 물체의 운동을 변경하는 것이 어려워집니다.

물체에 힘이 충분히 주어지면 물체가 움직이기 시작합니다.

이는 뉴턴의 제2운동법칙이 이러한 개념을 내포합니다.

 

 

 

  • : 물체에 작용하는 (Force, 단위: 뉴턴 N\text{N})
  • mm: 물체의 질량 (Mass, 단위: 킬로그램 kg\text{kg})
  • aa: 물체의 가속도 (Acceleration, 단위: m/s2\text{m/s}^2)

가속도란 물체의 변화율을 말합니다.

 

  1. 힘과 가속도의 관계:
    물체에 작용하는 힘이 클수록 물체의 가속도도 커집니다.
    이 말은 힘이 가해질 때 물체의 질량이 일정하다면,
    더 큰 힘은 더 큰 가속도를 낳는다는 것을 의미합니다.

  2. 질량과 가속도의 반비례 관계:
    같은 힘이 작용하더라도 물체의 질량이 클수록 가속도는 작아집니다.
    따라서 무거운 물체는 같은 힘을 받을 때 가벼운 물체보다 가속도가 작습니다.

 

오일러 적분을 사용한 위치 계산

 

수치 적분을 통해서 게임은 가속도를 기반으로 속도를 갱신한 다음,

속도를 기반으로 위치를 갱신합니다.

 

그러나 물체의 가속도를 계산하기 위해

게임은 물체에 적용된 힘뿐만 아니라 물체의 질량도 알 필요가 있습니다.

 

예를 들어 캐릭터가 점프할 때는 반발력으로 인해

플레이어는 땅에서 벗어날 수 있습니다.

그러나 캐릭터는 최종적으로 땅으로 돌아오게 될 것입니다.

 

왜냐하면 일정한 힘의 크기를 지닌 중력 때문입니다.

다양한 힘이 물체에 동시에 작용하며 모든 힘은 벡터이므로

모든 힘을 더하면 해당 프레임마다 물체에 적용될 전체 힘을 구할 수 있습니다.

힘들의 총합을 질량으로 나눈면 가속도를 얻습니다.

 

acceleration = sumOfForces / mass;

 

 

속도와 위치를 계산하기 위해 수치 적분인 오일러 적분을 사용하자.

 

// 오일러 적분
// 속도 갱신
velocity += acceleration * deltaTime;
// 위치 갱신
position += velocity * deltaTime;

 

이 계산에서는 힘, 가속도, 속도, 위치가 모두 벡터로 표시됩니다.

이 계산은 델타 시간에 의존하므로

물리를 시뮬레이션 하는 컴포넌트의 Update()함수 내부에 넣어줍니다.

 

가변 시간 단계 문제

 

물리 시뮬레이션에 의존하는 게임의 경우 가변 프레임 시간은 문제의 소지가 됩니다.

수치 적분의 정확도가 시간 단계의 크기에 의존하기 때문입니다.

시간 단계가 작을수록 근사값은 더욱더 정확합니다.

 

또한, 프레임 레이트가 프레임마다 다를 경우,

수치 적분의 정확도가 달라집니다.

정확도의 변화는 게임 동작에 매우 큰 영향을 미칩니다.

 

프레임 레이트가 낮으면 낮을수록 오차가 커집니다,

이는 왜곡된 점프 곡선을 초래할 수 있습니다.

 

이런 이유로 물체의 위치를 계산하는 물리를 사용하는 게임에서는

가변 프레임 레이트를 사용하지 않아야합니다.

적어도 물리 시뮬레이션 코드에서는 말입니다.

 

기본 충돌 감지

 

 

이 교차 테스트를 위해

abs( A의 센터 - B 센터 ) - A반지름 + B반지름 <= 0 인 식을 구하면 된다.