- 음악과 나 -/『 짬 통 』

네모네모 로직

noon2dy 2006. 5. 6. 02:09

 

2004-07-06 오후 8:35:32   /  번호: 210  / 평점:  (8.4) category: 2D Graphics  /  조회: 10,423 
 네모네모를 만들자 01 다비드 / _david  
다비드님께 메시지 보내기  다비드님을 내 주소록에 추가합니다.  다비드님의 블로그가 없습니다  

  나는 프로그래머다. 프로그래밍은 잘 못한다. 근데 잘하고 싶다. 나와 같은 꿈을 가진 젊은이들과 함께 꿈을 나누려고 이 강좌를 써본다.

 

  나는 프로그래머다. 그래서 디자인은 잘 못한다.(이상한 논리...) 말도 잘 못한다. 그냥 관심있는 사람들은 읽고 도움이 되길 바란다.

 

  게임을 하나 만들어 가면서 강좌를 올리려고 한다. 잘 따라한다면 간단한 게임을 만들 수 있을 것이다. 응용을 한다면 다른 것도 만들 수 있겠지...

 

  책을 많이 보진 않았지만 이책 저책을 보면 좀 어렵더라... 내 실력이 부족해서인지 그네들도 프로그래머라 말빨이 딸려서 그런 건지는 잘 모르겠지만 말이다... 그래서 최대한 쉽게 쓰려고 노력할 것이다. 어렵다 생각되면 리플을 달아라. 그러면 내가 아는 건 내가 답해주고 모르면 다른 사람이 답하겠지... 아무튼 열심히 하길 바란다.

 

  간단한 게임 만들 줄 아는 사람은 안 봐도 된다. 괜히 시간낭비 하지 말고 본인 공부나 더 해라. 게임 만들고 싶은데 어떻게 해야 될지 모르는 사람, c/c++ 기본은 아는데 어떻게 써먹어야 되는지 모르는 사람, 게임프로그래머가 되고 싶은데 어떤 건지 궁금한 사람들이 읽어주길 바란다.

 

  반말한다고 미워해도 소용없다. 존칭을 하면 더 많은 글자를 써야 되기 때문에 귀찮다. 하지만 강좌에 있어서만큼은 귀찮아하지 않을 것이다. 끝까지 올릴 거니 중간에 잘릴 거라는 생각도 안 해도 된다. 모처럼 1년만에 시간이 남아돌아서 올려본다...

 

  

  무슨(어떤) 게임을 만들면서 강좌를 쓰면 좋을까 생각해봤다. 그리고 뇌리에 많은 게임들이 스쳐 지나갔다. 책에 많이 나온 건 빼고 그나마 간단한 것을, 그리고 많은 내용을 담을 수 있는 것을 선택했다. 네모네모 로직이라고 다들 알 것이라 생각한다. 모르면 찾아봐라. 포털사이트에도 있는 것 같더라... 참고로 네트워크는 다음에 생각해보자. 시간 오래걸린다...

 

  진행은 하루에 뭐라도 하나 만들어 보게끔 하는 것으로 하련다. 첫날이라고 윈도우 생성하는 거 달랑하고 끝내는 게 아니라, 내일을 기약하며 생각해 볼 수 있게끔 하려고 한다. 욕할 사람들은 과감하게 욕해도 좋고, 니 마음대로 해라. 그럼....

 

 

  게임이든지 뭐든지 만들려면 계획을 짜야 된다. 계획을 짜는데 도움이 되는 여러 가지 도구들이 있지만, 그런건 책 사봐라. 도구없이 긁적이며 계획을 짜야 되는데.. 우선 네모네모를 하기 위해 필요한 것들을 적어보자.

 

1. 우선 화면에 판을 그려야 되고

2. 마우스 좌표를 읽어서 어느 칸에 올라가있는지 확인하고

3. 네모네모로직을 읽어오고

4. 마우스를 클릭 했을 때 상황에 맞게 열리거나 종료

5. 열릴 수 있는 것 다 열면 축하하며 다음 단계로...

 

  이정도면 게임이 다 만들어 지겠지... 더 필요한 건 당연히 있다. 만들면서 생각하자. 처음부터 로직 읽어 오는 것, 이거저거 다 따질 수도 있지만 우리 사이에 그런 걸 따져서 뭘 하는가. 그냥 진행하자.

 

  단계별로 고려해야 될 것들을 생각하면서 한번 만들어보자.

 

  우선 화면에 판을 그려야 되는데 GDI를 이용하든지, directx를 이용하든지 인데 그냥 directdraw를 이용하자. 게임프로그래머이기도 하고... 쉬우니까.

  그다음 결정할 것은 전체화면으로 할 것인지 창모드로 할 것인지인데.,.. 창모드로하면 디버깅이 쉽다. 창모드와 전체화면 모드 전환은 간단할 거라 생각하고 우선은 창모드로 하자. 그럼 1단계에서 할 것은 나왔다.

  창을 생성하고 그 창에다가 다이렉트드로우로 판그림을 그리면 된다. 판그림... 아니 이럴수가 그림로딩하는 것도 필요하다. 그래 다 하면 된다. 걱정하지 말자. 하다보면 되겠지. 자 그럼 다시 오늘 할 것을 정리 해보자.

 

1-1 창생성

1-2 다이렉트드로우로 그림 그리기

 

  해보자.

 

  프로젝트를 nemonemo로 생성해라. 그리고 main.cpp파일을 만들어 넣어라. 다 했는가? 나는 다 했다. 미리 만들어진 게임이 아니고 나도 이거 쓰면서 만들어 나가는 것이기 때문에 내가 다 쓴다. 손아프지만 내가 감수 한다.

 

  아래는 윈도우 프로그램의 기본 뼈대다. 모르는 사람들은 잘 기억하기 바란다. 그렇지만 잘 모르는 사람들은 한번 따라 쳐보는 것이 좋을 것이다.

 

  긁어다 붙이고 컴파일하면 하얀 창이 뜬다. 창이 아니라고 이상하게 여기지마라. 게임 화면만 나오게 하기 위해서 그렇게 했다. ESC를 누르면 종료된다.

 

  주석은 없다. 대신 아래에 설명을 할 것이니 걱정하지마라.

 

#include <windows.h>

 

#define KEYDOWN( vk_code ) ( ( GetAsyncKeyState( vk_code ) & 0x8000 ) ? 1 : 0 )

#define KEYUP( vk_code ) ( ( GetAsyncKeyState( vk_code ) & 0x8000 ) ? 0 : 1 )

 

#define CLASSNAME   "DAVID NemoNemo"

 

LRESULT WINAPI WndProc( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam )

{

    switch( Msg )

    {

        case WM_CLOSE:

            PostQuitMessage( 0 );

            break;

    }

 

    return DefWindowProc( hWnd, Msg, wParam, lParam );

}

 

int GameInit()

{

    return 1;

}

 

void GameShutdown()

{

    

}

 

int GameMain()

{

    if( KEYDOWN( VK_ESCAPE ) )

        return 0;

    

    return 1;

}

 

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )

{

    WNDCLASSEX wcx;

    wcx.cbSize = sizeof( wcx );

    wcx.cbClsExtra = wcx.cbWndExtra = 0;

    wcx.hbrBackground = ( HBRUSH ) GetStockObject( 0 );

    wcx.hCursor = LoadCursor( NULL, IDC_ARROW );

    wcx.hIcon = LoadIcon( NULL, IDI_APPLICATION );

    wcx.hIconSm = LoadIcon( NULL, IDI_APPLICATION );

    wcx.hInstance = hInstance;

    wcx.lpfnWndProc = WndProc;

    wcx.lpszClassName = CLASSNAME;

    wcx.lpszMenuName = NULL;

    wcx.style = 0;

 

    RegisterClassEx( &wcx );

 

    HWND hWnd = CreateWindowEx( 0, wcx.lpszClassName, "NemoNemo", WS_POPUP | WS_VISIBLE, 0, 0, 800, 600, NULL, NULL, hInstance, 0 );

 

    if( !hWnd )

    {

        MessageBox( NULL, "창만들기 실패", 0, 0 );

        return 0;

    }

 

    if( !GameInit() )

    {

        MessageBox( NULL, "초기화 에러", 0, 0 );

        return 0;

    }

 

    MSG msg;

    ZeroMemory( &msg, sizeof( MSG ) );

 

    while( 1 )

    {

        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )

        {

            if( msg.message == WM_QUIT )

                break;

 

            TranslateMessage( &msg );

            DispatchMessage( &msg );

        }

 

        if( !GameMain() )

        {

            break;

        }

 

        Sleep( 1 );

    }

 

    GameShutdown();

 

    return 0;

}

 

  여기까지다. 어떤가 흥미로운가? 다 아는 내용 그만하고 딴거 하라고...? 나는 생각보다 인내심이 강하다. 설명하겠다...

 

#include <windows.h>

 

#define KEYDOWN( key ) ( ( GetAsyncKeyState( key ) & 0x8000 ) ? 1 : 0 )

#define KEYUP( key ) ( ( GetAsyncKeyState( key ) & 0x8000 ) ? 0 : 1 )

 

#define CLASSNAME   "DAVID NemoNemo"

 

  include문은 다 알거라 생각한다. 윈도우 프로그래밍을 하는데 필요한 기본적인 헤더만 우선 인클루드 했다. 없으면 안 된다. 설마 모르진 않겠지?

  다음 define문 3개는 없어도 된다. 편하려고 만든 것이다. 뭔지 간단하게 설명한다. KEYDOWN과 KEYUP은 키입력을 간단하게 체크하기 위해서 만들었다. 함수처럼 사용하면 된다. 넘어온 값이 눌러졌거나 떼어지면 1을 반환한다. GetAsyncKeyState함수는 windows헤더에 정의 되어있는 함수이다. 그 키가 눌러졌는지 체크를 해준다. 사용법이고 뭐고 필요없다. 보이는 대로 사용하면 된다. 윈도우 메시지를 사용하지 않고 왜 이런 걸 쓰냐고? 내맘이다. 자기 편한대로 해라. 답답하면 코드를 추가하고 제거해라. 당신 스타일로 해라. 뭐라고 안한다.

  다음 정의는 문자열 정의인데... 내가 만들었으니 저렇게 넣었다. 보기 싫으면 다른 것으로 대체해도 된다. 클래스 이름자리에 넣을 거라서 이름을 CLASSNAME이라고 했다.

 

 

  다음으로 진행하자.

 

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd )

 

  윈도우 시작점이다. 앞에 왜 이런게 붙는지 묻지마라. 윈도 기본서를 보면 다 나와았다. 참고해라. 설명도 안 할꺼면서 왜 이걸 붙여서 설명하는 척하냐고? 내맘이다.... 나도 설명하려고 했었는데 귀찮아졌다. 다음으로 넘어가자.

 

    WNDCLASSEX wcx;

    wcx.cbSize = sizeof( wcx );

    wcx.cbClsExtra = wcx.cbWndExtra = 0;

    wcx.hbrBackground = ( HBRUSH ) GetStockObject( 0 );

    wcx.hCursor = LoadCursor( NULL, IDC_ARROW );

    wcx.hIcon = LoadIcon( NULL, IDI_APPLICATION );

    wcx.hIconSm = LoadIcon( NULL, IDI_APPLICATION );

    wcx.hInstance = hInstance;

    wcx.lpfnWndProc = WndProc;

    wcx.lpszClassName = CLASSNAME;

    wcx.lpszMenuName = NULL;

    wcx.style = 0;

 

    RegisterClassEx( &wcx );

 

  윈도우클래서 선언하고 등록하는 부분이다. 윈도우(운영체제)에게 이런 프로그램을 실행할거라고 알리는 것이다. 다 아리라 생각한다. RegisterClassEx로 등록을 하면 UnregisterClass 안해도 된다는 전설이 있다. 참고해라.

 

  HWND hWnd = CreateWindowEx( 0, wcx.lpszClassName, "NemoNemo", WS_POPUP | WS_VISIBLE, 0, 0, 800, 600, NULL, NULL, hInstance, 0 );

 

    if( !hWnd )

    {

        MessageBox( NULL, "창만들기 실패", 0, 0 );

        return 0;

    }

 

  윈도우 창을 만드는 거다. 다른 윈도우 폼없이 나오려면 WS_POPUP을 해야된다. WS_VISIBLE은 ShowWindow 함수 안부르기 위해서 넣은거다. 800*600 그냥 했다. 640*480으로 할걸 후회된다. 왜냐하면 나는 그림을 잘 못그리기 때문이다...

 

    if( !GameInit() )

    {

        MessageBox( NULL, "초기화 에러", 0, 0 );

        return 0;

    }

 

    MSG msg;

    ZeroMemory( &msg, sizeof( MSG ) );

 

    while( 1 )

    {

        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )

        {

            if( msg.message == WM_QUIT )

                break;

 

            TranslateMessage( &msg );

            DispatchMessage( &msg );

        }

 

        if( !GameMain() )

        {

            break;

        }

 

        Sleep( 1 );

    }

 

    GameShutdown();

 

    return 0;

 

  메인함수의 끝까지다. 예전에 도스모드에서 게임들은 거의 이런 형태였다. init, main, end.. 이런 것이 요즘도 잘 쓰인다. 원한다면 자기 마음대로 해도 좋다. init은 WM_CREATE 메시지에다 해도 된다. 마음대로 해라.

  GameInit 함수는 리턴형이 int 이다. 여러 가지 에러를 표현하기 위해서 그렇게 했다. 리턴이 0이면 무엇인가 실패다. try/catch 문을 왜 안쓰냐고? 내맘이다. 편한대로 해도 좋다. try/catch문을 사용하면 내용이 좀더 길어진다. 알아서 하자.

  GameMain 함수도 리턴형이 int 이다. 리턴형이 0이면 종료이다. 지금은 ESC키를 누르면 종료되게 했다.

  GameShutdown 함수는 리턴형이 없다. 종료할건데 실패 이런거 따지고 싶지 않다. 에러가 생기면 강제 종료 시킬 생각이다. 특이하다고 하지마라. 하여튼 종료는 잘 될거다.

 

int GameInit()

{

    return 1;

}

 

void GameShutdown()

{

    

}

 

int GameMain()

{

    if( KEYDOWN( VK_ESCAPE ) )

        return 0;

    

    return 1;

}

 

  윈도우 생성을 했다. 그러면 이제 directdraw를 생성하고 그림을 그리면 된다. 자 그럼 또 고민을 해보자. directdraw에 관계된 것을 다 작성할 것인가. 아니면 microsoft에서 sdk에 딸려 나오는 util을 사용할 것인가. 뭘로 할까 고민하다가... 그냥 util을 사용하기로 했다. 복잡하게 생각할 것 없다. 그냥 window함수 하나 더 익힌다 생각하고 하면 편하다. 그럼 진행해보자.

 

  direct sdk폴더를 찾아라. 엇.... 나는 9.0이 깔려있는데... directdraw폴더가 없다. 에거... 내가 찾아서 올릴테니 받아서 하든지 해라. 8.x를 깐 사람은 있을 꺼다. ddutil.h, ddutil.cpp를 찾아서 프로젝트에 삽입해라. 이놈들을 복사해서 작업중인 폴더에 넣어두고 사용하면 더 좋다. 혹시나 하다가 수정할 수도 있으니까. 준비가 다 되었는가?

  main 상단에 include를 추가하자.

 

#include "ddutil.h"

 

컴파일하면 에러가 생긴다. 따라가보면 dxutil.h가 없다고 하는데. 다시 잘 보면 SAFE_RELEASE 하나 때문이다. 치사하게... SAFE_RELEASE 만 하나 추가해주면 되지만, 귀찮으니까 그냥 dxutil.h와 dxutil.cpp를 복사해넣자. 그리고 프로젝트에 다시 추가하자.

 

상단에 이걸 삽입하는 것도 잊지말자

#include "dxutil.h"

 

다시 빌드하면

 

ddutil.obj : error LNK2001: unresolved external symbol _DirectDrawCreateEx@16

ddutil.obj : error LNK2001: unresolved external symbol _IID_IDirectDraw7

dxutil.obj : error LNK2001: unresolved external symbol __imp__timeGetTime@0

Debug/nemonemo.exe : fatal error LNK1120: 3 unresolved externals

 

이런 에러가 나온다. 라이브러리가 없어서 그런데.... 라이브러리를 추가하면 된다. 위에꺼 두개는 보다시피 ddraw.lib를 포함시키면 되고, 아래꺼는 winmm.lib를 포함시키면 된다. 이름이 이상하다. winmm.... mm은 MultiMedia에서 따왔다 알고 쓰자. 프로젝트 상에서 추가할 수도 있지만 그냥 소스상에서 포함시켜 버리자. 상단에 또 추가한다.

 

#pragma comment( "lib", "ddraw.lib" )

#pragma comment( "lib", "winmm.lib" )

 

  라이브러리를 포함시켜라 이거다. 또 빌드하면 또 에러가 뜬다. 열받는다...

 

ddutil.obj : error LNK2001: unresolved external symbol _IID_IDirectDraw7

Debug/nemonemo.exe : fatal error LNK1120: 1 unresolved externals

Error executing link.exe.

 

뭔 소리냐. IID_IDirectDraw7 이라니.... 앞에 IID 붙은거 보면 guid 문제다. guid를 쓰려면 프로젝트 옵션을 변경하는 것도 있지만... 난 늘 소스에 바로 쓰는 걸 좋아한다. main.cpp 제일 위에 또 추가하자.

 

#define INITGUID

 

  이 프로젝트에서 guid를 쓰겠다는 말이다. guid가 뭐냐? 묻지마라. 아마 Global Unique Identifier겠지... 이런걸로 딴지걸지마라. 잘모르면 COM관련 책을 봐라. 아니면 www.terms.co.kr을 추천한다. 사실 찾을 것도 없이 뜻 그대로다. 정 답답하면 찾아봐라.

  하여튼 이제 빌딩하면 괜찮다. 휴우.. 한시름 놓았군...

 

  다음이 뭐였더라? 그림 나오게. 자 dxutil, ddutil을 인클루드 했다. 유틸리티를 인클루드 했으니 써줘야 예의다. 음.... 점점 복잡해진다. 강좌도 굉장히 길어지고 있다. 시간도 생각보다 많이 흘렀다. 하지만 괜찮다. 난 인내심이 강하다. 오늘 그림찍는 것까지 할꺼다.

 

  유틸리티는 말 그대로 사용법만 익히면 된다. 쉽다. 유틸리티 헤더 파일을 한번 열어보자. 우선 ddutil 부터...

 

  열었는가? 아래위로 왔다갔다 해보면 클래스가 2개가 있는 것이 보일꺼다. CDisplay하고 CSurface인데... 아니... 이럴수가 또 설명할 것이 나왔다. 시간이 점점 더 오버되고 있다. 음.... 인내심이 조금씩 줄어든다. 하지만 걱정하지 마라. 아직 인내심이 남아있다. 그럼 DirectDraw에 대해서 간단히 설명해보겠다.

 

  DirectDraw는 DirectX 중에서 2D 그래픽을 담당하는 객체인데... 이놈을 이용하면 화면에 그림을 그릴 수 있다. 이용하자. 다음... ㅡㅡ;

  Surface는 해석하면 표면인데, DirectDraw에서 사용하는 메모리를 말한다.

 

  자 사전지식을 다 습득했으니 계속 진행해보자.

  

  CDisplay는 DirectDraw를 쉽게 쓰기 위해 만든 클래스이고, CSurface는 Surface를 잘 사용하기 위해서 만든 클래스이다. 클래스가 뭔지는 다 알거라 생각한다. 하나씩 해보자.

 

  우선 DirectDraw를 생성하고 제거하는 코드는 메인에 넣어보자. 간단하다. 생성은 CDisplay 클래스를 생성하면 되고, 제거는 그놈을 제거하면 된다. 한번 해보자.

 

  상단에  전역으로

CDisplay *g_pDisplay = NULL;

 

  GameInit() 함수에서

g_pDisplay = new CDisplay;

 

  GameShutdown()에서

 

    if( g_pDisplay )

    {

        SAFE_DELETE( g_pDisplay );

    }

 

  자 생성하고 종료하는 건 됐다. 이렇게 하면 뭐가 되는지 살펴보자.

 

class CDisplay

{

protected:

    LPDIRECTDRAW7        m_pDD;

    LPDIRECTDRAWSURFACE7 m_pddsFrontBuffer;

    LPDIRECTDRAWSURFACE7 m_pddsBackBuffer;

    LPDIRECTDRAWSURFACE7 m_pddsBackBufferLeft; // For stereo modes

 

    HWND                 m_hWnd;

    RECT                 m_rcWindow;

    BOOL                 m_bWindowed;

    BOOL                 m_bStereo;

 

public:

    CDisplay();

    ~CDisplay();

 

    // Access functions

    HWND                 GetHWnd()           { return m_hWnd; }

    LPDIRECTDRAW7        GetDirectDraw()     { return m_pDD; }

    LPDIRECTDRAWSURFACE7 GetFrontBuffer()    { return m_pddsFrontBuffer; }

    LPDIRECTDRAWSURFACE7 GetBackBuffer()     { return m_pddsBackBuffer; }

    LPDIRECTDRAWSURFACE7 GetBackBufferLeft() { return m_pddsBackBufferLeft; }

 

    // Status functions

    BOOL    IsWindowed()                     { return m_bWindowed; }

    BOOL    IsStereo()                       { return m_bStereo; }

 

    // Creation/destruction methods

    HRESULT CreateFullScreenDisplay( HWND hWnd, DWORD dwWidth, DWORD dwHeight,

                                     DWORD dwBPP );

    HRESULT CreateWindowedDisplay( HWND hWnd, DWORD dwWidth, DWORD dwHeight );

    HRESULT InitClipper();

    HRESULT UpdateBounds();

    virtual HRESULT DestroyObjects();

 

    // Methods to create child objects

    HRESULT CreateSurface( CSurface** ppSurface, DWORD dwWidth,

                           DWORD dwHeight );

    HRESULT CreateSurfaceFromBitmap( CSurface** ppSurface, TCHAR* strBMP,

                                     DWORD dwDesiredWidth,

                                     DWORD dwDesiredHeight );

    HRESULT CreateSurfaceFromText( CSurface** ppSurface, HFONT hFont,

                                   TCHAR* strText,

                                   COLORREF crBackground,

                                   COLORREF crForeground );

    HRESULT CreatePaletteFromBitmap( LPDIRECTDRAWPALETTE* ppPalette, const TCHAR* strBMP );

 

    // Display methods

    HRESULT Clear( DWORD dwColor = 0L );

    HRESULT ColorKeyBlt( DWORD x, DWORD y, LPDIRECTDRAWSURFACE7 pdds,

                         RECT* prc = NULL );

    HRESULT Blt( DWORD x, DWORD y, LPDIRECTDRAWSURFACE7 pdds,

                 RECT* prc=NULL, DWORD dwFlags=0 );

    HRESULT Blt( DWORD x, DWORD y, CSurface* pSurface, RECT* prc = NULL );

    HRESULT ShowBitmap( HBITMAP hbm, LPDIRECTDRAWPALETTE pPalette=NULL );

    HRESULT SetPalette( LPDIRECTDRAWPALETTE pPalette );

    HRESULT Present();

};

 

  CDisplay 선언부다. 좀 길다. 엄두가 안난다. 그냥 함수 이름대로의 기능을 한다고 하자... 그럼 우선 생성된 창에 DirectDraw가 적용되도록 해보자. 헤더부를 쭈욱보면 이 함수가 있다.

 

// Creation/destruction methods

    HRESULT CreateFullScreenDisplay( HWND hWnd, DWORD dwWidth, DWORD dwHeight,

                                     DWORD dwBPP );

    HRESULT CreateWindowedDisplay( HWND hWnd, DWORD dwWidth, DWORD dwHeight );

 

  풀스크린 디스플레이를 만들고, 윈도우 디스플레이를 만든단다. 딱 걸렸다. 이걸 이용하면 된다. 한번 해보자. GameInit() 함수에 추가해보자. 인자가 hWnd, dwWidth, dwHeight 이다. 너무 뻔하다. hWnd는 아까 창만들때 만든거 넣으면 되고, width, height는 창크기를 넣으면 될것이다. 넣어보자. 안되면 말고. hWnd를 넣기 위해서 함수에 넘겨주든지, 전역으로 잡고 있든지 둘 중 하나인데 전역으로 잡자.

 

//전역에

HWMD g_hWnd = NULL;

 

//WinMain에

//hWnd = CreateWindowEx.... 아래에

g_hWnd = hWnd;

 

//GameInit함수에

g_pDisplay = new CDisplay;

g_pDisplay->CreateWindowedDisplay( g_hWnd, 800, 600 );

 

  빌딩하고 실행해보자.

  어! 이게 무슨 변괴인가. 창에 타이틀바가 생겼다. 아까는 없었는데. 아마 CreateWindowedDisplay 함수에서 뭔짓을 하나보다. 못하게 하자. ddutil.cpp를 열어서 살펴보자.

 

    RECT  rcWork;

    RECT  rc;

    DWORD dwStyle;

 

    // If we are still a WS_POPUP window we should convert to a normal app

    // window so we look like a windows app.

    dwStyle  = GetWindowStyle( hWnd );

    dwStyle &= ~WS_POPUP;

    dwStyle |= WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX;

    SetWindowLong( hWnd, GWL_STYLE, dwStyle );

 

    // Aet window size

    SetRect( &rc, 0, 0, dwWidth, dwHeight );

 

    AdjustWindowRectEx( &rc, GetWindowStyle(hWnd), GetMenu(hWnd) != NULL,

                        GetWindowExStyle(hWnd) );

 

    SetWindowPos( hWnd, NULL, 0, 0, rc.right-rc.left, rc.bottom-rc.top,

                  SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE );

 

    SetWindowPos( hWnd, HWND_NOTOPMOST, 0, 0, 0, 0,

                  SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE );

 

  찾았는가? 친절하게 다 설명되어 있다.

 

// If we are still a WS_POPUP window we should convert to a normal app

// window so we look like a windows app.

  

  WS_POPUP 스타일 윈도우면 보통걸로 바꾼단다. 우리는 WS_POPUP인데 왜 바꾸냐 내 허락도 없이 과감하게 지우자. 다 지워도 되지만 우선은 이 부분만 지우자.

 

 // Aet window size  이거 위까지 지우면 된다.

 

  지우고 다시 빌드하고 실행해보자.

  원래대로 나온다. 한번 웃자. 마이크로소프트에서 만든 것도 수정해도 된다. 쫄지 말자. 다른 소스가 있는데 우선은 신경 쓰지 말자. 신경쓰면 내가 계속 쳐야되니 다음에 신경쓰자 ㅡㅡv

  우선 된다는데 의의를 가지자. 아무것도 안된 것 같은데 이게 뭐냐 궁금해 할 것이다. 준비한거다. 다이렉트드로우를 쓰려고. 커피를 마시려면 동전을 넣어야 된다. 내 손에 동전이 없는데 호주머니에서 동전을 꺼내는 작을 했다고 할까? 하여튼 된다. 이제 그림을 찍어 붙이면 된다. 사이즈가 800*600 이니까 이 사이즈로 그림을 붙이자. 작은걸 확대해도 되지만 그냥 같은 사이즈로 하자. 이제 그림을 준비하고 불러와야 되는데... 그림은 그냥 민자 그림으로 하자. 다시 말하지만 나는 그림을 잘 못 그린다. 포토샵을 열자. 캠버스 사이즈를 800*600으로 하고 bmp로 저장하자. 왜 bmp냐고?

 

HRESULT CreateSurfaceFromBitmap( CSurface** ppSurface, TCHAR* strBMP,

                                     DWORD dwDesiredWidth,

                                     DWORD dwDesiredHeight );

 

  CDisplay함수에서 이 함수를 봤는가? 글자 그대로다. Bitmap에서 Surface를 생성시켜 준단다. 인자를 봐라, strBMP BMP이름을 넣어라 이거다. 뭔소린가? bmp파일 읽는 걸 제공해 준단다. 우선은 이걸쓰자. 나중에 그림파일 로딩에 대해 또 쓰겠다. 왜 그러냐고? 시간이 너무 오버됐기 때문이다. 옆에서 자꾸 눈치준다. 난 인내심이 강하다. 빨리 그림찍고 오늘 그만하자. 하여튼 빨리 그림 그리자. 포토샵에서 전체를 적당한 색으로 칠하고 16bit로 저장하자. 왜 16비트냐고? 그냥 저장하자. 더 이상 시간끌면 곤란하다. 다음에 이것저것 다 해보자. 지금은 그냥 따라라.

 

  그림을 그리고 저장했는가? 저장은 nemonemo폴더에 img 폴더를 만들어 거기에 하자. 왜 저장하고 나서 이 말을 하냐고? 미안하다. 배째라.

 

  자 빨리 저장한 그림을 붙여보자.

 

HRESULT CreateSurfaceFromBitmap( CSurface** ppSurface, TCHAR* strBMP,

                                     DWORD dwDesiredWidth,

                                     DWORD dwDesiredHeight );

 

  아까 함수가 이거였지... 인자가 4개인데 CSurface **, TCHAR*, DWORD 2개이다. TCHAR* 는 그림파일 경로\이름 넣으면 되고, DWORD 2개는 그림파일 사이즈인데 그냥 0넣으면 된다. 남은게 CSurface ** 인데, 그럼 CSurface를 넘겨줘야 되니까, 따로 선언하라는 말이다. 한마디로 이 함수가 하는 역할이 빈 CSurface변수를 넣으면 그림넣어서 돌려주겠다 이말이다. 자 빨리 해보자.

 

//전역에

CSurface *g_pBack = NULL;

 

//GameShudown에

SAFE_DELETE( g_pBack );

 

//GameInit에

if( g_pDisplay->CreateSurfaceFromBitmap( &g_pBack, "img\\nemo.bmp", 0, 0 ) == E_FAIL )

        return -1;

 

  잘 못되면 종료다. 빌드하고 실행해보자. 어떤가? 흰화면이 가만히 있으면 성공이다. 장난하냐고? 아까 그림은 왜 안나오냐고? 말 그대로이다. 그림을 불러오기만 했지 화면에 그리라고 안해서 그렇다.

  자 이제 다 됐다. 빨리 그리라고 해보자. directdraw에서 그리는 것을 블리팅이라고 한다. 비슷한 함수가 있는가? 나는 찾았다. 아래에 있다.

 

HRESULT Blt( DWORD x, DWORD y, LPDIRECTDRAWSURFACE7 pdds,

                 RECT* prc=NULL, DWORD dwFlags=0 );

HRESULT Blt( DWORD x, DWORD y, CSurface* pSurface, RECT* prc = NULL );

 

  어 두개다. 잘 보자. x, y 위치는 찍히는 시작 위치를 말할꺼고... 그 다음 것이 하나는 이름이 굉장히 길다. LPDIRECTDRAWSURFACE7 이란다. 대문자로 나와서 읽기가 힘들었지만, 한마디로 다이렉트드로서피스7의 포인터 형이란다. 그럼 아까 CSurface 는 왜 만들었냐고? 걱정하지 말라 바로 밑에 또 있지 않은가? 그렇다. 우리는 저걸 쓰면 된다. 위에껀 안 가르쳐주냐고? 에거 지금 시간없다. 벌써 몇시간째 이러고 있는지 모르겠다. 눈치 많이 보인다. 담에 하자. 참고로 난 기억력이 떨어진다 ㅡㅡ;

  자 밑에껄 보면, 시작위치하고, 좀전의 그 서피스하고 RECT *prc = NULL 이란다. 뒤에 = NULL 이게 뭔가? 인자 값으로 안 넘기면 NULL이 들어 가겠다는 말이다. 참고로 여기서 NULL이 들어가면 그림 전체를 말한다. 창크기와 그림크기가 똑같다. 무슨 말인가? NULL로 하면 된단 말이다. 아 편하다. 유틸리티는 이렇게 편하다. 뭘 하는가? 빨리 해보자. 그림 안나오면 마이크로소프트에 따지자. 왜 이딴걸 만들었냐고. 어디에서 그리면 될까? 뻔하지 GameMain() 이쥐...

 

g_pDisplay->Blt( 0, 0, g_pBack );

 

  자 빌드하고 실행해보자. 어 또 흰화면이다. 장난하냐고? 너무 미워하지 마라. Blt는 빽버퍼에, 그러니까 화면말고 안보이는 메모리에 그리는 것이다. 왜 그렇게 하냐고? 담에 얘기해 주겠다. 우선은 그렇게만 알고 넘어가자. 이제 남은 문제는 뭔가? 빽버퍼에걸 화면에 보이게 하면 된다. 해보자. 화면에 보이게 한다... 비슷한 뜻을 가진 함수를 찾는다. 한번 찾아봐라... 안 찾아볼 걸 다 안다. 그냥 하자. 시간도 없는데...

 

  블리팅한 다음에 바로 다음을 삽입한다.

 

g_pDisplay->Present();

 

  빌드하고 실행해본다. 아까 포토샵에 있었던 그림이 나왔는가? 나는 초록색 민짜였는데... 그림 수준이 높은 사람은 다른 그림을 그렸으리라 생각한다. 다른 그림을 그렸으면 그 그림이 나왔을 것이다.

 

  아직 할것이 태산같다. 괜히 시작했나 싶다. 그래도 한번한 거 끝까지 해보자. 내일을 기약해본다.

 

  똑같이 했는데 왜 안되냐고 하지마라. 소스코드를 첨부하니 보고 틀린거 수정해 가면서 해라. 다시 말하지만 끝까지 따라해보면 게임하나 만들 수 있다.

 

  오타에 민감하지 마라.

 

  카테고리가 2D 프로그래밍이 없어서 그냥 2D 그래픽으로 올린다.

 

  모두 안녕~~

 

  ( 15번째 올린다 내껀 안올라간다 열받는다 )

  ( 소스 빼고 올려본다 )

  ( 자료실에도 안올라간다 )

이 글에 평점 주기:

 

 

 

 

  2004-07-07 오후 1:36:28   /  번호: 211  / 평점:  (7.2) category: 2D Graphics  /  조회: 3,756 
 네모네모를 만들자 02 다비드 / _david  
다비드님께 메시지 보내기  다비드님을 내 주소록에 추가합니다.  다비드님의 블로그가 없습니다  

 두 번째 시간이다. 설명이 빈약한 감이 많았지만 나름대로 다 했으리라 생각한다. 다만 아쉬운 것은 소스가 업로드가 안된다는 점이다. 데브피아 서버가 우리집 컴퓨터만 미워하나보다. 하여튼 첫 번째 시간에는 창을 만들고 다이렉트드로우 객체를 초기화하고 창에 연결하고 그림을 읽어와서 화면에 그리는 것 까지 했다. 부연 설명이라든지 그림 파일 읽기 등 더 말할 것이 많지만 나중으로 미루고 계속 진행 해보고자 한다. 우선 게임 배경으로 쓸 그림이 없다는 것이 가장 큰 난관이다.... 다시 말하지만 난 그림은 잘 못 그린다. 나름대로 선을 죽죽 그어서 배경으로 쓸 테니 비웃지 말길 바란다. 아.... 업로드가 안됐지... 난관에 부딪혔다. 어떻게 테스트를 한단 말인가. 나중에 고민하고 우선 진행해보자.

 

2. 마우스 좌표를 읽어서 어느 칸에 올라가있는지 확인하고

 

를 해야 된다.

 

너무 쉬운가? 하여튼 이번에는 이걸 한다. 우선 마우스 좌표를 읽어보자. 여기서 결정할 것은 마우스 좌표를 윈도우 메시지에서 얻을 것인가 또 지난번처럼 그냥 루프상에서 얻을 것인가이다. 루스상에서 얻으면 별도의 작업을 더 해줘야 되는 관계로 윈도우 메시지에서 얻도록 하겠다. 편한 것이 좋다. 리소스라든지 시간 같은 건 신경 쓰지 말자. 좌표를 읽어야 된다. 다음을 추가하자.

 

//전역에

POINT g_Mouse;

 

//WndProc에

LRESULT WINAPI WndProc( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam )

{

    switch( Msg )

    {

        case WM_CLOSE:

            PostQuitMessage( 0 );

            break;

 

        case WM_MOUSEMOVE:

            g_Mouse.x = LOWORD( lParam );

            g_Mouse.y = HIWORD( lParam );

            break;

    }

 

    return DefWindowProc( hWnd, Msg, wParam, lParam );

}

 

끝이다. 싱겁지만 다 알고 있었던 거라 생각한다. 자 이제 문제는 마우스가 어느 칸에 위치했나 부분인데.... 어느 칸이라고 하는 건 그림이 있어야 되는 거다. 다시 말하지만 난 그림을 못 그린다. 우선 그림에서 네모칸의 시작을 알리는 위치를 임의로 변경할 수 있게 변수를 선언하자.

 

//전역에

POINT NemoStart;

 

이 값을 초기화 하는 함수도 하나 만들자. 네모네모에서 처음 몇판은 사이즈가 10*10정도겠지만 스테이지가 지날수록 이 값이 변한다. 그러면 네모를 그릴 박스의 위치도 변해야 한다. 그래서 변수로 삼았다. 어차피 판이야 3, 4개 정도니까 define으로 잡아도 되겠지만 그냥 이렇게 하자. 다음 시간에 네모네모 로직의 템플리트 파일을 만들어서 읽어 와야 된다. 여기에 시작 위치도 넣기로 하자. 하지만 오늘은 그 작업을 안 하니... 대충 아무 곳이나 잡아서 넣어보자. 여기서 또 생각해 볼 것은.... 판이 3, 4개에서 막 변할 건데.... 이미지 몇 개를 루프로 돌면서 찍어야 된다. 전체 배경이야 됐고 게임판을 말하는 것이다. 그림없이 설명을 하지만 잘 알아들으리라 생각한다. 그러면 필요한 이미지가 3개다. 위쪽에 숫자 보여 줄 것, 왼쪽에 숫자 보여 줄 것, 그리고 가운데 네모..... 포토샵을 열어서 그려보자. 나도 해보겠다 ㅡㅡ;

 

다 그렸는가? 나는 포기했다 ㅡㅡ; 대신 줄이나 긋자....

줄 그리는 함수를 CDisplay 클래스에서 찾아보자. 찾았는가? 못 찾았을거라 생각한다. 없다. 그러면 선그리는 것을 포기해야하나? 아니다. 만들면 된다. 한번 만들어보자. 그러면 선을 어디에 그려야 되는가? 설명할 것이 생겼다.... 그냥 그림을 그릴 걸 그랬나 싶다....

 

하여튼 줄긋기로 했으니 그어보자. 여기서 대각선은 그을 일이 없으니 선그리는 알고리즘을 고려할 필요는 없고... 그냥 가로 세로만 그리도록 하겠다.

우선 선을 그리려면 DrawLine 이라는 함수가 하나 있으면 좋겠다. 만들고 보자.

ddutil.h 파일을 열어서 CDisplay 선언부에 넣자. public에 넣어야 된다. return 형은 다 HRESULT이니 그냥 따라하자.

 

public:

HRESULT DrawLine( int sx, int sy, int ex, int ey, DWORD color );

 

이정도면 될까? 참 말 나온김에 지난 시간에 빠진것...

 

HRESULT Blt( DWORD x, DWORD y, CSurface* pSurface, RECT* prc = NULL );

 

여기서 넘어가는 CSurface객체는 그릴 그림이 있는 CSurface를 말한다.

 

됐고...

 

라인 그리는데 시작점하고 끝점 라인색만 있으면 될 것 같다. 점선, 일점쇄선 이런말 하지 마라 열받는다.

 

HRESULT DrawLine( POINT &StartPoint, POINT &EndPoint, DWORD color );

 

이렇게 해도 괜찮을 것 같다. 무엇으로 할까? 1번으로 하자.

 

자 다시 정리해보자. 선언부에

 

HRESULT DrawLine( int sx, int sy, int ex, int ey, DWORD color );

 

ddutil.cpp 파일에

 

HRESULT CDisplay::DrawLine( int sx, int sy, int ex, int ey, DWORD color )

{

    return S_OK;

}

 

자 이제 함수도 만들었으니 진짜로 그리는 것을 해보자. 그리는 순서를 생각하자. 흰 스케치북에 크레파스로 그림을 그린다. 배경색을 다 칠하고 선을 그리는 것이 우리 목표다. 근데 선을 그리고 나서 배경색으로 다 칠하면 어떻게 될까? 선하고 배경색의 중간색이 나온다는 이상한 말 하지마라. 그냥 선이 배경색에 덮여서 배경만 남는다고 하자. 부탁이다.... ㅡㅡ; 어쨌든 여기서 말하고자 하는 요지는 그리는 순서가 중요하다는 거다. 배경을 그리고 선을 그리기. 그러니까 호출하는 곳은 GameMain에서 어디인지 딱 나온다. 배경 블리트 다음이다. 넣어보자.

 

int GameMain()

{

    if( KEYDOWN( VK_ESCAPE ) )

        return 0;

    

    g_pDisplay->Blt( 0, 0, g_pBack );

    

    g_pDisplay->DrawLine( 100, 100, 100, 200, RGB( 100, 0, 0 ) );

 

    g_pDisplay->Present();

 

    return 1;

}

 

숫자는 그냥 넣어봤다. 컴파일이 되야되니까.... 좌표를 보면 (100, 100)에서 (100, 200) 으로 세로로 그린다. 참고로 좌표는 좌상단이 (0,0)이다 우하로 갈수록 값이 커진다.

이것저것 따질 것 없이 그리자. 그리는데... 또 설명이 필요하다.... 다시 한번 그림그릴 걸하고 후회한다...

 

그림이 픽셀로 이루어져있다는 건 다 알고 있다. 픽셀은 색이 있는 한점이다. 이 점들이 모여서 그림을 이룬다. 이해했는가? 픽셀이 모여서 그림을 이루는 것을... 선은? 같은 색의 픽셀이 쭈욱 있으면 선이다. 화면에 색이 있는 픽셀을 찍으려면 모니터 화면에 보여질 비디오 버퍼에 색값이 있으면 화면의 어느 위치에 그 색에 해당하는 색의 점이 찍힌다. 찍히는 지 보겠는가? 싫다... 귀찮다. 그냥 선이 그려지면 점도 찍힌다고 알자. 윈도우 모드에서 선을 그리려면 이래저래 고려해야 될 것이 있다. 그런거 신경쓰지 말고 우선 선이나 그어보자. 나는 배경그림을 초록색으로 했는데... 선이 잘 보이게 하기 위해서 회색빛으로 바꿨다. 선은 빨간색 계통이다. 나중에 또 딴색으로 바꾸든지... 하여튼 그리는 걸 해보자...

 

화면에 점을 찍으려면 화면에 해당하는 비디오 메모리에 숫자가 써지면 된다고 했다. 도스에서 비디오 메모리 버퍼는 주소가 0xa0000000 이었는데.... 컴파일러에 따라 0xa000 만써도 됐다. 이걸 또 하려면 세그먼트 주소가 어쩌고 오프셋 주소가 어쩌고 한다. 근데 지금은 그런 게 있어도 안 쓴다. 아니 못 쓴다고 해야지... 윈도우가(운영체제) 다 막기 때문이다. 하여튼 중요한건 비디오 메모리에 접근을 해야 된다는 것이다. 어쩌나....

 

지금 우리는 화면에 초록색 배경 그림을 그리기 위해서, 800*600 사이즈의 Surface를 생성해서 거기에 그림을 로딩하고, 블리트한 다음 Present 함수로 화면에 보이게 했다. 단계가 이해가 되는가? 거기에 해답이 있다. Present함수로 화면에 해당하는 비디오 메모리에 보이게 한다고 했다. 보이게 한다는 게 무슨 말인가? 그 메모리에 값이 써졌다는 거다. 어떻게 써졌나? 복사했겠지.... 플립이 뭔지도 설명해야 되지만 넘어가자. 그냥 안다고 하자. 글자가 너무 많다... 하여튼 복사했다 치고... 그러면 복사를 하려면 그 원본이 있을텐데... 그게 어딨냐? 과정을 다시 생각해보자. 그림을 로딩하고, 블리트하고 Present 한다.... 감 잡았나? 나는 배 잡았다... 농담이고...... 로딩된 그림을 블리트 한다... 여기다... CDisplay는 화면 말고 백버퍼를 만들어 놓는다. 여기에다가 로딩된 그림을 그리고, 이 백버퍼에 있는 것을 화면에 해당하는 곳으로 전송하는 것이다. 그럼 우리는 선을 어디에 그려야 되는가? 백버퍼에 그려야지... 그러면 또 궁금해진다. 왜 백버퍼에 그리나? 이걸 더블 버퍼링이라고 하는데... 모니터는 왼쪽에서 오른쪽, 위에서 아래로 화면을 갱신한다. 화면에 메모리를 복사하는 것도 화면의 왼쪽에서 오른쪽, 위에서 아래로 복사를 한다. 그러면 뭐가 문젠데 더블버퍼링을 쓰는가? 화면에 바로 그리게 되면, 아무래도 둘다 그렇게 그리다 보니 여러개를 많이 처리하다보면 그리고 있는 과정이 중간에 보일 수도 있다는 것이다. 사람들은 깨진다고들 한다. 티어링(tearing)이니 하는 말은 집어치우자. 깨진다.... 그러니까 이렇게 하는거다. 예가 부적절한가? 다른 강좌에서 참고해서 봐라..... 할 수 없다.

하여튼 중요한건 백버퍼에든지 어디든지 그려야 된다. 화면 메모리에 바로 접근하는 건 좀 그러니까(이유가 불충분하다 싶으면 다른 강좌봐라 다 나와있다) 백버퍼에 접근해서 그리면 어차피 그 백버퍼가 화면으로 Present 될꺼니까... 우리의 목표를 만족하는 것이다. 계속 글자만 치다 보니 말장난 같다.... 사람들이 왜 책을 어렵게 쓰는지 이해가 되는 순간이다. 그림이 있으면 이해가 더 잘되겠지만 그렇게 안한다.... 내맘 알거라 생각한다.

에거 또 시간이 막 흐른다. 자 빨리 백버퍼에 접근해서 선을 그리자. 그럼 다시 CDisplay의 선언부를 보자.

 

protected:

    LPDIRECTDRAW7        m_pDD;

    LPDIRECTDRAWSURFACE7 m_pddsFrontBuffer;

    LPDIRECTDRAWSURFACE7 m_pddsBackBuffer;

    LPDIRECTDRAWSURFACE7 m_pddsBackBufferLeft; // For stereo modes

 

바로 찾았다. 선언부의 제일 위에 있다. 보이는가? 이름만 봐도 알 수 있다. FrontBuffer 이게 화면거시기다. 그 다음이 우리의 목표 BackBuffer다... 근데 앞의 형이 LPDIRECTDRAWSURFACE7 이다. 우리가 안 쓰던거다. 갑자기 혼란스러워진다. 또 설명해야 된다. 에거.... 하여튼 목표를 찾았다는 것에 우선 만족하자.

 

백버퍼는 LPDIRECTDRAWSURFACE7 형이다. LP는 포인터다 이 말이다. 그 다음 다이렉트 드로우7 버전대의 Surface다 이건데... 어떤가 감잡았나? 그렇다 CSurface에서 앞에 C자만 빠졌다. 전 시간에 적었던 것이 기억나는가? 나는 기억난다 ㅡㅡ; CSurface 클래스는 Surface를 쉽게 쓰기 위해 만든거라고 했다. 그러면 이놈이 원형(?)이라는 말인데.... 쫄지 말자. 다 할 수 있다.

 

#define IDirectDrawSurface7_QueryInterface(p,a,b)        (p)->QueryInterface(a,b)

#define IDirectDrawSurface7_AddRef(p)                    (p)->AddRef()

#define IDirectDrawSurface7_Release(p)                   (p)->Release()

#define IDirectDrawSurface7_AddAttachedSurface(p,a)      (p)->AddAttachedSurface(a)

#define IDirectDrawSurface7_AddOverlayDirtyRect(p,a)     (p)->AddOverlayDirtyRect(a)

#define IDirectDrawSurface7_Blt(p,a,b,c,d,e)             (p)->Blt(a,b,c,d,e)

#define IDirectDrawSurface7_BltBatch(p,a,b,c)            (p)->BltBatch(a,b,c)

#define IDirectDrawSurface7_BltFast(p,a,b,c,d,e)         (p)->BltFast(a,b,c,d,e)

#define IDirectDrawSurface7_DeleteAttachedSurface(p,a,b) (p)->DeleteAttachedSurface(a,b)

#define IDirectDrawSurface7_EnumAttachedSurfaces(p,a,b)  (p)->EnumAttachedSurfaces(a,b)

#define IDirectDrawSurface7_EnumOverlayZOrders(p,a,b,c)  (p)->EnumOverlayZOrders(a,b,c)

#define IDirectDrawSurface7_Flip(p,a,b)                  (p)->Flip(a,b)

#define IDirectDrawSurface7_GetAttachedSurface(p,a,b)    (p)->GetAttachedSurface(a,b)

#define IDirectDrawSurface7_GetBltStatus(p,a)            (p)->GetBltStatus(a)

#define IDirectDrawSurface7_GetCaps(p,b)                 (p)->GetCaps(b)

#define IDirectDrawSurface7_GetClipper(p,a)              (p)->GetClipper(a)

#define IDirectDrawSurface7_GetColorKey(p,a,b)           (p)->GetColorKey(a,b)

#define IDirectDrawSurface7_GetDC(p,a)                   (p)->GetDC(a)

#define IDirectDrawSurface7_GetFlipStatus(p,a)           (p)->GetFlipStatus(a)

#define IDirectDrawSurface7_GetOverlayPosition(p,a,b)    (p)->GetOverlayPosition(a,b)

#define IDirectDrawSurface7_GetPalette(p,a)              (p)->GetPalette(a)

#define IDirectDrawSurface7_GetPixelFormat(p,a)          (p)->GetPixelFormat(a)

#define IDirectDrawSurface7_GetSurfaceDesc(p,a)          (p)->GetSurfaceDesc(a)

#define IDirectDrawSurface7_Initialize(p,a,b)            (p)->Initialize(a,b)

#define IDirectDrawSurface7_IsLost(p)                    (p)->IsLost()

#define IDirectDrawSurface7_Lock(p,a,b,c,d)              (p)->Lock(a,b,c,d)

#define IDirectDrawSurface7_ReleaseDC(p,a)               (p)->ReleaseDC(a)

#define IDirectDrawSurface7_Restore(p)                   (p)->Restore()

#define IDirectDrawSurface7_SetClipper(p,a)              (p)->SetClipper(a)

#define IDirectDrawSurface7_SetColorKey(p,a,b)           (p)->SetColorKey(a,b)

#define IDirectDrawSurface7_SetOverlayPosition(p,a,b)    (p)->SetOverlayPosition(a,b)

#define IDirectDrawSurface7_SetPalette(p,a)              (p)->SetPalette(a)

#define IDirectDrawSurface7_Unlock(p,b)                  (p)->Unlock(b)

#define IDirectDrawSurface7_UpdateOverlay(p,a,b,c,d,e)   (p)->UpdateOverlay(a,b,c,d,e)

#define IDirectDrawSurface7_UpdateOverlayDisplay(p,a)    (p)->UpdateOverlayDisplay(a)

#define IDirectDrawSurface7_UpdateOverlayZOrder(p,a,b)   (p)->UpdateOverlayZOrder(a,b)

#define IDirectDrawSurface7_GetDDInterface(p,a)          (p)->GetDDInterface(a)

#define IDirectDrawSurface7_PageLock(p,a)                (p)->PageLock(a)

#define IDirectDrawSurface7_PageUnlock(p,a)              (p)->PageUnlock(a)

#define IDirectDrawSurface7_SetSurfaceDesc(p,a,b)        (p)->SetSurfaceDesc(a,b)

#define IDirectDrawSurface7_SetPrivateData(p,a,b,c,d)    (p)->SetPrivateData(a,b,c,d)

#define IDirectDrawSurface7_GetPrivateData(p,a,b,c)      (p)->GetPrivateData(a,b,c)

#define IDirectDrawSurface7_FreePrivateData(p,a)         (p)->FreePrivateData(a)

#define IDirectDrawSurface7_GetUniquenessValue(p, a)     (p)->GetUniquenessValue(a)

#define IDirectDrawSurface7_ChangeUniquenessValue(p)     (p)->ChangeUniquenessValue()

#define IDirectDrawSurface7_SetPriority(p,a)             (p)->SetPriority(a)

#define IDirectDrawSurface7_GetPriority(p,a)             (p)->GetPriority(a)

#define IDirectDrawSurface7_SetLOD(p,a)                  (p)->SetLOD(a)

#define IDirectDrawSurface7_GetLOD(p,a)                  (p)->GetLOD(a)

 

LPDIRECTDRAWSURFACE7의 선언이다... 뭔가 복잡하다. 괜히 붙였다 싶다... 이상하고 어렵다 싶을꺼다. 잘 모르면 COM을 공부해라. 하여튼 난 여기에 있는 걸 설명할 생각이 없다. 그냥 안 붙였다 생각하고 가자.... 보다시피 Surface도 클래스다 생각하자. 자 클래스인데 선언만 있다. 이런걸 인터페이스라고 한다. 뭐 몰라도 된다. 하여튼 Surface는 그냥 메모리가 아니고 이상한 기능들이 있다고 하는 거다. 자 쭉 살펴보면 포인터로 선언된 게 없다 ㅡㅡ; 그러면 도대체 어디서 어떻게 백버퍼에 접근해서 숫자를 써 넣냐 이 말이다... 방금 붙여 넣은 걸 잘 살펴보자. 포인터가 없으면 접근할 수 있게 해 주는 게 있겠지. 찾았는가? 이런식으로 질문하는 게 재미없나? 어차피 안 찾아 볼건데 괜히 물어보는게 아닌가 싶다. 접근할 수 있게 하는건 Lock함수다. 잠금... 잠궜으면 풀어야지 Unlock 함수도 있다. 사용법을 알아보자.

이거 두개만 하면 된다가 아니지만 우선 빨리 해치우자. 다시한번 그냥 그림 그릴걸 하고 후회하고 있는 중이다.

 

sdk 설명서에 보면 있는 선언이다.

 

HRESULT Lock( LPRECTlpDestRect, LPDDSURFACEDESC2 lpDDSurfaceDesc,DWORDdwFlags,HANDLEhEvent);

 

인자를 보자. DestRect란다. 목적지(백버퍼) 사각영역을 말하는 거겠지. 그 다음은 LPDDSURFACEDESC2 형이다. 이게 뭐냐? 잘 모르면 무조건 패스다. 그 다음은 플래그, 그다음은 핸들이다.... 에거. 또 후회하고 있다... 자 힘내자.

 

LPDDSURFACEDESC2 란다. 잘 보자. LP... 포인터다 이말이고, Surface Desc2다. Surface는 다 알고 Desc는 Description(설명)의 약자다. 2는 버전 2다 이렇게 생각하자. 그럼 다 합치면 뭐냐. Surface설명자의 포인터다 이말이다. 그러니까 우리의 목표인 백버퍼에 대한 설명자를 여기서 얻을 수 있다는 말이다. 그 다음 플래그는 백버퍼의 포인터를 돌려주십사하고 DDLOCK_SURFACEMEMORYPTR 이놈을 넣는다. 그 뒤 플래그는 안 쓰인다. NULL이다. 기쁘다.

 

하여튼 이제 드디어 한번 해보도록 하자. ddutil.cpp파일을 열어라. 그리고 아까 DrawLine함수를 완성해 보자.

 

HRESULT CDisplay::DrawLine( int sx, int sy, int ex, int ey, DWORD color )

{

    DDSURFACEDESC2 ddsd;

    ZeroMemory( &ddsd, sizeof( ddsd ) );

    ddsd.dwSize = sizeof( ddsd );

 

    m_pddsBackBuffer->Lock( NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL );

 

    unsigned char r, g, b;

    r = GetRValue( color );

    g = GetGValue( color );

    b = GetBValue( color );

 

    unsigned short *buffer = ( unsigned short * ) ddsd.lpSurface;

    int pitch = ddsd.lPitch >> 1;

    buffer[sx + sy * pitch] = ( b & 0x1f ) + ( ( g & 0x1f ) << 5 ) + ( ( r & 0x1f ) << 11 );

    m_pddsBackBuffer->Unlock( NULL );

 

    return S_OK;

}

 

빌드하고 실행하면 점이 하나찍힌다.

봤는가? 안 보이면 모니터를 닦아라...

설명해보자.

 

    DDSURFACEDESC2 ddsd;

    ZeroMemory( &ddsd, sizeof( ddsd ) );

    ddsd.dwSize = sizeof( ddsd );

 

이런류의 구조체를 사용하려면 늘 해줘야 되는 작업이다. 가끔 안해줘도 될 때도 있지만... 해주자. 안전빵이다. 초기화의 위력이라는 거다. 설마 뭘하는 건지 모르는 건 아니겠지? 잘모르면 함수이름을 보면서 넘겨짚자. ZeroMemory는 메모리를 0으로 세팅해 주겠다는 거다. sizeof 는 그 사이즈가 뭔지.... 돌 던지지 마라. 나도 시간 없는데 괜히 쳤다...

 

m_pddsBackBuffer->Lock( NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL );

 

어 DDLOCK_WAIT 이놈은 뭐가? 이놈을 설명하기 전에 Lock부터 설명하자. Lock은 말그대로 잠근다는 건데... 이 메모리에 접근하는 동안 다른 곳에서 이 메모리에 접근하지 못하도록 한다는 거다. 그럼 말그대로 다른 놈이 접근하고 있으면 나도 접근을 못하게 된다. 그래서 DDLOCK_WAIT 이란 놈이 필요한건데... 딴 애들이 블리팅을 하는 동안에는 락이 안걸리게 해라 이런 의미이다. 말이 어려우면 그런가보다 하고 쓰자.

 

m_pddsBackBuffer->Unlock( NULL );

 

목적 사각영역이 NULL이면 전체를 말한다. Unlock은 쌍이니 잊지말자. 해제 영역은 마찬가지 NULL이다. 잠그자 마자 바로 해제 해버리면 무슨 소용인가? 잠겨있는 동안 재빨리 숫자를 써넣자. 그런데... 도대체 메모리에는 언제 접근하는가? 맞다. 락만하고 메모리에 접근은 설명 안했다. 지금한다. 보채지 마라.

 

DDSURFACEDESC2 의 형태다.

typedef struct _DDSURFACEDESC2 {

    DWORD         dwSize;

    DWORD         dwFlags;

    DWORD         dwHeight;

    DWORD         dwWidth;

    union

    {

        LONG      lPitch;

        DWORD     dwLinearSize;

    } DUMMYUNIONNAMEN(1);

    DWORD         dwBackBufferCount;

    union

    {

        DWORD     dwMipMapCount;

        DWORD     dwRefreshRate;

    } DUMMYUNIONNAMEN(2);

    DWORD         dwAlphaBitDepth;

    DWORD         dwReserved;

    LPVOID        lpSurface;

    union

    {

        DDCOLORKEY    ddckCKDestOverlay;

        DWORD         dwEmptyFaceColor;

    } DUMMYUNIONNAMEN(3);

    DDCOLORKEY    ddckCKDestBlt;

    DDCOLORKEY    ddckCKSrcOverlay;

    DDCOLORKEY    ddckCKSrcBlt;

    DDPIXELFORMAT ddpfPixelFormat;

    DDSCAPS2      ddsCaps;

    DWORD         dwTextureStage;

} DDSURFACEDESC2, FAR* LPDDSURFACEDESC2;

 

뭐가 좀 많다. 이걸 이렇게 언제 다 일일이 외우냐고? 나도 모르겠다. 우선 필요한 것만 보자. lock을 걸면 이 값들이 다 채워진다. 그대로 사용하면 된다. 여기에 넘어온 것이 백버퍼의 설명이다. 여러 가지가 많다. 지금 우리가 쓸건 메모리에 접근하는 거다. 그 메모리를 가리키는 것이

 LPVOID        lpSurface;

이놈이다. 우선 뭐든지 한번 해보자. 선그릴 필요없이 점부터 찍어보자. 에거... 결국엔 점찍게 됐다.

 

    unsigned short *buffer = ( unsigned short * ) ddsd.lpSurface;

    int pitch = ddsd.lPitch >> 1;

    buffer[sx + sy * pitch] = 5;

 

 

점찍는 부분이다. DrawLine함수에 넘어오는 인자에 특별히 상관없이 찍는다. 시작좌표는 사용하고.... 자 설명해보자. ddsd.lpSurface가 메모리에 접근하는 주소라는 건 알겠는데 앞에 unsigned short 가 왜 있냐고? 궁금할 것이다. 갑자기 왜 복잡해지는지... 걱정마라. 쉽다. lpSurface의 형이 뭐였던가? LPVOID... 한마디로 void다. 형이 없다는 말이다. 쓰려면 다른 형으로 캐스팅 해야되는데.... 왜 void로 했을까? 웃긴다. 일부러 복잡하게 하려고? 금방 알아보면 곤란한 이유라도 있기 때문에? 재미없는가? 나는 재미있는데... 화면 색품질(나는 xp를 쓰는데 바탕화면 등록정보에 이렇게 되어있다. 보통은 깊이라는 말을 쓴다) 때문이다. 어떤 사람은 8비트로, 16비트로, 24비트, 32비트로 두고 있는데.... 각각 마다 알아서 캐스팅하라는 거다... 색품질가 32비트인데 뭘 캐스팅하냐고? 계속 궁금증이 생긴다. 뭔소린지 알아보자. 색품질이 8비트, 16, 32라고 하는 것은... 픽셀하나에 사용되는 컬러 수를 말한다. 8비트... 비트가 뭔가? 2진수의 한자리다. 2진수가 뭔데? 0 1로 이루어진 수의 체계다. 우리는 10진수를 쓴다. 8비트는 2진수를 8자리까지 쓰겠다는 건데... 8자리면 제일 큰수가 11111111이 된다. 그러니까 00000000 ~ 11111111 까지 수를 표현하겠다는 거다. 이걸 알아보기 좋게 10진수로 하면 0~255가 된다. 0~255의 숫자가 색을 가리킨다는 말이다. 256가지지... 어떤어떤 색 256가지인가? 니 맘대로 256가지이다. 많은 색들 중에 256가지 색을 덜어서 쓰는 거다. 포토샵에서 웹형gif 파일로 저장한 경험이 있는가? 저장할 때 오른쪽에 보면 256칸이 있다. 안 세어봐서 모르겠다고? 세보지마라. 맞다. 그 256칸이 많은 색들 중에서 덜어낸 색깔들이다. 그리고 특별히 덜어서 쓰기 때문에 이걸 팔레트라고 한다. 초등학교 때 말감을 팔레트에 덜어서 쓰던거 생각나겠지? 같이 이치이다. 하여튼 8비트는 그렇고... 16비트를 해볼까? 16비트는 2진수를 16자리까지 쓰겠다는 말이다. 그러니까....

0000000000000000~1111111111111111 까지 쓸 수 있다는 말...

0하고 1이 몇 개인지 세보지마라... 나도 나름대로 세면서 눌렀다 그냥 각각 16개라고 해다오. 하여튼 이걸 10진수로 다시 쓰면 0~65535이다. 65536개의 색.... 어디서 많이 본 숫자 같지 않은가? 잘은 몰라도 익숙하다. 그냥 그런가보다 하면 된다. 하여튼 색을 표현하는데 차지하는 바이트수가 다르니까. 픽셀 한 칸에서 그 다음 칸으로 가는 간격도 다르다. 8비트에서는 1바이트만 가도 그 다음픽셀인데... 16비트에서는 2바이트를 가야지 그 다음 픽셀이다. 24, 32는 말 안해도 알겠지?

근데 왜 나는 unsigned short로 했냐고? short가 몇 바이트냐? 컴파일러마다 같네마네 하는 소리하지마라. 2바이트다. 16비트란 소리쥐... 내 바탕화면 색품질이 16비트란 말이다... 당신이 쓰는 색품질이 32비트라면 뭘써야 되겠나? 32비트짜리가 뭔가? int다. 알아서해라... 답답하면 16비트로 낮추고 나서 다시 따라 해도 된다.

pitch는 ddsd.lPitch가 바이트로 되니 short가 되려면 2바이트씩 가줘야 된다. 그래서 /2 ( >> 1) 을 했다.

그러면 이거 하나 실행하려면 바탕화면 가서 일일이 체크해야 되냐.... 맞다. 그치만 방법이 그것만 있는 건 아니지. 마이크로소프트를 조금만 믿자. 이런거 다 생각하고 만들었겠쥐.... 그러면 색품질이 뭔지 정도는 그냥 알 수 있지 않을까라는 의문을 가지면서... 이런 것이 있는지 찾아보자. 어디서 찾냐고? DDSURFACEDESC2 다.. 위에 있으니 한번 찾아봐라... 쩝... 나도 왜이런지 모르겠다. 안 찾을 꺼 알면서도 계속이런다. 버릇이라 생각하자... 찾았는가? 나는 찾았다.

 

DDPIXELFORMAT ddpfPixelFormat;

 

딱 나왔다. 픽셀 포맷이란다. 우리나라하고 말이 약간 어설프게 다른점이 있는 것 같지만 통밥으로 때려잡으면 다 맞다.

 

typedef struct _DDPIXELFORMAT{

    DWORD dwSize;

    DWORD dwFlags;

    DWORD dwFourCC;

    union

    {

        DWORD dwRGBBitCount;

        DWORD dwYUVBitCount;

        DWORD dwZBufferBitDepth;

        DWORD dwAlphaBitDepth;

        DWORD dwLuminanceBitCount;

        DWORD dwBumpBitCount;

    } DUMMYUNIONNAMEN(1);

    union

    {

        DWORD dwRBitMask;

        DWORD dwYBitMask;

        DWORD dwStencilBitDepth;

        DWORD dwLuminanceBitMask;

        DWORD dwBumpDuBitMask;

    } DUMMYUNIONNAMEN(2);

    union

    {

        DWORD dwGBitMask;

        DWORD dwUBitMask;

        DWORD dwZBitMask;

        DWORD dwBumpDvBitMask;

    } DUMMYUNIONNAMEN(3);

    union

    {

        DWORD dwBBitMask;

        DWORD dwVBitMask;

        DWORD dwStencilBitMask;

        DWORD dwBumpLuminanceBitMask;

    } DUMMYUNIONNAMEN(4);

    union

    {

        DWORD dwRGBAlphaBitMask;

        DWORD dwYUVAlphaBitMask;

        DWORD dwLuminanceAlphaBitMask;

        DWORD dwRGBZBitMask;

        DWORD dwYUVZBitMask;

    } DUMMYUNIONNAMEN(5);

} DDPIXELFORMAT, FAR* LPDDPIXELFORMAT;

 

길다. 설명에 대한 고민... 다시 한번 그림그릴 걸 하는 후회를 한다... 하여튼 뭘 찾아야 겠는가? 한번 찾아봐라. 나는 찾았다. 뭘까? 안 가르쳐 줄란다.

농담이고...

 

DWORD dwRGBBitCount;

 

이놈이다. 이 값이 8이면 8비트 16이면 16비트... 나머지도 마찬가지이다. if를 하든 뭐를 하든 해서 이 값대로 형을 캐스팅 해주면 된다. 한번 해볼까? 디버깅을 해보자. Lock 아래 줄에 브레이크 포인트를 잡는다. 그리고 F5를 누른다. 딱갈렸는가? ddsd를 잡아서 오른쪽에 놓는다. +를 눌러서 다른 것도 나오게 한다. ddpfPixelFormat 이놈을 찾아서 다시 확장한다. dwRGBBitCount를 찾았는가? 숫자가 얼마로 나오는가? 나는 16이다. 해상도에 맞게 바꿔주는 것 정도는 알아서 해라. 나는 그냥 short로 하겠다. 근데 왜 short로 하지 unsigned short로 했냐고? 색에는 sign(부호)가 없다. 됐는가?

 

int pitch = ddsd.lPitch >> 1;

buffer[sx + sy * pitch] = 5;

 

unsigned short * 로 캐스팅 된 buffer는 이제 한 칸이 2바이트다. x로 sx만큼 y로 sy만큼 떨어진 곳을 찾는데... * ddsd.lPitch 는 뭐냐? 피치? 웨딩피치? 농담이다. 내 맘 알거라 믿는다. 계속 길어지다 보니 조금씩 정신이 이상해진다. lPitch L이 붙은걸 보면 long형이다. 피치... 이게 뭐냐? 피치, 요(yaw) 이런 거 들어본 적이 있는가? 없나? 들어 볼 기회가 있을 것이다. 에거 설명할게 또 있다. 쩝... 다시 그림 그릴걸 하는 후회를 한다.

메모리는 선형이다. 컴퓨터를 뜯어보면 메모리가 납작한 사각형인데... 메모리가 3차원으로 이루어져 있다고 생각하는 사람이 많다. 아니다. 메모리는 1차원이다. 1차원, 3차원이 뭐냐고? 중학교 때 배운걸로 기억하는데... 차원은 수직선 하나다. 그러니까. 3차원이면 수직선 3개로 이루어진 공간이라는 말이다. 다 알지? 괜히 썼다. 하여튼 메모리는 선형인데.... 화면은 2차원이다. 어렵나? 메모리는 1차원인데 화면은 2차원이다. 뭔소리냐? 뭐가 안 맞다는 말이쥐. (0, 0)에서 x가 쭈욱 커지다가 화면 끝에 가면 x가 0이 되고, y가 1증가한다. 근데 이게 1차원이란다. 800*600창에서 0에서 쭈욱 커지다가 화면끝에가면 799 그러면 0아래 칸은? 800이 된다. 그 다음부터 801... x, y가 없다. x 뿐이다. 나는 x, y가 편한데... 메모리는 x 뿐이다. 이 자식이... 그럼 x뿐인걸 xy로 끼워 맞추면 된다. 그것이 이거다.

 

x + y * x_size

 

이해가 되는가? 안되면 딴 강좌 봐라. 설명 잘 되었다... ㅡㅡ;

자 그러면

 

buffer[sx + sy * ddsd.lPitch] = 5;

 

여기에서 sx, sy는 나왔다. 그럼 ddsd.lPitch가 x_size다 이 말인데.... x_size는 그냥 800이 아닌가 하고 생각할 것이다. 맞다. 근데 여기서 또 메모리가 태클을 건다. 이놈의 자식이 메모리를 자기 마음대로 잡는다. 그러니까... 799 다음이 800 인건 맞는데 800이 한줄 바뀐다고 보장을 안 한다는 말이다. 한 줄 바뀌는 순간이 1000이 될지 2000이 될지 모른다. 그럼 어떡하라는 거야? 열받는다. 메모리.... 걸리면 죽었으... 그러다고 컴퓨터를 뜯어서 메모리를 박살내지 말자. 돈든다. 그래서 나온 것이 lPitch이다. 그 다음 칸으로 진행되는 것이 언제인지 나타내준다. 그럼 다 됐다. x로 이루어진 좌표를 x, y로 써먹을 수 있게 했다. 그러면... 끝에 5는 뭔가? 컬러 색상이다. color가 있는데 왜 저걸 넣었냐고? DWORD랑 short랑 안 맞아서이다.... 32비트로 넘어온 걸 16비트로 전환하는 게 또 필요해졌다. 에거 너무 복잡해진다. 어렵게 생각하지 말자. 쉽다.

 

    unsigned char r, g, b;

    r = GetRValue( color );

    g = GetGValue( color );

    b = GetBValue( color );

 

    unsigned short *buffer = ( unsigned short * ) ddsd.lpSurface;

    int pitch = ddsd.lPitch >> 1;

    bbuffer[sx + sy * pitch] = ( b & 0x1f ) + ( ( g & 0x3f ) << 5 ) + ( ( r & 0x1f ) << 11 );

 

이렇게 하면 된다. 한번 생각해봐라...

16비트 RGB565 버전이다. R이 5비트 G가 6비트 B가 5비트다.

점찍는 것이 됐으니, 이제 라인을 그려야 된다... 시간이 많이 흘렀다.

라인 그리는 건 다들 할 줄 아리라 생각한다.

숙제다 담시간에 또 하자....

 

담시간에 할건... 네모네모칸 시작 위치, 칸 사이즈, 네모네모 판의 사이즈를 얻어서 화면 가운데에 판을 그리고 마우스로 클릭하면 그 위치를 화면에 나오게 하는 것을 하겠다.

생각한거 보다 늘 시간이 더 걸린다. 에거...

 

미워하지 마라.

그럼...

 

참 소스는 또 안올라 갈 것 같다. 그냥 해 다오.

때되서 소스가 올라가면 올리겠다.

안뇽~~

 

이 글에 평점 주기:  

 

2004-07-08 오후 8:55:54   /  번호: 212  / 평점:  (9.0) category: 2D Graphics  /  조회: 3,548 
 네모네모를 만들자 03 다비드 / _david  
다비드님께 메시지 보내기  다비드님을 내 주소록에 추가합니다.  다비드님의 블로그가 없습니다  

 안녕들 하신가? 3번째 시간이다. 모두들 재미없는 강좌, 설명 잘 안되어 있는 강좌 읽느라 고생이 많으시다. 처음과는 달리 쉽게쉽게 안되서 미안하다. 모르는 것 있으면 질문해라. 그래야 실력이 는다.

 

이번 시간에 할건... 네모네모칸 시작 위치, 칸 사이즈, 네모네모 판의 사이즈를 얻어서 화면 가운데에 판을 그리고 마우스로 클릭하면 그 위치를 화면에 나오게 하는 것을 한다고 했다.

 

그러면 지난 시간에 남겨뒀던 라인 그리는 함수의 완성형을 보자. 개선점은 알아서 수정해라.

 

HRESULT CDisplay::DrawLine( int sx, int sy, int ex, int ey, DWORD color )

{

    DDSURFACEDESC2 ddsd;

    ZeroMemory( &ddsd, sizeof( ddsd ) );

    ddsd.dwSize = sizeof( ddsd );

 

    m_pddsBackBuffer->Lock( NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL );

 

    unsigned char r, g, b;

    unsigned short trans_color;

    r = GetRValue( color );

    g = GetGValue( color );

    b = GetBValue( color );

    trans_color = ( b & 0x1f ) + ( ( g & 0x3f ) << 5 ) + ( ( r & 0x1f ) << 11 );

 

    unsigned short *buffer = ( unsigned short * ) ddsd.lpSurface;

    int pitch = ddsd.lPitch >> 1;

 

    if( sx == ex ) //수직선

    {

        for( int i = sy; i <= ey; i++ )

        {

            buffer[sx + i * pitch] = trans_color;

        }

    }

    else    //수평선

    {

        for( int i = sx; i <= ex; i++ )

        {

            buffer[i + sy * pitch] = trans_color;

        }

    }

 

    m_pddsBackBuffer->Unlock( NULL );

 

    return S_OK;

}

 

수직선, 수평선만 그린다. 간단하지 않은가? 짧은 소스보다 명료한 소스가 더 보기 좋다. 그럼 오늘 강좌를 시작해보자.

오늘 할 것은 이미 위에서 언급했다. 우선 네모네모 보드를 그려야 되는데.... 스테이지마다 보드모양이 조금씩 틀릴 수 있다. 그래서 보드의 위치가 유동성이 있어야 한다. 그런 걸 고려해서 변수들을 설정해보자.

 

네모네모 보드를 그릴 시작위치.

네모네모 보드 한칸 사이즈

네모네모 보드 가로, 세로 칸수

 

이정도만 있으면 보드 그리는 것을 한 함수로 작성할 수 있을 것 같다. 말로 함수를 만들어보자. 우선 시작위치는 가장 마지막에 넣는다. 보드가 가운데 나오길 원하기 때문이다. 고정된 배경 그림이 있다면 템플릿에서 읽어올 때 수정할 수도 있다. 이래저래 변수로 두는 것이 좋다. 보드 한칸 사이즈는 전부 동일할 수도 아닐 수도 있지만 보통은 다 똑같더라. 보드 가로, 세로 칸수가 실질적인 보드 전체의 사이즈를 나타낸다고 할 수 있다. 보드 전체의 사이즈가 나오면 창 사이즈와 적당히 계산해서 가운데에 위치하도록 한다. 여기까지 해보자.

 

우선은 GameInit에서 한칸 사이즈와 보드 가로, 세로 칸수를 설정하고. 시작위치까지 계산한 후 GameMain에서 보드를 그리도록 하겠다. 지금 Init에서 작업할 것은 나중에 또 쓰일 것 같으니까. 따로 함수를 만들자. 쉽게 가자. 인자로 다 넘긴다.

 

//전역에

    int g_iNemoWidth = 0, g_iNemoHeight = 0,

    g_iNumNemoWidth = 0, g_iNumNemoHeight = 0,

    g_iStartX = 0, g_iStartY = 0;

 

//GameInit에

NemoNemoSetting( 30, 30, 10, 10 );

 

//GameInit위에

void NemoNemoSetting( int iNemoWidth, int iNemoHeight, int iNumNemoWidth, int iNumNemoHeight )

{

    int width, height;

    

    g_iNemoWidth = iNemoWidth;

    g_iNemoHeight = iNemoHeight;

    g_iNumNemoWidth = iNumNemoWidth;

    g_iNumNemoHeight = iNumNemoHeight;

 

    width = g_iNemoWidth * g_iNumNemoWidth;

    height = g_iNemoHeight * g_iNumNemoHeight;

 

    g_iStartX = ( 800 - width ) >> 1;

    g_iStartY = ( 600 - height ) >> 1;

}

 

초기화가 다 됐다. 특별히 어려운 건 없다. 전역에는 한 칸 사이즈, 칸 개수, 시작 위치를 변수로 선언하고, 아까 설정했듯이 초기화하는 함수를 따로 만들어서 GameInit에서 호출했다. 초기화 함수는 전역 값에 삽입했다.

 

자 이제 이 값으로 그려보자. GameMain에 DrawBoard라는 함수를 호출해서 그리자.

 

void DrawBoard()

{

    int x, y;

    int width, height;

 

    width = g_iNemoWidth * g_iNumNemoWidth;

    height = g_iNemoHeight * g_iNumNemoHeight;

 

    for( y = 0; y <= g_iNumNemoHeight; y++ )

    {       

        g_pDisplay->DrawLine( g_iStartX, g_iStartY + g_iNemoHeight * y,

                              g_iStartX + width, g_iStartY + g_iNemoHeight * y,

                              RGB( 200, 0, 0 ) );

    }

 

    for( x = 0; x <= g_iNumNemoWidth; x++ )

    {

        g_pDisplay->DrawLine( g_iStartX + g_iNemoWidth * x, g_iStartY,

                              g_iStartX + g_iNemoWidth * x, g_iStartY + height,

                              RGB( 200, 0, 0 ) );

    }

}

 

이해가 되는가? 우선 가로선을 11개 그리고 세로선을 11개 그린다. 11개를 그려야지 칸이 10개가 된다. 칸을 다 그렸으니... 우선 마우스가 몇 번째 칸에 가 있는지 화면에 표시해 보자. 지금은 WM_MOUSEMOVE에서 좌표를 잡고 있다. 클릭할 때 좌표를 잡지 않고 왜 여기서 잡는지 궁금해 할 수도 있다. 왜냐하면 마우스가 위치했을 때 롤오버 효과를 내기 위해서 라고나 할까? 아무튼, 진행해보자. 마우스가 올라가 있는 칸은 네모박스로 노란색으로 칠해보자. 노란색으로.... 칠한다.... 네모 그리는 함수를 또 만들어야 된다. 계속 설명하는 것이 좀 그렇다. 어차피 네모박스 이미지는 필요하니까... 이번에는 이미지를 진짜 만들어보자. 포토샵을 실행해서 만들어보자. 사이즈는 라인을 제외하고 순수한 안쪽 영역인 29*29로 했다. 포토샵에서는 네모 한칸 이미지를 3개 만들었다. 사이즈가 87*29로 했다. 첫 번째 29에서는 박스를 깬 이미지, 두 번째는 박스가 있는 이미지, 세 번째는 롤오버시 나오는 이미지이다. 내가 만든 이미지를 올리면 편하겠지만 업로드가 안되는 관계로 ㅡㅡ; 직접 만들어라. 하여튼 나는 다 만들었다. 자 이제 롤오버하면 가운데 이미지가 그 칸에 표시되도록 해보자.

 

클래스를 만들면 좋지 않을까? 하는 생각이 팍팍 들겠지만... 잘 모르는 사람들은 클래스를 만들면 성질낸다. 어차피 이해만 되면 만드는 거야 자기 마음이니까 마음대로 해라. 여기서는 그냥 변수 추가하면서 작업하겠다.

 

자 지금 할 것은 마우스가 움직였을 때 보드 안에 있으면 좀 전에 만든 이미지의 가운데 걸 그 칸에 그려준다고 했다. 그럼 뭐가 필요한가? 이미지를 하나 더 만들었으면 그 이미지를 메모리에 로딩시켜줘야 된다. 실시간으로 계속 읽어오는 이상한 생각은 하지말자. 이정도 메모리 아끼지 말고 초기화 때 마구마구 로딩하자. 그럼 우선 이 이미지를 담고 있을 CSurface 객체를 하나 더 만든다.

 

//전역에

CSurface *g_pNemoBox = NULL;

 

//GameInit에

if( g_pDisplay->CreateSurfaceFromBitmap( &g_pNemoBox, "img\\nemobox.bmp", 0, 0 ) == E_FAIL )

        return -1;

 

//GameShutdown에

SAFE_DELETE( g_pNemoBox );

 

어... 방금 추가하다가 발견했다. GameShutdown함수가 잘 못 됐다. 여기 수정본을 보여주겠다.

 

void GameShutdown()

{

    SAFE_DELETE( g_pNemoBox );

    SAFE_DELETE( g_pBack );

    SAFE_DELETE( g_pDisplay );

}

 

지난 번에는 g_pDisplay가 제일 위였는데 그러면 안된다. 차례차례 작은 것부터 해제해야 된다. 어쨌든 그래도 에러는 안나더라.... 각설하고.

이미지를 로딩했으니 마우스가 움직인 것에 맞추어 박스를 그려보자. GameMain에서 좌표잡아서 그려준다.

 

    if( g_iStartX <= g_Mouse.x && g_iStartX + width > g_Mouse.x &&

        g_iStartY <= g_Mouse.y && g_iStartY + height > g_Mouse.y )

    {

        x = ( g_Mouse.x - g_iStartX ) / g_iNemoWidth;

        y = ( g_Mouse.y - g_iStartY ) / g_iNemoHeight;

        DrawBox( x, y, 1 );

    }

g_Mouse는 WM_MOUSEMOVE에서 이미 입력되어졌다. 알고 있을 거라 생각한다. 마우스 위치가 보드의 시작 위치와 보드의 끝위치 사이에 있으면 보드의 어느 칸에 있다는 말이 된다. 그 때만 어느 칸인지 계산해서 박스를 그려주면 된다. 박스를 그리기 위해서 DrawBox라는 함수를 만들었다. 박스의 종류가 3개라서 x, y외에 하나를 더 집어넣었다. DrawBox함수를 한번 보자.

 

void DrawBox( int x, int y, int state )

{

    RECT rc;

    SetRect( &rc, (g_iNemoWidth-1) * state, 0, (g_iNemoWidth-1) * (state+1), g_iNemoHeight-1 );

 

    g_pDisplay->Blt( g_iStartX + 1 + x * g_iNemoWidth, g_iStartY + 1 + y * g_iNemoHeight, g_pNemoBox, &rc );

}

 

어려운 것이 있는가? 없을 거라 생각한다. 아쉬운 점은 다시 StartX 등이 계산에 사용된다는 건데... 뭐 별 수 있는가? 그냥 쓰는 거지 뭐.... 다시 말하지만 최적화 같은 거 신경쓰지말자. 최적화 신경 쓰면 게임 못 만든다. 다 만들고 나서 신경 쓰자. 참고로 나는 신경 안쓸꺼다...

 

빌드하고 실행해보면 잘 실행된다. 내가 방금 해봤다. 안되는 사람은 뭔가 잘 못 한거다. 뭐가 잘 못 됐는지 잘 찾아봐라.

 

자 마우스 위치 뽑아내는 것도 했으니... 이제 뭘해야 될까? 원래는 클랙해서 마우스 위치가 나오게 하려고 했는데... 마우스 움직이면 그 위치에 바로 박스를 그려줬다. 첨에 얘기한 거랑 좀 틀려서 미안하다. 하여튼 오늘 목표는 다 달성했다. 오늘은 좀 짧다... 그래도 시간은 많이 걸렸다. 첫째 시간에 모처럼 시간이 났다고 했는데... 다시 취소됐다... 그래도 이건 끝까지 하도록 하겠다. 집어치우지 왜 하냐고? 글쎄... 한번 했으면 끝을 보는게 내 성미다. 안 했으면 몰라도 했으니 끝까지 할 꺼다.

 

자 내일은 뭘 할건가? 최대한 첫째 시간의 계획에 맞춰서 하겠다. 남은 것이...

 

3. 네모네모로직을 읽어오고

4. 마우스를 클릭 했을 때 상황에 맞게 열리거나 종료

5. 열릴 수 있는 것 다 열면 축하하며 다음 단계로...

 

이건데... 3번을 해야 된다. 네모네모 로직인데... 로직을 게임 안에 하드코딩(직접 쳐 넣는 것)을 하면 융통성이 떨어지니 그림 파일처럼 이 로직만의 형식파일(템플릿)을 만들어서 할 수 있도록 하겠다. 그러면... 템플릿을 저장하는 것도 해야 되는데.... 이걸 한번에 하려면 내가 너무 시간이 많이 걸린다. 2번으로 나눠서 하도록 하려고 하는데... 어떻게 하는 게 좋을까? .......... 결정했다.

 

1. 포맷 결정 및 파일쓰기, 읽기

2. 포맷 읽어 와서 화면에 보여주기.

 

나름대로 어떻게 하는 것이 좋은지 생각해봐라...

소스는 오늘도 올려보겠다. 근데 잘 안 올라가더라. 안 올라가도 소스에 넣을 건 여기에 다 있으니 그런대로 복사해서 넣으면 될꺼다...

혹시 중간에 안 되는 건 너무 시비 걸지 말고, 적당하게 해결해봐라... 나는 다 수정했다. 그래서 안되는 것 없다. 안됐던건 최대한 여기에 다 썼는데... 혹시 빠진 것이 있을지도 모른다... 뭔 소리하는 지 모르겠다. 빨리 끝내자. 빠빠~~

 

 

2004-07-09 오후 11:29:27   /  번호: 213  / 평점:  (7.9) category: 2D Graphics  /  조회: 6,580 
 네모네모를 만들자 04 다비드 / _david  
다비드님께 메시지 보내기  다비드님을 내 주소록에 추가합니다.  다비드님의 블로그가 없습니다  

 네모네모 네 번째 시간이다. 몇 번째 시간까지 하면 다 될라나 싶다. 딴소리 말고 바로 진행하겠다. 오늘 할 것은...

 

포맷 결정 및 파일쓰기, 읽기

 

포맷을 결정하고 나서 파일에 저장하게 하고, 읽어오는 루틴을 만들면 된다. 차례대로 하면 되니까 참 좋은 것 같다.

자 우선 포맷을 결정해보자. 포맷을 결정하려면 도대체 이 파일로 무엇을 할 것인지가 명확해야 된다. 무엇을 할 것인가.... 네모네모 문제지를 만드는 것인데.... 네모네모 게임을 보면 가운데 닫힌 박스가 쭈욱 있고, 왼쪽과 위쪽에 각 그 줄에 열 수 있는 박스의 수가 적혀있다. 연상이 되는가? 문제지를 만들려면 최소한 무엇을 알면 만들 수 있을까? 쉽지 않은가?

 

1. 가로, 세로 칸 수

2. 가로, 세로에 열 수 있는 칸 수를 담고 있는 배열

 

1번은 2번에 나름대로 포함되게도 할 수 있을 것이다. 뭐 어떻게 하든지 사실 자기 마음이다. 그림 파일 포맷도 이렇게 하든지 저렇게 하든지 자기마음인 것이다. 내가 정한대로 읽고 쓰게 내가 프로그래밍만 하면 된다. 간단하지 않은가? 그림 파일에서 그림 데이터와 팔레트(있다면)를 따로 저장하든지... 그림 파일 정보를 다른 파일에 저장을 하든지... 그림 파일 전체 리스트와 정보를 한 파일에 저장하든지... 보안에 신경 써서 원래 데이터를 암호화를 하든지 말든지 자기 마음인 것이다. 우리가 만들 것은 간단하니까 이런 거 다 신경 쓰지 말자. 뭘 어떻게 하는 것은 다 자기마음대로니까 여기서 기본만 하고 나머지는 본인이 응용해서 하자.

그럼 다시 생각해보자. 어떻게 하는 것이 대충(?) 좋을까? 생각하는 끝에 편한 것을 택했다. 10*10이면 10*10 그대로 배열을 저장해 버리려고 한다. 이게 제일 쉽다. 또한 명확하다. 그러면 한 줄의 칸 수를 담고 있는 숫자들은 어떻게 할까. 저장할 때 같이 저장을 할까. 불러 올 때 저장을 할까? 나는 저장할 때 어렵게 불러올 때 쉽게를 선택했다. 실제로 프로그램을 짜다보면 그 반대가 될 수도 있지만, 저장할 때 같이 저장을 하도록 하겠다.

그럼 정리를 해보자. 파일에 필요한 정보는

 

1. nemo 파일인 체크하는 데이터

2. nemo 파일 가로, 세로칸 수

3. 실제 보드 배열

4. 각 줄별로 열 수 있는 칸 수를 저장한 배열

 

1번은 사실 없어도 되지만 대부분 파일들에서 다 체크하는 것이다. 왜 체크할까? 그것은.... 생각해봐라. 뻔하다.

자 이제 몇가지 규칙을 두도록 해보자. 1번은 그냥 친숙한 int 형으로 하자. 가로 세로 칸수도 각각 int 형으로. 그럼 지금까지 int가 3개 나왔다. char로도 되는데 왜 int로 했냐고 묻는 다면 나의 대답은 ‘그냥’이다... 이래도 된다는 걸 보여주려고 했다. 그럼 3번은 괜히 char 배열로 하자. 4번은 어떻게 저장을 할까? 복잡하게 생각하지 말자. 한 줄에 대한 포맷으로 이렇게 하자.

 

한 칸에 들어갈 띄운 숫자 개수, 개수만큼 숫자, 이 줄이 끝났다는 구분자...

 

사실 이렇게 하면 구분자가 필요없지만 그냥 한번 넣어봤다. 쓸데없는 데이터가 너무 많다고 생각 되는가? 별 상관없다. 아쉬우면 포맷을 연구해봐라. 줄을 저장하는 진행 순서는 위쪽에서 좌->우, 왼쪽 상->하... 이정도면 네모네모 게임을 하는데 별 지장이 없을 것이다.

 

그 외에 더 두어도 괜찮은 데이터는 시간이다. 스테이지 별로 시간제한을 줄 수도 있지만 데이터의 난이도에 따라 시간을 줘도 괜찮을 것이다. 하여튼 이건 안 넣는다. 기대하지 말자.

 

자 그럼 이제부터 프로그램을 만들어보자.

이건 굳이 네모네모에서 작성하지 말고, 프로젝트를 새로 만들자. 프로젝트 이름을 NemoFileProj 라고 하자. main.cpp 파일을 새로 만들고 작성하기 시작하자...

 

이번엔 클래스로 만들어 보자. 왜 갑자기 클래스냐고? 이것도 해보고 저것도 해보자는 것이 나의 취지이다. 싫으면 그냥 파일로 만들어도 좋다. 나도 구조체로 하려다가 그냥 클래스로 한다. 하지만 클래스나 구조체나 여기에서는 특별한 차이가 없을 것이다. C++에서는 구조체도 클래스와 같다고 하니까... 더 그럴 것이라 생각한다.

클래스를 추가하자. Insert메뉴의 New Class 메뉴를 이용하면 클래스 생성을 더 쉽게 할 수 있다. MFC말고 API도 이렇게 다 된다. 하여튼 해보자. 파일 이름을 CNemoFile로 하고 OK를 하면 자동으로 클래스가 생성된다. 파일도 생성된다. 자 이제 메인을 간단히 작성해보자.

 

#include <windows.h>

#include "NemoFile.h"

 

int __stdcall WinMain( HINSTANCE, HINSTANCE, LPSTR, int )

{

    return 0;

}

 

WINAPI는 어디가고 웬 __stdcall 이냐 할 수도 있다. 그거나 이거나 똑같다. 글자만 다르지. 모두 typedef로 정의 된 것이다. 별로 따질 것도 없다. 똑같다. 이런것도 있다는 걸 보여주는 거다. WinMain함수가 리턴방식이 __stdcall 이라는 거다.

하여튼 여기는 간단한 시범만 보일 것이기 때문에 윈도클래스를 생성한다든지, 창을 생성한다든지 하지 않아도 된다. 사실 콘솔프로젝트로 하려고 했는데... 누르고 보니까 이거였다. 다시 하기도 귀찮으니 그냥 이걸로 하자.

 

NemoFile.h에 다음과 같이 멤버들을 추가하였다.

 

#include <stdio.h>

#include <windows.h>

 

#define NEMOFILEFORMAT 0x7220

 

class CNemoFile  

{

public:

    int m_iNemoFileFormat;

    int m_iWidth, m_iHeight;

    char m_cBoard[30][30];

    char m_cTop[30][15];

    char m_cLeft[30][15];

 

    CNemoFile();

    virtual ~CNemoFile();

 

    void Init();

    bool Loading( char *filename );

    void Writing( char *filename );

};

 

각각이 무엇을 말하는지 알고 있을 것이다. 네모파일포맷은 파일의 제일 첫 부분에 위치할 것이다. 그 다음 가로, 세로 칸수, 다음은 보드 배열, 상단 줄마다 숫자들, 좌측 줄마다 숫자들, 생성자, 소멸자(여기서는 안쓰임), 초기화, 파일로딩, 파일 쓰기... 왜 Reading, Writing이라고 안 했는지 묻지 마라. 당연히 안 묻겠지만... 내가 점점 이상하게 된다.

자 읽어오는 클래스 구조를 다 만들었다. 유념해야 될 것은 이 클래스는 유동적이지 못하다는 거다. 여기 말고는 써먹을 때가 없을 것이다. 응용해서 다른 걸 만들 때 써먹어라. 특징은 보드와 상단, 좌측 배열이 확정적이라고 하는 것이다. 알다시피 몇 바이트 안 되는 것 동적으로 생성했다 지웠다하면 메모리 단편화 현상이 발생한다. 얼마 안되는 메모리니까 그냥 확 잡아서 쓰기로 했다. 그리고 30이라는 숫자는 네모네모를 하다보면 30보다 커지면 시간이 너무 오래 걸린다. 그래서 대략 최대치라고 생각하는 숫자를 넣었다. 좀 더 편하게 하려면 상단에 define문으로 사용해도 괜찮을 꺼다. 그 다음 15는 30칸에서 한 칸 띄고 깨는 것이 전부일 때 이렇게 나오는 것인데... 이렇게 될 일은 없을 거라 생각한다. 30칸정도 가면 아마 박스 한 칸의 사이즈가 변경 되어야 될 것이다. 하여튼... 우선은 cpp함수를 보자.

 

CNemoFile::CNemoFile()

{

    Init();

}

 

CNemoFile::~CNemoFile()

{

 

}

 

void CNemoFile::Init()

{

    ZeroMemory( m_cBoard, sizeof( m_cBoard ) );

    ZeroMemory( m_cTop, sizeof( m_cTop ) );

    ZeroMemory( m_cLeft, sizeof( m_cLeft ) );

}

 

별로 어려운 것 없다.

 

bool CNemoFile::Loading( char *filename )

{

    Init();

 

    FILE *fp = fopen( filename, "rb" );

    int i, j, half;

    char temp;

 

    if( fp == NULL ) return false;

 

    fread( &m_iNemoFileFormat, 1, sizeof( m_iNemoFileFormat ), fp );

 

    if( m_iNemoFileFormat != 0x7220 )

    {

        fclose( fp );

        return false;

    }

 

    fread( &m_iWidth, 1, sizeof( m_iWidth ), fp );

    fread( &m_iHeight, 1, sizeof( m_iHeight ), fp );

 

    for( i = 0; i < m_iHeight; i++ )

    {

        for( j = 0; j < m_iWidth; j++ )

        {

            fread( &m_cBoard[i][j], 1, 1, fp );

        }

    }

 

    //위쪽 숫자들..

    half = m_iWidth >> 1;

    for( i = 0; i < m_iWidth; i++ )

    {

        for( j = 0; j < half; j++ )

        {

            fread( &temp, 1, 1, fp );

            if( temp == '-' ) break;

 

            m_cTop[i][j] = temp;

        }

    }

 

    //왼쪽 숫자들...

    half = m_iHeight >> 1;

    for( i = 0; i < m_iHeight; i++ )

    {

        for( j = 0; j < half; j++ )

        {

            fread( &temp, 1, 1, fp );

            if( temp == '-' ) break;

 

            m_cLeft[i][j] = temp;

        }

    }

 

    fclose( fp );

 

    return true;

}

 

로딩하는 함수다. 구분자를 ‘-’로 줬다. 그냥 봐도 이해될 것이다.

쓰는 함수를 보자.

 

void CNemoFile::Writing( char *filename )

{

    FILE *fp = fopen( filename, "wb" );

    int i, j, half;

    char temp;

 

    if( fp == NULL ) return;

 

    m_iNemoFileFormat = 0x7220;

    fwrite( &m_iNemoFileFormat, 1, sizeof( m_iNemoFileFormat ), fp );

 

    fwrite( &m_iWidth, 1, sizeof( m_iWidth ), fp );

    fwrite( &m_iHeight, 1, sizeof( m_iHeight ), fp );

 

    for( i = 0; i < m_iHeight; i++ )

    {

        for( j = 0; j < m_iWidth; j++ )

        {

            fwrite( &m_cBoard[i][j], 1, 1, fp );

        }

    }

 

    //위쪽 숫자들..

    half = m_iWidth >> 1;

    for( i = 0; i < m_iWidth; i++ )

    {

        for( j = 0; j < half; j++ )

        {

            m_cTop[i][j] = temp;

            

            if( temp == 0 )

            {

                temp = '-';

                fwrite( &temp, 1, 1, fp );

                break;

            }

            fwrite( &temp, 1, 1, fp );          

        }

    }

 

    //왼쪽 숫자들...

    half = m_iHeight >> 1;

    for( i = 0; i < m_iHeight; i++ )

    {

        for( j = 0; j < half; j++ )

        {

            m_cLeft[i][j] = temp;

 

            if( temp == 0 )

            {

                temp = '-';

                fwrite( &temp, 1, 1, fp );

                break;

            }

            fwrite( &temp, 1, 1, fp );

        }

    }

 

    fclose( fp );

}

 

최대한 로딩 함수와 보조를 맞추기 위해서 이렇게 만들었다. 쓰기 함수를 호출하기 전에 CNemoFile 클래스 멤버 변수들의 값들을 채우고 나서 사용해야 할 것이다. 채워넣는 함수는 숙제로 하기로 하자. 내일은 주말이다. 그 다음은.... 어디서 많이 봤을 것이다. ‘주일은 쉽니다.’ 나도 쉴꺼다. 토요일인 내일은 다른 지방에 갈 일이 있다. 그래서 주말은 쉴꺼다. 별로 반응도 없는 상황에서 이런 말 해봤자 소용도 없겠지만. 그렇다는 거다. 그리고 이 템플릿 파일을 만들면서 생각했는데... 사실 작성하다가 좀 방향을 틀었었다. 물론 모를테지만... 하여튼 그거야 뭐 내 맘이고... 읽기 쓰기작업 클래스를 만들었는데... 정작 테스트를 못하는 것이 아쉽다. 다음 시간에는 테스트 해 볼 수 있도록 하자. 한 시간이 또 다시 추가 되겠지만... 간단하게 이 템플릿 파일을 만드는 에디터를 만들어 보도록 하겠다. 다음 시간에 에디터를 만들어서 템플릿 파일을 만들고, 파일에 저장하고, 읽어오고... 하는 것이 제대로 되는지 한번 보자. 그럼 다음 시간까지 안뇽~~..

 

참 그리고 왜 파일이 안 올라가는지 아는 사람 좀 알려 달라. 정말 데브피아가 나만 미워하는 건가?

'- 음악과 나 - > 『 짬 통 』' 카테고리의 다른 글

겜 프로그래밍 강좌  (0) 2006.05.06
슈팅게임  (0) 2006.05.06
저수준 제어를 이용한 WAVE 재생  (0) 2006.05.06
음성채팅  (0) 2006.05.06
FFT  (0) 2006.05.06