ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Direct2D] 비트맵 이미지 움직이기 (1)
    C++/미분류 2024. 3. 10. 15:59

    지난 글에서 DirectX와 WIP로 이미지를 출력하는 방법을 알아보았습니다.

    [Direct2D] 비트맵 이미지 그리기
    [Direct2D] 움직이는 비트맵 이미지 그리기

    이번 글에서는 위와 같이 출력할 수 있는 이미지가 있을 때, 이미지를 이동시키는 방법에 대해 알아보도록 하겠습니다.

     

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

     


     

    1. 아이디어

     

    더보기

    게임 뿐만 아니라, 화면 속 움직이는 대부분의 매체들은 정적인 이미지의 연속된 출력입니다.

    정적인 이미지가 출력될 때 이전 이미지에서 변화를 주는 것으로 움직이는 것 처럼 보이게 할 수 있습니다.

     

    WinAPI는 매 프레임마다 WM_PAINT메시지가 전송되고, 우리 코드에서는 그 때마다 OnRender함수를 호출합니다.

    그러므로, OnRender함수에서 이미지의 출력 위치 등을 변화시켜보도록 하겠습니다.

     

    2. MyBitmap 수정 (선언)

     

    더보기

    MyBitmap객체는 비트맵 인스턴스를 다루기 위해 정의한 객체입니다.

    gif등의 연속 이미지에도 대응할 수 있도록 인스턴스 벡터를 가지고 있으며, 연속 출력을 위해 진행된 프레임과 시간을 저장할 수 있는 필드도 있습니다.

    자세한 선언은 코드를 참조해주세요.

     

    우선, OnRender함수에서 구현한 MyBitmap이미지를 출력하는 코드를 살펴보도록 하겠습니다.

    if (mySequenceBitmap) {
        ID2D1Bitmap* tmp = mySequenceBitmap->GetBitmap();
        if (tmp) {
            myRenderTarget->DrawBitmap(tmp,
                D2D1::RectF(
                    0.0f, 0.0f,
                    rtSize.width, rtSize.height
            ));
        }
    }

    Direct2D의 이미지 출력은 크기와 위치가 아닌, 상하좌우 끝점의 위치를 인자로 받습니다.

    따라서, 위 코드는 mySequenceBitmap의 이미지를 화면 전체에 출력하는 코드가 됩니다.

    이를 크기와 위치를 기준으로 할 경우, 다음과 같은 값을 생각해볼 수 있습니다.

    이미지의 크기 {Left, Top, Right, Bottom} + 이미지의 위치 {X, Y, X, Y}

    오른쪽 방향이 +X, 아래쪽 방향이 +Y라고 가정할 경우, 위와 같은 방식으로 이미지의 크기와 위치를 기반으로 한 출력 범위를 지정할 수 있습니다.

     

    위 아이디어를 구현하기 위해, MyBitmap에 변수를 추가하도록 하겠습니다.

    class MyBitmap {
    private:
        std::vector<D2D1_SIZE_F> bitmapSize;
        D2D1_SIZE_F currentPosition;
        UINT currentFrame;
        ...
    }

     

    MyBitmap객체는 연속된 이미지에 대응하며, 이미지 별 크기가 다를 수 있습니다. 

    bitmapSize는 index에 해당하는 이미지의 크기를 저장하는 변수입니다.

    currentPosition은 이미지의 현재 위치를 저장하는 변수입니다.

    currentFrame은 현재 출력되는 이미지의 index를 확인하기 위해 추가했습니다.

     

    다음은 함수입니다.

    class MyBitmap{
    public:
        D2D1_RECT_F GetBitmapPosition();
        void SetPosition(FLOAT x, FLOAT y);
        void Move(FLOAT x, FLOAT y);
        ...
    }

    GetBitmapPosition은 이미지의 크기, 위치값을 Rect로 반환하는 함수입니다.

    SetPosition과 Move는 각각 지정된 위치로 이동, 현재 위치에서 지정한 값 만큼 이동하는 함수입니다.

     

    3. MyBitmap 수정 (구현)

     

    더보기

    위 문단에서 선언한 함수들을 구현하고, 그에 맞춰 이전에 구현한 함수들을 약간 수정하도록 하겠습니다.

     

    우선 수정입니다.

    MyBitamp::GetBitmap함수는 현재 프레임에 해당하는 비트맵 인스턴스를 반환합니다.

    기존 코드 및 변경한 코드는 다음과 같습니다.

    // Before
    ID2D1Bitmap* MyBitmap::GetBitmap() {
        if (bitmap.size() > 0) {
            return bitmap[(UINT)elapseTime%bitmap.size()];
        } else {
            return nullptr;
        }
    }
    
    // After
    ID2D1Bitmap* MyBitmap::GetBitmap() {
        if (bitmap.size() > 0) {
            currentFrame = (UINT)elapseTime % bitmap.size();
            return bitmap[currentFrame];
        } else {
            return nullptr;
        }
    }

    프레임을 계산해서 바로 참조하는 이전 코드에서, 계산된 프레임을 저장하는 코드로 변경되었습니다.

    현재 프레임 index는 bitmap의 크기를 참조할 때 사용됩니다.

     

    우선, SetPositino과 Move함수를 살펴보도록 하겠습니다.

    void MyBitmap::SetPosition(FLOAT x, FLOAT y) {
        currentPosition.width = x;
        currentPosition.height = y;
    }
    
    void MyBitmap::Move(FLOAT x, FLOAT y) {
        currentPosition.width += x;
        currentPosition.height += y;
    }

    두 함수는 각각 위치 변경과 이동을 수행합니다.

    구현도 이에 맞게 값에 대한 대입, 덧셈 연산의 차이만 존재합니다.

     

    GetBitmapPosition은 이미지의 크기, 위치에 대응하는 출력 범위를 반환하는 함수입니다.

    D2D1_RECT_F MyBitmap::GetBitmapPosition() {
    	return D2D1::RectF(
    		0 + currentPosition.width, // Left
            0 + currentPosition.height, // Top
    		bitmapSize[currentFrame].width + currentPosition.width, // Right
            bitmapSize[currentFrame].height + currentPosition.height // Bottom
    	);
    }

    비트맵의 크기와 위치는 이미 다른 함수에서 수정이 이루어지기 때문에, GetBitmapPositino함수는 변경된 값들로 Rect를 생성하는 역할만 수행합니다.

    이 함수는 Render target에 의해 이미지가 출력될 때 같이 호출됩니다.

     

    4. 출력하기

     

    더보기

    기존 코드에서 기능만 추가되었기 때문에 위 코드들은 MyApp코드의 다른 편집 없이 정상적으로 작동합니다.

    하지만 움직임과 관련된 기능을 추가하는 것이 목표이기 때문에, OnRender함수를 수정하도록 하겠습니다.

     

    다음은 기존 MyBitmap을 출력하는 코드입니다.

    if (mySequenceBitmap) {
        ID2D1Bitmap* tmp = mySequenceBitmap->GetBitmap();
        if (tmp) {
            myRenderTarget->DrawBitmap(tmp,
                D2D1::RectF(
                    0.0f, 0.0f,
                    rtSize.width, rtSize.height
                ));
        }
    }

     

    위 코드를 다음과 같이 수정하도록 하겠습니다.

    mySequenceBitmap->Move(deltaTime * 10, deltaTime * 10);
    if (mySequenceBitmap) {
        ID2D1Bitmap* tmp = mySequenceBitmap->GetBitmap();
        if (tmp) {
            myRenderTarget->DrawBitmap(tmp,
                mySequenceBitmap->GetBitmapPosition()
            );
        }
    }

    MyBitmap의 이동 함수를 호출하여 10pixel/second의 속도로 +X, +Y방향으로 움직입니다.

    움직이는 위치에 대응하기 위해, 고정된 값 대신 MyBitmap의 위치 함수를 호출하여 두 번째 인자로 전달합니다.

     


     

    이미지를 움직이는 방법에 대해 알아보았습니다.

    이미지의 이동, 애니메이션 진행 등 움직임을 표현할 때 주의할 점 중 하나는, 출력 함수의 대부분이 프레임 단위로 호출된다는 점 입니다.

    프로그램을 실행시키는 환경에 따라 프레임이 달라질 수 있으므로, 함수의 호출 간격과 같이 기준점이 되는 시간과 변위를 맞춰주는 것이 매우 중요합니다.

     

    이번 글에서 사용된 전체 코드는 다음 Github에서 확인하실 수 있습니다.

     

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

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

    github.com

     

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

    감사합니다.

    댓글

Designed by Tistory.