cyphen156
Don't Starve 모작 프로젝트 with Win32 API #2 키입력을 통한 리소스 변환 렌더링 본문
Don't Starve 모작 프로젝트 with Win32 API #2 키입력을 통한 리소스 변환 렌더링
cyphen156 2024. 10. 8. 00:37맨 처음에는 png를 그대로 쓰고싶어서 gdi+를 통해 작업하려고 했는데 왜인지 모르게 이미지가 로드되지 않더라.
그래서 그냥 개별적으로 파이썬 스크립트를 통해 ktech룰 통해.png로 변환된 latex파일을 다시 bmp파일로 변환해주는 스크립트를 통해 자동화해서 리소스를 죄다 추출했더니 용량이 1.5기가쯤 되더라. 아마 애니메이션을 표현하기 위한 scml파일도 포함되어 있어서 그럴거다.
어쨋든 주말동안 했던 내용을 정리하자면 WM_KEYDOWN 케이스문을 작성하여 개별적으로 키입력 동작 메세지 발생시 hBitMap 객체가 좌표이동을 하도록 하고, invalidateRect() 함수를 통해 무효화 영역을 강제로 발생시켜 다시 그리도록 지시했다.
우선 화면에 표시될 오브젝트 한개의 좌표를 저장할 정적 변수하나와 비트맵을 뒤집기 위한 플래그 변수 reverse를 전역변수로 선언한다.
static POINT point;
static BOOL reverse;
그리고 나서 화면에 플리커링을 제거하기 위한 메모리 DC 변수 한개와
화면에 표시될 hBitMap변수 한개, Defalut로 사용되던 비트맵 데이터를 저장할 oldBitMap
그리고 변경될 리소스들을 미리 로드해서 가지고 있을 hBitMaps[] 배열 한개
특정 프레임 마다 화면에 표시될 비트맵을 교체하기 위한 양수 정수 변수 한개 iframe
HDC MemDC;
HBITMAP hBitmap, oldBitmap;
HBITMAP hBitmaps[5];
static unsigned int iFrame = 1;
Main함수가 시작되면 애플리케이션을 초기화 하고, 좌표와 이미지 역전 설정을 초기화 하는 작업을 우선해준다.
// 애플리케이션 초기화를 수행합니다:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
// 기본 좌표 설정
point = { 0, 0 };
// 이미지 역전 설정
reverse = true;
이제 키입력을 처리하기 위한 switch 분기문 Case WM_KEYDOWN을 작성한다.
좌 우키를 통해 비트맵 객체가 이동하면서 그려지는데, 좌표가 +-5씩 이동하고, 한번의 이동이 1프레임이라 가정하고, 5개의 프레임이 연속해서 같은 방향으로 이동할 때마다 비트맵 이미지가 변화하여 애니메이션과 유사한 동작을 하도록 구현했다.
그리고 좌 우 이동을 통해 이미지를 수평반전 시키는 역할을 수행하도록 했다.
case WM_KEYDOWN:
switch (wParam)
{
case VK_LEFT:
{
reverse = false;
point.x -= 5;
if (iFrame > 1)
{
--iFrame;
}
break;
}
case VK_RIGHT:
{
reverse = true;
point.x += 5;
if (iFrame < 24)
{
++iFrame;
}
break;
}
case VK_UP:
point.y -= 5;
break;
case VK_DOWN:
point.y += 5;
break;
}
InvalidateRect(hWnd, NULL, TRUE);
break;
이제 화면에 비트맵을 그리기 위해 CASE WM_PAINT:를 수정한다.
플리커링을 제거하기 위해 더블버퍼링을 사용할 것인데 백버퍼와 메인 버퍼 두개를 놓고 사용한다.
더블버퍼링이 플리커링을 제거해줄 수 있는 이유는 매번 화면에 새로 그리는 것 보다 메모리에 미리 다음 정보를 저장해 놓고 화면에 옮기는 것이 훨씬 빠르기 때문이다.
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// MemDC가 처음일 때만 생성
if (MemDC == NULL) {
MemDC = CreateCompatibleDC(hdc);
HBITMAP hMemBitmap = CreateCompatibleBitmap(hdc, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top);
oldBitmap = (HBITMAP)SelectObject(MemDC, hMemBitmap);
}
// 백 버퍼(MemDC)에 배경 그리기
FillRect(MemDC, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
// 미리 로드된 비트맵을 백 버퍼에 그리기
hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1 + (iFrame / 5)));
HDC hTempDC = CreateCompatibleDC(MemDC);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hTempDC, hBitmap);
BITMAP bitmap;
GetObject(hBitmap, sizeof(BITMAP), &bitmap);
int originalWidth = bitmap.bmWidth;
int originalHeight = bitmap.bmHeight;
int scaledWidth = originalWidth / 10;
int scaledHeight = originalHeight / 10;
// 비트맵을 백 버퍼에 그리기
if (reverse) {
StretchBlt(MemDC, point.x + scaledWidth, point.y, -scaledWidth, scaledHeight, hTempDC, 0, 0, originalWidth, originalHeight, SRCCOPY);
}
else {
StretchBlt(MemDC, point.x, point.y, scaledWidth, scaledHeight, hTempDC, 0, 0, originalWidth, originalHeight, SRCCOPY);
}
// 백 버퍼(MemDC) 내용을 화면(hdc)에 복사
BitBlt(hdc, 0, 0, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, MemDC, 0, 0, SRCCOPY);
// 자원 해제
SelectObject(hTempDC, hOldBitmap);
DeleteDC(hTempDC);
EndPaint(hWnd, &ps);
}
break;
그런데 여기서 플리커링이 제거되지 않았다. 이유는 키 입력시 발생하는 InvalidateRect()함수의 인자 전달을 TRUE로 줬기 때문인데, 이건 무효화 영역의 배경화면을 지우고 다시그리라는 명령이기 때문에 백버퍼에 있던 배경 데이터도 싹지워버리고 다시 그리고 있기 때문이었다. 그리고 현재 두번째 인자를 NULL로 주고 있기 때문에 윈도우 전체 영역에 대해 무효화영역을 연산하고 있기 때문이다. 특정 공간에 대해서만 연산하고 싶다면 두번째 인자로 Rect객제 포인터를 주면 된다.
여기서 추가로 작업중인 내용은 추출된 리소스를 하나하나 리소스 파일에 등록할 수는 없는 노릇이다.
파일수가 1만개가 넘어가는데 저걸 언제한단 말인가?
그래서 ResourceManager에 대해 배워 폴더채로 등록하여 관리할 수 있도록 학습하고 있다.
또한 현재 작성된 코드에서는 캐릭터 에니메이션을 프레임당 비트맵 리소스 교체를 통해 구현하고 있는데 이것을 자동으로 할 수 있도록 하는 Animation클래스가 존재한다는 것을 알고 이에 대해 학습중이다.
전체 코드는 다음과 같다.
DST_2.cpp
// dontstarveCopy.cpp : 애플리케이션에 대한 진입점을 정의합니다.
//
#include "framework.h"
#include "dontstarveCopy.h"
#include "Gdiplus.h"
#pragma comment (lib, "gdiplus")
using namespace Gdiplus;
// GDIPlus 토큰 초기화
ULONG_PTR gdiplusToken;
// 좌표 변수
static POINT point;
static BOOL reverse;
#define MAX_LOADSTRING 100
// 전역 변수:
HINSTANCE hInst; // 현재 인스턴스입니다.
WCHAR szTitle[MAX_LOADSTRING]; // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING]; // 기본 창 클래스 이름입니다.
// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
HDC MemDC;
HBITMAP hBitmap, oldBitmap;
HBITMAP hBitmaps[5];
static unsigned int iFrame = 1;
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
// GDI+ 초기화
GdiplusStartupInput gpsi;
GdiplusStartup(&gdiplusToken, &gpsi, nullptr);
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 여기에 코드를 입력합니다.
//
//
// 전역 문자열을 초기화합니다.
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_DONTSTARVECOPY, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 애플리케이션 초기화를 수행합니다:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
// 기본 좌표 설정
point = { 0, 0 };
// 이미지 역전 설정
reverse = true;
// 비트맵 로드
//
/*hBitmaps[0] = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
hBitmaps[1] = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP2));
hBitmaps[2] = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP3));
hBitmaps[3] = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP4));
hBitmaps[4] = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP5));*/
/*for (int i = 0; i < 5; ++i)
{
if (hBitmaps[i] == NULL) {
MessageBox(NULL, L"비트맵을 로드할 수 없습니다.", L"Error", MB_OK);
}
}*/
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DONTSTARVECOPY));
MSG msg;
// 기본 메시지 루프입니다:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// GDI Plus 해제
GdiplusShutdown(gdiplusToken);
return (int) msg.wParam;
}
//
// 함수: MyRegisterClass()
//
// 용도: 창 클래스를 등록합니다.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_DONTSTARVECOPY));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_DONTSTARVECOPY);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
//
// 함수: InitInstance(HINSTANCE, int)
//
// 용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.
//
// 주석:
//
// 이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
// 주 프로그램 창을 만든 다음 표시합니다.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 함수: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 용도: 주 창의 메시지를 처리합니다.
//
// WM_COMMAND - 애플리케이션 메뉴를 처리합니다.
// WM_PAINT - 주 창을 그립니다.
// WM_DESTROY - 종료 메시지를 게시하고 반환합니다.
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 메뉴 선택을 구문 분석합니다:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// MemDC가 처음일 때만 생성
if (MemDC == NULL) {
MemDC = CreateCompatibleDC(hdc);
HBITMAP hMemBitmap = CreateCompatibleBitmap(hdc, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top);
oldBitmap = (HBITMAP)SelectObject(MemDC, hMemBitmap);
}
// 백 버퍼(MemDC)에 배경 그리기
FillRect(MemDC, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
// 미리 로드된 비트맵을 백 버퍼에 그리기
hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1 + (iFrame / 5)));
HDC hTempDC = CreateCompatibleDC(MemDC);
HBITMAP hOldBitmap = (HBITMAP)SelectObject(hTempDC, hBitmap);
BITMAP bitmap;
GetObject(hBitmap, sizeof(BITMAP), &bitmap);
int originalWidth = bitmap.bmWidth;
int originalHeight = bitmap.bmHeight;
int scaledWidth = originalWidth / 10;
int scaledHeight = originalHeight / 10;
// 비트맵을 백 버퍼에 그리기
if (reverse) {
StretchBlt(MemDC, point.x + scaledWidth, point.y, -scaledWidth, scaledHeight, hTempDC, 0, 0, originalWidth, originalHeight, SRCCOPY);
}
else {
StretchBlt(MemDC, point.x, point.y, scaledWidth, scaledHeight, hTempDC, 0, 0, originalWidth, originalHeight, SRCCOPY);
}
// 백 버퍼(MemDC) 내용을 화면(hdc)에 복사
BitBlt(hdc, 0, 0, ps.rcPaint.right - ps.rcPaint.left, ps.rcPaint.bottom - ps.rcPaint.top, MemDC, 0, 0, SRCCOPY);
// 자원 해제
SelectObject(hTempDC, hOldBitmap);
DeleteDC(hTempDC);
EndPaint(hWnd, &ps);
}
break;
case WM_KEYDOWN:
switch (wParam)
{
case VK_LEFT:
{
reverse = false;
point.x -= 5;
if (iFrame > 1)
{
--iFrame;
}
break;
}
case VK_RIGHT:
{
reverse = true;
point.x += 5;
if (iFrame < 24)
{
++iFrame;
}
break;
}
case VK_UP:
point.y -= 5;
break;
case VK_DOWN:
point.y += 5;
break;
}
InvalidateRect(hWnd, NULL, FALSE);
break;
case WM_DESTROY:
SelectObject(MemDC, oldBitmap);
for (int i = 0; i < 5; ++i) {
DeleteObject(hBitmaps[i]);
}
DeleteDC(MemDC);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// 정보 대화 상자의 메시지 처리기입니다.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
실습 코드는 다음 경로에 있습니다.
GitHub - cyphen156/Win32API
Contribute to cyphen156/Win32API development by creating an account on GitHub.
github.com
+++리소스 폴더가 용량이 너무 커서 구글드라이브로 대체합니다.
https://drive.google.com/drive/folders/1QGqzlj973L0dYcrACfHqXE9IgCX2qwYp?usp=sharing
Google Drive: 로그인
이메일 또는 휴대전화
accounts.google.com
'프로젝트 > dont'starve 모작' 카테고리의 다른 글
Don't Starve 모작 프로젝트 with Win32 API #3-2 게임 프로젝트를 위한 풀스크린 모드, 프레임 레이트 출력하기 (5) | 2024.10.10 |
---|---|
Don't Starve 모작 프로젝트 with Win32 API #3 사용자 입력 없이 항상 돌아가는 프로그램 작성하기 (3) | 2024.10.08 |
Don't Starve 모작 프로젝트 with Win32 API #1 리소스 추출 (6) | 2024.10.08 |