ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Direct2D] 충돌 및 지형 구현
    C++/미분류 2024. 5. 31. 17:35

    지난 글에서 등속, 등가속 운동을 적용하여 중력을 구현했습니다.

    [Direct2D] 등속 운동, 등가속 운동, 중력 구현

    위 글에 따라 중력이 적용된 객체는 바닥이 없어 계속 추락하는 상태가 지속되었습니다.

    이번 글에서는 간단한 지형과 충돌을 구현하여 이미지가 착지할 수 있도록 해보겠습니다.

     

    이번 글은 지난 글에서 코드가 이어집니다.

     


     

    1. 충돌 판정

     

    더보기

    이번 글에서 사용될 "충돌"은 두 기하(이미지)가 겹치는 경우를 의미합니다.

     

    우선 D2D에서 사용되는 사각형은 D2D1_RECT_F 자료형을 사용하며, 형태는 다음과 같습니다.

    사각형의 상, 하, 좌, 우 모서리의 좌표를 사용합니다.

     

    사각형 A와 B가 있다고 가정할 때, 두 사각형의 충돌 여부는 다음과 같이 판정할 수 있습니다.

    A의 오른쪽 모서리가 B의 왼쪽 모서리보다 오른쪽에 있는가? ( = 큰가?)
    A의 왼쪽 모서리가 B의 오른쪽 모서리보다 왼쪽에 있는가? ( = 작은가?)
    A의 하단이 B의 상단보다 낮게 있는가? ( = 큰가?)
    A의 상단이 B의 하단보다 높게 있는가? ( = 작은가?)

    괄호 내부의 대, 소 여부는 Direct2D의 좌표계 (오른쪽이 +X, 아래쪽이 +Y)를 따르는 표현입니다.

    두 사각형이 충돌하는 것과 위 4가지 조건은 서로 동일합니다.

     

    몇 가지 예시를 살펴보도록 하겠습니다.

    이 경우는 사각형의 Y축은 겹쳤지만, X축은 겹치지 않은 경우입니다.

    각각의 조건문은 다음과 같이 성립합니다.

    A의 오른쪽 모서리가 B의 왼쪽 모서리보다 오른쪽에 있는가? ( = 큰가?) (X)
    A의 왼쪽 모서리가 B의 오른쪽 모서리보다 왼쪽에 있는가? ( = 작은가?) (X)
    A의 하단이 B의 상단보다 낮게 있는가? ( = 큰가?) (O)
    A의 상단이 B의 하단보다 높게 있는가? ( = 작은가?) (O)

     

    반대의 경우입니다.

    이 경우는 X축은 겹치게 되었지만 Y축은 그렇지 않은 경우입니다.

    A의 오른쪽 모서리가 B의 왼쪽 모서리보다 오른쪽에 있는가? ( = 큰가?) (O)
    A의 왼쪽 모서리가 B의 오른쪽 모서리보다 왼쪽에 있는가? ( = 작은가?) (O)
    A의 하단이 B의 상단보다 낮게 있는가? ( = 큰가?) (X)
    A의 상단이 B의 하단보다 높게 있는가? ( = 작은가?) (X)

     

    다음과 같이 두 사각형이 완전히 겹쳤을 경우에만 모든 조건이 참이 됩니다.

    A의 오른쪽 모서리가 B의 왼쪽 모서리보다 오른쪽에 있는가? ( = 큰가?) (O)
    A의 왼쪽 모서리가 B의 오른쪽 모서리보다 왼쪽에 있는가? ( = 작은가?) (O)
    A의 하단이 B의 상단보다 낮게 있는가? ( = 큰가?) (O)
    A의 상단이 B의 하단보다 높게 있는가? ( = 작은가?) (O)

     

    위 조건을 코드로 구현할 경우 다음과 같이 함수를 작성할 수 있습니다.

    bool IsCollision(D2D1_RECT_F a, D2D1_RECT_F b) {
        if (a.right >= b.left &&
            a.left <= b.right &&
            a.bottom >= b.top &&
            a.top <= b.bottom)
            return true;
        return false;
    }

    위 함수는 충돌 시 물리량에 대해서는 고려하고 있지 않습니다.

    두 물체의 충돌에 의해 일어나는 물리현상은 다른 글에서 다룰 수 있도록 하겠습니다.

     

    2. 지형 구현

     

    더보기

    게임에서 지형이라는 용어는 여러가지 의미로 사용됩니다.

    이번 글에서 지형은 게임 내 오브젝트가 딛을 수 있는 바닥, 플랫폼을 의미합니다.

     

    이전 문단에서 구현한 충돌 판정을 사용하기 위해, 지형도 D2D1_RECT_F자료형을 사용하겠습니다.

    이번 글에서는 다음과 같이 플랫폼 2개를 만들었습니다.

    class MyApp {
    ...
    private:
        ...
        std::vector<D2D1_RECT_F> ground = {
            D2D1::RectF(0.0f, 400.f, 640.f, 480.f),
            D2D1::RectF(200.0f, 350.f, 440.f, 410.f)
        };
    };

     위 클래스 변수를 렌더링 함수에서 렌더링합니다.

    HRESULT MyApp::OnRender() {
        ...
        for (auto& i : ground) {
            myDirect2dContext->FillRectangle(&i, myCornflowerBlueBrush.Get());
        }
        ...
    }

    프로그램을 실행하면 다음과 같이 파란색 플랫폼 2개를 확인할 수 있습니다.

    하지만 아직 충돌 관련 코드가 적용되지 않았기 때문에, 떨어지는 플레이어 캐릭터는 플랫폼을 딛지 못하는 것을 볼 수 있습니다.

     

    3. MyBitmap 수정

     

    더보기

    충돌 판정을 하는 함수는 두 개의 D2D1_RECT_F를 받습니다.

    하지만 이번 글에서 충돌 판정을 할 객체 중 플레이어 캐릭터는 Bitmap객체입니다.

    따라서, Bitmap객체의 Rect를 구하는 함수를 추가로 작성해야 합니다.

     

    우선, 헤더에 현재 프레임의 Rect를 구하는 함수를 추가합니다.

    class MyBitmap {
    public:
        D2D1_RECT_F GetBitmapRect();
        ...
    };

    그리고, Bitmap의 프레임별 Rect를 저장하는 변수를 추가합니다.

    class MyBitmap {
    private:
        std::vector<D2D1_RECT_F> bitmapRect;
        ...
    }

    Initialize함수에 BitmapRect를 초기화하는 코드를 추가합니다.

    void MyBitmap::Initialize(...) {
        ...
        for (auto& i : bitmap) {
            ...
            auto tmp = i->GetSize();
            bitmapRect.push_back(D2D1::RectF(0, 0, tmp.width, tmp.height));
    	}
        ...
    }

    GetBitmapRect함수는 초기화된 Rect의 크기와 현재 위치를 이용해서 이미지의 Rect를 반환합니다.

    D2D1_RECT_F MyBitmap::GetBitmapRect() {
        if (bitmapRect.size() > currentFrame && bitmap.size() > currentFrame) {
            auto rect = bitmapRect[currentFrame];
            auto size = bitmap[currentFrame]->GetPixelSize();
            rect.left += currentPosition.x - size.width / 2;
            rect.right += currentPosition.x - size.width / 2;
            rect.top += currentPosition.y - size.height;
            rect.bottom += currentPosition.y - size.height;
            return rect;
        } else {
            return D2D1::RectF();
        }
    }

    이것으로, MyBitmap의 GetBitmapRect함수를 호출하면 현재 이미지의 Rect를 알 수 있게 되었습니다.

     

    4. Update함수 수정

     

    더보기

    Update함수는 현재 틱 (프레임)과 관련된 연산을 수행합니다.

    기존 코드는 각 이미지의 Tick함수 호출 및 플레이어 캐릭터에 중력을 적용하는 코드가 있었습니다.

     

    이제 플레이어 캐릭터가 지형과 충돌하면 멈출 수 있는 코드를 추가하도록 하겠습니다.

    void MyApp::Update() {
        ...
        D2D1_RECT_F t = myCharacterBitmap->GetBitmapRect();
        for (auto& i : ground) {
            if (IsCollision(t, i)) {
                downSpeed = 0;
                deltaDown = 0;
            }
        }
        ...
    }

    Bitmap의 Rect를 가져온 후, 각각의 지형에 대해 충돌 연산을 수행하는 코드입니다.

    충돌했다고 판정될 경우, 해당 프레임에서의 낙하 속도 및 변위를 0으로 만들어줍니다. 

     


     

    이전 글에서 중력을 적용할 때, 바닥을 만들어주지 않아 캐릭터가 계속 추락했었습니다.

    이번 글에서는 간단한 충돌을 구현하고, 바닥을 만들어 캐릭터를 착지할 수 있게 만들었습니다.

    이번 글에서 사용된 코드의 전문은 아래 Github에서 확인하실 수 있습니다.

     

    GitHub - ruru14/WinapiStudy: WinAPI및 DirectX를 공부하며, 간단한 운동 법칙을 적용해봅니다.

    WinAPI및 DirectX를 공부하며, 간단한 운동 법칙을 적용해봅니다. Contribute to ruru14/WinapiStudy development by creating an account on GitHub.

    github.com

     

    이번 글이 도움이 되셨길 바랍니다.

    감사합니다.

     

     

    댓글

Designed by Tistory.