본문으로 바로가기

20151118 - 게임 만들기 ( 2 일 차 )

category 마이 스토리/내용 정리중인 글들.. 2015. 11. 18. 17:36

어제 피부과 갔어서 잠시 복습.


CreateWindow() 인자 값 설정으로 사용자가 창을 확대, 축소 하는 것을 막는다.


CreateWindow() 는 윈도우 클래스와 이 함수의 인수 정보를 바탕으로 하여 윈도우를 생성한다. RegisterClass 함수로 직접 윈도우 클래스를 등록하여

메인 윈도우를 만들 수도 있으며 또는 이미 등록된 컨트롤을 생성할 수도 있다.

이 ㅎ마수는 윈도우 생성 후 WM_CREATE, WM_GETMINMAXINFO, WM_NCCREATE 메시지를 해당 윈도우로 차례대로 보내주며 WS_VISIBLE 스타일이

지정되어 있을 경우 윈도우를 보여주고 활성화 시키기 위한 모든 동작을 하며 관련 메시지를 보내준다.


HWND CreateWindow(lpszClassName, lpszWindowName, dwStyle, x, y, nWidth, nHeight, hwndParent, hmenu, hinst, lpvParam) 


보다시피 인수가 무척 많은 편이다. 윈도우즈 API 함수들은 대체로 인수가 많다. 각 인수의 의미를 알아보자.

þ lpszClassName

생성하고자 하는 윈도우의 클래스를 지정하는 문자열이다. 앞에서 정의한 WndClass구조체의 lpszClassName 멤버의 이름을 여기에 기입해 준다. 우리의 예제에서는 lpszClass 문자열에 윈도우 클래스 이름을 기억시켜 두었으므로 이 문자열을 그대로 넘겨주면 된다.

þ lpszWindowName

윈도우의 타이틀 바에 나타날 문자열이다. 여기서 지정한 문자열이 윈도우의 타이틀 바에 나타난다. 프로그래머가 마음대로 지정할 수 있는데 이 책에서는 예제의 프로젝트명(lpszClass 전역 문자열)을 타이틀 바에 나타내고 있다.

þdwStyle

만들고자 하는 윈도우의 형태를 지정하는 인수이다. 일종의 비트 필드값이며 거의 수십개를 헤아리는 매크로 상수들이 정의되어 있고 이 상수들을 OR연산자로 연결하여 윈도우의 다양한 형태를 지정한다. 윈도우가 경계선을 가질 것인가, 타이틀 바를 가질 것인가 또는 스크롤 바의 유무 등등을 세세하게 지정해 줄 수 있다. 가능한 스타일값에 관한 자세한 내용은 레퍼런스를 참조하되 WS_OVERLAPPEDWINDOW를 사용하면 가장 무난한 윈도우 설정 상태가 된다. 즉 시스템 메뉴, 최대 최소 버튼, 타이틀 바, 경계선을 가진 윈도우를 만들어 준다.

þX, Y, nWidth, nHeight

인수의 이름이 의미하듯이 윈도우의 크기와 위치를 지정하며 픽셀 단위를 사용한다. x, y좌표는 메인 윈도우의 경우는 전체 화면을 기준으로 하며 차일드 윈도우는 부모 윈도우의 좌상단을 기준으로 한다. 정수값을 바로 지정해도 되며 CW_USEDEFAULT를 사용하면 윈도우즈가 알아서 적당한 크기와 위치를 설정해 준다. 예제에서는 모두 CW_USEDEFAULT를 사용하였다.

þhWndParent

부모 윈도우가 있을 경우 부모 윈도우의 핸들을 지정해 준다. MDI 프로그램이나 팝업 윈도우는 윈도우끼리 수직적인 상하관계를 가져 부자(parent-child) 관계가 성립되는데 이 관계를 지정해 주는 인수이다. 부모 윈도우가 없을 경우는 이 값을 NULL로 지정하면 된다.

þhmenu

윈도우에서 사용할 메뉴의 핸들을 지정한다. WndClass에도 메뉴를 지정하는 멤버가 있는데 윈도우 클래스의 메뉴는 그 윈도우 클래스를 기반으로 하는 모든 윈도우에서 사용되는 반면 이 인수로 지정된 메뉴는 현재 CreateWindow 함수로 만들어지는 윈도우에서만 사용된다. 만약 WndClass에서 지정한 메뉴를 그대로 사용하려면 이 인수를 NULL로 지정하면 되며 WndClass에서 지정한 메뉴 대신 다른 메뉴를 사용하려면 이 인수에 원하는 메뉴 핸들을 주면 된다. First 예제의 경우 WndClass에도 메뉴가 지정되어 있지 않고 CreateWindow 함수에서도 메뉴를 지정하지 않았으므로 메뉴없는 프로그램이 만들어진다.

þhinst

윈도우를 만드는 주체, 즉 프로그램의 핸들을 지정한다. WinMain의 인수로 전달된 hInstance를 대입해 주면 된다.

þlpvParam

CREATESTRUCT라는 구조체의 번지이며 특수한 목적에 사용된다. 보통은 NULL값을 사용한다.

지금 당장 여기서 CreateWindow의 모든 인수에 대해 다 외우려고 할 필요까지는 없고 예제에서 어떤 값이 사용되었는가와 그 의미가 무엇인가만 대충 보고 가도록 하자. 여기서 잠깐 필자가 잔소리를 좀 하자면 API 공부를 할 때는 무시할 건 과감하게 무시하고 지나가는 요령이 필요하다. CreateWindow 함수는 API를 처음 배우는 사람에게 무척 중요하기는 하지만 그렇다고 처음부터 11개나 되는 인수들의 정확한 의미까지 속속들이 이해할 필요까지는 없다. x,y,lpszWindowName 등 쉽게 이해되는 인수들만 대충 봐두면 되지 lpvParam같은 전문적이고 어려운 인수의 의미나 dwStyle인수의 모든 스타일값에 대해 반드시 알아야 하는 것은 아니다. 물론 완벽하게 이해한다고 나쁠 것은 없겠지만 그렇게 하다가는 제풀에 지쳐 금새 흥미를 잃고 만다. 중요한 것은 이론적으로 의미가 있는 큰 줄기를 먼저 파악하는데 정성을 쏟으라는 것이다.

CreateWindow 함수는 윈도우에 관한 모든 정보를 메모리에 만든 후 윈도우 핸들을 리턴값으로 넘겨준다. 넘겨지는 윈도우 핸들은 hWnd라는 지역 변수에 저장되었다가 윈도우를 참조하는 모든 함수의 인수로 사용된다.

CreateWindow 함수로 만든 윈도우는 어디까지나 메모리상에서만 있을 뿐이며 아직까지 화면에 출력되지는 않았다. 메모리에 만들어진 윈도우를 화면으로 보이게 하려면 다음 함수를 사용해야 한다.



BOOL ShowWindow(hWnd, nCmdShow); 




















우선 수업. 현재 오전까지 완료된 소스는,


#include <windows.h>
#include "Smart.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass = L"Game";

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
  HWND hWnd;
  MSG Message;
  WNDCLASS WndClass;
  g_hInst = hInstance;

  WndClass.cbClsExtra = 0;
  WndClass.cbWndExtra = 0;
  WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hInstance = hInstance;
  WndClass.lpfnWndProc = (WNDPROC)WndProc;
  WndClass.lpszClassName = lpszClass;
  WndClass.lpszMenuName = NULL;
  WndClass.style = CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&WndClass);
  //X_WIN_SIZE, Y_WIN_SIZE 로 창이 어디에 뜨는지 설정.
  hWnd = CreateWindow
    (
      lpszClass,        // 윈도우의 클래스를 지정하는 문자열
      lpszClass,        // 윈도우의 타이틀 바 이름
      WS_OVERLAPPEDWINDOW,  // 윈도우의 형태를 지정, WS_OVERLAPPEDWINDOW 은 가장 무난한 형태의 윈도우 설정 상태
      CW_USEDEFAULT,      // 윈도우의 크기와 위치를 지정, x, CW_USEDEFAULT 은 적당한 크기와 위치를 설정, 차일드 윈도우는 부모 윈도우의 좌상단을 기준
      CW_USEDEFAULT,      // y
      X_WIN_SIZE,        // 차일드 윈도우의 창 크기 설정, x
      Y_WIN_SIZE,        // y
      NULL, (HMENU)NULL, hInstance, NULL  // 이건 나중에..
    );
  ShowWindow(hWnd, nCmdShow);

  while (GetMessage(&Message, 000)) 
  {
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }
  return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
  static HBITMAP hbmFront;
  static HBITMAP hbmBack;
  static HBITMAP hbmLeft;
  static HBITMAP hbmRight;

  switch (iMessage) 
  {
    case WM_CREATE:
      
      return 0;

    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
  }

  return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}


이러하며 윈 프로시져 내부 지역 변수를 선언하다가 일시중지했다.

어제 했듯이 캐릭터의 움직임에 따라 해당하는 비트맵 메모리에서 복사시켜 화면에 띄우는 가보다. 어제 안했으니 조금의 분석이 필요하다.

현재 내 캐릭터는,






이러하다.



어제 했던 게임 소스를 잠시 분석해보자.


#include <windows.h>
#include "resource.h"

#define XMOVE  8
#define YMOVE  8
#define XTILE  48
#define YTILE  48

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass = "Bitmap";

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
  , LPSTR lpszCmdParam, int nCmdShow)
{
  HWND hWnd;
  MSG Message;
  WNDCLASS WndClass;
  g_hInst = hInstance;

  WndClass.cbClsExtra = 0;
  WndClass.cbWndExtra = 0;
  WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hInstance = hInstance;
  WndClass.lpfnWndProc = (WNDPROC)WndProc;
  WndClass.lpszClassName = lpszClass;
  WndClass.lpszMenuName = NULL;
  WndClass.style = CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&WndClass);

  hWnd = CreateWindow(
    lpszClass,
    lpszClass,
    WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    735,
    510,
    NULL, (HMENU)NULL, hInstance, NULL);
  ShowWindow(hWnd, nCmdShow);

  while (GetMessage(&Message, 000))
  {
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }
  return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  static HDC MemDC;
  static RECT rt;
  PAINTSTRUCT ps;
  static HBITMAP MyBitmap;
  static HBITMAP OldBitmap;

  static int ixPos = 0;
  static int iyPos = 0;

  static int ixPos_max;
  static int iyPos_max;

  static HBITMAP BackBitmap;
  int iCntx;
  int iCnty;

  static HBITMAP frontBitmap;
  static HBITMAP leftBitmap;
  static HBITMAP rearBitmap;
  static HBITMAP rightBitmap;

  RECT rtArea;

  switch (iMessage)
  {
  case WM_CREATE:
    hdc = GetDC(hWnd);
    MemDC = CreateCompatibleDC(hdc);
    frontBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP3)); 
    leftBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP5));
    rightBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP6));
    rearBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP7));

    BackBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP4));

    OldBitmap = (HBITMAP)SelectObject(MemDC, frontBitmap);
    
    GetClientRect(hWnd, &rt);

    ixPos_max = rt.right;
    iyPos_max = rt.bottom;

    ReleaseDC(hWnd, hdc);
    MyBitmap = frontBitmap;      
    return 0;

  case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    SelectObject(MemDC, BackBitmap);
    for ( iCnty = 0; iCnty <10; iCnty++ )
    {
      for (iCntx = 0; iCntx <15; iCntx++)
      {
        BitBlt(hdc, (iCntx*48), (iCnty*48), 4848, MemDC, 00, SRCCOPY);
      }
    }
    SelectObject(MemDC, MyBitmap);
    BitBlt(hdc, ixPos, iyPos, 4848, MemDC, 00, SRCCOPY);
    EndPaint(hWnd, &ps);
    return 0;

  case WM_KEYDOWN:
    rtArea.left = ixPos;
    rtArea.right = ixPos + XTILE;
    rtArea.top = iyPos;
    rtArea.bottom = iyPos + YTILE;
    switch (wParam) 
    {
    case VK_LEFT:
      ixPos = ixPos - XMOVE;  
      rtArea.left = ixPos-XMOVE;
      if (ixPos <= 0)
      {
        ixPos = 0;
        rtArea.left = 0;
      }
      MyBitmap = leftBitmap;
      break;
    case VK_RIGHT:
      ixPos = ixPos + XMOVE;
      if (ixPos > ixPos_max - XTILE)
      {
        ixPos = ixPos_max - XTILE;
      }
      MyBitmap = rightBitmap;
      rtArea.right = ixPos + XTILE + XMOVE;
      break;
    case VK_UP:
      iyPos = iyPos - YMOVE;
      if (iyPos < 0)
      {
        iyPos = 0;
      }
      MyBitmap = rearBitmap;
      break;
    case VK_DOWN:
      iyPos = iyPos + YMOVE;
      if (iyPos > iyPos_max - YTILE)
      {
        iyPos = iyPos_max - YTILE;
      }
      MyBitmap = frontBitmap;
      break;
    }
    InvalidateRect(hWnd, &rtArea, TRUE);
    return 0;

  case WM_DESTROY:
    SelectObject(MemDC, OldBitmap);
    DeleteObject(frontBitmap);        
    DeleteObject(leftBitmap);

    DeleteObject(BackBitmap);
    DeleteDC(MemDC);

    PostQuitMessage(0);
    return 0;
  }
  return(DefWindowProcA(hWnd, iMessage, wParam, lParam));
}



량경 누나의 소스다.


조금 눈여겨 봐야할 부분은


 hWnd = CreateWindow(
    lpszClass,
    lpszClass,
    WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
    CW_USEDEFAULT,
    CW_USEDEFAULT,
    735,
    510,
    NULL, (HMENU)NULL, hInstance, NULL);

이 부분의 3번째 인자 값인 윈도우의 형태를 지정하는 디파인 값들이 적당한 크기로 만드는 것이 아닌 직접 옵션을 달아준 모습이다.





이와 같다.


이제 눈여겨 볼 달라진 부분은 윈 프로시져 내부의


switch (iMessage)
  {
  case WM_CREATE:
    hdc = GetDC(hWnd);
    MemDC = CreateCompatibleDC(hdc);
    frontBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP3)); 
    leftBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP5));
    rightBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP6));
    rearBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP7));

    BackBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP4));

    OldBitmap = (HBITMAP)SelectObject(MemDC, frontBitmap);
    
    GetClientRect(hWnd, &rt);

    ixPos_max = rt.right;
    iyPos_max = rt.bottom;

    ReleaseDC(hWnd, hdc);
    MyBitmap = frontBitmap;      
    return 0;

  case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    SelectObject(MemDC, BackBitmap);
    for ( iCnty = 0; iCnty <10; iCnty++ )
    {
      for (iCntx = 0; iCntx <15; iCntx++)
      {
        BitBlt(hdc, (iCntx*48), (iCnty*48), 4848, MemDC, 00, SRCCOPY);
      }
    }
    SelectObject(MemDC, MyBitmap);
    BitBlt(hdc, ixPos, iyPos, 4848, MemDC, 00, SRCCOPY);
    EndPaint(hWnd, &ps);
    return 0;

  case WM_KEYDOWN:
    rtArea.left = ixPos;
    rtArea.right = ixPos + XTILE;
    rtArea.top = iyPos;
    rtArea.bottom = iyPos + YTILE;
    switch (wParam) 
    {
    case VK_LEFT:
      ixPos = ixPos - XMOVE;  
      rtArea.left = ixPos-XMOVE;
      if (ixPos <= 0)
      {
        ixPos = 0;
        rtArea.left = 0;
      }
      MyBitmap = leftBitmap;
      break;
    case VK_RIGHT:
      ixPos = ixPos + XMOVE;            // 8 칸 이동.
      if (ixPos > ixPos_max - XTILE)
      {
        ixPos = ixPos_max - XTILE;
      }
      MyBitmap = rightBitmap;
      rtArea.right = ixPos + XTILE + XMOVE;
      break;
    case VK_UP:
      iyPos = iyPos - YMOVE;
      if (iyPos < 0)
      {
        iyPos = 0;
      }
      MyBitmap = rearBitmap;
      break;
    case VK_DOWN:
      iyPos = iyPos + YMOVE;
      if (iyPos > iyPos_max - YTILE)
      {
        iyPos = iyPos_max - YTILE;
      }
      MyBitmap = frontBitmap;
      break;
    }
    InvalidateRect(hWnd, &rtArea, TRUE);
    return 0;

  case WM_DESTROY:
    SelectObject(MemDC, OldBitmap);
    DeleteObject(frontBitmap);        
    DeleteObject(leftBitmap);

    DeleteObject(BackBitmap);
    DeleteDC(MemDC);

    PostQuitMessage(0);
    return 0;
  }


switch-case 문이다.

자세히 보면, 기본 예제에서 좀더 속도 최적화를 시킨 모습이 보인다. (프로그램이 실행되는 중에는 메모리에 비트맵을 올려놓고 복사해서 출력하는, 종료되면 전부 해제)

우선 윈도우를 생성하는 곳부터 살펴보자.

 case WM_CREATE:
    hdc = GetDC(hWnd);
    MemDC = CreateCompatibleDC(hdc);
    frontBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP3)); 
    leftBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP5));
    rightBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP6));
    rearBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP7));

    BackBitmap = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP4));

    OldBitmap = (HBITMAP)SelectObject(MemDC, frontBitmap);
    
    GetClientRect(hWnd, &rt);

    ixPos_max = rt.right;
    iyPos_max = rt.bottom;

    ReleaseDC(hWnd, hdc);
    MyBitmap = frontBitmap;      
    return 0;


1. DC 를 만든다.

2. 프로그램 내부에서 미리 그리기를 해 놓기 위해서 메모리 DC를 만든다.

3. 캐릭터의 바라보는 시점을 4단계로 나누어 메모리에 올린 뒤, 각 비트맵 변수에 핸들러를 저장한다.

4. 맨 처음 비트맵을 그릴 때, 비트맵 그림의 기본설정을 캐릭터 앞면으로 설정해 놓고 그 전 핸들러를 나중에 백업하기 위해 OldBitmap에 저장한다.

5. GetClientRect() 로 윈도우의 작업영역 크기를 계산한다.

6. 이건.. 작업영역의 끝 x, y 좌표를 변수에 저장해 놓는다.

7. 이제 DC는 메모리 DC가 있으므로 해제 시킨다.

8. MyBitmap 변수에 캐릭터의 앞 비트맵을 저장 시킨다.



 case WM_PAINT:
    hdc = BeginPaint(hWnd, &ps);
    SelectObject(MemDC, BackBitmap);
    for ( iCnty = 0; iCnty <10; iCnty++ )
    {
      for (iCntx = 0; iCntx <15; iCntx++)
      {
        BitBlt(hdc, (iCntx*48), (iCnty*48), 4848, MemDC, 00, SRCCOPY);
      }
    }
    SelectObject(MemDC, MyBitmap);
    BitBlt(hdc, ixPos, iyPos, 4848, MemDC, 00, SRCCOPY);
    EndPaint(hWnd, &ps);
    return 0;


1. 





현재 까지 한 소스이다.

현재 On_Create(), On_Destroy() 를 만들어 차일드 윈도우의 생성(LoadBitMap() 을 다 막고 메시지 박스로 우선적으로 잘되는 지 확인), 파괴(메모리에 올린 비트맵을 파괴 하는 것을 주석 처리하고 메시지 박스로 우선적으로 잘되는 지 확인)를 함수로 정의하고 asm 수업때처럼 함수 포인터로 가르키고 있다.

#include <windows.h>
#include "resource.h"
#include "Smart.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass = L"PushPushGame";

LRESULT On_Destroy(HWND, WPARAM, LPARAM);
LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam);
StMsgMap msgMap[] = 
{
  { WM_DESTROY, On_Destroy },
  { WM_CREATE, On_Create },
  { WM_NULL, 0 }
};

static HBITMAP hbmFront;
static HBITMAP hbmBack;
static HBITMAP hbmLeft;
static HBITMAP hbmRight;

static HBITMAP hbmGround;
static HBITMAP hbmRoad;
static HBITMAP hbmHero;
static HBITMAP hbmBox;
static HBITMAP hbmDot;

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
  HWND hWnd;
  MSG Message;
  WNDCLASS WndClass;
  g_hInst = hInstance;

  WndClass.cbClsExtra = 0;
  WndClass.cbWndExtra = 0;
  WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hInstance = hInstance;
  WndClass.lpfnWndProc = (WNDPROC)WndProc;
  WndClass.lpszClassName = lpszClass;
  WndClass.lpszMenuName = NULL;
  WndClass.style = CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&WndClass);
  //X_WIN_SIZE, Y_WIN_SIZE 로 창이 어디에 뜨는지 설정.
  hWnd = CreateWindow
    (
      lpszClass,        // 윈도우의 클래스를 지정하는 문자열
      lpszClass,        // 윈도우의 타이틀 바 이름
      WS_OVERLAPPEDWINDOW,  // 윈도우의 형태를 지정, WS_OVERLAPPEDWINDOW 은 가장 무난한 형태의 윈도우 설정 상태
      CW_USEDEFAULT,      // 윈도우의 크기와 위치를 지정, x, CW_USEDEFAULT 은 적당한 크기와 위치를 설정, 차일드 윈도우는 부모 윈도우의 좌상단을 기준
      CW_USEDEFAULT,      // y
      X_WIN_SIZE,        // 차일드 윈도우의 창 크기 설정, x
      Y_WIN_SIZE,        // y
      NULL, (HMENU)NULL, hInstance, NULL  // 이건 나중에..
    );
  ShowWindow(hWnd, nCmdShow);

  while (GetMessage(&Message, 000)) 
  {
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }
  return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
  StMsgMap *stpMap = msgMap;

  /*asm 때 했던 방식 그대로 활용, switch-case 를 함수포인터로 표현. - 이렇게 한다면 공유하는 변수들을 전역에 가지고 있어야 한다.*/
  while ((*stpMap).uiMsg != WM_NULL)
  {
    if (iMessage == (*stpMap).uiMsg)
    {
      ((*stpMap).fp)(hWnd, wParam, lParam);

      return 0;
    }

    ++stpMap;
  }

  /*
  switch (iMessage)
  {
  case WM_CREATE:
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  return 0;

  case WM_DESTROY:
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  PostQuitMessage(0);
  return 0;
  }
  */


  return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}

LRESULT On_Destroy(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
  MessageBox(hWnd, TEXT("On_Destroy() 호출"), TEXT("Button"), MB_OK);
  /*
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  */

  PostQuitMessage(0);
  
  return 0;
}

LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
  MessageBox(hWnd, TEXT("On_Create() 호출"), TEXT("Button"), MB_OK);
  /*
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  */

  return 0;
}



MFC 구조와 원리를 배우면 분할 컴파일 시 어디서 에러가 났는지 정확히 알 수 있다. 

이것은 Manager, view, controller 와 같이 MVC 방식으로 분할 컴파일 하는 것과 유사한 느낌이다.


TEXT() 를 사용하지 않고 () 안에 변수를 str 변수를 넣어 글자가 깨지지 않고 출력하는 어떤 좋은 함수가 있을까?


MessageBox()함수, 

새로운 창을 띄우고 프로그래머가 원하는 메세지를 띄운다.

사용자와 대화할 수 있는 가장 간단한 방법이다. 짧은 메시지와 함께 MB_OK 플래그로 간단하게 전달 사항만 전달하는 것이

보편적이다.

MessageBox("메세지", "창제목", "아이콘 | 버튼(상수값)")




이제 주석을 전부 지운다음 제대로 오류 없이 실행되는지 확인 해보자.


#include <windows.h>
#include "resource.h"
#include "Smart.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass = L"PushPushGame";

LRESULT On_Destroy(HWND, WPARAM, LPARAM);
LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam);
StMsgMap msgMap[] = 
{
  { WM_DESTROY, On_Destroy },
  { WM_CREATE, On_Create },
  { WM_NULL, 0 }
};

static HBITMAP hbmFront;
static HBITMAP hbmBack;
static HBITMAP hbmLeft;
static HBITMAP hbmRight;

static HBITMAP hbmGround;
static HBITMAP hbmRoad;
static HBITMAP hbmHero;
static HBITMAP hbmBox;
static HBITMAP hbmDot;

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
  HWND hWnd;
  MSG Message;
  WNDCLASS WndClass;
  g_hInst = hInstance;

  WndClass.cbClsExtra = 0;
  WndClass.cbWndExtra = 0;
  WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hInstance = hInstance;
  WndClass.lpfnWndProc = (WNDPROC)WndProc;
  WndClass.lpszClassName = lpszClass;
  WndClass.lpszMenuName = NULL;
  WndClass.style = CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&WndClass);
  //X_WIN_SIZE, Y_WIN_SIZE 로 창이 어디에 뜨는지 설정.
  hWnd = CreateWindow
    (
      lpszClass,        // 윈도우의 클래스를 지정하는 문자열
      lpszClass,        // 윈도우의 타이틀 바 이름
      WS_OVERLAPPEDWINDOW,  // 윈도우의 형태를 지정, WS_OVERLAPPEDWINDOW 은 가장 무난한 형태의 윈도우 설정 상태
      CW_USEDEFAULT,      // 윈도우의 크기와 위치를 지정, x, CW_USEDEFAULT 은 적당한 크기와 위치를 설정, 차일드 윈도우는 부모 윈도우의 좌상단을 기준
      CW_USEDEFAULT,      // y
      X_WIN_SIZE,        // 차일드 윈도우의 창 크기 설정, x
      Y_WIN_SIZE,        // y
      NULL, (HMENU)NULL, hInstance, NULL  // 이건 나중에..
    );
  ShowWindow(hWnd, nCmdShow);

  while (GetMessage(&Message, 000)) 
  {
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }
  return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
  StMsgMap *stpMap = msgMap;

  /*asm 때 했던 방식 그대로 활용, switch-case 를 함수포인터로 표현. - 이렇게 한다면 공유하는 변수들을 전역에 가지고 있어야 한다.*/
  while ((*stpMap).uiMsg != WM_NULL)
  {
    if (iMessage == (*stpMap).uiMsg)
    {
      ((*stpMap).fp)(hWnd, wParam, lParam);

      return 0;
    }

    ++stpMap;
  }

  /*
  switch (iMessage)
  {
  case WM_CREATE:
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  return 0;

  case WM_DESTROY:
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  PostQuitMessage(0);
  return 0;
  }
  */


  return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}

LRESULT On_Destroy(HWND hWnd, WPARAM wParam, LPARAM lParam)
{  
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  
  PostQuitMessage(0);
  
  return 0;
}

LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  
  return 0;
}


실행 화면은,


이처럼 아무것도 없지만,

예상해보자면 제대로 분명 메모리에 비트맵이 로드 되고, 이 실행 창을 닫을 때에 DestroyObject() 를 실행해서 메모리에 올려진 비트맵들을 해제할 것이다.



자 이제, Paint 쪽을 만들어보자.



LRESULT On_Paint(HWND hWnd, WPARAM wParam, LPARAM lParam)

{

  HDC hdc;

  PAINTSTRUCT ps;


  hdc = BeginPaint(hWnd, &ps);

  EndPaint(hWnd, &ps);


  return 0;


왜 색깔이.. 아무튼 이러하고, 위에도 함수 포인터로 가르킬 uiMsg 에 WM_PAINT 추가 해주고 이 함수명도 추가 해줘야 할 것이다.
음.. 그냥 전체 소스 다시.

#include <windows.h>
#include "resource.h"
#include "Smart.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass = L"PushPushGame";

LRESULT On_Destroy(HWND, WPARAM, LPARAM);
LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam);
LRESULT On_Paint(HWND hWnd, WPARAM wParam, LPARAM lParam);
StMsgMap msgMap[] = 
{
  { WM_DESTROY, On_Destroy },
  { WM_CREATE, On_Create },
  { WM_PAINT, On_Paint },
  { WM_NULL, 0 }
};

static HBITMAP hbmFront;
static HBITMAP hbmBack;
static HBITMAP hbmLeft;
static HBITMAP hbmRight;

static HBITMAP hbmGround;
static HBITMAP hbmRoad;
static HBITMAP hbmHero;
static HBITMAP hbmBox;
static HBITMAP hbmDot;

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
  HWND hWnd;
  MSG Message;
  WNDCLASS WndClass;
  g_hInst = hInstance;

  WndClass.cbClsExtra = 0;
  WndClass.cbWndExtra = 0;
  WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hInstance = hInstance;
  WndClass.lpfnWndProc = (WNDPROC)WndProc;
  WndClass.lpszClassName = lpszClass;
  WndClass.lpszMenuName = NULL;
  WndClass.style = CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&WndClass);
  //X_WIN_SIZE, Y_WIN_SIZE 로 창이 어디에 뜨는지 설정.
  hWnd = CreateWindow
    (
      lpszClass,        // 윈도우의 클래스를 지정하는 문자열
      lpszClass,        // 윈도우의 타이틀 바 이름
      WS_OVERLAPPEDWINDOW,  // 윈도우의 형태를 지정, WS_OVERLAPPEDWINDOW 은 가장 무난한 형태의 윈도우 설정 상태
      CW_USEDEFAULT,      // 윈도우의 크기와 위치를 지정, x, CW_USEDEFAULT 은 적당한 크기와 위치를 설정, 차일드 윈도우는 부모 윈도우의 좌상단을 기준
      CW_USEDEFAULT,      // y
      X_WIN_SIZE,        // 차일드 윈도우의 창 크기 설정, x
      Y_WIN_SIZE,        // y
      NULL, (HMENU)NULL, hInstance, NULL  // 이건 나중에..
    );
  ShowWindow(hWnd, nCmdShow);

  while (GetMessage(&Message, 000)) 
  {
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }
  return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
  StMsgMap *stpMap = msgMap;

  /*asm 때 했던 방식 그대로 활용, switch-case 를 함수포인터로 표현. - 이렇게 한다면 공유하는 변수들을 전역에 가지고 있어야 한다.*/
  while ((*stpMap).uiMsg != WM_NULL)
  {
    if (iMessage == (*stpMap).uiMsg)
    {
      ((*stpMap).fp)(hWnd, wParam, lParam);

      return 0;
    }

    ++stpMap;
  }

  /*
  switch (iMessage)
  {
  case WM_CREATE:
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  return 0;

  case WM_DESTROY:
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  PostQuitMessage(0);
  return 0;
  }
  */


  return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}

LRESULT On_Destroy(HWND hWnd, WPARAM wParam, LPARAM lParam)
{  
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  
  PostQuitMessage(0);
  
  return 0;
}

LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam)
{  
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  
  return 0;
}

LRESULT On_Paint(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;

  hdc = BeginPaint(hWnd, &ps);
  EndPaint(hWnd, &ps);

  return 0;
}


여기선 또 색깔로 잘 나오네. 어쨌든 이렇다.

실행 해보니 다시 잘 된다. 우선 Paint가 잘 동작하는지 확인하기 위해 내부 정의하는 곳으로 가서 직접 뭐 좀 그려보자.

soen의 예제 중 간단히 줄을 그리는 예제를 뽑아와서, 그렸더니



아주 잘 나오는 것을 확인 했다.

뭔가 내부적으로 서로서로 우선순위가 정해져 있고 WM_CREATE 를 먼저 실행 한 뒤, 그 다음 우선순위 우선순위 이런 식으로 진행하고

'어떠한 일이 벌어진다면' WM_~ 호출 이런식으로 되어 있는 느낌이다.

WM_PAINT, WM_CREATE 등등 은 간단히 선 처리 작업으로 만약 내부적으로 정의가 되어있다면 프로그램이 실행 도중에

반드시 우선 적으로 한 번은 실행을 하게끔 되어있는 느낌이다. 자세한건 나중에 더 알아 보도록 하고,


소스는.

#include <windows.h>
#include "resource.h"
#include "Smart.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass = L"PushPushGame";

LRESULT On_Destroy(HWND, WPARAM, LPARAM);
LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam);
LRESULT On_Paint(HWND hWnd, WPARAM wParam, LPARAM lParam);
StMsgMap msgMap[] = 
{
  { WM_DESTROY, On_Destroy },
  { WM_CREATE, On_Create },
  { WM_PAINT, On_Paint },
  { WM_NULL, 0 }
};

static HBITMAP hbmFront;
static HBITMAP hbmBack;
static HBITMAP hbmLeft;
static HBITMAP hbmRight;

static HBITMAP hbmGround;
static HBITMAP hbmRoad;
static HBITMAP hbmHero;
static HBITMAP hbmBox;
static HBITMAP hbmDot;

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
  HWND hWnd;
  MSG Message;
  WNDCLASS WndClass;
  g_hInst = hInstance;

  WndClass.cbClsExtra = 0;
  WndClass.cbWndExtra = 0;
  WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hInstance = hInstance;
  WndClass.lpfnWndProc = (WNDPROC)WndProc;
  WndClass.lpszClassName = lpszClass;
  WndClass.lpszMenuName = NULL;
  WndClass.style = CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&WndClass);
  //X_WIN_SIZE, Y_WIN_SIZE 로 창이 어디에 뜨는지 설정.
  hWnd = CreateWindow
    (
      lpszClass,        // 윈도우의 클래스를 지정하는 문자열
      lpszClass,        // 윈도우의 타이틀 바 이름
      WS_OVERLAPPEDWINDOW,  // 윈도우의 형태를 지정, WS_OVERLAPPEDWINDOW 은 가장 무난한 형태의 윈도우 설정 상태
      CW_USEDEFAULT,      // 윈도우의 크기와 위치를 지정, x, CW_USEDEFAULT 은 적당한 크기와 위치를 설정, 차일드 윈도우는 부모 윈도우의 좌상단을 기준
      CW_USEDEFAULT,      // y
      X_WIN_SIZE,        // 차일드 윈도우의 창 크기 설정, x
      Y_WIN_SIZE,        // y
      NULL, (HMENU)NULL, hInstance, NULL  // 이건 나중에..
    );
  ShowWindow(hWnd, nCmdShow);

  while (GetMessage(&Message, 000)) 
  {
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }
  return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
  StMsgMap *stpMap = msgMap;

  /*asm 때 했던 방식 그대로 활용, switch-case 를 함수포인터로 표현. - 이렇게 한다면 공유하는 변수들을 전역에 가지고 있어야 한다.*/
  while ((*stpMap).uiMsg != WM_NULL)
  {
    if (iMessage == (*stpMap).uiMsg)
    {
      ((*stpMap).fp)(hWnd, wParam, lParam);

      return 0;
    }

    ++stpMap;
  }

  /*
  switch (iMessage)
  {
  case WM_CREATE:
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  return 0;

  case WM_DESTROY:
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  PostQuitMessage(0);
  return 0;
  }
  */


  return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}

LRESULT On_Destroy(HWND hWnd, WPARAM wParam, LPARAM lParam)
{  
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  
  PostQuitMessage(0);
  
  return 0;
}

LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam)
{  
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  
  return 0;
}

LRESULT On_Paint(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;

  hdc = BeginPaint(hWnd, &ps);
  MoveToEx(hdc, 150150, NULL);
  LineTo(hdc, 180200);
  Rectangle(hdc, 200100250180);
  Ellipse(hdc, 200200250280);
  EndPaint(hWnd, &ps);

  return 0;
}



이러하다.

On_Paint() 의 내부를 살펴보면,


  hdc = BeginPaint(hWnd, &ps);
  MoveToEx(hdc, 150150, NULL);
  LineTo(hdc, 180200);
  Rectangle(hdc, 200100250180);
  Ellipse(hdc, 200200250280);
  EndPaint(hWnd, &ps);


이렇게 되어있는것을 볼 수 있는데, 분석해 보자면


1. BeginPaint() 로 그리기에 사용할 DC를 가져온다.

2. MoveToEx() 로 DC에 저장되어있는 현재 좌표를 이 함수로 해당하는 x, y 좌표로 이동시켜준다.

3. LineTo() 로 현재 좌표(hdc에 저장되어 있는 좌표)에서 지정되어 있는 좌표까지 선을 그린다.

4. Rectangle() 로 지정된 사각형을 그린다. 사각형의 변은 현재 DC에 선택된 펜으로 그려지며 <SelectObject(펜)> 내부는 현재 DC에 선택된 브러시로

채워진다. 터보 C의 Rectangle() 와는 달리 내부를 채우므로 도스 프로그래밍을 전문적으로 하셨던 프로그래머라면 주의 해서 코딩작업을 해야 한다.

내부를 채우지 않으려면 NULL_BRUSH 스톡 오브젝트를 선택한 후 사각형을 그려야 한다.

현재는 기본 적인 하얀 브러쉬로 WinMain에서 초기 설정을 해 줬기 때문에 하얀 브러쉬로 내부를 색칠할 것이다.

5. Ellipse(), 애는 타원을 그려준다. 사각형과 원리는 비슷하다. 사각형에 내접하는 타원을 그린다.

타원의 원주는 현재 DC에 선택된 펜으로 그려지며 타원의 내부는 현재 DC에 선택된 브러시로 채워진다.

6. EndPaint() 로 할당된 그리기 DC를 해제해 주는데 이 때 BeginPaint() 가 캐럿을 숨겼을 경우 캐럿을 다시 화면으로 출력해 주는 역활을 한다.



자, 이제 WM_KEYDOWN.

캐릭터 움직이는 소스를 짜보자. 현재 소스는.




#include <windows.h>
#include "resource.h"
#include "Smart.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass = L"PushPushGame";

LRESULT On_Destroy(HWND, WPARAM, LPARAM);
LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam);
LRESULT On_Paint(HWND hWnd, WPARAM wParam, LPARAM lParam);
LRESULT On_KeyDown(HWND hWnd, WPARAM wParam, LPARAM lParam);
StMsgMap msgMap[] = 
{
  { WM_DESTROY, On_Destroy },
  { WM_CREATE, On_Create },
  { WM_PAINT, On_Paint },
  { WM_KEYDOWN, On_KeyDown },
  { WM_NULL, 0 }
};

static HBITMAP hbmFront;
static HBITMAP hbmBack;
static HBITMAP hbmLeft;
static HBITMAP hbmRight;

static HBITMAP hbmGround;
static HBITMAP hbmRoad;
static HBITMAP hbmHero;
static HBITMAP hbmBox;
static HBITMAP hbmDot;

HDC memDC;

static int iXPos;
static int iYPos;

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
  HWND hWnd;
  MSG Message;
  WNDCLASS WndClass;
  g_hInst = hInstance;

  WndClass.cbClsExtra = 0;
  WndClass.cbWndExtra = 0;
  WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hInstance = hInstance;
  WndClass.lpfnWndProc = (WNDPROC)WndProc;
  WndClass.lpszClassName = lpszClass;
  WndClass.lpszMenuName = NULL;
  WndClass.style = CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&WndClass);
  //X_WIN_SIZE, Y_WIN_SIZE 로 창이 어디에 뜨는지 설정.
  hWnd = CreateWindow
    (
      lpszClass,        // 윈도우의 클래스를 지정하는 문자열
      lpszClass,        // 윈도우의 타이틀 바 이름
      WS_OVERLAPPEDWINDOW,  // 윈도우의 형태를 지정, WS_OVERLAPPEDWINDOW 은 가장 무난한 형태의 윈도우 설정 상태
      CW_USEDEFAULT,      // 윈도우의 크기와 위치를 지정, x, CW_USEDEFAULT 은 적당한 크기와 위치를 설정, 차일드 윈도우는 부모 윈도우의 좌상단을 기준
      CW_USEDEFAULT,      // y
      X_WIN_SIZE,        // 차일드 윈도우의 창 크기 설정, x
      Y_WIN_SIZE,        // y
      NULL, (HMENU)NULL, hInstance, NULL  // 이건 나중에..
    );
  ShowWindow(hWnd, nCmdShow);

  while (GetMessage(&Message, 000)) 
  {
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }
  return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
  StMsgMap *stpMap = msgMap;

  /*asm 때 했던 방식 그대로 활용, switch-case 를 함수포인터로 표현. - 이렇게 한다면 공유하는 변수들을 전역에 가지고 있어야 한다.*/
  while ((*stpMap).uiMsg != WM_NULL)
  {
    if (iMessage == (*stpMap).uiMsg)
    {
      ((*stpMap).fp)(hWnd, wParam, lParam);

      return 0;
    }

    ++stpMap;
  }

  /*
  switch (iMessage)
  {
  case WM_CREATE:
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  return 0;

  case WM_DESTROY:
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  PostQuitMessage(0);
  return 0;
  }
  */


  return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}

LRESULT On_Destroy(HWND hWnd, WPARAM wParam, LPARAM lParam)
{  
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  DeleteDC(memDC);
  
  PostQuitMessage(0);
  
  return 0;
}

LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam)
{  
  HDC hdc;

  hdc = GetDC(hWnd);
  memDC = CreateCompatibleDC(hdc);
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  ReleaseDC(hWnd, hdc);

  hbmHero = hbmFront;

  return 0;
}

LRESULT On_Paint(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;

  hdc = BeginPaint(hWnd, &ps);
  SelectObject(memDC, hbmHero);
  BitBlt(hdc, iXPos, iYPos, 4848, memDC, 00, SRCCOPY);
  EndPaint(hWnd, &ps);

  return 0;
}

LRESULT On_KeyDown(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
  switch (wParam) 
  {
    case VK_LEFT:
      iXPos = iXPos - X_MOVE;
      break;

    case VK_RIGHT:
      iXPos = iXPos + X_MOVE;  
      break;

    case VK_UP:
      iYPos = iYPos - Y_MOVE;
      break;

    case VK_DOWN:
      iYPos = iYPos + Y_MOVE;
      break;
  }

  InvalidateRect(hWnd, NULL, TRUE);

  return 0;
}


현재는 경계검사 없이 그저 위, 아래, 왼쪽, 오른쪽 전부 이동한다.









hbmHero 변수를 해당 방향키에 맞게끔 bmp를 조정만 해주면,




#include <windows.h>
#include "resource.h"
#include "Smart.h"

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPSTR lpszClass = L"PushPushGame";

LRESULT On_Destroy(HWND, WPARAM, LPARAM);
LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam);
LRESULT On_Paint(HWND hWnd, WPARAM wParam, LPARAM lParam);
LRESULT On_KeyDown(HWND hWnd, WPARAM wParam, LPARAM lParam);
StMsgMap msgMap[] = 
{
  { WM_DESTROY, On_Destroy },
  { WM_CREATE, On_Create },
  { WM_PAINT, On_Paint },
  { WM_KEYDOWN, On_KeyDown },
  { WM_NULL, 0 }
};

static HBITMAP hbmFront;
static HBITMAP hbmBack;
static HBITMAP hbmLeft;
static HBITMAP hbmRight;

static HBITMAP hbmGround;
static HBITMAP hbmRoad;
static HBITMAP hbmHero;
static HBITMAP hbmBox;
static HBITMAP hbmDot;

HDC memDC;

static int iXPos;
static int iYPos;

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
  HWND hWnd;
  MSG Message;
  WNDCLASS WndClass;
  g_hInst = hInstance;

  WndClass.cbClsExtra = 0;
  WndClass.cbWndExtra = 0;
  WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hInstance = hInstance;
  WndClass.lpfnWndProc = (WNDPROC)WndProc;
  WndClass.lpszClassName = lpszClass;
  WndClass.lpszMenuName = NULL;
  WndClass.style = CS_HREDRAW | CS_VREDRAW;
  RegisterClass(&WndClass);
  //X_WIN_SIZE, Y_WIN_SIZE 로 창이 어디에 뜨는지 설정.
  hWnd = CreateWindow
    (
      lpszClass,        // 윈도우의 클래스를 지정하는 문자열
      lpszClass,        // 윈도우의 타이틀 바 이름
      WS_OVERLAPPEDWINDOW,  // 윈도우의 형태를 지정, WS_OVERLAPPEDWINDOW 은 가장 무난한 형태의 윈도우 설정 상태
      CW_USEDEFAULT,      // 윈도우의 크기와 위치를 지정, x, CW_USEDEFAULT 은 적당한 크기와 위치를 설정, 차일드 윈도우는 부모 윈도우의 좌상단을 기준
      CW_USEDEFAULT,      // y
      X_WIN_SIZE,        // 차일드 윈도우의 창 크기 설정, x
      Y_WIN_SIZE,        // y
      NULL, (HMENU)NULL, hInstance, NULL  // 이건 나중에..
    );
  ShowWindow(hWnd, nCmdShow);

  while (GetMessage(&Message, 000)) 
  {
    TranslateMessage(&Message);
    DispatchMessage(&Message);
  }
  return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
  StMsgMap *stpMap = msgMap;

  /*asm 때 했던 방식 그대로 활용, switch-case 를 함수포인터로 표현. - 이렇게 한다면 공유하는 변수들을 전역에 가지고 있어야 한다.*/
  while ((*stpMap).uiMsg != WM_NULL)
  {
    if (iMessage == (*stpMap).uiMsg)
    {
      ((*stpMap).fp)(hWnd, wParam, lParam);

      return 0;
    }

    ++stpMap;
  }

  /*
  switch (iMessage)
  {
  case WM_CREATE:
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  return 0;

  case WM_DESTROY:
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  PostQuitMessage(0);
  return 0;
  }
  */


  return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}

LRESULT On_Destroy(HWND hWnd, WPARAM wParam, LPARAM lParam)
{  
  DeleteObject(hbmFront);
  DeleteObject(hbmBack);
  DeleteObject(hbmLeft);
  DeleteObject(hbmRight);
  DeleteObject(hbmGround);
  DeleteDC(memDC);
  
  PostQuitMessage(0);
  
  return 0;
}

LRESULT On_Create(HWND hWnd, WPARAM wParam, LPARAM lParam)
{  
  HDC hdc;

  hdc = GetDC(hWnd);
  memDC = CreateCompatibleDC(hdc);
  hbmFront = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_FRONT));
  hbmBack = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_BACK));
  hbmLeft = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_LEFT));
  hbmRight = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_CHAR_RIGHT));
  hbmGround = LoadBitmap(g_hInst, MAKEINTRESOURCE(IDB_BITMAP_BACKGROUND));
  ReleaseDC(hWnd, hdc);

  hbmHero = hbmFront;

  return 0;
}

LRESULT On_Paint(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;

  hdc = BeginPaint(hWnd, &ps);
  SelectObject(memDC, hbmHero);
  BitBlt(hdc, iXPos, iYPos, 4848, memDC, 00, SRCCOPY);
  EndPaint(hWnd, &ps);

  return 0;
}

LRESULT On_KeyDown(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
  switch (wParam) 
  {
    case VK_LEFT:
      hbmHero = hbmLeft;
      iXPos = iXPos - X_MOVE;
      break;

    case VK_RIGHT:
      hbmHero = hbmRight;
      iXPos = iXPos + X_MOVE;  
      break;

    case VK_UP:
      hbmHero = hbmBack;
      iYPos = iYPos - Y_MOVE;
      break;

    case VK_DOWN:
      hbmHero = hbmFront;
      iYPos = iYPos + Y_MOVE;
      break;
  }

  InvalidateRect(hWnd, NULL, TRUE);

  return 0;
}

 



마구 이동한다.




여기서 메모리 DC인 memDC 를  해제해주는 함수인 DeleteDC() 에 대해 한번 알아보고 가자.


CDC 객체와 관련된 윈도우 디바이스 컨텍스트를 삭제한다.


이것만 봐서는 이해가 안될듯하다.

관련하여 CDC 객체의 컨텍스트를 생성하는 함수일 것이라 추정되는 CreateCompatibleDC() 에 대해 살펴보자


다른 디바이스 컨텍스트와 호환이 가능한 메모리 디바이스 컨텍스트를 만든다. 이것은 메모리에 이미지를 미리 읽어 들이기 위해서 사용할 수 있다.


즉, 화면 DC(Device Context를 의미하며 그림을 그리고자 하는 캔버스를 의미 한다고 생각하면 편하다.)와 메모리 DC 간의 호환성을 체킹하여 반환한 메모리 DC와 서로 비트맵 블럭을 주고 받을 수 있게 된다. 좀 더 자세히 생각해보면,

memDC 란 비트맵을 출력하기 위해 CDC( CDC = Graphics 이렇게 생각하면 편하다. )를 메모리 상에 올려놓고 사용하는 것이다. 이것을 '더블 버퍼링' 이라 칭하는 듯 하다. 나중에 좀 더 알아보고, 

비트맵을 블록단위로 전송하기 위하여 또 하나의 비트맵을 메모리상에 올려놓고 전송을 해주는 것이다. 비트맵을 운용할 때 메모리상에 있는 비트맵을 가지고 사용을 하면 화면의 끈김이 없다라는 장점이 생긴다.


메모리 DC

일반적으로 DC는 화면에 그래픽을 출력하기 위해 사용하지만,  프린터나 플로터 같은 출력 장치에 그래픽을 출력할 때

도 DC를 사용한다. 하지만 BitBIt함수를 이용하여 비트맵 블록을 전송하기 위해 화면 윈도우에서 얻어진 DC와 호환되는 

메모리 DC를 사용하는 건 어떠한 출력 장치와도 연결되어 있지 않고, 순수히 메모리에 존재하는 DC이다.


메모리 비트맵의 유용성

그래픽 데이터 저장

- 화면에서 얻어진 DC에 비트맵을 선택한 후 그림을 그리면 화면에도 그려지지만 비트맵에도 그려진다.
나중에 이 비트맵만 가지고 있으면, 이것을 다른 윈도우에 출력 할 수도 있고, 화면이 지워진 후에 이를 이용해 화면을 복원 할 수 도있다.

고속 그래픽 출력을 위한 버퍼

- 그림을 그리기 위해 그래픽을 함수를 호출하면 선과 원을 이용해 도형을 그린다고 하면 한장의 그림을 그리기 위해
여러번의 그래픽 함수가 호출되고 화면이 그려지는 과정이 드러나면서 화면이 깜빡거린다. 이런 경우에, 메모리 비트맵을 사용하면좋다. 메모리 DC를 만들고, 여기에 메모리 비트맵을 선택한다. 그런 다음 메모리 DC를 참조하여 여러가지 그래픽 작업을 수행하면 된다.

메모리 비트맵에는 그림이 그려지지만 아직까지 화면에 보이는 것은 아니다. 화면 구성이 끝나면, 메모리 비트맵블록을 화면의 DC로 전송한다. 비트맵을 블록 단위로 전송하는 것은 아주 고속으루 수행 되기 때문에, 깨끗한 출력 결과를 얻을수 있다.


CBitmap - 비트맵 데이터를 읽어오고 관리하는 기능을 한다.

CDC - 메모리 DC

CClientDC - 윈도우의 클라이언트 영역으로 한정되는 DC


윈도우즈는 비트맵을 곧바로 화면 DC로 출력하는 함수는 제공하지 않는다. 비트맵을 출력하는 함수라면 아마 다음과 같은 형태를 상상할 수 있을 것이다.

OutBitmap(hdc,x,y,비트맵 이름);

이런 함수가 있다면 무척 편리하게 비트맵을 출력할 수 있겠지만 안타깝게도 이런 함수는 없다. 왜냐하면 비트맵은 크기가 큰 데이터 덩어리이며 따라서 출력 속도가 형편없이 느리기 때문에 화면으로 곧바로 출력할 경우 여러 가지 꼴사나운 현상이 발생할 수 있기 때문이다. 게다가 다른 좋은 대안이 있기 때문에 직접 화면으로 출력하는 방법은 쓰지 않는다. 그 대안이 바로 여기서 논하고자 하는 메모리 DC이다.

 

메모리 DC란 화면 DC와 동일한 특성을 가지며 그 내부에 출력 표면을 가진 메모리 영역이다. 메모리에 있기는 하지만 화면 DC에서 사용할 수 있는 모든 출력을 메모리 DC에서도 할 수 있다. 선, 면, 원 등의 작도 함수는 물론 화면 DC에서는 불가능한 것까지도 가능하다. 그래서 메모리 DC에 먼저 그림을 그린 후 사용자 눈에 그려지는 과정은 보여주지 않고 메모리 DC에서 작업을 완료한 후 그 결과만 화면으로 고속 복사하는 방법을 많이 사용한다.


사용자 눈에 화면이 그려지는 과정을 보여주는 것은 그리 깔끔한 모양은 아니다. 여기서 우리가 하고자 하는 비트맵 출력을 위해서도 반드시 메모리 DC를 사용해야 한다. 비트맵도 일종의 GDI 오브젝트이지만 화면 DC에서는 선택할 수 없으며 메모리 DC만이 비트맵을 선택할 수 있다. 그래서 메모리 DC에서 먼저 비트맵을 읽어온 후 화면 DC로 복사하는 것이다. 메모리 DC를 만들 때는 다음 함수가 사용된다.

 

HDC CreateCompatibleDC( HDC hdc ); 

인수로 화면 DC의 핸들을 주면 이 화면 DC와 동일한 특성을 가지는 DC를 메모리에 만들어 그 핸들값을 리턴해 준다. 동일한 특성을 가진다(=호환된다)는 말은 사용하는 색상수, 색상면(plane)이 같다는 뜻이다. 호환되지 않는 DC끼리는 정보를 공유할 수 없기 때문에 화면 DC와 호환되는 메모리 DC를 만들어야 한다.

 

메모리 DC를 만든 후에는 비트맵을 읽어온 후 이 비트맵을 메모리 DC에 선택해 준다. 선택하는 방법은 여타의 GDI 오브젝트와 마찬가지로 SelectObject 함수를 사용하며 비트맵을 읽어올 때는 LoadBitmap 함수를 사용한다.

 

HBITMAP LoadBitmap( HINSTANCE hInstance, LPCTSTR lpBitmapName ); 

첫번째 인수는 비트맵 리소스를 가진 인스턴스의 핸들이며 두번째 인수는 비트맵 리소스의 이름이다. 읽어온 비트맵을 SelectObject 함수로 메모리 DC에 선택하면 메모리 DC의 표면에는 리소스에서 읽어온 비트맵이 그려져 있을 것이다. 
이제 남은 일은 메모리 DC에 그려진 비트맵을 화면으로 복사하기만 하면 된다.



BitBlt() 를 사용하려면 반드시 이 CreateCompatibleDC() 를 사용해서 CDC를 메모리 상에 올려놓고 사용해야 한다.




경계검사 하지말고 이 게임은 벽으로 막히게끔 하는 게임이라,

아모튼 이 맵을 만들껀데 일일히 찍지말고(for문) 맵핑 기법을 사용할 것.

아마 내가 했던 테트리스 처럼 맵에 기본 값 정해놓고 그 값들만 바꿔가며 블록을 출력 시켰던것과 비슷한 느낌이다.


이런걸 맵핑 방식이라 한다.

이미 기본적으로 1 : 1로 전체 값을 만든 뒤에  내부 적인 값들만 조금씩만 바꾼다.