본문 바로가기
ETC.../DirectX

DirectX 간단한 사용 강좌

by izen8 2011. 4. 13.
반응형

DirectX 간단한 사용 강좌(이준곤님글)


안녕하세요~ 이준곤(LeeChen) 입니다. 

  많은 분께서 "언제나 강좌를 하나요?"라고 많은 질문을 해오시더군요~  사실 2D 강좌를
  해놓고 본의아니게 자료가 날라 가는 바람에~  (제 하드가 간혹 멈추는 짖을 하거든요~ 
  그러면 시스템이 다운이....) 
  지난번에는 주로 API를 위주로 다이렉트 엑스를 설명을 한것 같습니다.  
  이번에는 많은 량의 강좌는 안될것 같은데 간단하게 집고 넘어 가려고  글을 쓰기로   
  하였습니다. 기다려 주시는 분들도 계시고 해서... 
  제가 다이렉트 엑스라는 것을 처음 접한것은 2버전때 였습니다. 벌써 2년이상이
  지났군요~ 그후로 버전이 3.0....5.2까지 나와 있는데 이미 게임 관련된 루틴은 3버전  
  에서 확고히 자리가 잡힌 것같습니다.  
  5.0은 3차원 기능을 향상한점에서 조금 나아 졌고~ 5.2는 DVD기능을 첨가했다고   
  하는 군요~ 
  그리고 앞으로 나올 6.0에서는 어떻게 나올지는 모르지만 3차원 기능이 Opengl과   
  혼합형태로 나올것 같다는 전망이 많더군요~ 
  제가 본격적으로 이야기 할건 이런 겉치장의 기능향상이 아닌 실속있는 기능 향상을   
  보자면, 새로운 인터페이스 IDDVideoPortContainer, DirectDrawColorControl,  
  IDirectDrawVideoPort, 그리고  IDirectDrawSurface3포함 되어 있고,  
  IDirectDrawSurface2가 보충 공유되어 있고. 주요한 개선된 점은 넓은 표면을 생성할   
  수 있다는 것입니다. 
  사실 이부분은 제가 인터넷을 통해 얻은 자료를 번역해서 올리려다 이렇게 다시 편집  
  해서 올립니다.  각각으 기능은 이미 5.0 레퍼런스의 서두에 나오는 것인데 자세한 것은   
  레퍼런스를 참조해 주세요~  
  (해석이 안되시는 분은 저에게 질문을 주셔도 됩니다. ) 
  지금 인터넷을 통해 게임 컴퓨터 동향을 살펴 보면 3차원을 가지고 치열한 전쟁을   
  치루고 있더군요~ 특히 오픈지엘이냐~ 다이렉트엑스냐~ 의 두진영이 치열하고   
  간혹 독자적인 그러나 그리 힘을 발휘할수 없는 독자노선을 추구하는 팀이나   
  회사도 있더군요~ 
  어쨌거나 3차원에서 많이 사용하고 있는 것은 오픈지엘과 다이렉트 엑스인데   
  오픈지엘은 이미 리눅스나 유닉스, 그리고 솔라리스, 오에스투, 윈도우즈 95등등   
  많은 운영체제에서 사용되어 왔고 기능또한 편리하게 되어 있어 함수 레퍼런스만을   
  가지고 3차원 감각이 있는(개념이 잡혀 있는) 독자라면 누구나 구현할수 있게 되어   
  있습니다.  
  그런 반면 다이렉트 엑스는 빠르다는 것을 추구하지만 워낙 많은 변수와 인자와의   
  전쟁을 한바탕치루고 난후에야 가능하니 조금 번거롭더군요~ 
  하지만 다이렉트 3차원을 각자 본인의 구미에 맞게 잘 구성해놓고 나름데로   
  인터페이스를 구성만 된다면 오픈지엘보다 편리하게 쓸수도 있겠죠~ (이것은 독자의 몫!) 
  일단 많은 독분들이 MFC를 두려워하고 있는 것 같아 다이렉트엑스를 어떻게 하면   
  MFC와 쉽게 접목해서 사용할 수 있는지를 2차원 프로그램과 3차원 프로그램을 각각   
  제작하면서 배워 보도록 하죠~  
  ( 약간 글이 어색할 수도 있는데 자료중 원어를 그데로 번역해서 넣은 부분도 있다보니   
  조금 이상할 수도 있는데 제가 나름데로 정리하려고 했는데 아직 자료들이 정리가 안되어   
  이렇게 서둘러 작성해 올립니다. )  .



1. 1. 다이렉트 엑스의 사용
이미 앞서 저의 글을 읽으신 분이나 공부를 하신분은 다이렉트 엑스가 어떤거구나 하는
것을 아실겁니다. 보다 빠른 그래픽 처리를 위한 드라이버인데, 이것을 사용하는 방법은
풀화면을 다 이용하는 방법과 윈도우즈 모드로 작성하는 방법 두가지가 있다는 것을
아실겁니다.   모른다구요~ 그렇다면 이렇게 두가지로 사용할수 있습니다. 
저는 다이렉트 엑스를 사용할때 IDirectDrawSurface2것을 자주 사용하는데 이유는
기존의 다이렉트 엑스를 사용하는 프로그램이 있다면 그것과 충돌을 일으키지 않으려고
이것을 사용합니다. 보통 IDirectDrawSurface를 사용합니다. 특히 온에어라는 
TV수신카드의  프로그램은 IDirectDrawSurface를 이용해 화면에 TV내용을 찍는데
이것과의 충돌을 읽으키지 않으려면 다른 Surface를 사용해서 출력 하는 것이 낳겠죠~
(이것은 서페이스의 버전의 차이로 생긴 겁니다. 보다 안전성이 있게 제작된것이 뒤에 붙는
숫자가 커집니다. 현재(98.5)2버전까지 나와있습니다. 일반적으로 서페이스를 사용하는
분들도 계신데 조금 안정적인 서페이스를 사용하는 것도 괜찮을 듯합니다.)  
일단 다이렉트 엑스를 MFC에서 사용하기 위해 그에 적합한 코딩을 해놓아야 겠죠~ 
class CDirectDraw  
{  
public:  
       // two phase construction  
       CDirectDraw ();  
       void Create (HWND hWnd,   
           DWORD dwDisplayWidth, DWORD dwDisplayHeight,  
               DWORD dwBitPlanes, DWORD RefreshRate=0); 
       ................  
       ................ 
       ~CDirectDraw (); 
public:  
       // surface를 제어  
       CPrimarySurface& FrontSurface ();  
       CBackBuffer& BackBuffer (int iNumBackBuffer=0); 
       .................  
       ................. 
       // Load a palette from a bitmap:  
       void  LoadPalette (LPCSTR szBitmap); 
       // 파레트 관련 함수  
       void GetEntries (DWORD dwBase, DWORD dwNumEntries, LPPALETTEENTRY
lpEntries);  
       void SetEntries (DWORD dwStartingEntry, DWORD dwCount,  LPPALETTEENTRY
lpEntries); 
private:  
       // Guard against copies  
       CDirectDraw (const CDirectDraw& src);  
       CDirectDraw& operator = (const CDirectDraw& src);  
}; 
다이렉트 엑스를 생성시 IDirectDraw2를 최초의 면에 이용했고 그다음 뒷면도 이것을
이용해 생성했다. 그리고 단일 파레트만을 사용하게 해놓았다. 이것은 어디까지나 예제를 위해  제작
된 것이므로...  
다음에 제작된 CreateDDInterface는 생성하면서 발생되는 에러를 적절하게 디버그   
모드상에서 처리되겠금 되어 있다.  
void CDirectDraw::CreateDDInterface ()  
{  
       LPDIRECTDRAW lpDD_v1;  
       HRESULT ddrval = DirectDrawCreate (NULL, &lpDD_v1, NULL);  
       switch (ddrval)  
       {  
       case DD_OK:  
               // 성공하면  
               break;  
#ifdef _DEBUG  
       case DDERR_INVALIDPARAMS:  
               TRACE ("Invalid parameters.\n");  
               ASSERT (FALSE);  
               break;  
       case DDERR_INVALIDDIRECTDRAWGUID:  
               TRACE ("Invalid GUID.\n");  
               ASSERT (FALSE);  
               break;  
       case DDERR_DIRECTDRAWALREADYCREATED:  
               TRACE ("DirectDraw object was alreadyaleady created.\n");  
               ASSERT (FALSE);  
               break; 
       case DDERR_GENERIC:  
               TRACE ("Generic error creating the interface.\n");  
               throw ddrval;  
               break;  
       case DDERR_NODIRECTDRAWHW:  
               TRACE ("DirectDraw hardware is not available.\n");  
               throw ddrval;  
               break;  
       case DDERR_OUTOFMEMORY:  
               TRACE ("Out of memory.\n");  
               throw ddrval;  
               break;  
#endif 
       default:  
               TRACE ("Undocumented error.\n");  
               throw ddrval;  
       }  
       // 바로 이부분이 제2의 평면을 사용하게 하는 부분이다.  
  ddrval = lpDD_v1->QueryInterface ( IID_IDirectDraw2, (LPVOID *)&m_lpDD);  
       ......  
       ......  

앞의 내용을 다시 간단하게 정리하면... 
void CDirectDraw::CreateDDInterface ()  
{  
       LPDIRECTDRAW lpDD_v1;  
       HRESULT ddrval = DirectDrawCreate (NULL, &lpDD_v1, NULL);  
       switch (ddrval)  
       {  
       case DD_OK:  
               // 성공하면  
               break; 
       default:  
               TRACE ("Undocumented error.\n");  
               throw ddrval;  
       }  
       ddrval = lpDD_v1->QueryInterface ( IID_IDirectDraw2, (LPVOID *)&m_lpDD);  
       ......  
       ......  
}  
 
  자~ 여기서 앞으로 사용된 평면에 관해 알아 보도록 하자~  



2. 사용되는 평면들
필자는 평면들을 구성하는데 있어 각각의 클래스로 정의해 두었다. 아마도 많은 분은   
변수하나로 처리하면 될걸? 왜? 클래스로 하지?  하실테데.. 각각의 기능을 구분을 하고
그에 따라 독자적인 처리로 보다 효과를 주기 위해서 입니다. 
1) CPrimarySurface class  
   아주 간단하게 구성이 되어 있는데, 뒤에 생성된 평면과 화면  
   전환을 위해 생성. 오직 보여주기 위한 평면 클래스다. 
2) CBackBuffer class  
   실제 독자가 직접 그림을 그리고 변형을 가하는 평면이다. 
3) CBitmapSurface class  
   게임에 사용되는 그림을 담아 두는 주로 비트맵을 담는데 사용  
   되는 평면 클래스이다.  
 
다음 소스는 어디서 많이 본듯한 예제인데 어디서 보던 소스인데... 어딜까요? SDK의   
Sample에 보시면 도너츠를 움직이는 예제가 있을 겁니다. 한번 살펴 보세요~  
  
   ddrval = DirectDrawCreate( NULL, &lpDD, NULL );  
   if( ddrval != DD_OK )  
   {  
       return initFail(hwnd);  
   } 
   // Get exclusive mode  
   ddrval = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | 
                                                                       DDSCL_FULLSCREEN );  
   if( ddrval != DD_OK )  
   {  
       return initFail(hwnd);  
   } 
   // Set the video mode to 640 by 480 by 8640x480x8  
   ddrval = lpDD->SetDisplayMode( 640, 480, 8);  
   if(ddrval != DD_OK)  
   {  
       return initFail(hwnd);  
   } 
   // Create the primary surface with 1 back buffer  
   ddsd.dwSize = sizeof( ddsd );  
   ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;  
   ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |DDSCAPS_FLIP |
                                                                          DDSCAPS_COMPLEX;
   ddsd.dwBackBufferCount = 1;  
   ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL );  
   if( ddrval != DD_OK )  
   {  
       return initFail(hwnd);  
   } 
   ddscaps.dwCaps = DDSCAPS_BACKBUFFER;  
   ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps, &lpDDSBack);  
   if( ddrval != DD_OK )  
   {  
       return initFail(hwnd);  
   } 
지난번 글을 소화해낸 분이라면 이정도는 아무것도 아니게 이해가 ....^^;  
아주 간단한 다이렉트 엑스 생성 부분입니다. 위의 코드와 다른점이몇군데 있죠~   
지난번 강좌까지는 이런 형태의 코드로 이야기 했고 앞으로는 저위의 코드로 강좌를  합니다.  
 
위의 클래스의 함수들을 어떻게 쓸까? 하는 의문이 생길텐데 다음과 같이 사용됩니다.   
파레트 셋팅과 다이렉트 엑스 생성~ 
   //  
   // 다이렉트 엑스를 생성하는 부분  
   //  
       m_DirectDraw.Create (hwnd, 640, 480, 8); 
   // create and set the palette  
       m_Palette.Create (); 
       m_DirectDraw.SetPalette (m_Palette);  
 
일단 여기까지 하고요~ 다음에 하기 전에 알려 드릴것은~   
MFC에는 SDI 와 MDI라고 하는 것이 존재하는데 저는 이강좌에서 SDI를 위주로 하려고  
합니다. 보다 간편하기 때문이죠~  

MFC를 이용해 전체화면을 사용하는 방법을 모색해보고 그다음 창모드에서 작업하는 
방법을 알려 드리죠~  


API를 쓰다가 MFC를 사용해보면 이렇게 편리한 것을 몰랐나 할 겁니다.
두 번다시는 API를 안쓰지 않을까 하네요~   
제2의 강좌에서는 본격적인 MFC를 이용하도록 하죠~

----------------------------------------------
실제 MFC의 메세지 맵 구성


1.3. 메세지 맵의 구성
보통 MFC를 어떻게 배워야 할지? 그리고 매우고 난후 어떻게 써야 할지를 망설이는 분이 많습니다.
그런데 잘 활용하면 무척이나 쉽고 간단한게 처리를 할수 있는 장점이 많습니다. 
Microsoft Developer Stidio[VC 에디터]에서 New를 선택하면 탭 메뉴  중 Project를 선택하면
에구~ 무슨 위자드(Wizard)가 많은지? 얼레?  
무얼 선택해야~ 하나? (음...단번에 또는 통밥으로 맞추시는 분이  있군요~) 
MFC AppWizard(exe)라는 메뉴가 있는데 이것을 선택해서  다음에 물어보는 것을 상세히
읽어보신후 나중에 완성이 된 메세지가  무조건적으로 다음과 같은 메세지가 나옵니다. 
  +- Application   : CDemoApp class  
  |  
  +- Frame          : CMainFrame class  
  |  
  +- Document     : CDemoDoc class  
  |  
  +- View            : CDemoView class 
위의 4개의 클래스가 생성이 됩니다. 바로 클래스가 메세지를 뜻하는데 처음에 관련 정보 생성, 각각
의 윈도우의 프래임을 관리, 문서의 정보 관리, 마지막으로 보여 주면 되겠죠~ 
일단 이렇게 알고 계세요~ 더욱 자세한 내용은 MFC관련 책자를 보시면 보다 상세히 각각의
메세지에 대해 알아 볼수 있을 겁니다.  
(요건 무슨 의미이냐면 적어도 MFC관련 책자는 한권은 있어야  한다는 이야기죠~ ^^;) 
자~ 그럼 저기 4개의 메세지가 꼭! 필요할까요? ..............  
필요합니다. 그런데 여기서 두가지를 생각해 두어야 합니다. 무얼까?  바로 어떤 모드에서
MFC를 사용할 것인가 하는 문제가 있는데 바로  이 문제를 전제로 하고서 메세지 맵을 관리 해야 합
니다.  
윈도우즈 모드에서 다이렉트 엑스를 사용한다면 윈도우즈 규약에 마추어서 제작을 해야
합니다. 그건 문서관리와 보여지는 처리를 해야  하는 번거로움이 따르기 때문입니다. 
하지만 객체 처리는 독자적으로  하고 보이는 화면을 보여주기만 하면 어떻까요? 
보통 게임에서는 이런 방법을 사용하죠~  
일단 가장 간단해 보이는 전체화면을 사용하는 프로그램을 작성해 보도록 하죠~  
풀화면을 사용하면 문서관리가 필요할까요? 화면전체를 가득 독자가  제작한 그림들로
체우는 상태에서 별도의 보이라는 메세지를 윈도우즈에게 보낼 필요가 있을까요? 그러니가~ 여기서
메세지는 MFC의 메세지를 말합니다. 
일단 필요한 것을 보자면 초기화를 위한 메세지가 필요합니다.  게임을 처음 생성하면서
앞으로 사용될 모드에 대해 윈도우즈에게  '나 이렇게 쓸꺼여~'라고 알려 주는 메세지와~
도스에서 제작하던  방식인 Main()함수격인 프래임 처리 메세지만 있으면 끝이겠죠~ 
쓸때 없는 화일은 지우자~ 무얼 지워야 할까? 바로 도큐먼트함수가  있는 화일과 비~유
클래스가 있는 화일을 지우면 됩니다.~ 아주 간단하죠~ 그러면 무엇이 남죠? 
  +- Application : CDemoApp class  
  |  
  +- Frame        : CMainFrame class 
이것만이 남게 되는데 이것을 적절하게 사용하면 됩니다.  
메세지중에 Idle이라는 메세지가 있는데 이것은 윈도우즈 프로그램을 공부하신 분이라면 

아시겠지만 메세지가 없는 상태를 포착해서 독자가 그시간을 사용하게 해주는 

메세지입니다. 
이메세지는 MFC에서 함수로 제공을 해줍니다. 보통 API에서는 함수로 제공되기 보다는  
   while(무조건 돌아라~) {  
       while( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) !=0 ) {  
           if( GetMessage( &msg, NULL, 0, 0) == 0 ) 프로그램 끝내자~;  
           TranslateMessage(&msg);  
           DispatchMessage(&msg);  
       }  
       if (활성화 되었냐?)  
           그럼 내가 처리하고 픈 함수 실행할래~  
   } 
하하~ 이렇게 API에서는 WinMain()이라는 부분에 메세지를 별도로 체크해서 사용해야

했습니다. 그런데 MFC에서는 달랑~ OnIdle() 이라는 함수가 제공이 된다니....

(비통하구나~ 이것을 빨리 알았으면  ... 하시는 분도 있겠죠~)  



4. Application Message
자~ 그러면 메뉴중에서 클래스 위자드를 실행해서 뛰우자~ 그것을  뛰우면 ClassName을
뒤적뒤적하면 '나클래스야App'라는 메세지가 있는데 이것에 고정하고 메지창에서
OnIdle라는 것을 클릭하면 와우~   
멤버 함수란을 보면 이함수가 들어 와있을 겁니다.  그러면 에디트 코드라는 버튼바를 눌러
보면 
   BOOL 나클래스야App::OnIdle(LONG lCount)   
   {  
           CWinApp::OnIdle (lCount);  
           return 참일깨롱~;  
   } 
이렇게 나타난다는 전설아닌 전설이 있더군요~ 
그럼 이부분을 어떻게 이용할까요? 독자분이 돌리고자하는 프로그램을 이함수에 넣으면
그냥~ O~K~ 
잠깐 예를 들자면   
 
BOOL 나클래스야App::화면을 업데이트할래()  
{  
   if (!((CMainFrame*)m_pMainWnd)->m_bActive)  
                                       return FALSE; 
   DWORD               thisTickCount;  
   RECT                rcRect;  
   DWORD               delay[4] = {50, 78, 13, 93};  
   int                 i;  
   PALETTEENTRY        pe[256];  
   HRESULT             ddrval; 
   // Decide which frame will be blitted next  
   thisTickCount = GetTickCount();  
   ...................  
   ...................  
   return 참일깨롱~;  

void 나클래스야App::모두 복구할래()  
{  
   // m_DD.FrontSurface().Restore ();  
   // m_Surface.Restore();  

그렇다면 사용을 어떻게 해야죠? 음..간단하야~ 업데이트함수를  
이용하면 됩니다. 
   BOOL 나클래스야App::OnIdle(LONG lCount)   
   {  
           CWinApp::OnIdle (lCount);  
           return 화면을 업데이트할래();  
   } 
바뀌었죠~ 이렇게 되면 일단 화면이 갱신이 되겠금 메세지로  구성했습니다.  
우와~ 아주 쉽죠~  
일단 이부분의 메세지를 이렇게 제작을 해놓고~ 


5. MainFrame Message
이메세지는 당연 프로그램의 핵심부분이라고 해도 과언이 아닌  부분이라고 짐작하시는 분이
계시겠죠~ 당근입니다! 
다시 보자면~ "MFC의 모든 것은 클래스 위자드로 클래스 위자드는 MFC의 메세지로~"
이것이 진리요~ 참이로다~ ( 우헤헤~ )  웬헛소리가...이것이 바로 MFC를 사용하다 보면
나타나는 증세? 
일단 클래스 위자드를 실행하자~ 당연 메뉴에서 클래스 위자드를 찾아서 클릭을 하면
되겠죠~ 거기에 보면 클래스 이름에 보면 'CMainFrame'이라는 것이 있죠~ 그것을 고르면
멤버함수란을 보면 달랑~ PreCreateWindow() 함수만이 존재하는 것을  알수 있을
겁니다.   
이함수를 일단 에디팅합니다. 에디트 코드바를 눌러서 화면을  갱신합시다.  
 
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)  
{  
       // TODO: Modify the Window class or styles here by modifying  
       //  the CREATESTRUCT cs 
       return CFrameWnd::PreCreateWindow(cs);  

요렇게 구성이 되어 있습니다. 이함수의 특징은 윈도우즈를  생성할때 그러니까~ 독자께서
제작하는 프로그램의 윈도우즈  스타일을 정의해서 생성하게 하는 부분입니다.  
그러면 CREATESTRUCT 구조체에다가 커서를 갔다데고~ F1키를  눌러 보세요~ 어떤 쓸때
없는 말들이?? (잉? 왜 내겐 암호처럼  보이는 글자들이 나타나는 걸까? 하하~)  
거기 구조체를 살펴보면 화면의 크기를 정의 하는 변수가 있습니다.  
그변수에 값을 넣어 윈도우즈 크기를 정의 합시다. 
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)  
{  
       // TODO: Modify the Window class or styles here by modifying  
       //  the CREATESTRUCT cs 
       cs.x = cs.y = 0;  
       cs.cx = GetSystemMetrics (SM_CXSCREEN);  
       cs.cy = GetSystemMetrics (SM_CYSCREEN);  
       return CFrameWnd::PreCreateWindow(cs);  
}  

이렇게 바꿉시다~ 여기서 시작 좌표는 0이라는 값으로 초기화  하여 두었고~ 윈도우즈
크기는 현재 시스템의 최대값을 받아서  셋팅하겠금 되어 하여 두었습니다.  
와~우 MFC도 별개 아니네?  
 
자~ 그럼 이것가지고 다이렉트 엑스가? 뜰까나? 앞으로 갈길이  
남아 있고....잠시후....(무려 5시간 후 우랄랄~ 그사이에 어떤   
일이?) 
다시 마음을 가다듬고...이번에는 게임을 진행하는데 있어 키보드는 놀고 있을까요? 전혀
아니죠~ 간단하게 게임을 빠져 나오려고 할때 ESC 키를 누르면 그에 해당하는 반응을 해야
겠지요~ 그리고 기타 독자분들이 어떤 키를 넣어 보겠다고 한다면  그에 해당하는 메세지도
첨가해야 겠지요~ 
어떻게 첨가해야 하죠? 에고고...어쩌나...그런데 API에서는  어떻게 처리를 했죠~ 그때를
아십니까?  
     case WM_KEYDOWN :  
          if( wParam == VK_UP )  
                            올리래();  
          if( wParam == VK_DOWN )  
                            내릴래();  
          if( wParam == VK_LEFT )   
                            왼쪽으로 갈래();  
          if( wParam == VK_RIGHT )  
                            오른쪽으로 갈래();  
          .............  
          .............  
          break; 
이런식으로 처리를 했었습니다. 음 그리고 별도로 함수를 만들기도 하지만 그래도 어째건
이런식으로 처리를 했습니다.  
이미 윈도우즈 프로그램을 했던분이라면 낮익는 소스 부분이라  꺼리김 없이 아하~ 그렇군
하시겠죠~  그렇다면 MFC에서는 이런 부분을 어떻게 처리를 할꼬나? 
클래스 위자드에서 보면 WM_KEYDOWN이라는 부분이 있습니다.  이부분을 두번 클릭하면
멤버함수에 OnKeyDown이라는 함수가  생성이 됩니다. 그럼 역시 에디트 버튼을 눌러보면
다음과 같은 소스가 나타날겁니다. 
void CMainFrame::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)   
{  
       // 버츄얼 키중에 ESC와 F12를 눌렀을때 프로그램을 끝냄   
       if ((nChar == VK_ESCAPE) || (nChar == VK_F12))  
               PostMessage(WM_CLOSE, 0, 0);  
  
       CFrameWnd::OnKeyDown(nChar, nRepCnt, nFlags);  

와우~ 간단해 보이죠~ 그럼요~ 간단하게 처리되겠금 되어 있는 것이 MFC입니다. 
그다음 화면을 갱신하는 메세지가 있어야 적절한 윈도우즈 내부 시간에 맞추어 화면이
갱신이 되겠지요~ 이부분은 다음 어떻게 처리를 해야 좋을까나? 
앞서 배운 방법처럼 메세지 부분에서 WM_PAINT라는 부분을 클릭하여 함수를 생성하면
됩니다. 윈도우즈 창을 이용해서 사용되는  프로그램이 아니므로 별도의 갱신처리 루틴은
필요 없습니다.   단순이 이메세지를 넣어 줌으로 화면 갱신이 이루어 지게 됩니다. 
그럼 다음과 같은 코드가 생성이 되어 있을 겁니다. 
void CMainFrame::OnPaint()   
{  
       CPaintDC dc(this); // device context for painting  
  
       // TODO: Add your message handler code here  
       // We need do nothing.  
  
       // Do not call CFrameWnd::OnPaint() for painting messages  

여기서 this가 나타내듯 자신의 DC를 갱신하게 됩니다. 
[#참고] 여기서 this 연산자는 C++에서 사용되는 연산자로 자신을 나타내는 연산자 또는
프로그램의 예약어라고 할수 있습니다.  
즉, 메모리의 포인터 연산자와는 별개로 처리되지만 실제 뜯어  보면 비슷한 기능을 하면서
사용상 의미는 다르다. 메모리가 지시하는 것의 명확성을 두기위해 this연산자를 사용한다.  
(이구 이것이 맞다고 주장할수 없지만 이렇게 이해하면 한결 도움이 되겠지요~ ^^;)  

  기타 활성화 되어 있는지의 여부를 판단하는 메세지도 있고 첨가 가능한 메세지는 클래스
위자등에 있습니다.  첨가할 수 있는 기능, 하지만 꼭 필요한 것만을 넣어야 합니다.  
자~ 이정도 되면 어느정도 MFC 의 풀스크린을 이용한 게임을  제작할수 있습니다. 그다음
나머지 캐릭터의 동작이나 인공지능  처리, 찍기등등의 게임의 기능은 별도의 함수로
제작하고 어디?  바로 Application 클래스의 Idle함수에 첨가하면 됩니다.  



  여기까지 1장의 MFC를 이용한 풀스크린 활용편이였습니다.  
  다음시간에 제 2장 윈도우즈 모드를 이용하는 방법을 알아 보도록 하지요~ 
  조금 복잡한데 (메세지가 많거든요~) 하나하나 살펴  보면서 제작을 해보도록 하지요~     
  

----------------------------------------------

MFC의 클래스와 메세지 구조


벌써 3번째 강의를 준비해야 하는 시간이 되었군요. 매번 강좌를 준비 하면서 무척이나
긴장되고 걱정이 됩니다. 왜냐구요? 어떻게 하면 보다 쉽게 독자분들께 이해를 시켜드릴까
싶은 마음에... ^^; 
이번 시간부터는 다이렉트 엑스를 이용해 맵에디터나 스프라이트 동작  툴을 제작할수 있는
기반을 다지기 위하여 MFC를 이용한 윈도우즈 모드용으로 공부하도록 합시다.  



1. MFC로 제작된 프로그램의 특징
일반적으로 공통적인 표준을 가지고서 윈도우즈 프로그램을 제작하게 됩니다. 이것은 이미
API를 공부하면서 윈도우즈의 표준을 따르면서 발생되는 인터페이스의 디자인의 표준화가
생기어 사용하는 유저로 하여금 혼돈을 일으킴이 없이 체계성을 보여주기 위함이 아닐까
합니다. 
그런 면에서 MFC로 제작하는데에도 어떠한 공통된 통일성이 따릅니다.  툴바의 생성,
도움말 보기 기능, 메뉴기능 등등 도스시절에는 복잡하게 팝업이니 풀다운이니 아이콘 생성
방법이니 하여 별도로 배우고 습득해서 처리하니 이리 저리 시간도 걸리고 사용하는
사람으로 하여 습득력도 필요로 하게 했습니다. 그러나 윈도우즈는 통일된 인터페이스로
쉽게 제작하고 사용하는 것이 마음이 들죠~ 
자~ 필자가 왜 이런 말을 했냐면 바로 윈도우즈 모드로 제작하는 프로그램에 있어
인터페이스 부분을 신경을 안쓸수 없다는 것이죠~  

하지만 API에서는 어떻게 했나요? 일일이 해당 부분을 제작해주어야  했지요~ 그러나
MFC는 처음 프로젝트 생성시 조건에 맞는 코드생성이  이루어지므로 인해 간단한 
인터페이스는 구성이 되어 있다는 것이죠~  

지금이라도 쉽게 생성된 소스를 F7를 눌러 컴파일후 실행을 해보시면 쉽게 아실겁니다. 
1.1 메세지의 쉬운 관리  

이건 API를 사용하다 MFC를 사용하면 처음부터 느끼는 점인데 메세지를 함수단위로
관리하고 특히 클래스 위자드로 인해 관리되므로 특별히 신경쓰지 않아도 원하는 기능을
추가하면 됩니다. 
이보다 MFC의 특징은 많은데 제가 다 늘어 놓을 수는 없고, 실제로  독자분들이 제작을
하면서 피부로 느끼는 것이 빠를겁니다. 
 


 
2. 윈도우즈 모드에서의 메세지 구조
앞서 배운 풀스크린 모드에서는 Application과 Frame이라는 두개의 클래스를 이용했다.
그것은 별도의 화면 갱신이 필요없다 쓸때 없는 메세지로 인해 번거로움을 없애기
위해서이다.  

특별히 툴바라던가 메뉴가 나오는 것도 안닌데 도규멘트 메세지니 뷰메세지가 필요없기
때문이다.  이방법말고도 아이알로그를 이용해 하는 방법이 있는데 처음 소스를  
생성할때 다이알로그로 생성하면 많은 량의 메세지가 없는데 이것을 사용해서 게임을
제작하는 방법도 좋을 것이다. 
그렇다면 이번제작하는 윈모드용 게임은 어떤식으로 메세지가 구성되어 있나를 살펴보면 
  +- Application : CDemoApp class  
  |  
  +- Frame        : CMainFrame class  
  |  
  +- Document   : CDemoDoc class  
  |  
  +- View          : CDemoView class 
오잉? 기존의 메세지와 전혀 다를것이 없네? 음~ 이이상의 메세지는 독자분들이 필요한
다이알로그를 생성할때 필요 하지만 제가 제시하는 MFC로 에니메이션이 되는 프로그램은
이정도 클래스면 충분합니다. 
이번에 제작되는 윈모드용은 풀모드에 비해 View 부분이 강조가 되는 부분이다. 그러므로
이부분을 상세히 집고 넘어 가도록 하고 일단 각각의 메세지에 대해 알아 보고 지나가자~  

1) CAboutDlg 는 도움말을 처리하기 위한 부분이데 특별이 신경쓸건 없습니다. 다만
    리소스 관리기에서 도움말이 나오는 박스를 이리저리 모양을 내어 멋을 들여 놓으면
    끝~ 
2) CDemoApp 에서 사용되는 주된 기능은 Idle Time발생하는 것을 이용해 독자분들이
   필요로 하는 기능을 사용하면 됩니다. 
3) CMainFrame 에서는 지난번과는 다르게 중요성은 떨어진다. 하지만  없으면 안되는
   클래스입니다. 그러면 어떤 기능을 첨가하느냐? 바로 툴바를 생성하고 메뉴을 생성하고,
   그리고 가장 중요한 윈도우의 기본 바탕이 되는 화면 박스의 디자인을 해야 겠죠~ 이건
   지난번 시간에 이야기 했으니 그것을 참조~ 
4) CDemoDoc 현재 에니메이션을 구현하므로 별도의 문서를 관리할 필요는 없지요~
   그러니 이부분은 신경을 꺼도 됩니다. 허나 지우거나 없애면 크나큰 오류가....(이걸
   지울경우 감당할 문제는 허허~  어찌도리런지?) 
5) CDemoView 드디어 가장 중요한 이클래스는 윈모드의 게임제작에 지대한 영향력을
    발휘하는 클래스라고 해도 과언이 아니라 합니다  
 


 
3. Application 메세지의 구성
이번에 제작하는 Application 메세지는 지난번과 별다른 것은 없다.  하지만 지난번보다 수월하다
왜냐면 쉬는 시간을 체크하는 함수이외에 추가 함수를 이곳에 집중할 필요가
없다. 
메세지중 OnIdle를 추가해서 다음과 같은 소스를 약간 수정을 해준다. 
BOOL CMfcddex3App::OnIdle(LONG lCount)   
{  
       CWinApp::OnIdle (lCount);  
       // Find the active view:  
       CDemo3View* pActiveView = (CMfcddex3View*)((CMainFrame*)  
                                (m_pMainWnd))->GetActiveView ();  
       return pActiveView->updateFrame ();  

여기서 CDemo3View 클래스는 앞서 설명했지만 윈모드에서 사용되는 모든 처리는
View에 있으므로 그곳에 화면 갱신처리 함수가 있다 그러므로 이곳 응용프로그램의
메세지중 쉬는 시간에 화면을 갱신 해주겠금  하면 일단 자동 플립핑이 일어나겠지요~ 
와우...쉽다 쉬워~ 쉬운건 누구나 할수 있는 것이죠~ MFC도 누구나 할  수 있는
것입니다. 
 


 
[참고] 리소스로 제작된 다이알로그의 메세지 
보통 독자분이 제작된 다이알로그를 화면에 뛰어야 하는데 어떻게 해야 할까? API에서는 다이알로
그를 뛰우기 위해 DialogBox()라는 함수를 이용해서 생성하고 EndDialog()로 닫고 했을 겁니다.  
하지만 MFC에서는 해당 리소스의 이름을 클릭하면 DoDataExchange라는 함수가 있는데 다이알
로그를 초기화와 생성을 해줍니다. 이렇게  하므로 쉽게 등록을 할수 있습니다.   
이외에도 리소스의 기능을 추가하기위해 Command 함수가 생깁니다.  
이런것은 직접 메세지 레퍼런스나 MFC의 읽기 괜찮은 책을 읽어 보시면 자세히 나옵니다.  
 
----------------------------------------------------------------
DirectX 사용 위치와 전체적인 프로그램


1. 다이렉트를 생성을 해야 하는데?
MFC를 사용하면서 초보자로서 처음문제가 어디에 다이렉트 엑스의 초기화를 어느부분에

해야 할까? 어떤 클래스의 생성자에다 놓아야 할까? 아니면 별도의 생성이나 초기화를 

하기 위한 메세지가 존재하는 것은 아닐까? 이런생각을 합니다. 
# 프로그래머가 알아야할 명언 -----------------------------------  

  프로그래머가 생각한 모든 생각은 모두 프로그램으로 제작이 가능하다.  
  그러므로 지금 생각하는 것에 대해 잘 안된다면 그것은 길을 발견을 못  
  했을뿐! 언젠가는 그것을 직접 제작할 수 있다.
-----------------------------------------------------
그렇다면 다이렉트 엑스를 초기화하기 위한 메세지는 어떤것인 길을 찾아 보자~ 
MFC 3편의 강좌에 이번에 윈모드로 제작은 View를 중심으로 제작을 한다고 했다.
그렇다면 눈치가 있는 독자라면 클래스 위자드를 view 클래스에 맞추어 놓고 앞으로 제작될
메세지들을 입력할 준비를 하고 있을 것이다. 
윈도우즈나 다이알로그 박스의 처리 방법을 알아 보자~  
일단 수많은 윈도우즈가 화면에 떠있다. 그리고 그속에서 자신이 제  
작한 다이알로그나 윈도우즈가 뜰때 처음 한번은 생성과 동시에 화면  
에 보여지게 된다. 그리고 그 다음부터는 변화된 것만 윈도우즈자원에  
메세지를 보내어 변화된 부분을 갱신해준다. 이것이 GUI 의 핵심 알고  
리즘 즉, 커널의 이부분에 해당하는 부분이라고 할수 있는데 하여간  
깊이 들어갈 필요없이 여기까지만 이야기 하고(궁금하신 분은 시스템   
프로그램이나 운영체제에 대해 관련 서적이 서점에 즐비하므로 여기서  
번역서적은 쓰레기이고 원서들을 사보시는 것이 있어요~ 책은 빨강색,  
노랑색, 파랑색으로 많이 나와 있더군요~ ^^;) MFC의 메세지 구조도   
최초의 초기화를 해주는 부분과 나중에 갱신이 되는 부분, 그리고 필  
요시 유저의 강제메세지나 어떤 외부의 메세지로 인해 변화를 갱신하  
기위한 메세지로 나위어 집니다. 
#참고   
윈도우즈 메세지 관련된 정보는 윈도우즈 NT 서적이 자세히 나와 있으  
므로 윈도우즈 NT관련 프로그래밍 책을 참고 하세요~ (경제가 어려우  
니 책을 사시지 마시고 도서관이나 서점에서 읽어서 참고만하세요~  
그럼 도움이 될겁니다. 여유가 있으신 분은 NT관련 서적도 한권은 가  
지고 계시는 것도 ... ^^; ) 
그렇다면 일단 최초에 생성을 하면서 단한번만 메세지가 발생되는 함  
수를 만들어 보자면 역시 클래스 위자드로 가세요~ (이미 가 있다군요  
~)  
메세지중에 OnInitialUpdate를 선택해서 보면 
void 클래스야View::OnInitialUpdate()   
{  
       CView::OnInitialUpdate();  
  
     //  다이렉트 엑스를 생성한다. 640x480의 크기로  
       m_DD.Create (*this, 640, 480); 
     // 파레트를 생성하고  
       m_Palette.Create ();  
       m_DD.SetPalette (m_Palette);  
 
     // 유저가 사용할 평면을 생성  
       m_Surface.Create(); 
     ............  
     ............  

보통 보여주기 관련 클래스는 CView에서 상속을 받습니다. 그러니   
CView의 형을 참고하는 것은 당근이죠? 다이렉트엑스는 하나의 프로그  
램에 한번만 생성이 되니 위의 메세지가 이조건에 맞으니 필요조건인  
것과 동시에 충분조건이죠~ (잉? 필요는 아닌듯~~--;)  



2. 찍어 주기도 해야 하는데...
화면에 하나의 변화가 생기면 그것을 그려주는 메세지는 당연이 있어야 화면에 무언가가
등장합니다. 없어도 되지만 그래도 있으면 ....  
그러므로 이것을 가능하게 해주는 메세지를 찾아 보자면 (뒤적뒤적~)  잉? 누구죠? Paint를
쓰시다고 하신분이?  
Paint도 맞긴합니다만 제가 여기서 구분을 별도록 짖기는 뭐하지만 인위성문제로 Paint와
Draw를 구분을 짓는데 일단 자연적인 것을 추구하는 필자로썬 Draw를 쓰려고 합니다.  

void 클래스맞아View::OnDraw(CDC* pDC)  
{  
       CMfcddex3Doc* pDoc = GetDocument();  
       ASSERT_VALID(pDoc); 
       // TODO: add draw code for native data here  

요렇게 생성이 되어 있죠? 이부분은 바꾸거나 추가할건 없습니다. 다만 독자님들이
추가하실 것이 있으시다면 추가해넣으셔도 됩니다. 
그렇다면 여기서 실제로 게임을 진행하는 부분은 어느 부분일까? 
앞서배운 전체모드 MFC를 생각해보자 거시서는 어플리케이션 클래스에  화면을 복구하는
함수를 만들어 마치 도스의 게임을 제작하듯 제작을  했다. 이번에도 다를 것은 없다. 
도스에서 게임을 제작할때 Main에 몽땅 집어 넣는 방법처럼 다음 함수는 그러한 방식으로
코딩이 되어 있긴 한데  
BOOL 클래스마져View::Main()  
{  
//      if (!((CMainFrame*)m_pMainWnd)->m_bActive)  
//              return FALSE;  
// 활성화가 되어 있느냐?에 관련된 변수의 값을 참고하는 것인데  
// 이프로그램에서 배경을 복구하는데 필요없다.  
   DWORD               thisTickCount; // 시간을 채크하기위한  
   RECT                rcRect;  
   DWORD               delay[4] = {50, 78, 13, 93};  
   int                 i;  
   PALETTEENTRY        pe[256]; // 파레트를 담기위한  
   HRESULT             ddrval; 
   // 현재 시간을 보간하고  
   thisTickCount = GetTickCount();  
   for(i=0; i<3; i++)  
   {  
       if((thisTickCount - m_lastTickCount[i]) > delay[i])  
       {  
           // Move to next frame;  
           m_lastTickCount[i] = thisTickCount;  
           m_currentFrame[i]++;  
           if(m_currentFrame[i] > 59)  
               m_currentFrame[i] = 0;  
                       // Added to move the sprites.  
                       if (++m_xpos[i] > (640-64))  
                               m_xpos[i] = 0;  
                       if (++m_ypos[i] > (480-64))  
                               m_ypos[i] = 0;  
       }  
   } 
   // 640x480의 한프래임을 가져오기 위한 그림 크기  
   rcRect.left = 0;  
   rcRect.top = 0;  
   rcRect.right = 640;  
   rcRect.bottom = 480;  
   while( 1 )  
   {  
       ddrval = m_DD.BackBuffer().BltFast (0, 0, m_Surface, rcRect,   
                                               DDBLTFAST_NOCOLORKEY ); 
       if( ddrval == DD_OK )  
       {  
           break;  
       }  
       if( ddrval == DDERR_SURFACELOST )  
       {  
           restoreAll();  
       }  
       if( ddrval != DDERR_WASSTILLDRAWING )  
       {  
           return TRUE;  
       }  
   }  
   if(ddrval != DD_OK)  
   {  
       return TRUE;  
   } 
   for(i=0; i<3; i++)  
   {  
       rcRect.left   = m_currentFrame[i]%10*64;  
       rcRect.top    = m_currentFrame[i]/10*64 + 480;  
       rcRect.right  = m_currentFrame[i]%10*64 + 64;  
       rcRect.bottom = m_currentFrame[i]/10*64 + 64 + 480; 
       while( 1 )  
       {  
           ddrval = m_DD.BackBuffer().BltFast (m_xpos[i], m_ypos[i],   
                                         m_Surface,rcRect, DDBLTFAST_SRCCOLORKEY ); 
           if( ddrval == DD_OK )  
           {  
               break;  
           }  
           if( ddrval == DDERR_SURFACELOST )  
           {  
               restoreAll();  
           }  
           if( ddrval != DDERR_WASSTILLDRAWING )  
           {  
               return TRUE;  
           }  
       }  
   } 
  // Flip the surfaces  
   while( 1 )  
   {  
       ddrval = m_DD.Paint (); // 찍어~ 무조건 찍어~  
       if( ddrval == DD_OK )  
       {  
           break;  
       }  
       if( ddrval == DDERR_SURFACELOST )  
       {  
           restoreAll();  
       }  
       if( ddrval != DDERR_WASSTILLDRAWING )  
       {  
           break;  
       }  
   } 
       return TRUE;  

(이거 MFC 맞아요? 모양만 MFC 아니예요? ) 울랠래~ 분명히 이방법을  사용하면
게임제작툴을 SDI로 구성하고 다이렉트의 평명을 잡고 복잡  한 처리없이 인터페이스 부분을
신경써서 툴을 제작할 수 있다.  

다시 정리를 하자면~
1. 메세지를 구성하는 방법을 알아야 한다. 
2. 다이렉트 엑스를 MFC에 응용하는 벙법은 다양한다.  
   1) 다이알로그로 생성해서 제작  
   2) 도규먼트와 뷰가 없이 제작한다.  
   3) 윈도우즈 모드에서 충돌없이 사용한다.  
   3) 기타 방법 
3. 어디에서 생성하고 어디서 소멸을 하는지를 파악한다.  
   [ 메세지 분석을 통한 위치결정 ] 
4. 응용 프로그램실력을 쌓는다. 
제가 위의 2번에서 2),3)을 다루어 보았는데 앞으로 기회가 있다면 기타방법을 알려 드리고
보다 다양한 방법이 나와 독자분들께서 선택을 해서 사용할 수 있는 때가 오기를 바라면서
다이렉트 엑스를 MFC에서 사용하자편을 마감하겠습니다. 

  앞으로 보다 빠른 방법으로 화면에 찍는 방법, 3차원 음향효과 처리  방법, 기타 제가
  가지고 있는 정보를 기술해 보도록 하겠습니다. 
  이미 알고 계시는 분들은 아무것도 아니지만 처음 공부하거나 접하는  분들에게 마땅한
  자료가 없어 고생하시는 분들을 생각하면 별 도움  안되는 글을 적어 보았습니다. 
  ( ?? 어색한 문구.... )


------------------------------------------------------------
MFC에서 D3D 8.0 사용하기(툴만들때 용이)

참고로 이강좌는 외국사이트를 번역기로 번역해서 옮긴것이라 문맥이 앞뒤가 어색할수 있습니다.
이강좌는 저에게 개인적으로 "MFC에서 D3D를 붙일려구 하는데 어떻게 해야 하나요?"라는 관련질문을 많이 해오셔서 이내용을 시간이 없는 관계로 그냥 캡쳐해서 올립니다.
개인적으로 시간이 많다면 강좌도 쓰고 책도 쓰고 싶지만... 이래저래 핑계로 못하고 있네요~ ^^;
그럼.도움이 되셨으면 합니다.

Direct3D를 MFC로 이용하는 방법

 Direct3D를 이용한 툴을 작성하는 경우에는, Win32SDK를 이용해도 할 수 있습니다만, MFC를 이용하는 편이 무엇인가 편리한 일이 많다고 생각합니다. 게다가 드큐먼트뷰아키테크체와 공존할 수 있으면(자) 더욱더 툴이 만들기 쉬워지겠지요. 또, MFC를 사용해 Direct3D를 이용하는 해설은 어디를 찾아도 눈에 띄지 않기 때문에, MFC를 사용한 Direct3D의 초기화 방법을 소개합니다.
조금이라도 여러분의 참고가 되면 다행입니다.

또, 여기에서는 MFC의 간단한 지식이나 Direct3D의 초기화 방법은 기존의 것으로 해 해설해 갈 것입니다. MFC나 Direct3D의 초기화 방법은, 여러가지 곳에서 해설되고 있기 때문에, 그 쪽을 봐 주세요.

이쪽으로부터샘플을 다운로드할 수 있습니다. 샘플에서는, Direct3D를 초기화하는 클래스 CDirect3D 클래스를 작성해, CView 클래스의 base class로 하고 있습니다. 이 방법은, DirectXSDK 부속의MFCFog샘플로, CD3DApplication 클래스를 CFormView 클래스의 base class로 하고 있으므로, 똑같이 하고 있습니다. CDirect3D 클래스는, CD3DApplication 클래스의 MFC 대응 간이판이라고 생각해 주세요.


--------------------------------------------------------------------------------

1 어플리케이션 체제를 작성

우선 처음에, 체제를 작성합시다.
MFC AppWizard로,작성하는 어플리케이션의 종류를 SDI,문서/뷰아키테크체를 서포트에 체크를 넣어 후는 적당하게 옵션을 붙여 작성해 주세요.

이 시점에서 이하의 클래스가 완성됩니다.

CxxxApp
CMainFrame
CxxxDoc
CxxxView
여기로부터, Direct3D를 초기화해 나가는 것입니다만, Direct3D의 표시를 맡는 클래스, 즉, Direct3DDevice8와 관련짓는 클래스는 어떤 것이 좋을까요? 디바이스는, 윈도우와 관련지으므로, 이 안에서는,CMainFrame 클래스, 혹은CxxxView 클래스라는 것이 됩니다. 또, 드큐먼트뷰아키테크체를 채용하고 있으므로, 디바이스와 관련짓는 클래스는, 표시를 맡는 클래스, 즉CxxxView 클래스라는 것이 됩니다. CMainFrame 클래스와 디바이스를 관련지을 수 있습니다만, 드큐먼트뷰를 이용한 툴을 작성이라고 하는 것이므로, 여기에서는 의미가 없을 것입니다.


2 Direct3D를 초기화한다

그럼,CxxxView 클래스에 Direct3D의 초기화 처리를 추가해 나갑시다. 그 전에, 어디에서 Direct3D의 초기화 처리를 실시하면 좋은 것일까요? 디바이스를 작성할 때는, 유효한 윈도우 핸들이 필요하게 됩니다. 그 말은, 벌써 윈도우는 작성 끝난 상태로 없으면 안됩니다. 어플리케이션의 초기화 처리는,CxxxApp::InitInstance 함수가 담당하고 있습니다. 이 함수 중(안)에서 Direct3D의 초기화 처리를 혼자서 맡아 처리해도 될 것입니다. 여기에서는,CxxxView 클래스가 Direct3D의 초기화를 담당하기로 하겠습니다.

CxxxView 클래스에서 초기화를 실시하는 경우,CView::OnInitialUpdate 함수를 오버라이드(override) 해, Direct3D의 초기화를 실시하는 것이 좋을 것입니다. DirectXSDK 샘플에서도 그렇게 하고 있습니다 해···. 초기화 코드는 이하와 같이 될까요.

void CxxxView::OnInitialUpdate()
{
   CView::OnInitialUpdate();

   HWND hDevice, hFocus;
   HRESULT hr;

   // 윈도우 핸들의 취득
   hDevice = GetSafeHWnd();
   hFocus  = GetTopLevelParent() ->GetSafeHwnd();

   // 윈도우의 클라이언트 영역을 취득
   GetClientRect(&m_rcClient);

   // Direct3D 오브젝트의 작성
   m_pD3D = Direct3DCreate8(D3D_SDK_VERSION);

   // 디스플레이 모드의 취득
   D3DDISPLAYMODE d3ddm;
   m_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm);

   // D3DPRESENT_PARAMETERS 구조체의 설정
   ::ZeroMemory(&m_d3dpp, sizeof(m_d3dpp));
   m_d3dpp.Windowed               = TRUE;
   m_d3dpp.BackBufferCount        = 1;
   m_d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;
   m_d3dpp.EnableAutoDepthStencil = TRUE;
   m_d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
   m_d3dpp.hDeviceWindow          = hDevice;
   m_d3dpp.BackBufferWidth        = m_rcClient.Width();
   m_d3dpp.BackBufferHeight       = m_rcClient.Height();
   m_d3dpp.BackBufferFormat       = m_d3ddm.Format;

   // 디바이스 작성
   hr = m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,
                             D3DDEVTYPE_HAL,
                             hFocus,
                             D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                             &m_d3dpp,
                             &m_pd3dDevice);

   /* 중략··· */

   // 초기화 완료
   m_bReady = TRUE;
}


툴이므로, 윈도우 모드로 초기화하고 있습니다. 또, 포커스 윈도우를 톱 레벨 윈도우, 디바이스 윈도우를 CxxxView, 백 버퍼의 사이즈는 디바이스 윈도우의 클라이언트 영역 전체적으로 초기화하고 있습니다.

그런데 여담이 됩니다만, 포커스 윈도우란 누구일까요?
헬프에는 이하와 같이 쓰여져 있습니다. 이것은, IDirect3D8::CreateDevice 함수의 인수, hFocusWindow의 설명입니다.

hFocusWindow
[in] 이 Microsoft Direct3D 디바이스로 포커스를 설정하는 윈도우 핸들. 지정하는 윈도우는, 풀 스크린의 최상정도 윈도우가 아니면 안된다.


작성하는 디바이스의 포커스-(영) focus (n) 1, 초점 2, 중심 :다이슈칸 「GENIUS」보다-즉, 작성하는 디바이스의 중심이 되는 윈도우라고 하는 것인가.

이것으로, Direct3D의 초기화는 완료입니다. 후는, 묘화 메세지에 응해 백 버퍼의 내용을 카피하면 OK입니다.
묘화 메세지에 대응한다CxxxView::OnDraw 함수는 이하와 같이 기분이 듭니다.

void CxxxView::OnDraw(CDC* pDC)
{
   CxxxDoc* pDoc = GetDocument();
   ASSERT_VALID(pDoc);
   if(m_bReady)
   {
       m_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0, 1.0f, 0);
       if(SUCCEEDED(m_pd3dDevice->BeginScene()))
       {
           // 여기에 장면을 묘화 합니다
           m_pd3dDevice->EndScene();
       }
       m_pd3dDevice->Present(NULL, NULL, NULL, NULL);
   }
}


이것으로, 빌드 해 실행해 보세요. 잘 실행할 수 있을까요.
여기에는 쓰고 있지 않습니다만, CxxxView가 파기될 때에서도, Direct3D의 종료 처리-Direct3DDevice8, Direct3D8 오브젝트의 해방-를 잊지 않고 가 주세요.
제대로 동작할 것입니다. 그러나, 유감스럽지만 이 상태에서는 터무니없게 동작이 늦어집니다. 그 원인은 무엇인가?

자세한 내부 사정은 모릅니다만, 아마, 작성하는 백 버퍼와 윈도우의 클라이언트 영역의 사이즈에 차이가 있는 것 같습니다. 이러한 사이즈가 다르면(자), Direct3D는, Present 함수를 실행하면(자) 마음대로 확대 축소 처리를 해 윈도우에 카피합니다. 이 처리가 터무니없고 늦기 때문에, 전체의 동작이 늦어집니다.
GDI 묘화 때에, DIB 화상의 폭을 DWORD 경계에 갖추고 있었습니다만, 그것과 닮은 것 같은 것일까요?

그렇다 치더라도 이 늦음은, 견딜 수 있는 것이 아닙니다. DirectXSDK 샘플을 실행해, 윈도우를 적당한 크기에 변경해도 동작에 지장을 초래하는 일은 없기 때문에, 더좋은 방법은 있을 것입니다.


관련 참고사이트
http://www.gamedev.net/reference/articles/article1778.asp
http://www.devx.com/premier/mgznarch/vcdj/2000/07jul00/mf0007/mf0007.asp
http://codeguru.earthweb.com/directx/index.shtml
http://www.hackorama.com/d3dview/

----------------------------------------------------------------
MFC 윈도우 프로그램 작성법및 구조


1.    먼저 Visual C++ 5.0 부터 6.0까지의 비주얼 컴파일러를 가지고 설명을 엮어나가려고 한다.
    Visual C++의 최신 버전을 이용함은 보다 편리하다는 장점이 있다. 특히 5.0과 6.0은 차이가 많이
    난다. 비주얼 6.0은 비주얼 베이직과 같은 함수의 형식을 미리 보여주는 기능이 있어서 사용이 
    편리하다. 물론 컴파일러의 성능도 좋아 졌다. 그러나 용량이 장난이 아니다. MSN까지 깔면 대략
    600M정도가 된다.

       MFC나 API로 짜나 별상관은 없다. 게임에서 프로그래밍하는 부분은 골격이라는 것이 존재하기
    에 .. 단지 많은 Visual C++로 프로그래밍을 하는 사람들의 고민이 이 많은 윈도우 함수중에서
    디다 무슨 함수를 써서 구현을 해야하는지가 망망하기 때문에 프로그래밍을 하다가  포기를 하는
    경우가 허다하다고 생각이 된다. 
  
     처음에는 아마 Visual C++ 완벽가이드와 같은 책을 볼 것이다. 감을 잡기 위해서...
    그 다음에 조금 숙련되면 레퍼런스를 보게 될 것이다. 그리고 함수 찾는 데에 시간을 보내게될 것이
    다. 프로그래머가 보는 책중에서 제일 좋은 책은 마이크로 소프트에서 나온 3권짜리 레퍼런스
    일 것이다. 3권짜리가 약 15만원정도 하는 것 같다. 물론 원서이다.
    이것만큼 좋은 참고서는 없을 것이며, 이 강좌를 듣는 여러분들도 이 레퍼런스만을 갖고 프로그램
    을 짜는 날이 빨리 오기 바란다.  
   
2.   게임프로그래밍을 간단히 말하자면 무한 루프속에서 각종의 이벤트를 처리한다고 말할 수 있다.
    게임을 하다가 갑자기 Escape키가 들어오면 처리를 해야 하는 데 어디서 처리를 .......
    그럼 Visual C++에서의 무한 루프는 어디일까? DOS에서의 프로그램의 무한루프의 시작은 main()
    일 것이다. 그럼 윈도우 프로그램은 어디일까? 이런 것들을 하나하나 이해하고 그원리를 찾아 나
    가면 된다. 한가지 기억할 것은 도스와 윈도우 프로그래밍이 틀린 것이 있다. 

      제 나름대로 정의를 한다면 도스는 사용자의 인터페이스 즉 입력을 받아들이는 형식을 프로그
    래머가 정의를 하였다. 그러나 윈도우로 넘어오면서 이런 것들이 통일 되었다. 즉 사용자로 하여금 
    하나에만 익숙하도록 유도를 한 것이다. 그것이 바로 다이얼로그 박스와 같은 것들이다.
    즉 사용자는 다이얼로그 박스를 통하여서 선택만 하면 실행되도록 하여 사용자의 사용방법을 
    통일 시켰다. 

      이와 같은 것을 프로그래머 입장에서 보면 불만일 수도 있다. 어쩌랴~~~
    그럼 이와 같은 것은 어떻게 해야할까? 
    걱정할 필요가 없다. 모든 것들이 클래스로 만들어져 있다.
    우리는 클래스를 정의하고 사용하고 Return되어서 오는 값을 가지고 이런 저런 처리를 해주면
    된다. 
    Class는 모든 것을 간편화 시켰다. 또한 전역함수의 사용을 배제하도록 하여서 프로그램을 단순
    화 시켰고, 프로그램을 읽는 사람들도 조금은 편하다. 그러나 너무 많은 상속은 읽기가 힘들다.
    내가 경험한 바로는 그렇다. 3~4개정도가 상속이 되면 도무지 뭐가 뭔지........
    어떻게 보면 클래스를 분석하는 것도 OOP의 개념에는 위배가 된다. 분석 시간과 코딩 시간을 
    줄이기 위한 것이 바로 클래스이니깐 말이다. 그냥 정의하고 사용하는 것이다.
    대학 다닐 때 모 교수님께서 앞으로는 클래스를 다양하게 가지고 있는 프로그래머가 잘나가게 될
    것이라고 하신 말씀이 기억이 난다. 좋은 클래스는 코드의 재사용시에 개발 기간을 단축해준다.    
    왜? 그냥 쓰면 되니깐?
    그러면 게임의 기본 골격을 만들고 거기다가 살을 붙여 보자. 

3.   먼저 게임 골격을 만들어 보자.
    MFC를 이용하기에 ClassWizard를 이용하기에 보다 간편하게 만들 수 있다.  
    게임을 만들 경우에는 MFC에서는 App Class와 MainFrame Class만 있으면 된다. 
    이것은 MFC의 상속도를 조금 보기 바란다. 도움이 될 것이다.
    먼저 우리는 App Class와 MainFrame Class만을 남기고 모두 지우자.
    하나 하나 지우기가 힘들 것이다. 간단한 방법이 있다.
    우선 App와 MainFrame 클래스와 헤더만 남기고 나머지는 프로젝트에서 지운다.
    이제 Build를 시켜서 컴파일을 시키면 에러가 뜬다. 프로젝트에서 빠졌기 때문에 이제 그부분을
    지워나가면 된다. 그리고 이제 컴파일을 다시 시키면 error 0 waring 0이 나오면 제거가 완벽하게
    된 것이다.

      여기서는 API와 MFC를 비교하면서 설명을 할 것이다.
    MFC는 결국에는 API를 한 대 묶은 것이기 때문이다.
    MFC의 기본은 결국 API이기 때문에 API로 프로그램을 하던사람은 MFC를 보면 금방 눈치를 챌 수
    있을 것이다. 그렇다고 해서 API를 하라는 것은 아니다. 어느 홈페이지에서는 API로 프로그래밍을
    하는 것을 어셈플리를 하는 것에 비유한 사람도 있다. 모두 장단점이 있다. 나도 이 부분에 대해서
    고민을 많이 한 사람중에 한사람이다. 근데 그 원리를 들여다 보게 되니, API나 MFC나 똑같더라는 
    것을 금방 알 수 있게 되었다. 결국 로직이고, 결국 C의 주위를 맴도는 정도라는 것을 ......
    자 그러면 시작해 보자.
    
    API의 구조를 보면 WinMain()와 WndProc()로 이루어 져 있다. WinMain()는 윈도우의 등록을하는
   부분이다. 이 부분은 App class와 같다. App Class의 InitInstance()가 그와 같은 역할을 한다.
    그럼 WndProc()를 보자 . 이부분은 각종 키보드의 입력을 처리하고 그리고 메시지를 처리한다.
    지금 당장 ClassWizard를 실행시키고 MainFrmae을 선택하고, 그쪽으로오는 각종 윈도우 메시지
   를 보라. 어라! WndProc()에서 표현 할 수 있는 모든 메시지가 MainFrame class로 다 들어옴을 
   알 수 있다. 
      결국 App와 MainFrame()만으로도 모든 프로그램이 가능하나. 역할의 분리를 통하여서 구조화
   하려고 한 것임을 알 수 있다. 이제 확신이 들 것이다. MFC와 윈도우 프로그램은 메시지 방식
   이어서 어느 부분에 어떻게 코딩을 해야 하는 것이 중요하고, 그 부분을 잘알고 있으면 쉽게 할 수
   있다. 

   API로 짤 때에 우리는 이런 부분이 있었다.
   

Main message loop:
       while (1)
       {
               if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
               {
                   if( msg.message == WM_QUIT )
                               break;
                       TranslateMessage(&msg);
                       DispatchMessage(&msg);
               }
                 Game_Main();  // 이 부분은 아님 설명을 위한 부분 임
       }


     위에서 무한 루프를돌릴 수 있는 부분은 바로 if 절이 끝나는 Game_Main()부분이다.
     if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
               {
                   if( msg.message == WM_QUIT ................
        이 부분은 윈도우의 메시지를처리를 하는 부분이다. 메시지만 처리를 한다. 갑자기 키보드가
     눌려 지면 이 분에서 메시지를 처리하기 위해서 WndProc()를 부를 것이다. 
     우리의 게임프로그램은 윈도우 메시지때 마다 처리하는 것이 아니다.
     그럼 무한 루프가 될 수 있는 부분은 위의 Game_Main() 부분밖에 없다는 것을 알 수 있다. 
     물론 다른 방법도 있을 것이지만 말이다.
       앞에서도 얘기를 했지만 이 부분은 상당히 중요하다. 왜냐하면 게임이란 어떻게 보면 계속적인
     루프를 돌면서 어떤 순간에 일어난 일을 처리하기 때문이다. 
     또한 윈도우도 while()을 좋아한다. 매순간 마다 틀려지는 윈도우 메시지를 받을려면 while()을       좋아
할 수밖에 없다.

       그럼 이 부분이 과연 MFC에서는 어딜까?? 
     위의 부분은 메시지를 처리하는 부분이 아니니깐, 바로 APP Class라는 것을 직관적으로 알 수
     있다.  이 부분은 APP Class의 Run()함수 와 같다. App Class의 Run()함수를 마구 해부해 보면
     아래와같다.
    

CWinApp:: Run()
  {
       MSG msg;
       if( m_pMainWnd == NULL ) ::PostQuitMessage(0);
      while(1)
      {
            if( PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) )
            {
                 if( !GetMessage(&msg, NULL, 0, 0)) return  msg.wParm;
                      TranslasteMessage(&msg);
                      DispatchMessage(&msg);
            }
            else
            {
                 OnIdle();
             }
       } 
       return 0;
    }


    위와 똑 같지는 않지만 위와 같은 로직을 가지고 있다. 
    실제 Run()의 내부 코드는 알 수 없겠죠. 마이크로 소프트에서 클래스화하였기 때문이죠.
    단지 아마 저런 로직을 가지고 처리를 할 것이라는 것입니다. 즉 Run()의 역할을 그려놓은 것이죠.
    
    그럼 API의 WinMain()을 개념적으로 MFC로 빗대어 보면 아래와 같다고 할 수 있다.
    WinMain()
    {
          CWinApp *pApp = AfxGetApp();
         pApp->InitAppication();
         pApp->initInstance();
         pApp->Run();
         pApp->ExitInstance():
    }
   이제 이해가 완전히 갈 것이다.

4. 위의 사항을 이제 골격에 맞추어 보자.
      
      if( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) )
      {
                   if( msg.message == WM_QUIT )
                               break;
                       TranslateMessage(&msg);
                       DispatchMessage(&msg);
      }
      
     이부분은  MainFrame()에서 해결을 한다. 위의 부분은 메시지를 처리하는 부분이니깐.
       그럼 GameMain()부분은 어디에...... 바로 여기에 숨어 있다.
     OnIdle()함수가 이와 같은 역할을 한다.
     OnIdle()함수는 메시지의 처리가 없으면 실행되는 함수이다.
     즉 프로세서가 아무 처리도 하지 않고 쉴 때에 실행되는 함수이다. 우리는 이 함수를 오버라이딩
     하여서 위와 같은 로직을 만들 수 가 있다. 메시지가 날라올 때는 메시지 처리를 하러가야하고 
     아닐때는 OnIdle()코드를 처리해야함이 당연하다. 잘 비교해보라.
      다시 보라. 위의 코드를 읽어보면 딱 맞다는 것을 알 수 있다.
      GameMain()도 메시지의 처리가 없 을 시에 실행되는 것이기에 OnIdle()가 이에 해당한다는
     것이....... 이제 명쾌히 해결이 되었을 것이다.

       그럼 OnIdle()함수에 GameMain()을 넣어 주면 되겠구나.......OnIdle()는 무한루프를 돈다.

5.    OnIdle()을 그대로 쓰면 MFC에 의한 작동을 한다. OnIdle()로부터 return 되는 값을 1로 고쳐
      주라.  즉 return 1;로 해주라. 그래야만 MFC에서의 OnIdle()을 실행하지 않고, 우리가 정의한 루프로
      할 수 있다.
    
       근데 이 부분을 나는 이렇게 하고 싶다. App Class의 역할과 MainFrame()의 역할을 나누고
     싶다
     그렇게 하면 역할이 분명하고 헛갈리지 않는 다.
     App Class는 MainFrame()의 실행과 메시지 처리를 App Class는 윈도우의 등록과 MainFrame
     을 실행 시켜주는 것을 할 수 있도록 한다.
     이렇게 하기 위해서는 App Class에서 MainFrmae()을 참조할 수 있어야 한다. 
     이 부분을 이렇게 고치면 된다

     App  Class의 InitInstance()함수에서 MainFrame()을 참조하기 위해서     

     CMainFrame* pFrame = new CMainFrame; 부분을 pFrame = new CmainFrame;로 고치고
     CMainFrmae* pFrmae의 Class의 public으로 변수 선언을 해주라. 그러면 App Class의 어느 
     곳에서도 MainFrame을 접근할 수 있다. 위의 코드는 이해가 갈 것이다. 이 부분이 이해가  안된
     다면 C++을 다시 공부를 하기 바란다. 

    class CRPG1App : public CWinApp
   {
      public:
                CRPG1App();
                CMainFrame* pFrame ;
     그리고 OnIdle()함수에서 pFrame->GameMain()을 선언해주면 CMainFrame에 있는 Game심장
     을 무한루프로 돌릴 수 있다. 

5. 이제 App Class에서 해줄 것은 모두 해주었다. 그럼 다음은 CMainFrame  Class에서 해주면 된다
    이제 App Class에 대한 고민은 접어두고 CMainFrmae의 코딩만 신경을 쓰면 된다.
    CMainFrame에 DirectDraw도 초기화 하고  하면 된다. 그 부분은 나와 클래스를 만들어 가면 된다
    명쾌히 풀리길 바란다. 
    일단 API로 짠 것과 MFC가 별로 틀리지 않다는 것을 알 것이다. 단지 어디다 코딩을 해야할지만
    안다면 말이다. 다음은 더욱 쉽게 풀려 나간다.  


----------------------------------------------------------------
MFC를 이용해서 골격 만들기

1. MFC를 이용하여서 만들 경우에 많은 부분들이 많들어 진다. 특히 Frame Class, View Class,
  App, Document로 4부분으로 골격이 만들어 진다. 이 부분에서 먼저 필요 없는 부분이 있다.
  Document와 View이다.
  왜 필요가 없을 까? 먼저 View의 역할을 DirectX가 대신한다. 즉 DirectDraw가 대신화면의 모든
  것을 처리하고 관리하기에 View에 관한 것이 필요가 없다. 또한 CWind함수를 상속받는 위저드가
  Frame 위저드이기에 DirectDraw+Frame만있으면 이  View의 모든 것을 처리 할 수 있다.
  Document 도 필요가 없다 . 그 이유는 아직 게임에서 도큐먼트를 써보지 못했기에...
2. Project를 이용하여서 만들 경우에는 VC6.0에서는 이 옵션만 해주면 생성시에 View와 Document
  Class는 생성을 하지 않는 다. 결국 App Class 에 이 두 클래스는 등록이 안된다. 
  근데 ChildView Class가 등록 이 되어 있을 것이다. 이것에 관계된 사항은 모두 지워 주기 바란다.

3. library를 VC의 project 메뉴에 Setting에 등록을 해주어야 한다.
   Project setting에서 Link탭을 누르고 Link Object /Module에서  이 부분을 넣어 주기 바란다.
   dxguid.lib ddraw.lib 첨가해주기 바라
  
4. 여기의 코드는 IDirectDraw4의 인터 페이스를 사용한다. 한가지 알아 둘 것은 위에 서도 보듯이 
    dxguid.lib가 들어가 있을 것이다. 이 라이브러리를 빼고 컴파일을 하면 
     pDD->QueryInterface( IID_IDirectDraw4, (LPVOID *) & g_pDD);에서 
   IID_IDirectDraw4가 링크 에러가 날 것이다. 새로운 인터페이스는 dxguid.lib에 추가 됨으로 꼭 
     dxguid.lib를 넣어서 링크를 하기 바란다.
5. 이제 App와 Frame을 이용하여서 프로그램을 만들어 보자.. 
   App의 소스 
// War1.cpp : Defines the class behaviors for the application.
//
#include "stdafx.h"
#include "War1.h"
#include "MainFrm.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CWar1App
BEGIN_MESSAGE_MAP(CWar1App, CWinApp)
       //{{AFX_MSG_MAP(CWar1App)
       //}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CWar1App construction
CWar1App::CWar1App()
{
       // TODO: add construction code here,
       // Place all significant initialization in InitInstance
}
/////////////////////////////////////////////////////////////////////////////
// The one and only CWar1App object
CWar1App theApp;
/////////////////////////////////////////////////////////////////////////////
// CWar1App initialization
BOOL CWar1App::InitInstance()
{
       AfxEnableControlContainer();
       // Standard initialization
       // If you are not using these features and wish to reduce the size
       //  of your final executable, you should remove from the following
       //  the specific initialization routines you do not need.
       // Change the registry key under which our settings are stored.
       // TODO: You should modify this string to be something appropriate
       // such as the name of your company or organization.
       SetRegistryKey(_T("Local AppWizard-Generated Applications"));

       // To create the main window, this code creates a new frame window
       // object and then sets it as the application's main window object.
       CMainFrame* pFrame = new CMainFrame;
       m_pMainWnd = pFrame;
       // create and load the frame with its resources
       pFrame->LoadFrame(IDR_MAINFRAME,
               WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL,
               NULL);


       // The one and only window has been initialized, so show and update it.
       pFrame->ShowWindow(SW_SHOWMAXIMIZED);
       pFrame->UpdateWindow();
       return TRUE;
}
위에서 보듯이 Child View에 대한 사항은 모두 지웠다.
이제 프레임을 보도록 하자.
// MainFrm.cpp : implementation of the CMainFrame class
//

#include "stdafx.h"
#include "War1.h"
#include "MainFrm.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define TIMER_ID            1
#define TIMER_RATE          500
/////////////////////////////////////////////////////////////////////////////
// CMainFrame
IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
       //{{AFX_MSG_MAP(CMainFrame)
       ON_WM_CREATE()
       ON_WM_DESTROY()
       ON_WM_KEYDOWN()
       ON_WM_SETCURSOR()
       ON_WM_TIMER()
       ON_WM_ACTIVATEAPP()
       //}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction
CMainFrame::CMainFrame()
{
       szMsg = "Page Flipping Test: Press F12 to exit";
       szFrontMsg = "Front buffer (F12 to quit)";
       szBackMsg = "Back buffer (F12 to quit)";
       g_pDD = NULL;        // DirectDraw object
       g_pDDSPrimary = NULL;// DirectDraw primary surface
       g_pDDSBack = NULL;   // DirectDraw back surface
       g_bActive = FALSE; 
       
}
CMainFrame::~CMainFrame()
{
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
       if( !CFrameWnd::PreCreateWindow(cs) )
               return FALSE;
       // TODO: Modify the Window class or styles here by modifying
       //  the CREATESTRUCT cs
       cs.style = WS_OVERLAPPED | WS_CAPTION | FWS_ADDTOTITLE
               | WS_MAXIMIZE;
       cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
       cs.lpszClass = AfxRegisterWndClass(0);
       return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics
#ifdef _DEBUG
void CMainFrame::AssertValid() const
{
       CFrameWnd::AssertValid();
}
void CMainFrame::Dump(CDumpContext& dc) const
{
       CFrameWnd::Dump(dc);
}
#endif //_DEBUG

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
       DDSURFACEDESC2              ddsd;
   DDSCAPS2                    ddscaps;
   HRESULT                     hRet;
   LPDIRECTDRAW                pDD;
       if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
               return -1;
       hRet = DirectDrawCreate(NULL, &pDD, NULL);
   if (hRet != DD_OK)
       InitFail(hRet, "DirectDrawCreate FAILED");
   // Fetch DirectDraw4 interface
       hRet = pDD->QueryInterface( IID_IDirectDraw4, (LPVOID *) & g_pDD);
   if (hRet != DD_OK)
       InitFail(hRet, "QueryInterface FAILED");
   // Get exclusive mode
       hRet = g_pDD->SetCooperativeLevel( GetSafeHwnd(), DDSCL_EXCLUSIVE |
DDSCL_FULLSCREEN);
   if (hRet != DD_OK)
       InitFail(hRet, "SetCooperativeLevel FAILED");
   // Set the video mode to 640x480x8
   hRet = g_pDD->SetDisplayMode(640, 480, 8, 0, 0);
   if (hRet != DD_OK)
       InitFail(hRet, "SetDisplayMode FAILED");
   // Create the primary surface with 1 back buffer
   ZeroMemory(&ddsd, sizeof(ddsd));
   ddsd.dwSize = sizeof(ddsd);
   ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
   ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
       DDSCAPS_FLIP |
       DDSCAPS_COMPLEX;
   ddsd.dwBackBufferCount = 1;
   hRet = g_pDD->CreateSurface(&ddsd, &g_pDDSPrimary, NULL);
   if (hRet != DD_OK)
       InitFail(hRet, "CreateSurface FAILED");
   // Get a pointer to the back buffer
   ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
   hRet = g_pDDSPrimary->GetAttachedSurface(&ddscaps, &g_pDDSBack);
   if (hRet != DD_OK)
       InitFail(hRet, "GetAttachedSurface FAILED");
   // Create a timer to flip the pages
   if ( TIMER_ID != SetTimer(TIMER_ID, TIMER_RATE, NULL))
        InitFail(hRet, "SetTimer FAILED");
       
       return 0;
}
void CMainFrame::InitFail(HRESULT hRet, LPCTSTR szError)
{
   //ReleaseAllObjects();
       AfxMessageBox(szError, MB_OK|MB_ICONSTOP);
       SendMessage(WM_DESTROY); 
}
void CMainFrame::OnDestroy() 
{
       ReleaseAllObjects();
   //PostQuitMessage(0);
       CFrameWnd::OnDestroy();
               
}

void CMainFrame::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
       // TODO: Add your message handler code here and/or call default
       switch( nChar )
       {
               case VK_ESCAPE :
               case VK_F12:
                                                PostMessage(WM_CLOSE);
                                                break;
       }
       CFrameWnd::OnKeyDown(nChar, nRepCnt, nFlags);
}
//---------------------------------------------------------
// page 428
//---------------------------------------------------------
BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
       SetCursor(NULL);
       return TRUE;  
}
//---------------------------------------------------------
// page 248
//---------------------------------------------------------
void CMainFrame::OnTimer(UINT nIDEvent) 
{
       HRESULT hRet;
       if(g_bActive && TIMER_ID == nIDEvent )
   {
       UpdateFrame();
       while (TRUE)
       {
              hRet = g_pDDSPrimary->Flip(NULL, 0);
              if (hRet == DD_OK)
                  break;
              if (hRet == DDERR_SURFACELOST)
              {
                   hRet = g_pDDSPrimary->Restore();
                   if (hRet != DD_OK)
                               break;
               }
               if (hRet != DDERR_WASSTILLDRAWING)
                   break;
        }
       }
    
       CFrameWnd::OnTimer(nIDEvent);
}
void CMainFrame::OnActivateApp(BOOL bActive, HTASK hTask) 
{
       CFrameWnd::OnActivateApp(bActive, hTask);
       g_bActive = bActive;
}
void CMainFrame::UpdateFrame()
{
       static BYTE                 phase = 0;
   HDC                         hdc;
   DDBLTFX                     ddbltfx;
   RECT                        rc;
   SIZE                        size;
   // Use the blter to do a color fill to clear the back buffer
   ZeroMemory(&ddbltfx, sizeof(ddbltfx));
   ddbltfx.dwSize = sizeof(ddbltfx);
   ddbltfx.dwFillColor = 0;
   g_pDDSBack->Blt(NULL, NULL, NULL, DDBLT_COLORFILL | DDBLT_WAIT, &ddbltfx);
   if (g_pDDSBack->GetDC(&hdc) == DD_OK)
   {
           SetBkColor(hdc, RGB(0, 0, 255));
       SetTextColor(hdc, RGB(255, 255, 0)); 
       if (phase)
       {
           GetClientRect(&rc);
           GetTextExtentPoint(hdc, szMsg, lstrlen(szMsg), &size);
           TextOut(hdc, (rc.right - size.cx) / 2, (rc.bottom - size.cy) / 2, szMsg , szMsg.GetLength());
           TextOut(hdc, 0, 0, szFrontMsg, szFrontMsg.GetLength());
           phase = 0;
       }
       else
       {
           TextOut(hdc, 0, 0, szBackMsg, szBackMsg.GetLength());
           phase = 1;
       }
       g_pDDSBack->ReleaseDC(hdc);
   }
}
void CMainFrame::ReleaseAllObjects()
{
       if (g_pDD != NULL)
   {
       if (g_pDDSPrimary != NULL)
       {
           g_pDDSPrimary->Release();
           g_pDDSPrimary = NULL;
       }
       g_pDD->Release();
       g_pDD = NULL;
   }
}
위의 코드는 DDEx1의 예제 이다. 그것을 그대로 MFC로 옮긴 것이다. 

------------------------------------------------------------------
Dialog Box에 DirectX8.0 출력하기

DDEx2 Sample

  1. DDEx2에서는 특이하게 비트맵을 로딩해주는 ddutil.cpp와 ddutil.h가 포함된다.
   이 파일 역시 외부함에서 클래스로 include 시켜서 사용한다. 허나 내가 해보니 컴파일 에러도 나고
   디버깅하는 데 많은 시간이 걸렸다. 그래서 아예 클래스의 함수로 포함 시켰다.
   어짜피 이 함수는 DirectDraw와는 떨어질 수 없는 함수들이기 때문이다. 이것만 안다면 MFC로 
   프로그램짜는 것은 식은죽 먹기다.

2. MFC로 프로그래밍을 하면서 알게된 것은 API는 일면 C의 구조이고 MFC는 클래스이기에 이에 
    따른 각 장점들을 살려 주어야 한다. MFC는 클래스의 개념을 도입하여서 이것이 끝나면 게임 엔진
    등을 클래스로 만들어서 MainFrame 위저드에 제공을 해야 할 것 같다. 

3. MFC는 클래스의 상속이다. 그러기에 이 상속만 잘이용한다면 굉장히 편하다.
   MFC가 API보다는 짜기가 횔씬 수월하다. 알기만 한다면 말이다.

4. MFC로 프로그램을 짜는 방법은 여러 가지 이다.
   . 다이얼로그만으로 골격을 세우고 짜는 방법
   . APP와 Frame만으로 골격을 세우고 짜는 방법 
   . 윈도우즈 모드에서 짜는 방법 등등이 있습니다. 

5. Project의 Setting에 ddraw.lib와 dxguid.lib를 꼭 링크 시켜 주기 바란다.

6. 소스는 앞으로 앞축을 하여서 올려 놓겠다. 

-----------------------------------------------------------------
화면에 커서 안보이게 하기

1. 마우스로 주인공의 움직임을 하는 것이아니라. 키보드로 움직일 경우에는 커서가 화면에 나오면 
    안된다. 이 부분은 간단하다. 
    MainFrame 에서 ClassWizard를 이용하여서 윈도우 메시지를 모면 ON_WM_SETCURSOR이란
    메시지를 받는 함수가 있다. 그러면 위저드에선 그에해당하는 함수를 만들어 줄 것이다.
    이 함수는 MainFrme이 Active되었을 경우에 마우스의 셋팅을 하는 부분 이다.
    여기서 우리는 API함수인 SetCursor()함수를 이용하면 된다. 
    SetCursor(NULL);로 셋팅을 해주어라. 그러면 커서가 않보일 것이다.
    그리고 한가지 주의 할점은 MFC에서는 OnSetCursor()를 만들면서 기본적으로 만드는 MFC식
    함수 부분이 코딩이 된다. 자동으로만들어 진다.
      


BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
       // TODO: Add your message handler code here and/or call default
       
       return CFrameWnd::OnSetCursor(pWnd, nHitTest, message);
}


   
기본적으로 이와 같이 만들어질 것 이다. 이 부분에서 CFrameWnd()의  OnSetCursor 함수를
실행하게 되기에 SetCursor()을 해도 소용이 없다. 이 부분을 아래와 같이 바꾸어라.
  그러면 커서가 사라 질 것이다. 우리는 CFrameWnd()의 함수를 무시하기 위함이다.
     


BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
{
       SetCursor(NULL);
       
       return 1;
}
  
위와 같이 하면 커서는 사라 진다. 




반응형

댓글