Deeper Learning

[WinAPI] Camera 구현 본문

Game Development/WinAPI

[WinAPI] Camera 구현

Dlaiml 2024. 6. 12. 13:03

많은 게임에서 카메라는 캐릭터에 포커스 되어 있고 캐릭터를 따라 움직인다. WinAPI로 만든 현재 프로젝트에서 카메라가 없다면 현재 창의 크기보다 맵(레벨, 씬)을 더 크게 제작하는 것이 불가능하다.

 

전체 맵을 표현하던 창을 대신 현재 카메라가 보고 있는 영역으로 대체하는 작업이 있어야 캐릭터가 이동함에 따라 카메라가 이동하여 창 밖에 있는 맵이 창 안에 들어오도록 할 수 있다.

 

카메라 이동 구현 예시

 

왼쪽 그림에서 큰 검은색 사각형이 창의 크기가 되어야 하고 캐릭터가 우측으로 이동하면 카메라도 캐릭터를 따라 우측으로(빨간 사각형) 이동하여야 한다. 시점 밖에 있던 오브젝트도 카메라가 이동함에 따라 창 안으로 들어온다.

 

구현 전에 이해가 필요한 부분이 있는데, 바로 카메라를 도입하는 순간 두 좌표계가 필요하다는 것이다. 먼저 WinAPI에서 사용하는 좌표인 창의 좌측 상단이 (0,0)인 Reder Position과 카메라가 이동하더라도 유지되는 Screene Position이다.

 

WinAPI에서 모든 좌표는 창의 좌측 상단을 기준으로 하는 Render Position이므로 만약 (200,200)이 중앙이 되도록 시점을 고정하라는 코드가 있으면 (200, 200)으로 이동하는 순간 상대좌표인 Render Position이 달라지기 때문에 계속 우하단으로 시점이 이동하게 된다.

 

따라서 상대좌표가 아닌 절대 좌표인 Screen Position이 있어야 하며 이 두 좌표계 사이의 Transformation이 정의되면 쉽게 시점을 고정하거나 이동할 수 있다.

 

 

마우스를 초록 타일의 시작점인 좌측 상단에 대고 Screen Position, Render Position을 좌상단에 출력해 보았다.

Screen Position (0,0)에서 타일의 렌더링이 시작되기 때문에 마우스의 Screen position이 (0,0) 주변에 위치하는 것을 볼 수 있다. Render Position(MOUSE Pos)는 (431, 204)로 창의 좌측상단에서 마우스가 얼마나 떨어져 있는지를 말해준다.

 

카메라 이동

 

이제 카메라의 이동에 대해 생각해 보자. 

카메라가 이동하거나 캐릭터에 고정되었다는 것을 알기 쉽도록 타일을 일부러 화면에 꽉 차지 않게 깔았다. 

좌상단이 Screen Position에서도 (0,0) Render Position에서도 (0,0)이 되도록 카메라를 조정한 화면은 아래와 같다.

 

여기에서 카메라를 우측으로 이동시켜 보자.

 

사용자가 보기에는 카메라가 우측으로 이동했다는 것을 쉽게 알 수 있다. 

하지만 창의 크기는 고정이고 WinAPI는 창의 좌상단을 기준으로 하는 상대좌표인 Render Position 만을 알고 있고 이를 토대로 Paint 한다. 

즉, 실제 Rendering 위치를 수정해야 카메라 이동효과를 구현할 수 있는 것이다. 

 

Screen Position은 카메라와 상관없이 고정된 좌표로 기준점이 필요하다. wWinMain 함수에서 창 크기를 정할 때, 그 창의 중앙을 기준점으로 하자

 

Vec2 vResolution = CCore::GetInstance()->GetResolution();
Vec2 vCenter = vResolution / 2.f;

 

(1920, 1080)을 resolution으로 설정하였으므로 (960, 540)이 Screen Position의 중심점이 된다.

Resolution은 카메라와 상관없는 창의 크기이기 때문에 중심점인 vCenter는 변하지 않는다.

 

다음으로 카메라의 현재 위치가 필요하다. 마찬가지로 카메라 시점의 중심을 기준으로 하자.

이를 가리키기 위해 Camera 클래스의 멤버 변수로 "m_vCurLookPos"를 선언하였다.

"카메라를 우측으로 이동한다"라는 것은 "카메라 시점의 중심의 x좌표를 증가시킨다"를 말한다.

 

두 좌표계의 Transformation은 중심점의 차를 활용하여 가능하다.

검은색 사각형 -> 빨간색 사각형으로 카메라가 이동하였다. 검은색 화면에서 우측상단에 보이는 사각형은 빨간색 화면에서는 더 좌측으로 이동한 상태로 렌더링 되었고 검은색 화면에서 좌측에 걸쳐있던 사각형은 빨간색 화면에서는 보이지 않는다.

 

이는 상대 좌표인 Render position의 x축 값이 감소한 것이다. 검은색 화면에서 좌측에 걸쳐있던 사각형은 Render Position의 x축은 0이었고 빨간색 화면에서 Render Position은 음수가 되어 창에서 보이지 않는다.

 

예시로 x축으로 카메라가 100 이동하면, (0, 100) 위치에 렌더링 되던 오브젝트는 (-100,100) 위치에 랜더링 되어야 한다는 것이다. 

 

현재 시점의 Screen Position에서 Screen Position의 중심점을 빼면 위 그림에서 빨간색 화살표에 해당하는 벡터를 구할 수 있다. (= (100, 0))

 

이를 Screen Position에서 뺀 값이 Render Position이 되는 것이고 반대로 Render Position에서 이 값을 더하면 Screen Position이 된다.

 

Vec2 ScreenPosToRenderPos(Vec2 _v) const { return _v - m_vDiff; }
Vec2 RenderPosToScreenPos(Vec2 _v) const { return _v + m_vDiff; }

 

 

아래 그림에서 초록색 사각형은 Screen Position (0, 300)에 배치되어, 초기 Screen Position의 중심과 현재 카메라 시점의 중심이 일치하였을 때는 Screen Position이 Render Position과 동일하기 때문에 (0,300) Render Position에 배치되었다.

 


하지만 현재 카메라 시점의 중심 좌표가 (960, 540) -> (1160, 540)으로 변경되면 

Screen Position의 중심에서 카메라 시점 중심좌표를 뺀 값이 (1160, 540) - (960, 540) = (200, 0)이 되고

Screen Position (0, 300)에 배치된 사각형의 Render Position은 (0, 300) - (200, 0) = (-200, 300)이 되어 현재 카메라 시점에서 보이지 않게 된다.

 

오브젝트 배치

위 초록 사각형의 예시에서 다룬 것처럼 오브젝트의 배치도 모두 Screen Position 기준으로 배치 -> Render Position으로 변경 후 렌더링이 되어야 한다.

 

위 그림에서 이번엔 파란 사각형을 보자. 초기 카메라 시점에서 파란색 사각형은 화면에 보이지 않는다. 화면 크기가 (1920 x1080)이므로 Screen Position으로 (2300, 800) 정도에 배치가 된 것이다.

Screen Position의 중심과 카메라 시점의 중심이 초기 화면(검은색 프레임)에서는 동일하므로 Render Position도 (2300, 800)이 되어 현재 화면 밖에 렌더링 된 것이다(=렌더링 하지 않도록 처리). 

 

카메라 고정

이제 카메라 고정 기능도 쉽게 구현할 수 있다. 카메라 고정이란 고정하고자 하는 오브젝트의 Render Position이 고정된 것이다. 1920, 1080이 창 크기라면 중심점인 (960, 540)이 항상 고정 오브젝트의 Render Position이 되는 것이다. 이를 어떻게 구현해야 할까?

 

모든 오브젝트의 위치는 Screen Position으로 관리하기 때문에 캐릭터의 이동도 Screen Position의 값이 변하는 것과 같다. 화면 중앙에 위치하던 캐릭터 (960, 540)에 카메라를 고정한다고 하자.

 

캐릭터가 x축으로 200만큼 이동하면 캐릭터의 위치는 (1160, 540)이 되고 카메라가 이동하지 않는다면 Render Position도 (1160, 540)이 되어 중앙에서 x축으로 200만큼 떨어진 위치에 렌더링 될 것이다.

 

카메라를 고정하려면 캐릭터의 위치가 현재 시점의 위치가 되어야 한다.

if (m_pFocusObj)
{
        m_vCurLookPos = m_pFocusObj->GetPos();  
}

 

현재 카메라 시점의 중심점인 m_vLookPos를 고정할 오브젝트의 Screen Position으로 매 Tick에 업데이트하는 식으로 이를 구현할 수 있다.

 

카메라 시점의 중심은 캐릭터가 x축으로 200 이동하면 마찬가지로 (1160, 540)이 될 것이다.

Screen Position의 중심에서 카메라 시점 중심좌표를 뺀 값이 (1160, 540) - (960, 540) = (200, 0)이 되고 

캐릭터의 Render Position은 Screen Position (1160, 540) - Diff (200, 0) = (960, 540)으로 처음 렌더링 된 중심위치와 동일한 것을 알 수 있다.

 

사용자 입장에서는 캐릭터가 항상 창의 중심(Render Position이 고정)에 있지만 다른 타일, 적 오브젝트는 렌더링 위치가 변화되기 때문에 캐릭터가 고정된 채로 맵이 움직인다, 즉 캐릭터가 움직인다고 여기게 된다.

 

 

Reference

[0] https://learn.microsoft.com/ko-kr/windows/win32/api/

[1] https://www.youtube.com/playlist?list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK

Comments