◉Game
●mapping 기법
- 길을 보여줄 비트맵을 추가로 등록한다.
- static HBITMAP HbmLoad;
- On_Paint에서 2중 for문을 사용하여 전체적인 맵을 출력한다.
- 내부에 if문을 사용하여 미리 만들어둔 Map에서 길과 배경을 분리하여 출력한다.
- 소스<Game.c => On_Paint>
LRESULT On_Paint(HWND hWnd, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; int iXCnt; int iYCnt; hdc = BeginPaint(hWnd, &ps); for (iYCnt = 0; iYCnt < YFRAME; ++iYCnt) { for (iXCnt = 0; iXCnt < XFRAME; ++iXCnt) { if ('#' == ucMap[iYCnt][iXCnt]) { SelectObject(MemDC, HbmGround); } else if (' ' == ucMap[iYCnt][iXCnt]) { SelectObject(MemDC, HbmLoad); } BitBlt(hdc, iXCnt*XTILE, iYCnt*YTILE, XTILE, YTILE, MemDC, 0, 0, SRCCOPY); } } SelectObject(MemDC, HbmHero); BitBlt(hdc, iXPos, iYPos, XTILE, YTILE, MemDC, 0, 0, SRCCOPY); EndPaint(hWnd, &ps);
return 0; } |
- 결과
![](https://t1.daumcdn.net/cfile/tistory/26328C48564D88191D)
●LoadMap() 함수
- 캐릭터의 이동에 따라 변하는 맵을 처리하기 위해서 판(Stage) 개념을 추가한다.
- #define STAGE 2
![](https://t1.daumcdn.net/cfile/tistory/22210B48564D881F2D)
- 이중 for문을 사용하여 ucMap을 만든다.
- memcpy로도 사용이 가능하다.
- @도 같이 복사한다.
- @의 위치를 알 수 없다.
- 이중 for문을 사용하면 @의 위치를 알 수 있다.
- ucMap을 만드는 도중에 캐릭터를 뜻하는 ‘@’를 만나면 그 좌표를 저장해둔다.
- 소스
void LoadMap(void) { int iXCnt; int iYCnt;
for (iYCnt = 0; iYCnt < YFRAME; ++iYCnt) { for (iXCnt = 0; iXCnt < XFRAME; ++iXCnt) { ucMap[iYCnt][iXCnt] = ucStageMap[uiStage][iYCnt][iXCnt]; if ('@' == ucMap[iYCnt][iXCnt]) { iXPos = iXCnt; iYPos = iYCnt; } } }
return; } |
●캐릭터 이동
- 캐릭터가 제자리에서는 움직이지만, 이동은 하지 않는다.
- 캐릭터를 이동할 수 있도록 다음과 같이 수정한다.
case VK_RIGHT: ++iXPos; ucMap[iYPos][iXPos] = '@'; HbmHero = HbmRight; break; |
![](https://t1.daumcdn.net/cfile/tistory/26416448564D881B0F)
- 결과 : 캐릭터가 움직인다.
- 문제점 : 캐릭터는 움직이지만, 지나간 자리에 캐릭터가 잔상으로 남는다.
- 지나간 자리에 캐릭터를 지워야 한다.
- 화면에 ‘@’는 이동하지만, ucMap에 ‘@’는 그대로 있다.
- 화면에 보이는 캐릭터가 아니라 ucMap에 존재하는 캐릭터가 움직여야한다.
case VK_RIGHT: ucMap[iYPos][iXPos] = ' '; ++iXPos; ucMap[iYPos][iXPos] = '@'; HbmHero = HbmRight; break; |
![](https://t1.daumcdn.net/cfile/tistory/271EE448564D881C31)
- 결과 : 캐릭터가 이동한 지점만 캐릭터가 존재한다.
- 캐릭터가 지나간 길을 전부 Load로 채운다.
- 문제점 : 맵이 지워진다.
- 캐릭터가 지나간 길을 전부 Load로 채운다.
- 캐릭터가 지나간 자리를 원본의 Stage에서 들고 온다.
- 다음과 같이 수정한다.
case VK_RIGHT: ucMap[iYPos][iXPos] = ucStageMap[uiStage][iYPos][iXPos]; ++iXPos; ucMap[iYPos][iXPos] = '@'; HbmHero = HbmRight; break; |
![](https://t1.daumcdn.net/cfile/tistory/233E6C48564D881E12)
- 결과 : 캐릭터가 지나가도 맵은 그대로 존재한다.
- 문제점 : 처음에 캐릭터가 나타난 자리는 캐릭터로 고정된다.
- 우선 ucMap에 캐릭터를 지우고, 좌표만 저장해서 따로 그린다.
- '@'가 사라진다.
- 캐릭터가 이동하여도 좌표 값으로 그리기 때문에 문제가 없다.
/*On_Paint 함수 마지막에 추가한다.*/ SelectObject(MemDC, HbmHero); BitBlt(hdc, iXPos*XTILE, iYPos*YTILE, XTILE, YTILE, MemDC, 0, 0, SRCCOPY);
/*LoadMap 함수*/ void LoadMap(void) { int iXCnt; int iYCnt; for (iYCnt = 0; iYCnt < YFRAME; ++iYCnt) { for (iXCnt = 0; iXCnt < XFRAME; ++iXCnt) { if ('@' == ucStageMap[uiStage][iYCnt][iXCnt]) { iXPos = iXCnt; iYPos = iYCnt; ucMap[iYCnt][iXCnt] = ' '; continue; } ucMap[iYCnt][iXCnt] = ucStageMap[uiStage][iYCnt][iXCnt]; } }
return; }
|
- 결과
![](https://t1.daumcdn.net/cfile/tistory/27459B48564D88210B)
![](https://t1.daumcdn.net/cfile/tistory/23237A48564D88222B)
●경계 검사
- 갈 수 있는 조건은 다음과 같다.
- O : 길, 닷, 박스(+길, 닷)
- if문이 6가지이다.
- X : 벽, 박스(+벽, 박스)
- if문이 5가지이다.
- 소스
case VK_RIGHT: HbmHero = HbmRight; if (' ' == ucMap[iYPos][iXPos + 1]) //길 일 때 { ++iXPos; } else if ('.' == ucMap[iYPos][iXPos + 1]) //Dot일 때 { ++iXPos; } else if ('B' == ucMap[iYPos][iXPos + 1]) //박스 일 때, { if (' ' == ucMap[iYPos][iXPos + 2]) //박스이고, 길 일 때 { ++iXPos; } else if ('.' == ucMap[iYPos][iXPos + 2]) //박스이고, Dot 일 때 { ++iXPos; } } break; |
- 결과는 생략한다.
●박스 이동
- 박스를 이동한 후에 박스가 지나간 자리는 이전 상황에 맞게 다시 채워 주어야 한다.
- 원본에 ucStageMap에서 정보를 들고 온다.
- 원래 Dot였다면 Dot, 길이나 박스였다면 길을 채워주면 된다.
case VK_RIGHT: HbmHero = HbmRight; if (' ' == ucMap[iYPos][iXPos + 1]) { ++iXPos; } else if ('.' == ucMap[iYPos][iXPos + 1]) { ++iXPos; } else if ('B' == ucMap[iYPos][iXPos + 1]) { if (' ' == ucMap[iYPos][iXPos + 2]) { ucMap[iYPos][iXPos+2] = ucMap[iYPos][iXPos+1]; if ('.' != ucStageMap[uiStage][iYPos][iXPos+1]) { ucMap[iYPos][iXPos + 1] = ' '; } else { ucMap[iYPos][iXPos + 1] = '.'; } ++iXPos; } else if ('.' == ucMap[iYPos][iXPos + 2]) { ucMap[iYPos][iXPos + 2] = ucMap[iYPos][iXPos + 1]; if ('.' != ucStageMap[uiStage][iYPos][iXPos + 1]) { ucMap[iYPos][iXPos + 1] = ' '; } else { ucMap[iYPos][iXPos + 1] = '.'; } ++iXPos; } } break; |
- 결과
![](https://t1.daumcdn.net/cfile/tistory/2636454F564D882431)
![](https://t1.daumcdn.net/cfile/tistory/2440994F564D882628)
- 최적화는 알아서 하도록 하자.
●클리어 조건
- ucMap에 dot위에 올라간 박스 개수와 ucStageMap에 dot의 개수가 같으면 클리어 된다.
- ucStageMap에 dot 개수는 LoadMap에서 확인할 수 있다.
- ucMap에 dot위에 올라간 박스 개수는 키보드 입력이 끝날 때마다 if문을 통해 체크한다.
- 소스
/*On_KeyDown 함수*/ InvalidateRect(hWnd, &stArea, FALSE);
uiDotCnt = 0; for (iYCnt = 0; iYCnt < YFRAME; ++iYCnt) { for (iXCnt = 0; iXCnt < XFRAME; ++iXCnt) { if ('B' == ucMap[iYCnt][iXCnt]) { if ('.' == ucStageMap[uiStage][iYCnt][iXCnt]) { ++uiDotCnt; } } } }
/*LoadMap 함수*/ void LoadMap(void) { int iXCnt; int iYCnt; uiDotNum = 0;
for (iYCnt = 0; iYCnt < YFRAME; ++iYCnt) { for (iXCnt = 0; iXCnt < XFRAME; ++iXCnt) { if ('.' == ucStageMap[uiStage][iYCnt][iXCnt]) { ++uiDotNum; } if ('@' == ucStageMap[uiStage][iYCnt][iXCnt]) { iXPos = iXCnt; iYPos = iYCnt; ucMap[iYCnt][iXCnt] = ' '; continue; } ucMap[iYCnt][iXCnt] = ucStageMap[uiStage][iYCnt][iXCnt]; } }
return; } |
- 결과
-
●InvalidateRect() 함수
- 2번째 인자가 NULL로 되어 있어 On_Paint를 할 때마다 전부 다 그린다.
- 그릴 부분을 RECT함수를 통해 제한한다.
- InvalidateRect(hWnd, &stArea, FALSE);
- 소스
/*On_KeyDown*/ RECT stArea;
stArea.left = iXPos * XTILE; stArea.right = (iXPos + 1) * XTILE - 1; stArea.top = iYPos * YTILE; stArea.bottom = (iYPos + 1) * YTILE - 1;
case VK_RIGHT: HbmHero = HbmRight; if (' ' == ucMap[iYPos][iXPos + 1]) { ++iXPos; stArea.right = (iXPos + 1) * XTILE - 1; } else if ('.' == ucMap[iYPos][iXPos + 1]) { ++iXPos; stArea.right = (iXPos + 1) * XTILE - 1; } else if ('B' == ucMap[iYPos][iXPos + 1]) { if (' ' == ucMap[iYPos][iXPos + 2]) { ucMap[iYPos][iXPos+2] = ucMap[iYPos][iXPos+1]; if ('.' != ucStageMap[uiStage][iYPos][iXPos+1]) { ucMap[iYPos][iXPos + 1] = ' '; } else { ucMap[iYPos][iXPos + 1] = '.'; } ++iXPos; stArea.right = (iXPos + 2) * XTILE - 1; } else if ('.' == ucMap[iYPos][iXPos + 2]) { ucMap[iYPos][iXPos + 2] = ucMap[iYPos][iXPos + 1]; if ('.' != ucStageMap[uiStage][iYPos][iXPos + 1]) { ucMap[iYPos][iXPos + 1] = ' '; } else { ucMap[iYPos][iXPos + 1] = '.'; } ++iXPos; stArea.right = (iXPos + 2) * XTILE - 1; } } break;
InvalidateRect(hWnd, &stArea, FALSE); |
●Stage 및 Score.
- Push Push 게임의 특성을 활용하여, 움직인 거리가 작을수록, 즉 점수가 낮을수록 잘 한 것이다.
- 캐릭터가 이동한 거리를 카운팅하여 그것을 점수화 한다.
- 키보드 입력을 받을 때마다 ++하면 된다.
- ↑↓←→를 제외한 키의 입력은 배제하기 위해서 모든 case문 안에 입력한다.
- 출력은 SetWindowText 함수를 사용한다.
- 원형
- BOOL SetWindowText(HWND, hWnd, LPCSTR lpString);
- hWnd의 이름을 lpString로 바꿔주는 함수이다.
- 소스
/*On_Paint 함수 마지막 부분*/ wsprintf(cTitle, TEXT("Stage : %d Key Count : %d"), uiStage + 1, uiScore*100); SetWindowText(hWnd, cTitle);
/*On_KeyDown 함수*/ case VK_RIGHT: ++uiScore; |
- 결과
![](https://t1.daumcdn.net/cfile/tistory/2330BA4F564D882B36)
![](https://t1.daumcdn.net/cfile/tistory/2566EE4F564D882C01)
●Reset.
- 게임이 잘못 되었거나, 다시 하고 싶은 상황을 위해서 만들었다.
- Score 또한 초기화가 되어야 한다.
- Home키를 눌렀을 때 Reset 되도록 한다.
- 소스
/*On_KetDown 함수*/ case VK_HOME: iRet = MessageBox(hWnd, TEXT("다시 하시겠습니까?"), TEXT("알림"), MB_OKCANCEL); if (IDOK == iRet) { LoadMap(); } InvalidateRect(hWnd, NULL, TRUE); return 0; |
- 결과
●최종 결과.
- 전체적인 틀에 의한 결과물이다.
◆제 7장. 차일드.
◉7-1. 버튼.
●7-1-가. 컨트롤의 정의.
- 컨트롤이란?
- 사용자와의 인터페이스를 이루는 도구이다.
●7-1-나. Button.
- 결과
![](https://t1.daumcdn.net/cfile/tistory/264FDB45564D8B4306)