WINAPI의 마지막이다.
다음 수업부터는 DirectX에 들어간다.
그림을 그리는 과정이 이해가 안돼서 개념정리를 살짝 하고 가려고 한다.
2.2 그리기 예 [Windows API] (ehclub.co.kr)
이 블로그의 자료들을 참고 했다.
이 블로그 주인분은 아래와 같은 글을 쓰셨다.
지금에 와서 Windows API를 학습하는 이유는 윈도우즈 프로그램이 어떠한 원리로 동작하는지 이해하기 위한 부분이 실제 프로그래밍에 사용하기 위한 것보다 많다고 볼 수 있습니다. 이미 MFC나 Windosw Form, WPF 등의 보다 강력하고 개발 비용이 적게 드는 기술들이 많기 때문에 실제 Windows API를 이용하여 윈도우즈 프로그래밍을 주로 할 일은 많지 않습니다.
나도 윈도우즈 프로그램을 만들어보고 싶어서 윈폼을 건드려본적이 있는데 WinAPI보다 훨씬 간편한 기억이 있어서 아무리 극한의 효율을 낸다고 하더라도 아직도 이런 복잡한 WINAPI를 쓰려나..라는 의문이 해결되었다.
아무튼 공부기 때문에 프로세스를 공부해보자.
HINSTANCE 와 HWND 의 차이
헷갈리는 개념이 있어서 이 개념부터 잡고 가려고 한다.
[API] 핸들 HWND , HINSTANCE , HDC : 네이버 블로그 (naver.com)
이 블로그를 참조했다.
HWND
1. 핸들 윈도우의 약자
2. 윈도우의 핸들 번호를 저장해서 사용
3. 하나의 프로그램에서 많은 양의 윈도우를 띄울 수 있기때문에 , HWND로 어떤 윈도우인지 구분 하는 것이다.
HINSTANCE
1. 핸들 인스턴스.
2. 프로그램의 인스턴스 식별자.
3 쉽게 생각하면 프로그램의 실체화된 주소.
정리하자면
HWND는 프로그램안의 윈도우창 번호이며,
HINSTANCE는 프로그램 자체의 핸들이다.
WINAPI에서의 그림그리기
그리기 프로세스
DC가 뭘까?
DC? 시작부터 DC가 뭔지 모르겠다.
DC를 파기 전에 알아야 할게 있다.
윈도우즈는 크게 3가지로 동적 연결 라이브러리를 구성한다고 한다.
Kernel : 메모리를 관리하고 프로그램 실행을 담당
GDI: 화면 처리와 그래픽을 담당
User: UI와 윈도우를 관리
이 동적 연결 라이브러리가 없다면 프로그램을 만들때 수작업으로 처리해야할 일이 늘어난다.
우리는 이 동적 연결 라이브러리를 통해 더더욱 쉽게 작업을 할 수 있는 환경을 제공 받았다.
WINAPI의 함수 대부분은 이 세가지 DLL에 의해 제공이 된다.
그래서 DC를 얘기하는데 동적 연결 라이브러리를 왜 얘기했냐면
윈도우에 그림을 그리려면 GDI 함수를 사용해야 한다.
바로 DC(Device Context)는 '출력에 필요한 모든 정보를 가지는 데이터 구조체' 이며
GDI에 의해 관리가 되고 , 그리기에 필요한 정보를 담고 있다.
그래서 우리는 이 DC를 이용해 그림을 그려야 한다.
HDC
그런데 우리가 그림을 그릴때 보면 DC가 아닌 HDC라는 것이 있다.
HDC는 또 뭘까?
HDC(Handle to a Device Context)
GDI의 중요한 부분이며, DC 개체를 참조하는데 사용되는 번호라는 의미
즉, HDC는 DC를 다루는 핸들 구조체 이다.
BeginPaint ? GetDC?
BeginPaint는 WM_PAINT 메세지에서만 사용하는 함수.
윈도우가 다른 윈도우에 의해 가려졌다가 다시 보여져야 할때와 같은 상황들을 큐의 마지막에 저장했다가 처리함.
화면의 변화만 인지하기 때문에 마우스로 클릭한다거나 할때는 그려주지 않음.
(그래서 다른 메세지에서 InvalidateRect를 사용해 갱신했던것)
EndPaint로 해제함.
GetDC 는 말그대로 DC만 얻어오는것을 말함.
즉각적인 출력이 이루어지지만 일시적으로 그 이후의 변화에는 책임지지 않음.
ReleaseDC로 해제함.
요약하면 BeginPaint는 정적이고, GetDc는 동적이라고 생각하면 됨.
윈도우가 다른 윈도우에 가려지거나 최소화 최대화를 통해 '무효화 영역' 이 생기면 그 부분이 가려지는데,
다시 위의 창을 치우거나 해서 전체를 다 그려야하면
WM_PAINT에 메세지를 보내주어 해당 무효화 영역을 다시 그리고 유효화가 된다.
여기서 GetDC는 무효화 영역은 상관하지 않고 DC만 얻고 돌려 주고, 무효화 영역은 계속 무효화로 있기 때문에 WM_PAINT에서 계속 그리려고 시도 한다.(리소스를 계속 사용한다);
이와 반대로 BeginPaint는 무효화 영역을 그리고나면 무효화 영역이 해제 된다.(리소스 사용 후 반환)
그리기를 시작 한 후 프로세스
이어서 프로세스를 보면,
1. 그리기의 개체를 생성하고(붓이나 , 펜을 생성)
2. 개체를 선택하고(붓을들고 글꼴이라던지,선의 속성,채우기 속성 같은 것들을 바꿀수있음)
3. 그림을 그리고
4. 개체를 소멸 시키고(붓을 반납)
5. DC해제
이 과정이라고 볼 수 있다.
그림하나 그리는게 참 복잡하다.
개념을 살짝 정리했으니 다시 수업으로 돌아가보자.
움직이는 캐릭터 띄우기
키보드 입력이라던지 마우스 입력을 통한 갱신이 없다면 윈도우즈 프로그램은 따로 처리를 하지 않는다.
그렇기 때문에 당장 움직임을 만드는 캐릭터를 띄우더라도 누를때나 화면을 옮기는 등의 처리를 할때만 움직일 것이다.
그래서 time.h의 clock이라는 함수를 이용했다.
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 천분의 1초
long curTick = clock();
long Tick = curTick - oldTick;
oldTick = curTick;
float delta = Tick / 1000.0f;
if (Character != nullptr) Character->Update(delta);
}
메세지가 오면 전달해주는 GetMessage와 달리
PeekMessage를 통해 윈도우에 계속 메세지가 왔는지 물어보게 했다.
그리고 전날 만들었던 AnimationSprite 클래스의 함수들을 수정해주었다.
AnimationSprite::AnimationSprite(HINSTANCE hInst,HWND hWnd,int intResource)
{
this->hWnd = hWnd;
Image = LoadBitmap(hInst, MAKEINTRESOURCE(intResource));
//가져온 비트맵 리소스로부터 비트맵 정보를 가져옴
GetObject(Image, sizeof(BITMAP), &Bitmap);
myDrawRC.left = 0;
myDrawRC.top = 0;
myDrawRC.right = 54;
myDrawRC.bottom = 94;
RECT rc;
GetClientRect(hWnd,&rc);
myPos.x = (rc.right - rc.left) / 2 - (myDrawRC.right - myDrawRC.left) /2;
myPos.y = (rc.bottom - rc.top)/2 - (myDrawRC.bottom - myDrawRC.top) / 2;
curFrame = 0;
maxFrame = Bitmap.bmWidth / myDrawRC.right;
frameSpeed = 1.0f / (float)maxFrame;
}
void AnimationSprite::Draw(const HDC& hDC)
{
HDC imageDC = CreateCompatibleDC(hDC);
SelectObject(imageDC, Image);
//BitBlt(hDC, 0, 0, myDrawRC.right - myDrawRC.left,myDrawRC.bottom - myDrawRC.top, imageDC, 0, 0, SRCCOPY);
TransparentBlt(hDC, myPos.x, myPos.y, myDrawRC.right - myDrawRC.left, myDrawRC.bottom - myDrawRC.top,
imageDC, curFrame * myDrawRC.right, 0, myDrawRC.right, myDrawRC.bottom, RGB(255, 0, 255));
DeleteDC(imageDC);
}
void AnimationSprite::Update(float delta)
{
playTime += delta;
if (playTime >= frameSpeed)
{
playTime = 0.0f;
curFrame = ++curFrame % maxFrame;
}
//필셀단위임
myPos.x += 20.0f * delta;
}
따로 Vector2라는 구조체를 만들어 x,y를 선언했다.
그리고 vecor2형 변수 x,y값에 클라이언트의 중앙값과 캐릭터의 중앙 값을 계산하여 화면 중앙에 캐릭터의 중앙이 오게했다.
MaxFrame에는 그림의 총 폭에서 그림 한 폭의 값을 나누어주어 몇장의 그림을 재생해야 하는지 담아주었다.
Update함수에는 정해둔 시간이 지나면 현재 프레임이 증가하며 maxframe과 나눈 나머지 값을 대입했는데,
증가하다가 maxframe값과 같아지면 0을 반환하기 때문에 계속 반복이 되는것이다.
이중버퍼
위와 같이 코드를 짜고 그대로 실행하면 문제가 있는데 , 하나의 화면에서 계속 배경을 다시그리며 캐릭터를 움직이다보니
아무리 빨리 다시 그린다고 하더라도 사람의 눈에는 깜빡거리는 것이 보인다.
이것을 해결하기 위해 이중버퍼라는 것을 쓴다.
다른 곳에 다시 그려놓고 바꿔치기 하는 방법이다.
case WM_CREATE:
Character = std::make_shared<AnimationSprite>(g_Inst,hWnd,IDB_BITMAP3);
//비트맵 리소스를 가져오고
Image = LoadBitmap(g_Inst, MAKEINTRESOURCE(IDB_BITMAP1));
//가져온 비트맵 리소스로부터 비트맵 정보를 가져옴
GetObject(Image, sizeof(BITMAP), &bitMap);
//Create BackBuffer
GetClientRect(hWnd, &rc);
//1000 = 1sec
SetTimer(hWnd, 0, 50,NULL);
break;
case WM_TIMER:
InvalidateRect(hWnd, NULL, false);
break;
case WM_PAINT:
{
//create함수를 통해 만드는건 heap에 생성하는 것임.
HDC backBuffer = CreateCompatibleDC(GetDC(hWnd));
HBITMAP backBitMap = CreateCompatibleBitmap(GetDC(hWnd), rc.right - rc.left, rc.bottom - rc.top);
SelectObject(backBuffer, backBitMap);
PAINTSTRUCT ps;
//비긴페인트,앤드페인트는 WM_PAINT에서만 처리 가능
HDC hDC = BeginPaint(hWnd, &ps);
//현재 DC로부터 호환되는 DC를 생성을하고
HDC imageDC = CreateCompatibleDC(hDC);
// 소스 dc를 만드는 과정..
SelectObject(imageDC, Image);
//카피해서 생성한다.
BitBlt(backBuffer, 0, 0, bitMap.bmWidth, bitMap.bmHeight, imageDC, 0, 0, SRCCOPY);
DeleteDC(imageDC);
Character->Draw(backBuffer);
//Swap Chain
GetObject(backBitMap, sizeof(BITMAP), &backBit);
BitBlt(hDC, 0, 0, bitMap.bmWidth, backBit.bmHeight, backBuffer,0,0,SRCCOPY);
EndPaint(hWnd, &ps);
//지워주어서 메모리 누수를 방지함
DeleteObject(backBitMap);
DeleteDC(backBuffer);
}
break;
CreateCompatibleDC -> 인자값의 DC와 호환되는 새로운 DC를 생성한다.
BitBlt -> 이미지를 화면에 출력해주는 함수. 이 함수를 쓰기전에 비트맵파일을 불러와야함(ex. LoadImage함수)
GetObject -> DC에 연결된 비트맵의 정보를 얻음
이렇게 캐릭터가 깜빡거리지 않고 문워크를 열심히 하게됐다.
WINAPI를 끝내며
사실 이중버퍼나 어려운 함수들은 아직 이해 못한게 너무 많다.
윈도우즈 프로그램의 돌아가는 원리를 공부하기 보다는 c++을 좀 더 배우면 좋겠다 라는 마음으로 해보긴 했는데
아직까진 정말 어려운거 같다.
또 언제 손댈지 모르겠으나 WINAPI를 다루는 것은 당장 취업에 필요한건 아니니 내가 해야할거부터 해야겠다.
다음 수업부턴 DirectX에 들어간다.
'프로그래밍 공부 > C++ 프로그래밍' 카테고리의 다른 글
렌더링 파이프라인에 대해서.. (0) | 2022.06.30 |
---|---|
Direct X 7일차💻 DirectX의 시작 (0) | 2022.06.22 |
Direct X 5일차💻 <WINAPI> #3 (0) | 2022.06.19 |
Direct X 4일차💻 <WINAPI> #2 (0) | 2022.06.19 |
WINAPI 추가정리 (0) | 2022.06.19 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!