원본 : http://www.sarangnamu.net/basic/basic_v ··· ory%3D33
윈도우 모드
윈도우 모드로 DirectX를 움직이려면 , 기반이 되는 윈도우의 정의도
변경해야 합니다.
지금까지 프로그램은 풀 스크린에 적절한 형태의 윈도우 정의가 되어 있었으므로
이번에는 윈도우 모드에 적절한 윈도우 정의를 합니다.
hwnd = CreateWindowEx(
WS_EX_TOPMOST,
"DirectX GamePrograming",
"DirectX GamePrograming",
WS_POPUP,//윈도우 스타일
0,//윈도우 X좌표 시점
0,//윈도우 Y좌표 시점
GetSystemMetrics(SM_CXSCREEN),//윈도우 사이즈 X
GetSystemMetrics(SM_CXSCREEN),//윈도우 사이즈 Y
(HWND) NULL,
(HMENU) NULL,
hInstance,
(LPVOID) NULL);
위는 윈도우를 생성하는 처리의 부분입니다.
여기서 윈도우 핸들 「HWND」에 윈도우의 ID를 건네받는 부분도
변경해야 합니다.(위의 코드는 풀 스크린에 적절한 형태)
제4 파라미터를 이하와 같이 변경합니다.
WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION,
이것은 「메뉴」 「테두리」 「윈도우 기본 기능」등을 추가하는 플래그입니다.
(플래그의 의미는 헬프 참조)
다음으로 변경 해야 할 것은 제7, 8 파라미터입니다.
이것은 생성하는 윈도우의 사이즈에 대한 파라미터입니다.
윈도우 모드에서는 「테두리」「메뉴」등을 가지므로 기본 사이즈에 이 「테두리」등
의 사이즈를 플러스해야 합니다.
(윈도우 모드에서는 그 때문에 조금 커진다(클라이언트는 같은 사이즈))
시스템의 폭을 조사하려면 「GetSystemMetrics 함수」를 사용합니다.
테두리등은 경우에 따라서 폭이 바뀔 수 있으므로 위의 함수를 사용해 그 사이즈를 취득하지 않으면 올바른 값을 취득할 수 없습니다.
int GetSystemMetrics(
int nIndex // system metric or configuration setting to retrieve
);
파라미터는 어느 시스템의 폭을 리턴할까? 의 플래그로 리턴 값은 INT형입니다.
SM_CXFIXEDFRAM 범위 사이즈(다른 한쪽)
SM_CYCAPTION 타이틀 바의 높이
SM_CYMENU 1행의 메뉴의 높이
위는 플래그의 하나의 예입니다.
함수를 사용해서 게임 화면 사이즈+시스템 폭을 윈도우 생성의 사이즈로 합니다.
640+GetSystemMetrics(SM_CXFIXEDFRAME)*2;//가로의 시스템폭
480+GetSystemMetrics(SM_CYCAPTION) +GetSystemMetrics(SM_CYFIXEDFRAME)*2;//세로
(이 값에서 메뉴의 높이는 포함되지 않는다)
이 변경으로 윈도우 모드의 생성은 종료입니다.
윈도우 모드의 제한
풀 스크린과 다르다
풀 스크린 모드에서는 화면을 독점하지만 윈도우 모드에서 그것은 불가능합니다.
윈도우 모드에서는 다른 어플리케이션과 공존하지 않으면 안 되기 때문에
제약이 발생합니다.
①:팔레트의 제한
해상도가 「256칼라」일 경우, 윈도우 모드로 사용할 수 있는 팔레트가 한정됩니다.
이 경우, 팔레트의 「0~9번」 「246~255번」은 윈도우 시스템이 사용하는
시스템 칼라로서 등록되어 있으므로 이 팔레트를 변경하면 WINDOWS 자신의 색이
이상하게 되어 버립니다.
②:플립을 할 수 없다
풀 스크린 모드에서는 attach 된 백 버퍼와 플립으로 묘화 하고 있었지만
윈도우 모드에서는 attach 된 백 버퍼를 만들 수가 없습니다.
윈도우 모드에서 묘화는 attach되어 있지 않은 서페이스에서 프라이머리에 전송이 묘화
처리가 됩니다.
③:프라이머리서페이스
프라이머리서페이스는 디스플레이 전체가 됩니다.
묘화 좌표도 화면 좌표 단위가 됩니다.
(초기설정 한 클라이언트 이외에도 묘화 가능하게 된다.)
보통 게임에서는 이것을 고려해 묘화해야 합니다.
Draw 초기화
if(dd->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN)!=DD_OK)
return FALSE; //윈도우의 설정을 변경
Draw 초기화의 하나···협조를 하고 있는 곳도 변경해야 합니다.
제2 파라미터가 「DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN」가 되어 있습니다.
이것은 「배타 또는 풀 스크린」이라는 플래그입니다.
이것을 「DDSCL_NORMAL」로 변경합니다.
이렇게 함으로 협조 레벨이 「윈도우 모드」가 됩니다.
SURFACE 초기화
서페이스의 파라미터도 풀 스크린과 윈도우 모드에서는 달라집니다.
DDSURFACEDESC2 ddsd; //DDSURFACEDESC 구조체
memset((VOID *) &ddsd, 0, sizeof(ddsd));
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE|DDSCAPS_COMPLEX|DDSCAPS_FLIP;
ddsd.dwBackBufferCount=1;
위에 있는 부분이 프라이머리 서페이스를 설정하는 행입니다.
적색 문자의 부분은 「윈도우 모드」에서는 필요없는 부분입니다.
윈도우 모드는 attach 할 수 있는 백 버퍼를 가질 수 없기 때문에
플립을 할 수가 없다····이것을 고려하면 위의 플래그를 지우는 이유를 이해할 수 있다고 생각합니다
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
ddsd.ddsCaps.dwCaps=DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = 640;
ddsd.dwHeight = 480;
dd->CreateSurface(&ddsd, &BackSurface, NULL);
다음은 변경되는 백 버퍼의 서페이스를 생성합니다.
여기는 보통의 서페이스를 만듭니다.
윈도우 모드에서의 묘화
윈도우 모드에서는 attach 된 백 버퍼를 가질 수가 없기 때문에
플립 메소드에 의한 묘화를 할 수 없습니다.
거기서 윈도우 모드일 때는 클라이언트 영역과 같은 사이즈의 서페이스를 생성해서
묘화 해 묘화가 끝나면 그 서페이스의 내용과 프라이머리 서페이스로 전송 하는
방법으로 묘화를 합니다.
윈도우 모드에서의 묘화의 흐름은 위에 그림에 나타난 대로 백 버퍼(플립 불가)에서 「Blt」를 합니다.
그 전송할 곳의 좌표는 유저의 손에 의해 항상 변경 가능한 것이 윈도우 모드입니다.
그 때문에 항상 올바른 위치의 윈도우 좌표를 취득해 거기에 「Blt」해야 합니다.
윈도우가 움직였다고 하는 메세지 처리를 추가합니다.
윈도우가 움직이면 「WM_MOVE 메세지」가 발행되므로 그 처리에
현재의 윈도우의 구형 위치를 얻는 처리를 합니다.
「Blt」는 이것으로 구할 수 있던 값을 바탕으로 묘화 합니다.
우선 「WM_MOVE 메세지」로 윈도우의 값을 얻는 처리(함수)를
생각해 봅시다.
이라고 할까, 회답이 이하입니다
void GetClientRect(void)
{
POINT clientpos={0,0};//클라이언트 포지션
ClientToScreen(hwnd, &clientpos);
//윈도우의 좌상단 좌표를 화면 좌표로 한다
//ScreenClientRect는 글로벌의 RECT 구조체
ScreenClientRect.left=clientpos.x;//시점 X좌표
ScreenClientRect.top=clientpos.y;//시점 Y좌표
ScreenClientRect.right=clientpos.x + _ScreenXsize;
//종점 X좌표
ScreenClientRect.bottom=clientpos.y + _ScreenYsize;
//종점 Y좌표
// _ 앞부분에 셋팅되어 있는 매크로입니다.
//시점에 윈도우 사이즈의 매크로를 추가함으로
// 클라이언트의 크기를 알 수 있습니다.
}
그럼 처리의 설명을 합니다.
「ClientToScreen 함수」는 지정된 윈도우 핸들(HWND)의 윈도우의 클라이언트
좌표의 값을 화면의 좌표 값으로 변경해 줍니다.
이 경우 클라이언트 값 「clientpos」는 화면의 「0, 0」를 이 함수에 건네주어
결과, 「clientpos」에는 스크린(디스플레이의)의 좌표가 격납됩니다.
위의 샘플 코드의 「ScreenClientRect」는 RECT 구조체로 「Blt」할 때의
표시할 곳의 값을 격납하는 글로벌 구조체입니다.
그 구조체에서 얻을 수 있던 화면 좌표를 바탕으로 클라이언트의 값(rect)을 건네줍니다.
시작점은 「ClientToScreen 함수」로 얻는 값과 같습니다.
끝점은 얻은 값+클라이언트의 사이즈로 얻을 수가 있습니다.
묘화는 이렇게 된다
윈도우 모드에서는 「플립」을 할 수 없기 때문에.
소스코드의 「Flip」함수(화면 반영 처리)는 위의 값(rect)으로 「Blt」됩니다.
void flip(void)
{
//MainSurface->Flip(NULL, DDFLIP_WAIT);
//위는 풀 스크린 모드에서만 유효
//윈도우 모드에서는 blt에 의한 전송 묘화를 실시한다
MainSurface->Blt(&ScreenClientRect, BackSurface, NULL, DDBLTFAST_WAIT, NULL);
}
플립 불가능한 백 버퍼로부터 「Blt」를 사용하고 전송합니다.
전송할 곳의 rect에는 방금 전 위에서 설명한 RECT 구조체를 지정합니다.(제1 파라미터)
유저가 윈도우의 위치를 변경하면 묘화할 곳의 좌표도 바뀝니다
프라이머리 서페이스의 클립
스크린 전체가 「프라이머리 서페이스」가 되므로 윈도우 모드의 DirectDraw는
자신의 클라이언트 영역 외에도 묘화가 가능합니다.
(miss는 화면 전체를 묘화 해 버려 대단한 일이 되었습니다. . .)
이것을 막기 위해 「윈도우에 클립」이 필요합니다.
이것을 하지 않으면 화면이 이상하게 되어 버린다.
dd->CreateClipper(0, &LpClip, NULL);
if(LpClip->SetHWnd( 0, hwnd )!= DD_OK)
return FALSE;
if(MainSurface->SetClipper(LpClip) !=DD_OK)
return FALSE;//서페이스에 클립 세트
여기서 클리퍼를 설정하는 것이 「프라이머리서페이스」라고 하는 것에 주의하십시오.
물론, 백서페이스에도 필요하겠지만. . .
윈도우 모드의 경우, 프라이머리 서페이스의 클리핑은 반듯이 필요합니다.
백 버퍼의 클립
실제로 묘화를 실시하는 백 버퍼에도 클립을 Set하지 않으면 화면끝에서 캐릭터가 사라지는 현상이 되어 버립니다.
그러나 프라이머리 서페이스에 Set함으로···
if(LpClip->SetHWnd( 0, hwnd )!= DD_OK) return FALSE;
이러한 처리는 불가능합니다.
「SetHWnd 메소드」는 지정한 윈도우 핸들에 클립 영역을 Set함으로
백 버퍼는 관계가 없는 단순한 메모리 영역에 지나지 않기 때문입니다.
「GetHWnd 메소드」로 클립 영역을 세트 하면, 윈도우의 사이즈와 같은 크기의
클립 영역이 화면 좌표「0, 0」로부터 Set됩니다.
이 경우, 윈도우의 위치를 「0, 0」로 하면 정상적으로 동작하고 있는 것처럼 보입니다만
윈도우를 움직이면 안되게 됩니다.
이것을 해결하려면 백서페이스에 묘화 할 때 자작의 클립 처리를 만들까····
적당한 방법으로.
if(LpClip->SetHWnd( 0, hwnd )!= DD_OK) return FALSE;
그리고 클립을 세트 하면 윈도우 핸들이 가리키는 윈도우의 사이즈가
스크린의 「0, 0」로부터 세트 되기 때문에····
윈도우 모드에서는 화면의 사이즈와 같은 크기의 클립 영역을 세트 합니다.
typedef struct{
RGNDATAHEADER rdh;
RECT rect[256];
}MYRGNDATA;
MYRGNDATA rgn;
rgn.rdh.dwSize = sizeof(RGNDATAHEADER);
rgn.rdh.iType = RDH_RECTANGLES;
rgn.rdh.nCount = 1;
rgn.rdh.nRgnSize = sizeof(RECT);
rgn.rdh.rcBound.left = 0;
rgn.rdh.rcBound.top = 0;
rgn.rdh.rcBound.right = GetSystemMetrics(SM_CXSCREEN);
rgn.rdh.rcBound.bottom = GetSystemMetrics(SM_CYSCREEN);
rgn.rect[0].left = 0;
rgn.rect[0].top = 0;
rgn.rect[0].right = GetSystemMetrics(SM_CXSCREEN);
rgn.rect[0].bottom = GetSystemMetrics(SM_CYSCREEN);
if(dd->CreateClipper(0, &LpClipBack, NULL) != DD_OK)
return FALSE;
LpClipBack->SetClipList((RGNDATA*) &rgn, 0);
if(BackSurface->SetClipper(LpClipBack) !=DD_OK)
return FALSE;//서페이스에 클립 세트
위는 화면 전체를 클립 영역에 세트 하는 처리입니다.
세트 하는 영역을 격납하는 처리가 대부분입니다.
또, 이 처리를 때문에 또 하나의 클립 오브젝트를 필요로 합니다.
「LpClipBack」새로운 클리퍼 오브젝트 포인터를 정의합니다.
「SetClipList 메소드」는 MYRGNDATA 구조체에 건네주고 있는 값을 클리퍼 오브젝트
에 Set합니다.
이 후, 「SetClipper」를 하면 설정이 됩니다.
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
윈도우·풀 스크린 양모드 대응
시판되고 있는 WINDOWS 게임은 풀 스크린, 윈도우 모드의 양쪽 모두를
대응하고 있는 게임이 많습니다.
이번은 프로그램을 양 대응으로 설정해 봅니다.
양대응의 프로그램은 유저의 모드 변환을 항상 검색해 모드변환이 발생하면
윈도우 클래스를 재정의해서 묘화(플립)를 변경합니다.
엔터키를 누르면 스크린 모드가 변경됩니다.
묘화 계통을 변경한다
처음의 변경할 곳은「플립」····묘화 처리의 부분입니다.
풀 스크린 모드에서는 「플립」은 가능하지만····윈도우 모드에서는
「플립」을 할 수가 없습니다.
때문에 윈도우 모드일 때, 풀 스크린 모드일 때로.
디스플레이에 묘화를 반영하고 있던 처리를 나눠야 합니다.
MainSurface->Flip(NULL, DDFLIP_WAIT);
//플립은 풀 스크린 모드일 때 밖에 실행할 수 없다
MainSurface->Blt(NULL, BackSurface, NULL, DDBLTFAST_WAIT, NULL);
//윈도우 모드에서는 blt에 의한 전송 묘화를 한다
묘화에는 모드에 의해 위의 2가지 방법이 있습니다.
위의 방법을 현재의 모드에 의해 분기 하려면 현재의 협조 레벨을 조사하는 것으로 가능합니다.
if(dd->SetCooperativeLevel(hwnd, ncoop)!=DD_OK)
return FALSE; //협조
위가 협조레벨을 Set하고 있는 부분입니다.
제2 파라미터는 협조 플래그를 건네줍니다.
이 부분을 변수로 할 필요가 있습니다.
(모드를 변환할 때는 이 변수를 바꾸어 협조 레벨을 설정을 다시 하기 위해서 사용한다)
DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN
위의 코드는 풀 스크린의 플래그입니다.
특히 강조 문자의 「DDSCL_FULLSCREEN」은 반드시 풀 스크린 모드일 때 필요하게 됩니다.
····로 「ncoop 변수(WORD형)」가 「DDSCL_FULLSCREEN」일때
풀 스크린 모드라는 것을 알수 있습니다.
「ncoop 변수」에 「DDSCL_FULLSCREEN」가 없을 때는 윈도우 모드가 됩니다.
if(ncoop&DDSCL_FULLSCREEN)
{
if( MainSurface->Flip(NULL, DDFLIP_WAIT) != DD_OK)
return FALSE;
}
else
{
if( MainSurface->Blt(&rect, BackSurface, NULL, DDBLT_WAIT, NULL) != DD_OK)
return FALSE;
}
묘화 처리는 위처럼 됩니다.
「ncoop 변수」를 「DDSCL_FULLSCREEN 플래그」와「AND 처리(&연산자)」하는 것으로
「ncoop 변수」안에 「DDSCL_FULLSCREEN 플래그」가 포함되는지를 체크합니다.
포함되는 경우는 현재의 모드가 「풀 스크린 모드」가 되므로
「Flip 메소드」에 의한 묘화를 합니다.
서페이스 파괴에 대해
풀 스크린 모드에서 윈도우 모드로 변경할 때, 메모리상에 있는 서페이스는 모두
삭제됩니다.(반대의 경우도 같다)
풀 스크린은 비디오 메모리를 독점하고 있는 모드지만, 윈도우 모드는 모든 어플리케이션이 비디오메모리를 공유하고 있습니다.
풀 스크린으로부터 윈도우 모드로 변경할 때, 풀 스크린 모드가
사용하고 있던 비디오메모리를 시스템 복귀를 위해 해방해야 하는 것입니다.
서페이스가 파괴되는(내용이 소실된다) 것으로 화면 모드 변경 후 서페이스를
다시 생성할 필요가 있습니다.
개념)
핑크색의 크기의 메모리 공간이 필요하게 됩니다.(장소는 불명)
블루의 이미지를 격납하고 있던 공간은 방해가 되므로 해방합니다.
서페이스 복귀
서페이스의 내용···이미지는 파괴되지만, 서페이스의 인터페이스는 아직 존재하고 있습니다.
화면 모드의 변환이 종료되어 어플리케이션의 처리가 개시되면 서페이스 메소드 「Restore」를 사용함으로 없어진 서페이스 영역을 다시 확보해 줍니다.
그러나 메모리 영역을 확보해 줄 뿐이므로 영역에 다시 묘화 처리를 실시할 필요가 있습니다.
「Restore 메소드」는 생성한 모든 서페이스에서 사용해야 합니다.
서페이스로스트의 검출
서페이스 파괴는 스크린 모드를 변경했을 때에도 발생하지만 다른 원인에서도 발생합니다.
「Blt 메소드」를 사용했을 때 「Blt 메소드」가 리턴 값으로 해서 「DDERR_SURFACELOST」를 돌려주었을 때, 서페이스가 파괴된 것을 의미합니다.
(헬프 참조)
「DDERR_SURFACELOST」가 리턴 됐다·····즉, 디스플레이 모드가 변경되었는지
하등의 이유로 서페이스가 파괴(로스트)된 것이 됩니다.
이것으로 서페이스의 파괴를 검출할 수 있었으므로 나머지는 다른 때의 처리(Restore 처리)를 실행시키면 OK····일 것입니다.
소스코드 해설
우선 풀 스크린 모드와 윈도우 모드의 양쪽 모두를 가능하게 하기 위해서 현재 어느 모드인지를 알아 둘 필요가 있습니다.
ncoop=DDSCL_NORMAL;
「ncoop 변수」는 디스플레이의 협조를 실시하는 플래그입니다.
이 변수에는 모드의 플래그가 들어갑니다.초기 값을 「DDSCL_NORMAL」로 하므로
처음의 기동은 「윈도우 모드」가 됩니다.
소스코드에서는 「엔터키」가 눌렸을 때에 스크린 모드를 변경하므로. . .
현재 윈도우 모드일 때는 「ncoop 변수」에 「DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN」를 건네주면 풀 스크린이 됩니다.
덧붙여서 엔터키메크로는 「VK_RETURN」입니다
void changescreen(void);
위의 함수는, 엔터키가 눌렸을 때에 처리되는 함수입니다.
처리의 내용은····
현재의 모드를 조사해서 플래그를 고쳐 쓴다.
윈도우 클래스를 갱신.
협조를 재차 실시한다.
BOOL changescreen(void)
{
if(nccop&DDSCL_FULLSCREEN){ //현재 풀 스크린
if(DD_OK!=dd->RestoreDisplayMode())
return FALSE; //해상도를 바탕으로 되돌린다
nccop = DDSCL_NORMAL;
//플래그를 윈도우 모드로 한다
}
else{ //현재 윈도우 모드
ncoop = DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN;
}
return TRUE;
}
위는 플래그 변환 처리입니다.
「ncoop 변수」의 내용을 「&(비트마다의 AND)」로 현재의 모드를 조사합니다.
현재, 풀 스크린 모드일때·····「ncoop 변수」에 「DDSCL_NORMAL」
을 넣습니다.(윈도우 모드를 나타내는 플래그)
그 전에 디스플레이의 해상도를 바탕으로 되돌립니다.
아마 640×480이라고 하는 사이즈의 해상도가 되어 있을 것이므로.
변경전의 해상도로 다시 돌려 줍니다.
이것은 「RestoreDisplayMode 메소드」로 가능합니다.(파라미터 없음)
그런데····이것으로 원하는 모드에 플래그를 변경할 수 있었지만.
이대로는 문제가 있습니다.
윈도우 클래스를 재정의해 윈도우의 형상을 변경하지 않으면
윈도우로부터 풀 스크린에 변경했을 경우에 테두리나 메뉴가 남는 일도 생각할 수 있습니다.
반대로 풀 스크린의 윈도우의 경우는 메뉴나 테두리가 표시되지 않는 것이 되어 버립니다.
BOOL changescreen(void)
{
if(ncoop&DDSCL_FULLSCREEN){
//현재 풀 스크린-윈도우에
if(DD_OK!=dd->RestoreDisplayMode())
//해상도를 바탕으로 되돌린다
return FALSE;
ncoop = DDSCL_NORMAL;
//플래그를 윈도우 모드로 한다
}
else{ //현재 윈도우 모드-풀 스크린에
ncoop = DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN;
}
//윈도우 클래스를 갱신
if(ncoop&DDSCL_EXCLUSIVE)
if(!SetWindowLong(hwnd, GWL_STYLE, WS_POPUP))
//풀 스크린에 있던 윈도우로 한다
return FALSE;
if(dd->SetCooperativeLevel(hwnd, ncoop)!=DD_OK)
return FALSE; //협조
if(ncoop&DDSCL_EXCLUSIVE){
//디스플레이 모드의 변경
// . . . 이것은 풀 스크린으로 할 때만
if(dd->SetDisplayMode(640,480,16)!=DD_OK)
return FALSE;
}
else{
if(!SetWindowLong(hwnd, GWL_STYLE,
//윈도우 모드에 있던 윈도우에 재정의한다
WS_POPUP | WS_CAPTION |
WS_SYSMENU | WS_BORDER|
WS_MINIMIZEBOX | WS_VISIBLE))
return FALSE;
RECT rect={0,0,640,480};
AdjustWindowRect(&rect, GetWindowStyle(hwnd),
GetMenu(hwnd)!=NULL);
//윈도우를 묘화 하는 위치를 rect 구조체에 넣습니다.
//↓:윈도우 표시 좌표를
// Set(파라미터는 헬프 참조 (WindowsX.h))
SetWindowPos(hwnd, NULL, 0, 0,
(rect.right - rect.left),
(rect.bottom - rect.top),
SWP_NOMOVE|
SWP_NOZORDER|
SWP_NOACTIVATE);
SetWindowPos(hwnd, HWND_NOTOPMOST, 0,0,0,0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
}
//위에서 스크린 모드는 변경되었다. 그러나 서페이스는 없어졌다.
return TRUE;
}
윈도우 클래스의 재정의는 「SetWindowLong 함수」로 가능합니다.
주로 윈도우 클래스 정의에 「 제4 파라미터」···윈도우의 모습을 변경합니다.
(플래그에 대해서는 헬프 참조)
풀 스크린에서 윈도우로
이 때, 해상도를 원래대로 되돌린 것 만으로는 어플리케이션의 윈도우 폭은 원래대로
돌아오지 않습니다.
원래의 해상도····하지만 화면에는 하나의 윈도우로서 표시됩니다.
거기서 윈도우 모드로 전환할 때는 그 사이즈.위치도 설정해 주지 않으면 안됩니다
그것이 생성되는 것은 「SetWindowPos 함수」입니다.
위의 경우일때 디스플레이 모드는 교체되지만 서페이스의 내용은 파괴되고
「Blt 메소드」는 「DDERR_SURFACELOST」의 반환 묘화가 반영되지 않게 됩니다.
여기서 서페이스의 복원이 필요하게 되는 것입니다.
「Restore 메소드」를 사용하는 방법이 있으면(위에서도 다루었으나)
서페이스 로스트가 발생했을 때는 서페이스를 완전하게 파기해 다시 새롭게 만든다.
서페이스 로스트가 발생했을 때는 「initsurface 함수」를 호출합니다(강요는 아님).
이 함수는 새롭게 서페이스를 생성하는 함수로 준비되어 있습니다.
그리고, 그 선두에····
if(MainSurface){
//프라이머리 서페이스가 생성되어 있다면 해방
MainSurface->Release();
MainSurface=NULL;
}
if(mapsurface){
// 프라이머리 서페이스가 생성되어 있다면 해방
mapsurface->Release();
mapsurface=NULL;
}
if(sprite){ 프라이머리 서페이스가 생성되어 있다면 해방
sprite->Release();
sprite=NULL;
}
를 추가합니다.
보면 알 수 있듯이 서페이스가 존재하면 파기합니다.
어플리케이션을 기동해 1회째에 이 처리는 실행되지 않습니다.(당연 서페이스는 없으니까)
그러나 서페이스로스트가 발생해 이 함수를 부르면
현존 하는 서페이스를 완전 파기해 다시 새롭게 만들어 줍니다.
스크린 모드에 의해 서페이스의 속성이 바뀌므로 그 처리도 추가하고 있다.
프로그램이 커져 가므로 위의 해방 처리는 함수화 하는 것이 좋을지도 모른다
그리고, 함수의 마지막에····
if(!(sprite=loadbmp("mychr.bmp")))
return FALSE;
if(!(mapsurface=loadbmp("map.bmp")))//맵 표면 생성
return FALSE;
를 추가하는 것으로 서페이스를 완전하게 원래대로 되돌릴 수가 있습니다.
(덧붙여서 위의 처리는 지금까지 「CreateDraw 함수로 호출했었습니다만 부적절한 곳이 되므로 장소 변경)
서페이스의 파라미터
서페이스도 풀 스크린·윈도우에서는 그 성질이 달라 집니다.
풀 스크린일 때는 「attach 가능」으로 「플립 가능」
윈도우 모드일 때는 「attach 불가」,「플립 불가」입니다.
때문에 「initsurface 함수」도 변경하고 싶은 모드의 플래그에 의해
생성 또는 지우는 기능을 서페이스의 파라미터를 나누지 않으면 안됩니다.
윈도우 모드로 하고 싶으면·····
「attach 가능」 「백 버퍼 있음」 「플립 가능」의 서페이스를
만들어야 합니다.
지금까지의 「initsurface 함수」는 어느 쪽인지 다른 한쪽의 서페이스 생성만의 처리였습니다.
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
윈도우 모드의 묘화
지금까지의 윈도우 모드의 샘플은 화면이 의도하지 않는 형태로 확대되고 있지 않았습니까?
그것은 「flip 함수」에 문제가 있기 때문입니다.
void flip(void)
{
if(ncoop&DDSCL_FULLSCREEN){
MainSurface->Flip(NULL, DDFLIP_WAIT);
}
else{
MainSurface->Blt(NULL, BackSurface, NULL,
DDBLTFAST_WAIT, NULL);
}
}
이 부분이 플립(묘화의 화면 반영) 처리입니다.
풀 스크린 모드의 경우는 좌표가 변함없기 때문에 문제 없습니다.
윈도우 모드의 경우는 묘화할 곳의 좌표는 「화면 좌표」····
즉 디스플레이 단위의 좌표 값으로 묘화할 곳을 건네주지 않으면 안됩니다.
MainSurface->Blt(NULL, BackSurface, NULL, DDBLTFAST_WAIT, NULL);
위는 윈도우 모드일 때의 플립 처리입니다.
제1 파라미터는 전송할 곳의 네모를 나타내는 값입니다. 거기를 지금까지는 NULL로 했었습니다.
이것은 화면 전체에 묘화하라는 의미가 됩니다.
대부분의 사람은 디스플레이의 해상도를 「640×480」보다 높게 설정하고 있을 것이므로
클라이언트 영역에는 확대된 이미지가 표시되고 있을 것입니다.
(그래픽 카드에 따라서는 희미해지든지····)
확대하지 않고 제대로 된 사이즈로 묘화 하려면, 「전송할 곳」을 값을 올바르게 얻을 필요가 있습니다.
윈도우는 유저의 손에 의해 항상 이동이 가능해서 이동이 발생되면
「전송할 곳의 값」를 변경하도록 프로그래밍 하지 않으면 안됩니다.
윈도우가 작동되면·····
「WM_MOVE」메세지가 발생됩니다.
이 메세지를 취득해 전송할 곳의 값을 변경하는 처리를 추가합시다.
메세지 처리
LRESULT WINAPI WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message){
case WM_KEYDOWN:
switch(wParam)
{
case VK_ESCAPE:
releaseobject();
PostMessage(hwnd, WM_CLOSE, 0,0);
break;
case VK_RETURN:
//스크린 모드 변경
changescreen();
break;
}
break;
case WM_MOVE:
GetClientRect();
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
위와 같이 「WM_MOVE 메세지」에 대한 메세지를 추가합니다.
「GetClientRect 함수」는 현재의 클라이언트 영역의 위치를 얻는 함수입니다.
(추가했습니다)
void GetClientRect(void)
{
POINT clientpos={0,0}; //클라이언트 포지션
ClientToScreen(hwnd, &clientpos);
//윈도우의 좌상 좌표를 화면 좌표로 얻는다
//ScreenClientRect 는 글로벌 RECT 구조체
ScreenClientRect.left=clientpos.x;//시작점 X좌표
ScreenClientRect.top=clientpos.y;//시작점 Y좌표
ScreenClientRect.right=clientpos.x + _ScreenXsize;//끝점 X좌표
ScreenClientRect.bottom=clientpos.y + _ScreenYsize;//끝점 Y좌표
//시작점에 윈도우 사이즈의 매크로를 가산하는 것으로 //클라이언트의 크기를 알 수 있습니다.
}
현재의 윈도우의 좌표를 클라이언트 좌표로 취득하는 처리입니다.
처리는 간단합니다.
「ClientToScreen 함수」로 윈도우의 좌상의 좌표를 화면 좌표계로 얻을 수가 있으므로
그래서 얻은 값을 RECT 구조체에 건네줍니다.
시작점(좌상 좌표)과 끝점(우하 좌표)을 얻습니다.
그리고 RECT 구조체를 묘화 할 때, 「Blt 메소드」의 제1 파라미터에 건네줍니다.
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
Z소트
Z소트는 깊이를 표현할 때에 사용됩니다.
일정한 게임····2D지만 「깊이(Z축)」의 개념이 있는 게임을
만들 때에 사용하면 스프라이트의 묘화가 비교적 간단하게 됩니다.
지금까지의 샘플 프로그램은 화면에 묘화 할 때····
1.「배경의 묘화」
2.「캐릭터의 묘화」
etc···
등으로 안쪽에 있으면 있는 차례로 묘화하도록 프로그램 해 왔습니다.
묘화 순서가 반대라면,「캐릭터 이미지」는 배경에 가려지기 때문에
보이지 않을 것입니다.
2D에서는 「묘화 하는 차례」로 깊이를 표현합니다.
Z소트가 필요한 게임이 예를 들면(자)·····「디아블로」
등 맵에 「앞과 안쪽」의 개념이 있는 경우입니다.
캐릭터는 맵의 안쪽으로 가면 앞의 캐릭터의 뒤로 숨어 버립니다.
안쪽으로 갔으니까 당연합니다만,
이 코너의 지금까지 방법에서는 이것은 할 수 없습니다.
1.「배경의 묘화」
2.「캐릭터 A의 묘화」
2.「캐릭터 B의 묘화」
etc···
처리를 만들고 있으면 「캐릭터 B」는 항상 「캐릭터 A」의 앞에 묘화 되고.
맵의 안쪽으로 이동했다고 해도 맵의 앞에 있는 「캐릭터 A」의 뒤에 숨는 일은 없습니다.
는 그 예····「적색 화상」은 「청색 화상」보다 뒤로 묘화 하고 싶어도
할 수 없습니다.
이것은 「묘화의 차례」에 문제가 있기 때문입니다.
Z소트에서는 스프라이트에 「Z축의 값」을 각각 갖게하고 그 값에 의해 묘화 하는 차례를
변경하는 처리합니다.
「Z축의 값」은 「얼마나 안쪽에 있을까?」라고 하는 값으로 「0」이라면 제일 앞 「α」이라면 안쪽이라 생각합니다.
α값이 높을 수록 처리에 시간이 걸리지만 보다 정밀도가 높은 원근감을 표현할 수 있다
그리고 「Z축의 값」이 「큰(작은 순서로도 가능) 차례」로 묘화 해 나가면 OK입니다.
처리의 흐름
5개의 스프라이트를 Z소트에 의한 묘화를 실시한다
5개의 스프라이트는 모두 다른 이미지를 사용합니다.
그 이미지들은 모두 하나의 서페이스에 모을 수 있습니다.
표시하는 「5개」의 스프라이트에 구조체를 만듭시다.
스프라이트 구조체에도 추가해야 멤버는 이하와 같은 것이라 생각됩니다.
■스프라이트가 있는 서페이스의 주소
소스코드에서는 「하나의 서페이스」가 되고 있지만 나중에 확장할 때에
어느 서페이스로부터도 가능하도록 이것을 멤버에 넣습니다.
묘화 처리는 이 멤버가 지정하는 서페이스부터 묘화를 합니다.
■표시할 곳의 구역
「blt」로 묘화 할 때, 2개의 RECT 구조체로 전송할 곳과 전송해올 곳의 구역이
필요하므로 이것도 멤버에 넣도록 합시다.
X·Y의 위치만으로도 묘화를 처리하는 쪽에 처리를 추가하는 일로 가능
■Z값
그 스프라이트가 얼마나 안쪽으로 존재하는지를 나타내는 값입니다.
■앞과 다음의 구조체의 주소
양방향을 체인 리스트로 소트를 할 때에 이것이 필요합니다.
스프라이트가 증가할 때, 구조체를 동적으로 생성해 체인 안에 짜넣어 가는 것이
제일 좋다고 생각합니다.
그러나 이 소스에서는 체인 리스트에서도 소트는 하고 있지 않습니다.
다만 배열을 정의해 거기서 소트 하고 있습니다.
샘플을 그대로 사용하면····화면에 나오는 스프라이트의 최대분량의 구조체가
항상 메모리에 필요하기 때문에 최적이라고는 할 수 없습니다.
할 수 있으면 체인 리스트에서의 Z소트에 챌린지하는 편이 더욱 효율적입니다.
구조체의 정의
구조체를 정의합시다.
typedef struct{//Z소트 구조체 정의
DWORD LpSurface; //서페이스에의 포인터
RECT Trect; //전송할 곳의 네모
RECT Nrect; //전송해올 곳의 네모
int Zs; //Z값
}Zsort, *LpZsort;
이번 샘플은 캐릭터 5개와 맵 1개의 「6개」의 구조체 배열이 필요합니다.
구조체의 생성을 하면 초기의 소트 값을 격납하는 함수를 1회만 호출하도록 해 둡니다.
void ZsortInit(void)
{
//맵 이미지의 Z소트 초기화
sort[5].LpSurface = (DWORD) mapsurface;
sort[5].Zs = _ZLAST;//맵은 제일 안쪽에 Set한다
SetRect(&sort[5].Nrect, 0,0,256,240);
SetRect(&sort[5].Trect, 0,0,640,480);
//캐릭터 5개의 초기화
for(int cnt=0;cnt<=4;cnt++){
sort[cnt].LpSurface = (DWORD) sprite;
sort[cnt].Zs = cnt; //맵은 제일 안쪽에 Set한다
SetRect(&sort[cnt].Nrect, cnt*32, 0, cnt*32+32, 32);
//전송해올 곳 좌표
SetRect(&sort[cnt].Trect, cnt*32, 200, (cnt*32)+32, 232);
//전송할 곳 좌표
}
//캐릭터는 나중에 묘화 한다
//캐릭터 A만 움직임으로. . . Z값을 변경할 수가 있습니다.
}
초기 Z값은 맵의 제일 안쪽에서 캐릭터가 0·1·2·3·4로 Z 값을 할당하고 있습니다.
움직일 수 있는 캐릭터는 「A(초기 Z값:0)」만으로 기동했을 때는 캐릭터 A는
어느 캐릭터보다 앞에 있기 때문에 뒤로 숨는 일은 없습니다.
Z값의 초기화(캐릭터의 좌표 초기화)는 아무런 특색도 없는 값을 넣는 처리이므로
설명은 생략합니다.
다음에····「Z소트에 의한 묘화」입니다.
이 처리는 「void Zsortrender(void)」에서 처리됩니다.
for(cnt=_ZLAST;cnt>=0;cnt--) //안쪽부터 검색
{
for(x=0;x<=5;x++)//Z값이 높은 것부터 묘화 한다
{
if(sort[x].Zs == cnt) //Z심도와 동일하니까
BackSurface->Blt(&sort[x].Trect,
(IDirectDrawSurface*) sort[x].LpSurface,
&sort[x].Nrect,
DDBLTFAST_WAIT | DDBLT_KEYSRC, &ddbltfx);
}
}
기술적으로는 의외로 짧은 처리입니다.
루프 처리로 안쪽부터 차례로 묘화하는 처리가 되어 있습니다.
바깥쪽의 루프(for(cnt=_ZLAST;cnt>=0;cnt--)
「cnt」는 「Z값」을 나타내고 있어 「Z치의 최대값 매크로_ZLAST」부터 차례로 검색해 갈 것입니다.
「0」이 되면 차례로 묘화 다 한 것이 되므로 처리는 종료가 됩니다.
안쪽의 루프(for(x=0;x<=5;x++)
는 스프라이트 구조체의 검색입니다.
프로그램은 방향 키로 캐릭터 이동이 가능합니다.
Z키로 Z 값을 플러스 해···X키로 「Z값」을 마이너스 합니다
어디까지나 배열 테이블을 사용한 Z소트의 방법(보충)
새롭게 「스프라이트 구조체 포인터 배열」(구조체에의 포인터만을 모은 배열)을 만들어 둡니다.
프로그램 실행중에 「Z값」의 변경이 있었을 때 만 「스프라이트 구조체」에 데이터를 격납합니다.
「스프라이트 구조체」모두를 소트나 오름차순, 내림차순으로 「스프라이트 구조체 포인터 배열」의 테이블모두를 갱신합니다.
이미지의 묘화는 이 「스프라이트 구조체 포인터 배열」의 차례로 묘화 해 나갑니다.
소트는 배열의 내용을 순서에 맞추어 바꾸는 것을 말한다
이번 소스의 요점은「Z값을 스프라이트마다 갖게한다」를 알 수 있었다면 된다고 생각합니다.
다음 소스는 배열을 이용한 Z소트입니다.
//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////
Z소트이
전회에서 간단한(?) Z소트 같은 것을 했으므로 이번은 그것을 발전(?) 시켜
볼까하고 생각합니다.
목표는 스프라이트의 데이터를 메모리에 동적으로 생성해 그것을 체인으로 묶어
소트·묘화에 이용한다····입니다.
체인 리스트의 연습도 포함한다
체인 리스트체인 리스트의 개념을 모르는 분을 위해····
게임의 장르에 따라서는 화면에 표시되는 이미지의 순서가 정해져 있지 않은 경우가 있다고 합니다.
정해져 있어도 된다.
이 때, 이미지의 수만큼 그것을 관리하는 「좌표 변수」를 준비해야 합니다.
화면에 나타내는 이미지의 「최대수」의 변수를 「처음에 정의하면」····
즉···
int x[256];
int y[256];
라고 프로그램의 처음으로 필요한 최대양을 정의하는 것입니다.
위의 경우는 화면에 이미지 256개를 관리할 수 있는 좌표를 정의한 것이 되는군요.
그러나 화면에 이미지가 하나 밖에 표시되어 있지 않은 것을 생각해 보세요!
이 경우····256개의 관리용 변수를 보관 유지하는 메모리 영역중···
1개 밖에 사용하지 않기 때문에 나머지의 255개 분의 메모리 영역은 「낭비」가 되고 있습니다.
거기서····필요한 양만큼 그때 그때 생성해 거기에 데이터를 보관하자고 하는 것이
체인 리스트입니다.
이것이라면 필요한 양만큼 메모리를 소비하므로 낭비가 없습니다.
어느 함수(나중에 나올 함수)에서 메모리에 필요한 사이즈의 영역을 생성합니다.
(프로그램 루프내에서)
이 때 생성된 변수·구조체등의 메모리 위치는 어디가 될까 결정하는 일은 기본적으로
불가능해서 컴퓨터가 자동으로 적절한 위치에 영역을 확보해 줍니다.
위 그림과 같이 메모리에 뿔뿔이 흩어지게 데이터가 배치됩니다.
이 상태로는 필요한 데이터의 억세스를 할 수가 없습니다····거기서 동적으로 생성된
데이타에 「다음의 요소와 전의 요소」의 「주소(address)」를 가지는 변수를 갖게합니다.
메모리상의 체인의 개념은 그림에 나타난 대로
앞의 요소와 다음의 요소의 주소를 가져 오는 것으로 「체인(쇠사슬)」과 같이 연결되어 갑니다.
주소로 연결하므로 메모리상의 위치는 관계없다!
또 요소의 추가 삭제도 체인을 연결해서 바꾸는 것으로 간단하게 할 수 있고 테이블 처리에서는 하기 어려운(처리가 무거워진다) 「도중에 추가·삭제」등도 간단하게 고속으로 실시할 수 있습니다.
아무튼 주소의 앞을 변경하는 것만으로 가능하기때문에.
덧붙여서 체인 리스트는 「노벨 게임」이나 RPG에서의 이벤트 메세지 처리에
사용하면 좋은 알고리즘이라고 할 수 있습니다.
개념적인 물건
우선은 스프라이트 데이터의 구조체를 정의합니다.
이 구조체를 동적으로 생성해 이 구조체에 격납되고 있는 이미지를 묘화하도록 합니다.
우선 필요한 것은 「앞의 요소」와「다음의 요소」의 포인터 변수입니다.
샘플은 양방향의 체인이므로 이 두 개의 포인터가 필수입니다.
「체인 리스트」의 설명을 보면 알 수 있다고 생각합니다.
위의 포인터는 필수로서···나머지의 필요한 것을 생각하면.
이미지가 나오므로 「서페이스의 포인터」나 「표시를 나타내는 RECT 구조체」등으로
하나의 스프라이트 데이터 구조체로 하면 좋다고 생각합니다.
Z심도
깊이를 표현하므로 당연히 「Z심도」를 나타내는 「값」이 필요하게 됩니다.
이 데이터를 「스프라이트 구조체」마다 갖게하면····
위와 같이 됩니다.
일반적인 체인이라고 할 수 있습니다.
그리고 이미지 표시가 필요하게 되면 리스트에 모두 추가합니다.
추가·삭제 위치에 대해서는 검색 처리를 해야 합니다.
데이터는 내림차순에 줄지어 있으므로 「차례차례 검색」만으로
아닌 「순서대로의 검색」을 효율적이라고 생각됩니다.
메모리의 동적 생성의 방법
C언어 베이스라면 「malloc 함수」로 C++베이스라면 「new」가 있습니다.
여기는 C언어 베이스이므로···「malloc 함수」에 대해.
우선, 이 함수를 사용하려면 , stdlib.h 와 malloc.h를 인클루드해야 합니다.
void *malloc( size_t size );
이 함수의 파라미터는 1개로 「어느 메모리에 확보할까?」입니다.
리턴 값은 확보한 영역의 맨 앞의 주소입니다.
확보에 실패했을 경우는 NULL이 리턴 됩니다.
사용하는 경우는 구조체의 바이트 수를 판단하는 함수 「sizeof」등으로 병용해서 동적으로 확보합니다.
체인 리스트의 포인터 = malloc(sizeof(SpriteStruct));
영역이 불필요하게 되었을 경우는 프로그램쪽에서 명시적으로 해방 처리를 실시하지 않으면
메모리에 남아 있습니다.
「free 함수」에 의해 확보한 영역을 개방합니다.
void free( void *memblock );
파라미터는 해방하고 싶은 주소를 건네줍니다.
malloc로 확보한 선두 주소를 건네줍니다.
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
서페이스의 조작
이번은 「서페이스의 조작」을 해 봅시다.
간단하게 이미지가 보관되고 있는 메모리를 마구 주무른다는 의미입니다.
1회로서 「Blt」를 사용하지 않는 이미지의 묘화를 해 봅시다.
이「서페이스의 조작」의 대표적으로「반투명 처리」등을 들 수 있습니다.
또, 「선」을 긋거나 「점」을 묘화 한다면 꽤 좋은 방법이 아닐까요?
개념부터 우선 방법은 다음에 소개한다고 하고 개념부터 설명합니다.
우선, 이미지가 보관 유지되고 있는 메모리 영역을 DirectX에서는 「서페이스」라고
부르고 있었습니다.
무엇인가 어려울 것 같지만, 결국 메모리에 「이미지의 정보」가 격납되고 있는 것일뿐
입니다.
16비트 칼라 이상으로 설명합니다.
예를 들면 16비트 칼라는 이미지의 1 도트를 「16비트」로 표현하고 있습니다.
「2바이트」군요. (8비트=1바이트)
메모리의 「2바이트」에는 「R · G · B · 그 외(?)」의 정보가 격납되고
다음의 번지의 메모리에는 다음 도트의 정보가 격납되고 있습니다.
프로그램에서 어느 번지의 칼라정보의 처리(갱신등)가 어떻게 될까요?
당연히 서페이스의 이미지 그 자체가 변화됩니다.
반투명의 처리를 실시한다면 「전송할 곳의 이미지의 1 도트」 「전송해올 곳 이미지의 1 도트」의 칼라정보를 비교해 「전송할 곳의 메모리를 다시 기입하면」반투명 처리의 완성입니다.
(반투명도의 정도에 의해 계산식도 바뀔 것이다)
여기에서 「전송해올 곳의 서페이스」의 이미지를 「Blt 메소드」를 사용하지 않고
「전송할 곳」에 묘화 합니다.
흐름은 위의 같습니다.
아주 단순하게「전송해올 곳」의 정보를 읽어서···그것을 「전송할 곳」에 보내면 됩니다.
그럼 그 방법이란!
서페이스를 직접 조작하려면?
사실 이 기능을 DirectDraw는 가지고 있습니다.
간단히 말하면, 메모리라는 것을 그다지 의식하지 않아도 가능하다고 할 수 있습니다.
서페이스의 록
서페이스에 직접 억세스를 하려면 「DirectDraw 오브젝트」의 「Lock 메소드」
를 사용해서 서페이스를 「록」합니다.
잠그는 것으로 다른 어플리케이션이 「서페이스」에의 억세스가 불가능하게 되어
자신의 어플리케이션은 서페이스에 억세스 할 수 있도록 됩니다.
(메모리내의 데이타의 정합성을 유지하기 위해 록을 실시하겠지요.)
HRESULT Lock(
LPRECT lpDestRect,
LPDDSURFACEDESC lpDDSurfaceDesc,
DWORD dwFlags,
HANDLE hEvent
);
록 메소드는 위에 있는대로입니다.
여기서 지정하는 서페이스의 일부 영역(또는 전역)을 잠글 수 있습니다.
제1 파라미터:
이것은 잠그는 영역을 지정하는 RECT 구조체입니다.
서페이스의 내용을 변경하고 싶은 영역을 미리 RECT 구조체에 Set 하고 나서
건네줍니다.「null」를 건네주면 서페이스 전역이라는 의미가 됩니다.
제2 파라미터:
록하는 서페이스의 정보를 격납하는 구조체 「LPDDSURFACEDESC」입니다.
위의 설명 대로 록한 후 서페이스의 정보가 격납됩니다.
lpSurface 서페이스의 메모리상의 주소
dwWidth, dwHeight 서페이스의 폭과 높이
lPitch 서페이스의 가로라인의 바이트의 폭입니다.(중요)
ddpfPixelFormat 픽셀 포맷(이하 설명)
라고 위와 같은 멤버를 가지는 구조체에 데이타가 주어집니다.
제3 파라미터:
잠글 때의 동작을 결정하는 플래그, 이하의 플래그가 있습니다.
DDLOCK_EVENT
현재, 이 플래그는 실장되지 않았다.
DDLOCK_NOSYSLOCK
가능하면 Win16Lock 를 사용하지 않는다. 프라이머리 표면을 잠글 때 이 플래그는 무시된다.
DDLOCK_READONLY
잠그는 표면은 읽어내기 전용인 것을 나타내는 플래그.
DDLOCK_SURFACEMEMORYPTR
지정한 범위의 선두의 유효한 메모리포인터를 돌려주지 않으면 안 되는 것을 나타내는 플래그.
범위가 지정되지 않는 경우, 맨 위의 표면의 포인터가 리턴된다.
디폴트로는 이 플래그를 지정한다.
DDLOCK_WAIT
블록 전송 처리중이어서 록을 획득할 수 없는 경우, 이 메소드는 록을 획득할 수 있는지
또는 DDERR_SURFACEBUSY 등의 다른 에러가 생길 때까지 시도한다.
DDLOCK_WRITEONLY
잠그는 표면은 기입 전용인 것을 나타낸다.
제4 파라미터:
항상 NULL
프로그램은 「Blt」를 사용하지 않고 이미지 전송을 실시합니다.
「캐릭터」와「백 버퍼」를 잠그어 1 도트씩 메모리를 동일하게 해 갈 것입니다.
전송해올 곳의 서페이스는 32×32 도트입니다.
BYTE *pPixel; //픽셀(도트)의 데이타를 격납하는 주소를 얻는다
BYTE *pPixels; //스프라이트용 포인터
DDSURFACEDESC ddsd; //록할 때의 데이타를 격납하는 구조체
DDSURFACEDESC ddsds; //록할 때의 데이타를 격납하는 구조체
ZeroMemory(&ddsd, sizeof(ddsd)); //데이터의 보관, 유지구조체를 제로 클리어
ddsd.dwSize = sizeof(ddsd); //구조체의 사이즈를 건네준다
ZeroMemory(&ddsds, sizeof(ddsds)); //데이터의 보관, 유지 구조체를 제로 클리어
ddsds.dwSize = sizeof(ddsds); //구조체의 사이즈를 건네준다
if(BackSurface->Lock(NULL, &ddsd, DDLOCK_WAIT| DDLOCK_SURFACEMEMORYPTR, NULL) == DD_OK)
{
if(sprite->Lock(NULL, &ddsds, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR , NULL) == DD_OK)
{
pPixel = (BYTE*) ddsd.lpSurface; //주소를 취득(잠근 백 버퍼의)
pPixels = (BYTE*) ddsds.lpSurface;
//주소의취득(잠근 오프서페이스의)
for(int y=0;y<32;y++){
for(int x=0;x<32;x++){
pPixel[y*ddsd.dwWidth+x] =
pPixels[(y*ddsds.dwWidth) +x];
}
}
}
else
return FALSE;
}
else
return FALSE;
//서페이스의 록을 푼다
if(BackSurface->Unlock(NULL)!=DD_OK)
return FALSE;
if(sprite->Unlock(NULL)!=DD_OK)
return FALSE;
위의 처리는 백 버퍼와 스프라이트 서페이스를 록해서 스프라이트의 메모리의 내용을 백 버퍼의 메모리 공간에 복사하는 처리입니다.
BYTE형 변수에 서페이스의 주소를 격납해···
pPixel = (BYTE*) ddsd.lpSurface; //주소를 취득(잠근 백 버퍼의)
pPixels = (BYTE*) ddsds.lpSurface;
//주소의 취득(잠근 오프서페이스의)
메모리 공간은 연속하는 1 차원적인 메모리이므로····
for(int y=0;y<32;y++)
{
for(int x=0;x<32;x++)
{
pPixel[y*ddsd.dwWidth+x] =
pPixels[(y*ddsds.dwWidth) +x];
}
}
라고 하는 식으로 임의의 도트에 억세스 하는 것이 가능합니다.
예)
pPixel[10]
라면, 잠근 영역의 선두 도트부터 10 도트째의 주소를 가리킵니다.
pPixel[y*ddsd.dwWidth+x]
라고 소스는 기술하고 있습니다.
「dwWidth 멤버」는 서페이스의 가로 도트수를 가지고 있습니다.
y × 서페이스 가로 도트수 + x
로 함으로 임의의 사이즈를 분명하게 전송 할 수 있습니다.
for(int y=0;y<32;y++)
{
for(int x=0;x<32;x++)
{
덧붙여서 루프가 위와 같이 되고 있는 것은 스프라이트 서페이스 쪽의 사이즈가 32×32인 것과 전송 하고 싶은 사이즈가 32×32이기 때문입니다.
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
반투명 처리
「반투명」이라고 하는 묘화 처리는 「서페이스록」을 서페이스 메모리를
자유롭게 조작하는 일로 가능하게 됩니다.
이 장은 전장의 응용입니다.
게임에서는 「실체가 없는 · 숨어 있는 케렉터등」을 표현할 때에 사용될 것 같습니다.
예를 들면, 유령이라든지·····캐릭터의 잔상등으로서 사용되고 있네요.
알고리즘
그럼 반투명은 어떤 알고리즘일까?
서페이스의 록이 되고 비디오메모리의 주소의 내용(픽셀 데이타)을
취득할 수가 있으면 간단합니다.
배경이 되는 「맵서페이스」의 「픽셀 데이타」와
거기에 묘화되는 「켁릭터서페이스」의 「픽셀 데이타」를 얻습니다.
위의 그림과 같이 「반투명 묘화에 필요한 영역」의 픽셀 데이타를 취득합니다.
아마 「RGB」라고 하는 데이타가 그 주소에 격납되어 있을 것입니다.
(256 이하의 칼라의 화상을 제외)
취득한 「2개(이상)의 RGB 데이타」를 바탕으로 「계산」을 행한
계산 결과의 「RGB 데이타」를 전송할 서페이스의 주소에 전송합니다.
50% 50%인 반투명
이것은 가장 간단한 계산이므로 이것을 소개합니다.
「배경의 칼라」 「캐릭터의 칼라」 양쪽 모두 50%의 칼라를 화면에 표현하는 것입니다.
어느 픽셀 데이타의 내용이 이하와 같았다고 합시다.
RGB: 125 50 40 배경쪽
RGB: 10 80 255 캐릭터쪽
이 두 개의 데이타를 단순하게 플러스합니다.
RGB: 135 130 255
오버 플러그를 조심!
그리고 「2」로 나누어 봅시다.
RGB: 67 65 127
결과, 칼라는 이와 같이 되었습니다.
응~이럴꺼라 생각합니다 분명!
프로그램에서는 「50%브랜드」인 반투명 처리를 하고 있습니다.
처리 코드 소개
처리로 우선 주의해야 할 것은 칼라 모드에 의해 픽셀 포맷이 다른 것입니다.
16칼라의 경우····256칼라의 경우, 16비트 칼라의 경우, 32비트의 경우등
(요즘24비트는 잘 사용이 안 하므로 생략합니다. 3바이트이고 복잡한 처리가 된다)
칼라 모드에 의해 포맷이 다르므로 각각의 모드에 대해서 코드가 필요하게 됩니다.
그 때문에 우선 실시해야 하는 것은 「서페이스」의 「픽셀 포맷」을 취득하는 것입니다.
「픽셀 포맷」을 얻으려면 우선 DDPIXELFORMAT 구조체를 정의합니다.
이 구조체는 DirectDrawSurface의 「GetPixelFormat 메소드」를 사용해서 「픽셀 포맷 정보」를 얻을 때에 필요한 구조체입니다.
(DDPIXELFORMAT 구조체에 픽셀 포맷 정보가 격납됩니다. )
DDPIXELFORMAT pixel;
pixel.dwSize=sizeof(pixel);
if(DrawSurface->GetPixelFormat(&pixel)! =DD_OK) return FALSE;
위의 코드와 같이 기술하면 「DrawSurface(서페이스)」의 픽셀 포맷을 얻을 수가 있습니다.
「DDPIXELFORMAT 구조체」의 「dwRGBBitCount 멤버」에는 서페이스의 칼라 모드 정보
가 격납됩니다.
(4, 8, 16, 24, 또는 32)의 값이 이 멤버에 격납됩니다.
우선 여기서 칼라 모드에 의한 반투과처리(알파블렌딩)의 처리의 나누기를 실시합니다.
if(pixel.dwRGBBitCount == 4) Alpha_4();
//16칼라일 때의 알파 브랜드 처리 함수
if(pixel.dwRGBBitCount == 8) Alpha_8();
//256칼라일 때의 알파 브랜드 처리 함수
if(pixel.dwRGBBitCount == 16) Alpha_16();
//16비트 칼라일 때의 알파 브랜드 처리 함수
if(pixel.dwRGBBitCount == 24) Alpha_24();
//24비트 칼라일 때의 알파 브랜드 처리 함수
if(pixel.dwRGBBitCount == 32) Alpha_32();
//32비트 칼라일 때의 알파 브랜드 처리 함수
이렇게 나누어 처리하는 것이 효율적입니다.
보충으로 256칼라의 경우는 칼라가 팔레트에 관리되기때문에 위에서 설명하고 있는 것 같은 픽셀의 칼라 정보를 얻어 그것을 계산한다···약간 방법이 다릅니다.
그럼, 칼라 모드에 의한 처리 나누기로 「16비트 모드 처리」로 진행되었다고 합시다.
1 픽셀(1 도트)을 표현하는데 「16비트」의 메모리를 사용해서.
그 중에 「R·G·B」의 요소가 격납되고 있는 것입니다.
알파 브랜드이기 때문에 계산하려면····16비트 안에서 「R·G·B」를 개별적으로 뽑아낼
필요가 있습니다.
그 때문에「마스크 프로세싱」이라고 하는 비트 단위가 있는 처리를 실시합니다.
거기에 필요한「마스크」를 「DDPIXELFORMAT 구조체」의 「lBBitMask」 「lGBitMask」 「lRBitMask」멤버는 가지고 있습니다.
마스크 프로세싱의 하나의 예
어느 서페이스의 픽셀 포맷이 이하의 그림대로였다고 합시다.
그 포맷인 픽셀이 이하의 같은 칼라 데이타를 가지고 있었다고 합니다.
이 때, G(green)의 칼라 값만을 얻으려면? 어떻게 하면 좋을까요?
역시 제일 좋다고 생각되는 것이 「마스크 프로세싱」입니다.
「마스크 프로세싱」은 비트 단위로 「AND」 「OR」 「XOR」등을 취해 임의의 위치의
비트만을 조작할 수가 있습니다.
이 경우,「있는 부분만을 남긴다」라는 처리이므로.
「GetPixelFormat 메소드」는 「G정보」의 비트 위에만 「1」로 「마스크」를
준비하고 있을 것입니다.
준비된 마스크 데이타와 칼라 데이타를 「AND」하면 「G데이타」만이 비트에 남습니다.
「AND」는 양쪽 모두의 비트가 「1」이었을 경우, 비트의 결과가「1」이 된다.
그 이외는 「0」(true, false의 개념)
Green의 칼라 데이타만 비트가 있습니다.
그 후의 처리로서 비트 쉬프트등을 실시하면 올바른 값으로 해서 칼라 값을 얻을 수가 있습니다.
DWORD Rmask;
DWORD Bmask;
DWORD Gmask;
Rmask=pixel.dwRBitMask;
Bmask=pixel.dwBBitMask;
Gmask=pixel.dwGBitMask;
마스크 데이타를 보관한다면 보관해 둡시다.
■처리에 필요한 변수를 준비한다
WORD *LpBg; //배경의 맵(백 버퍼)의 포인터
WORD *LpCha; //배경에 거듭하는 케릭터 서페이스의 포인터
이것은 서페이스를 「록」할 때(잠글 때)에 필요합니다.
픽셀의 칼라 정보를 얻기 위해도 대상 서페이스의 주소가 필요하기 때문입니다.
이 포인터에는 「록」할 때에 이미지 데이타의 선두 주소가 격납됩니다.
BYTE *Chatop;
BYTE *Bgtop;
샘플에서는 32×32의 범위를 알파 처리합니다.
그 「행」의 선두 픽셀의 주소를 보관하는 변수입니다.
DDSURFACEDESC ddsd; //록할 때의 데이타를 격납하는 구조체
DDSURFACEDESC ddsds; //록할 때의 데이타를 격납하는 구조체
서페이스를 잠그려면, 「Lock 메소드」를 사용합니다.
그 때, 파라메타로서 「DDSURFACEDESC」가 필요하게 되므로 준비합니다.
DWORD red_1, red_2, red_3
DWORD green_1, green_2, green_3;
DWORD blue_1, blue_2, blue_3;
비트열 계산에 사용하는 변수입니다.
***_1에는···배경쪽 픽셀의 칼라 정보의 비트열
***_2에는···묘화쪽 픽셀의 칼라 정보의 비트열
***_3에는···계산 처리 후의 칼라 정보 비트열을 격납 합니다.
처리
--------------------------------------------------------------------------------
A
if(BackSurface->Lock(NULL, &ddsd, DDLOCK_WAIT |
DDLOCK_SURFACEMEMORYPTR, NULL) == DD_OK)
{
if(sprite->Lock(NULL, &ddsds, DDLOCK_WAIT |
DDLOCK_SURFACEMEMORYPTR , NULL) == DD_OK)
{
//양서페이스를 잠근다
LpBg = (WORD*) ddsd.lpSurface;
//주소를 취득(잠근 백 버퍼)
LpCha = (WORD*) ddsds.lpSurface;
//주소를 취득(잠근 오프서페이스)
// Ppixel에는 잠근 서페이스의 좌상 첫번째의
// 도트 정보의 주소가 들어간다
for(int y=0;y<32;y++)
{
Chatop = (BYTE*) LpCha;
Bgtop = (BYTE*) LpBg;
for(int x=0;x<32;x++)
{
//캐릭터 사이즈가 32*32(루프)
//알파 브랜드 처리
red_1 = *LpBg & Rmask;
red_2 = *LpCha & Rmask;
red_3 = ((red_1 + red_2)/2);
blue_1 = *LpBg & Bmask;
blue_2 = *LpCha & Bmask;
blue_3 = ((blue_1 + blue_2)/2);
green_1 = *LpBg & Gmask;
green_2 = *LpCha & Gmask;
green_3 = ((green_1 + green_2)/2);
*LpBg = (WORD)((red_3 & Rmask)|
(green_3 & Gmask)|(blue_3 & Bmask));
LpBg++;
LpCha++;
//마지막
}
LpBg = (WORD*)(Bgtop+ddsd.lPitch);
LpCha = (WORD*)(Chatop+ddsds.lPitch);
}
}
else
return FALSE;
}
else
return FALSE;
//서페이스의 Unlock
if(BackSurface->Unlock(NULL)! =DD_OK) return FALSE;
if(sprite->Unlock(NULL)! =DD_OK) return FALSE;
알파 브랜드의 처리는 위에 있는 대로입니다.
서페이스를 잠그고 그 선두 픽셀의 주소를 얻는 부분은 전장과 같아서 생략합니다.
루프가 위와 같이 되어 있는 것은 32×32 픽셀의 케릭터를 알파 처리하기 때문입니다
for(int y=0;y<32;y++)
{
Chatop = (BYTE*) LpCha;
Bgtop = (BYTE*) LpBg;
우선 위의 처리입니다.
Y축의 루프가 처리의 종료 조건이 되어 있습니다.
1열 마다
Chatop = (BYTE*) LpCha;
Bgtop = (BYTE*) LpBg;
하는 처리를 반복하는 것이 됩니다.
루프에 들어가기 전에 LpCha·LpBg의 포인터 변수(WORD형)에는 서페이스를 록할 때의 선두
픽셀의 주소가 대입되어 있습니다.
루프 처리의 1회째에 「Chatop·Bgtop」에는 선두 픽셀 주소가 격납됩니다.
그 후 LpCha·LpBg는 「++연산자」로 다음 주소로 내용이 변화합니다.
「Chatop·Bgtop」에는 각 열의 선두 픽셀 주소가 대입되어 간다
픽셀의 반투명 처리
//알파 브랜드 처리
red_1 = *LpBg & Rmask;
red_2 = *LpCha & Rmask;
red_3 = ((red_1 + red_2)/2);
blue_1 = *LpBg & Bmask;
blue_2 = *LpCha & Bmask;
blue_3 = ((blue_1 + blue_2)/2);
green_1 = *LpBg & Gmask;
green_2 = *LpCha & Gmask;
green_3 = ((green_1 + green_2)/2);
루프내에서 위의 처리를 반복해 실시하도록 되어 있습니다.
이것이 반투명 처리의 중심이라고 할 수 있는 연산 처리입니다.
위에서도 설명하고 있는 것처럼, 마스크와 픽셀의 비트 열을 「AND」연산해서 칼라정보만을 뽑아내서 「***_*」의 변수로 유지하고 있는 것입니다.
***_1 의 처리에는 배경(백 버퍼)의 대응 픽셀의 칼라정보를 알파 처리
***_2 의 처리에서는 스프라이트(캐릭터)의 대응 픽셀의 칼라정보를 알파 처리
***_3 의 처리에서는 1과 2로 얻을 수 있던 칼라 데이터를 더해서 2로 나누고 있습니다.
2로 나누면 50%투명도의 알파 브랜드가 됩니다.
백 버퍼·스프라이트의 양서페이스의 픽셀칼라요소를 반반이라고 하는 연산 결과가 되기 때문입니다
*LpBg = (WORD)((red_3 & Rmask) | (green_3 & Gmask) | (blue_3 & Bmask));
LpBg++;
LpCha++;
이 처리는 R·G·B의 칼라의 요소마다 계산된 칼라데이타를 하나에 모으고
「LpBg」(백 버퍼에의 포인터)가 가리키는 주소에 데이타를 전송 합니다.
여기까지의 처리로 알파 브랜드의 1 도트의 처리가 끝난 것이 됩니다.
다음 도트에 처리를 옮기기 위해서 「LpBg · LpCha」의 포인터 변수에 「++」합니다.
이렇게 함으로 포인터 변수는 1 주소로 나아갑니다. (오른쪽의 도트에)
LpBg = (WORD*)(Bgtop + ddsd.lPitch);
LpCha = (WORD*)(Chatop + ddsds.lPitch);
}
이 처리는 일행(옆 32개)의 처리가 끝났을 때에 실행되는 처리입니다.
이것은 「LpBg · LpCha」포인터 변수에 다음의 행의 주소를 건네주고 있습니다.
이것이 없으면 사각형에 전송 되지 않습니다.
ddsd.lPitch에는 그 서페이스의 가로의 픽셀수가 격납되고 있습니다.
「Bgtop · Chatop」에 그 값을 더하면····결과, 주소는 다음의 행의 선두와 동일해집니다.
샘플에서는, 32×32의 스프라이트 이미지를 백 버퍼의 0, 0 좌표에 전송하고 있습니다.
투과 처리를 하지 않기 때문에 스프라이트의 배경도 반투명 되고 있습니다.
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
스프라이트 반투명 처리
전번의 반투명 처리에서는 캐릭터에서 투명하게 만들고 싶은 색을 반투명 처리했습니다.
이번에는 그 점을 해소해 「스프라이트용 반투명 묘화」를 합니다.
전번에는 위의 캐릭터 이미지의 핑크의 부분까지 묘화 되었습니다.
■알고리즘
기본적인 부분은 전번과 큰 차이는 없습니다.
추가 처리로서 서페이스의 포인터가 가리키는 픽셀의 색이 「핑크」라면
그 색을 알파 브랜드 처리의 대상으로 하지 않고, 「백 버퍼(배경)」의 색을
100%반영시킵니다.
「투과 시키는 값과 비교」하는 처리가 추가 되므로 이 쪽의 스프라이트 알파 브랜드가
약간 무거운 처리가 됩니다.
■투과 색의 칼라 값(비트 열)
이것을 결정하려면 다양하게 방법이 있습니다.
일반적인 것이 「Black」을 투과색으로 하는 방법입니다(물론 반듯이 Black일 필요는 없다).
그 이유는 아마도 「Black」의 칼라 값은 「00000000...」이므로 결정하는 것이 간단합니다.
이 경우는 당연히 스프라이트 그 자체에 「00000...Black을 사용할 수 없습니다」
이 소스에서는 전송하는 스프라이트의 범위로···제일 좌상 픽셀로 사용되고 있는 색을
마스크 칼라로 합니다.
이 경우는 캐릭터 이미지의 제일 좌상의 픽셀은 마스크 칼라여야 하는 제약이
따릅니다. ··하지만, 거기까지 케릭터 이미지가 될 일은 적기 때문에 우선 괜찮습니다.
■제일 좌상 픽셀의 칼라 값을 얻으려면?
이것은 간단합니다.
서페이스를 잠그고 「DDSURFACEDESC 구조체 lpSurface 멤버」의 주소의 내용을 취득합니다.
DDSURFACEDESC ddsd;
memset(&ddsd, 0, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
//서페이스를 잠급니다
surface->Lock(NULL, &ddsd, DDLOCK_WAIT |
DDLOCK_SURFACEMEMORYPTR, NULL);
char *p=(char *) ddsd.lpSurface; //좌상각의 색을 투과 색으로 합니다
DWORD transparent=*(DWORD *) p;
소스는 이렇게 되겠지요.
■처리의 흐름
LPBgp =(WORD*) ddsds.lpSurface;//좌상각의 색을 투과색으로 합니다
for(int y=0;y<32;y++)
{
Chatop = (BYTE*) LpCha;
Bgtop = (BYTE*) LpBg;
for(int x=0;x<32;x++){//캐릭터 사이즈가 32*32이므로 그 루프
if(*LPBgp != *LpCha)
{
//알파 브랜드 처리
red_1 = *LpBg & Rmask;
red_2 = *LpCha & Rmask;
red_3 = ((red_1 + red_2)/2);
blue_1 = *LpBg & Bmask;
blue_2 = *LpCha & Bmask;
blue_3 = ((blue_1 + blue_2)/2);
green_1 = *LpBg & Gmask;
green_2 = *LpCha & Gmask;
green_3 = ((green_1 + green_2)/2);
*LpBg = (WORD)((red_3 & Rmask) |
(green_3 & Gmask)|(blue_3 & Bmask));
}
LpBg++;
LpCha++;
//마지막
}
LpBg = (WORD*)(Bgtop+ddsd.lPitch);
LpCha = (WORD*)(Chatop+ddsds.lPitch);
}
적색 문자의 부분이 추가되었습니다.
if(transparent != *LpCha)
{
의 처리는 투과 하는 칼라 값(transparent)과 캐릭터의 픽셀 칼라 값을
비교해서 「같지 않을 때」if안을 처리합니다.
동일할 때는 그대로 포인터가 나갑니다.
결론적으로 백 버퍼는 아무것도 변화가 없고 그 부분은 투과 된 형태가 됩니다.
코드
내용은 전회의 소스의 알파 브랜드 함수와 아래의 처리를 바꿔 준다면 아무런 문제가 없을 것입니다.
//이 함수는 서페이스를 잠근(록) 메모리를 하나씩 참조하면서
//대상의 서페이스에 묘화 해 나간다.
//Blt를 사용하는 방법과 비교하면···스피드는 불명
BOOL LockBlt(void)
{
DWORD Rmask;//마스크 데이터
DWORD Bmask;
DWORD Gmask;
DDPIXELFORMAT pixel;
WORD *LpBg;//배경의 맵(백 버퍼)의 포인터
WORD *LpCha;//배경에 거듭하는 캐릭터 서페이스의 포인터
BYTE *Chatop;//투과 대상의 첫번째 픽셀의 주소
BYTE *Bgtop;
WORD *LPBgp;
DDSURFACEDESC2 ddsd; //lock할 때의 데이터를 격납하는 구조체
DDSURFACEDESC2 ddsds; //lock할 때의 데이터를 격납하는 구조체
DWORD red_1, red_2, red_3; //red
DWORD green_1, green_2, green_3; //green
DWORD blue_1, blue_2, blue_3; //blue
ZeroMemory(&ddsd, sizeof(ddsd)); //데이터 구조체를 제로 클리어
ddsd.dwSize = sizeof(ddsd); //구조체의 사이즈를 건네준다
ZeroMemory(&ddsds, sizeof(ddsds)); //데이터 구조체를 제로 클리어
ddsds.dwSize = sizeof(ddsds); //구조체의 사이즈를 건네준다
pixel.dwSize=sizeof(pixel);
if(sprite->GetPixelFormat(&pixel)!=DD_OK) return FALSE;
Rmask=pixel.dwRBitMask;
Bmask=pixel.dwBBitMask;
Gmask=pixel.dwGBitMask;
//초기 처리
if(pixel.dwRGBBitCount!=16) return false;
//칼라 모드가 16비트 이외일 때 처리를 중지
if(BackSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL) == DD_OK)
{
if(sprite->Lock(NULL, &ddsds, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR , NULL) == DD_OK)
{
//양서페이스를 잠근다
LpBg = (WORD*) ddsd.lpSurface;
//주소를 취득(잠근 백 버퍼)
LpCha = (WORD*) ddsds.lpSurface;
//주소를 취득(잠근 오프서페이스)
LPBgp =(WORD*) ddsds.lpSurface;
//좌상각의 색을 투과색으로
//DWORD *transparent=(DWORD *) p;
//Ppixel에는 잠근 서페이스의 좌상첫번째 도트의 정보에 주소가
// 들어간다
for(int y=0;y<32;y++)
{
Chatop = (BYTE*) LpCha;
Bgtop = (BYTE*) LpBg;
for(int x=0;x<32;x++)
{
//캐릭터 사이즈가 32*32이므로 그 루프
if(*LPBgp != *LpCha)
{
//알파 브랜드 처리
red_1 = *LpBg & Rmask;
red_2 = *LpCha & Rmask;
red_3 = ((red_1 + red_2)/2);
blue_1 = *LpBg & Bmask;
blue_2 = *LpCha & Bmask;
blue_3 = ((blue_1 + blue_2)/2);
green_1 = *LpBg & Gmask;
green_2 = *LpCha & Gmask;
green_3 = ((green_1 + green_2)/2);
*LpBg = (WORD)((red_3 & Rmask) | (green_3 & Gmask)|(blue_3 & Bmask));
}
LpBg++;
LpCha++;
}
LpBg = (WORD*)(Bgtop+ddsd.lPitch);
LpCha = (WORD*)(Chatop+ddsds.lPitch);
}
}
else
return FALSE;
}
else
return FALSE;
//서페이스의 Unlock
if(BackSurface->Unlock(NULL)!=DD_OK) return FALSE;
if(sprite->Unlock(NULL)!=DD_OK) return FALSE;
return TRUE;
}
윈도우 모드
윈도우 모드로 DirectX를 움직이려면 , 기반이 되는 윈도우의 정의도
변경해야 합니다.
지금까지 프로그램은 풀 스크린에 적절한 형태의 윈도우 정의가 되어 있었으므로
이번에는 윈도우 모드에 적절한 윈도우 정의를 합니다.
hwnd = CreateWindowEx(
WS_EX_TOPMOST,
"DirectX GamePrograming",
"DirectX GamePrograming",
WS_POPUP,//윈도우 스타일
0,//윈도우 X좌표 시점
0,//윈도우 Y좌표 시점
GetSystemMetrics(SM_CXSCREEN),//윈도우 사이즈 X
GetSystemMetrics(SM_CXSCREEN),//윈도우 사이즈 Y
(HWND) NULL,
(HMENU) NULL,
hInstance,
(LPVOID) NULL);
위는 윈도우를 생성하는 처리의 부분입니다.
여기서 윈도우 핸들 「HWND」에 윈도우의 ID를 건네받는 부분도
변경해야 합니다.(위의 코드는 풀 스크린에 적절한 형태)
제4 파라미터를 이하와 같이 변경합니다.
WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION,
이것은 「메뉴」 「테두리」 「윈도우 기본 기능」등을 추가하는 플래그입니다.
(플래그의 의미는 헬프 참조)
다음으로 변경 해야 할 것은 제7, 8 파라미터입니다.
이것은 생성하는 윈도우의 사이즈에 대한 파라미터입니다.
윈도우 모드에서는 「테두리」「메뉴」등을 가지므로 기본 사이즈에 이 「테두리」등
의 사이즈를 플러스해야 합니다.
(윈도우 모드에서는 그 때문에 조금 커진다(클라이언트는 같은 사이즈))
시스템의 폭을 조사하려면 「GetSystemMetrics 함수」를 사용합니다.
테두리등은 경우에 따라서 폭이 바뀔 수 있으므로 위의 함수를 사용해 그 사이즈를 취득하지 않으면 올바른 값을 취득할 수 없습니다.
int GetSystemMetrics(
int nIndex // system metric or configuration setting to retrieve
);
파라미터는 어느 시스템의 폭을 리턴할까? 의 플래그로 리턴 값은 INT형입니다.
SM_CXFIXEDFRAM 범위 사이즈(다른 한쪽)
SM_CYCAPTION 타이틀 바의 높이
SM_CYMENU 1행의 메뉴의 높이
위는 플래그의 하나의 예입니다.
함수를 사용해서 게임 화면 사이즈+시스템 폭을 윈도우 생성의 사이즈로 합니다.
640+GetSystemMetrics(SM_CXFIXEDFRAME)*2;//가로의 시스템폭
480+GetSystemMetrics(SM_CYCAPTION) +GetSystemMetrics(SM_CYFIXEDFRAME)*2;//세로
(이 값에서 메뉴의 높이는 포함되지 않는다)
이 변경으로 윈도우 모드의 생성은 종료입니다.
윈도우 모드의 제한
풀 스크린과 다르다
풀 스크린 모드에서는 화면을 독점하지만 윈도우 모드에서 그것은 불가능합니다.
윈도우 모드에서는 다른 어플리케이션과 공존하지 않으면 안 되기 때문에
제약이 발생합니다.
①:팔레트의 제한
해상도가 「256칼라」일 경우, 윈도우 모드로 사용할 수 있는 팔레트가 한정됩니다.
이 경우, 팔레트의 「0~9번」 「246~255번」은 윈도우 시스템이 사용하는
시스템 칼라로서 등록되어 있으므로 이 팔레트를 변경하면 WINDOWS 자신의 색이
이상하게 되어 버립니다.
②:플립을 할 수 없다
풀 스크린 모드에서는 attach 된 백 버퍼와 플립으로 묘화 하고 있었지만
윈도우 모드에서는 attach 된 백 버퍼를 만들 수가 없습니다.
윈도우 모드에서 묘화는 attach되어 있지 않은 서페이스에서 프라이머리에 전송이 묘화
처리가 됩니다.
③:프라이머리서페이스
프라이머리서페이스는 디스플레이 전체가 됩니다.
묘화 좌표도 화면 좌표 단위가 됩니다.
(초기설정 한 클라이언트 이외에도 묘화 가능하게 된다.)
보통 게임에서는 이것을 고려해 묘화해야 합니다.
Draw 초기화
if(dd->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN)!=DD_OK)
return FALSE; //윈도우의 설정을 변경
Draw 초기화의 하나···협조를 하고 있는 곳도 변경해야 합니다.
제2 파라미터가 「DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN」가 되어 있습니다.
이것은 「배타 또는 풀 스크린」이라는 플래그입니다.
이것을 「DDSCL_NORMAL」로 변경합니다.
이렇게 함으로 협조 레벨이 「윈도우 모드」가 됩니다.
SURFACE 초기화
서페이스의 파라미터도 풀 스크린과 윈도우 모드에서는 달라집니다.
DDSURFACEDESC2 ddsd; //DDSURFACEDESC 구조체
memset((VOID *) &ddsd, 0, sizeof(ddsd));
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS|DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE|DDSCAPS_COMPLEX|DDSCAPS_FLIP;
ddsd.dwBackBufferCount=1;
위에 있는 부분이 프라이머리 서페이스를 설정하는 행입니다.
적색 문자의 부분은 「윈도우 모드」에서는 필요없는 부분입니다.
윈도우 모드는 attach 할 수 있는 백 버퍼를 가질 수 없기 때문에
플립을 할 수가 없다····이것을 고려하면 위의 플래그를 지우는 이유를 이해할 수 있다고 생각합니다
ddsd.dwSize=sizeof(ddsd);
ddsd.dwFlags=DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
ddsd.ddsCaps.dwCaps=DDSCAPS_OFFSCREENPLAIN;
ddsd.dwWidth = 640;
ddsd.dwHeight = 480;
dd->CreateSurface(&ddsd, &BackSurface, NULL);
다음은 변경되는 백 버퍼의 서페이스를 생성합니다.
여기는 보통의 서페이스를 만듭니다.
윈도우 모드에서의 묘화
윈도우 모드에서는 attach 된 백 버퍼를 가질 수가 없기 때문에
플립 메소드에 의한 묘화를 할 수 없습니다.
거기서 윈도우 모드일 때는 클라이언트 영역과 같은 사이즈의 서페이스를 생성해서
묘화 해 묘화가 끝나면 그 서페이스의 내용과 프라이머리 서페이스로 전송 하는
방법으로 묘화를 합니다.
윈도우 모드에서의 묘화의 흐름은 위에 그림에 나타난 대로 백 버퍼(플립 불가)에서 「Blt」를 합니다.
그 전송할 곳의 좌표는 유저의 손에 의해 항상 변경 가능한 것이 윈도우 모드입니다.
그 때문에 항상 올바른 위치의 윈도우 좌표를 취득해 거기에 「Blt」해야 합니다.
윈도우가 움직였다고 하는 메세지 처리를 추가합니다.
윈도우가 움직이면 「WM_MOVE 메세지」가 발행되므로 그 처리에
현재의 윈도우의 구형 위치를 얻는 처리를 합니다.
「Blt」는 이것으로 구할 수 있던 값을 바탕으로 묘화 합니다.
우선 「WM_MOVE 메세지」로 윈도우의 값을 얻는 처리(함수)를
생각해 봅시다.
이라고 할까, 회답이 이하입니다
void GetClientRect(void)
{
POINT clientpos={0,0};//클라이언트 포지션
ClientToScreen(hwnd, &clientpos);
//윈도우의 좌상단 좌표를 화면 좌표로 한다
//ScreenClientRect는 글로벌의 RECT 구조체
ScreenClientRect.left=clientpos.x;//시점 X좌표
ScreenClientRect.top=clientpos.y;//시점 Y좌표
ScreenClientRect.right=clientpos.x + _ScreenXsize;
//종점 X좌표
ScreenClientRect.bottom=clientpos.y + _ScreenYsize;
//종점 Y좌표
// _ 앞부분에 셋팅되어 있는 매크로입니다.
//시점에 윈도우 사이즈의 매크로를 추가함으로
// 클라이언트의 크기를 알 수 있습니다.
}
그럼 처리의 설명을 합니다.
「ClientToScreen 함수」는 지정된 윈도우 핸들(HWND)의 윈도우의 클라이언트
좌표의 값을 화면의 좌표 값으로 변경해 줍니다.
이 경우 클라이언트 값 「clientpos」는 화면의 「0, 0」를 이 함수에 건네주어
결과, 「clientpos」에는 스크린(디스플레이의)의 좌표가 격납됩니다.
위의 샘플 코드의 「ScreenClientRect」는 RECT 구조체로 「Blt」할 때의
표시할 곳의 값을 격납하는 글로벌 구조체입니다.
그 구조체에서 얻을 수 있던 화면 좌표를 바탕으로 클라이언트의 값(rect)을 건네줍니다.
시작점은 「ClientToScreen 함수」로 얻는 값과 같습니다.
끝점은 얻은 값+클라이언트의 사이즈로 얻을 수가 있습니다.
묘화는 이렇게 된다
윈도우 모드에서는 「플립」을 할 수 없기 때문에.
소스코드의 「Flip」함수(화면 반영 처리)는 위의 값(rect)으로 「Blt」됩니다.
void flip(void)
{
//MainSurface->Flip(NULL, DDFLIP_WAIT);
//위는 풀 스크린 모드에서만 유효
//윈도우 모드에서는 blt에 의한 전송 묘화를 실시한다
MainSurface->Blt(&ScreenClientRect, BackSurface, NULL, DDBLTFAST_WAIT, NULL);
}
플립 불가능한 백 버퍼로부터 「Blt」를 사용하고 전송합니다.
전송할 곳의 rect에는 방금 전 위에서 설명한 RECT 구조체를 지정합니다.(제1 파라미터)
유저가 윈도우의 위치를 변경하면 묘화할 곳의 좌표도 바뀝니다
프라이머리 서페이스의 클립
스크린 전체가 「프라이머리 서페이스」가 되므로 윈도우 모드의 DirectDraw는
자신의 클라이언트 영역 외에도 묘화가 가능합니다.
(miss는 화면 전체를 묘화 해 버려 대단한 일이 되었습니다. . .)
이것을 막기 위해 「윈도우에 클립」이 필요합니다.
이것을 하지 않으면 화면이 이상하게 되어 버린다.
dd->CreateClipper(0, &LpClip, NULL);
if(LpClip->SetHWnd( 0, hwnd )!= DD_OK)
return FALSE;
if(MainSurface->SetClipper(LpClip) !=DD_OK)
return FALSE;//서페이스에 클립 세트
여기서 클리퍼를 설정하는 것이 「프라이머리서페이스」라고 하는 것에 주의하십시오.
물론, 백서페이스에도 필요하겠지만. . .
윈도우 모드의 경우, 프라이머리 서페이스의 클리핑은 반듯이 필요합니다.
백 버퍼의 클립
실제로 묘화를 실시하는 백 버퍼에도 클립을 Set하지 않으면 화면끝에서 캐릭터가 사라지는 현상이 되어 버립니다.
그러나 프라이머리 서페이스에 Set함으로···
if(LpClip->SetHWnd( 0, hwnd )!= DD_OK) return FALSE;
이러한 처리는 불가능합니다.
「SetHWnd 메소드」는 지정한 윈도우 핸들에 클립 영역을 Set함으로
백 버퍼는 관계가 없는 단순한 메모리 영역에 지나지 않기 때문입니다.
「GetHWnd 메소드」로 클립 영역을 세트 하면, 윈도우의 사이즈와 같은 크기의
클립 영역이 화면 좌표「0, 0」로부터 Set됩니다.
이 경우, 윈도우의 위치를 「0, 0」로 하면 정상적으로 동작하고 있는 것처럼 보입니다만
윈도우를 움직이면 안되게 됩니다.
이것을 해결하려면 백서페이스에 묘화 할 때 자작의 클립 처리를 만들까····
적당한 방법으로.
if(LpClip->SetHWnd( 0, hwnd )!= DD_OK) return FALSE;
그리고 클립을 세트 하면 윈도우 핸들이 가리키는 윈도우의 사이즈가
스크린의 「0, 0」로부터 세트 되기 때문에····
윈도우 모드에서는 화면의 사이즈와 같은 크기의 클립 영역을 세트 합니다.
typedef struct{
RGNDATAHEADER rdh;
RECT rect[256];
}MYRGNDATA;
MYRGNDATA rgn;
rgn.rdh.dwSize = sizeof(RGNDATAHEADER);
rgn.rdh.iType = RDH_RECTANGLES;
rgn.rdh.nCount = 1;
rgn.rdh.nRgnSize = sizeof(RECT);
rgn.rdh.rcBound.left = 0;
rgn.rdh.rcBound.top = 0;
rgn.rdh.rcBound.right = GetSystemMetrics(SM_CXSCREEN);
rgn.rdh.rcBound.bottom = GetSystemMetrics(SM_CYSCREEN);
rgn.rect[0].left = 0;
rgn.rect[0].top = 0;
rgn.rect[0].right = GetSystemMetrics(SM_CXSCREEN);
rgn.rect[0].bottom = GetSystemMetrics(SM_CYSCREEN);
if(dd->CreateClipper(0, &LpClipBack, NULL) != DD_OK)
return FALSE;
LpClipBack->SetClipList((RGNDATA*) &rgn, 0);
if(BackSurface->SetClipper(LpClipBack) !=DD_OK)
return FALSE;//서페이스에 클립 세트
위는 화면 전체를 클립 영역에 세트 하는 처리입니다.
세트 하는 영역을 격납하는 처리가 대부분입니다.
또, 이 처리를 때문에 또 하나의 클립 오브젝트를 필요로 합니다.
「LpClipBack」새로운 클리퍼 오브젝트 포인터를 정의합니다.
「SetClipList 메소드」는 MYRGNDATA 구조체에 건네주고 있는 값을 클리퍼 오브젝트
에 Set합니다.
이 후, 「SetClipper」를 하면 설정이 됩니다.
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
윈도우·풀 스크린 양모드 대응
시판되고 있는 WINDOWS 게임은 풀 스크린, 윈도우 모드의 양쪽 모두를
대응하고 있는 게임이 많습니다.
이번은 프로그램을 양 대응으로 설정해 봅니다.
양대응의 프로그램은 유저의 모드 변환을 항상 검색해 모드변환이 발생하면
윈도우 클래스를 재정의해서 묘화(플립)를 변경합니다.
엔터키를 누르면 스크린 모드가 변경됩니다.
묘화 계통을 변경한다
처음의 변경할 곳은「플립」····묘화 처리의 부분입니다.
풀 스크린 모드에서는 「플립」은 가능하지만····윈도우 모드에서는
「플립」을 할 수가 없습니다.
때문에 윈도우 모드일 때, 풀 스크린 모드일 때로.
디스플레이에 묘화를 반영하고 있던 처리를 나눠야 합니다.
MainSurface->Flip(NULL, DDFLIP_WAIT);
//플립은 풀 스크린 모드일 때 밖에 실행할 수 없다
MainSurface->Blt(NULL, BackSurface, NULL, DDBLTFAST_WAIT, NULL);
//윈도우 모드에서는 blt에 의한 전송 묘화를 한다
묘화에는 모드에 의해 위의 2가지 방법이 있습니다.
위의 방법을 현재의 모드에 의해 분기 하려면 현재의 협조 레벨을 조사하는 것으로 가능합니다.
if(dd->SetCooperativeLevel(hwnd, ncoop)!=DD_OK)
return FALSE; //협조
위가 협조레벨을 Set하고 있는 부분입니다.
제2 파라미터는 협조 플래그를 건네줍니다.
이 부분을 변수로 할 필요가 있습니다.
(모드를 변환할 때는 이 변수를 바꾸어 협조 레벨을 설정을 다시 하기 위해서 사용한다)
DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN
위의 코드는 풀 스크린의 플래그입니다.
특히 강조 문자의 「DDSCL_FULLSCREEN」은 반드시 풀 스크린 모드일 때 필요하게 됩니다.
····로 「ncoop 변수(WORD형)」가 「DDSCL_FULLSCREEN」일때
풀 스크린 모드라는 것을 알수 있습니다.
「ncoop 변수」에 「DDSCL_FULLSCREEN」가 없을 때는 윈도우 모드가 됩니다.
if(ncoop&DDSCL_FULLSCREEN)
{
if( MainSurface->Flip(NULL, DDFLIP_WAIT) != DD_OK)
return FALSE;
}
else
{
if( MainSurface->Blt(&rect, BackSurface, NULL, DDBLT_WAIT, NULL) != DD_OK)
return FALSE;
}
묘화 처리는 위처럼 됩니다.
「ncoop 변수」를 「DDSCL_FULLSCREEN 플래그」와「AND 처리(&연산자)」하는 것으로
「ncoop 변수」안에 「DDSCL_FULLSCREEN 플래그」가 포함되는지를 체크합니다.
포함되는 경우는 현재의 모드가 「풀 스크린 모드」가 되므로
「Flip 메소드」에 의한 묘화를 합니다.
서페이스 파괴에 대해
풀 스크린 모드에서 윈도우 모드로 변경할 때, 메모리상에 있는 서페이스는 모두
삭제됩니다.(반대의 경우도 같다)
풀 스크린은 비디오 메모리를 독점하고 있는 모드지만, 윈도우 모드는 모든 어플리케이션이 비디오메모리를 공유하고 있습니다.
풀 스크린으로부터 윈도우 모드로 변경할 때, 풀 스크린 모드가
사용하고 있던 비디오메모리를 시스템 복귀를 위해 해방해야 하는 것입니다.
서페이스가 파괴되는(내용이 소실된다) 것으로 화면 모드 변경 후 서페이스를
다시 생성할 필요가 있습니다.
개념)
핑크색의 크기의 메모리 공간이 필요하게 됩니다.(장소는 불명)
블루의 이미지를 격납하고 있던 공간은 방해가 되므로 해방합니다.
서페이스 복귀
서페이스의 내용···이미지는 파괴되지만, 서페이스의 인터페이스는 아직 존재하고 있습니다.
화면 모드의 변환이 종료되어 어플리케이션의 처리가 개시되면 서페이스 메소드 「Restore」를 사용함으로 없어진 서페이스 영역을 다시 확보해 줍니다.
그러나 메모리 영역을 확보해 줄 뿐이므로 영역에 다시 묘화 처리를 실시할 필요가 있습니다.
「Restore 메소드」는 생성한 모든 서페이스에서 사용해야 합니다.
서페이스로스트의 검출
서페이스 파괴는 스크린 모드를 변경했을 때에도 발생하지만 다른 원인에서도 발생합니다.
「Blt 메소드」를 사용했을 때 「Blt 메소드」가 리턴 값으로 해서 「DDERR_SURFACELOST」를 돌려주었을 때, 서페이스가 파괴된 것을 의미합니다.
(헬프 참조)
「DDERR_SURFACELOST」가 리턴 됐다·····즉, 디스플레이 모드가 변경되었는지
하등의 이유로 서페이스가 파괴(로스트)된 것이 됩니다.
이것으로 서페이스의 파괴를 검출할 수 있었으므로 나머지는 다른 때의 처리(Restore 처리)를 실행시키면 OK····일 것입니다.
소스코드 해설
우선 풀 스크린 모드와 윈도우 모드의 양쪽 모두를 가능하게 하기 위해서 현재 어느 모드인지를 알아 둘 필요가 있습니다.
ncoop=DDSCL_NORMAL;
「ncoop 변수」는 디스플레이의 협조를 실시하는 플래그입니다.
이 변수에는 모드의 플래그가 들어갑니다.초기 값을 「DDSCL_NORMAL」로 하므로
처음의 기동은 「윈도우 모드」가 됩니다.
소스코드에서는 「엔터키」가 눌렸을 때에 스크린 모드를 변경하므로. . .
현재 윈도우 모드일 때는 「ncoop 변수」에 「DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN」를 건네주면 풀 스크린이 됩니다.
덧붙여서 엔터키메크로는 「VK_RETURN」입니다
void changescreen(void);
위의 함수는, 엔터키가 눌렸을 때에 처리되는 함수입니다.
처리의 내용은····
현재의 모드를 조사해서 플래그를 고쳐 쓴다.
윈도우 클래스를 갱신.
협조를 재차 실시한다.
BOOL changescreen(void)
{
if(nccop&DDSCL_FULLSCREEN){ //현재 풀 스크린
if(DD_OK!=dd->RestoreDisplayMode())
return FALSE; //해상도를 바탕으로 되돌린다
nccop = DDSCL_NORMAL;
//플래그를 윈도우 모드로 한다
}
else{ //현재 윈도우 모드
ncoop = DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN;
}
return TRUE;
}
위는 플래그 변환 처리입니다.
「ncoop 변수」의 내용을 「&(비트마다의 AND)」로 현재의 모드를 조사합니다.
현재, 풀 스크린 모드일때·····「ncoop 변수」에 「DDSCL_NORMAL」
을 넣습니다.(윈도우 모드를 나타내는 플래그)
그 전에 디스플레이의 해상도를 바탕으로 되돌립니다.
아마 640×480이라고 하는 사이즈의 해상도가 되어 있을 것이므로.
변경전의 해상도로 다시 돌려 줍니다.
이것은 「RestoreDisplayMode 메소드」로 가능합니다.(파라미터 없음)
그런데····이것으로 원하는 모드에 플래그를 변경할 수 있었지만.
이대로는 문제가 있습니다.
윈도우 클래스를 재정의해 윈도우의 형상을 변경하지 않으면
윈도우로부터 풀 스크린에 변경했을 경우에 테두리나 메뉴가 남는 일도 생각할 수 있습니다.
반대로 풀 스크린의 윈도우의 경우는 메뉴나 테두리가 표시되지 않는 것이 되어 버립니다.
BOOL changescreen(void)
{
if(ncoop&DDSCL_FULLSCREEN){
//현재 풀 스크린-윈도우에
if(DD_OK!=dd->RestoreDisplayMode())
//해상도를 바탕으로 되돌린다
return FALSE;
ncoop = DDSCL_NORMAL;
//플래그를 윈도우 모드로 한다
}
else{ //현재 윈도우 모드-풀 스크린에
ncoop = DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN;
}
//윈도우 클래스를 갱신
if(ncoop&DDSCL_EXCLUSIVE)
if(!SetWindowLong(hwnd, GWL_STYLE, WS_POPUP))
//풀 스크린에 있던 윈도우로 한다
return FALSE;
if(dd->SetCooperativeLevel(hwnd, ncoop)!=DD_OK)
return FALSE; //협조
if(ncoop&DDSCL_EXCLUSIVE){
//디스플레이 모드의 변경
// . . . 이것은 풀 스크린으로 할 때만
if(dd->SetDisplayMode(640,480,16)!=DD_OK)
return FALSE;
}
else{
if(!SetWindowLong(hwnd, GWL_STYLE,
//윈도우 모드에 있던 윈도우에 재정의한다
WS_POPUP | WS_CAPTION |
WS_SYSMENU | WS_BORDER|
WS_MINIMIZEBOX | WS_VISIBLE))
return FALSE;
RECT rect={0,0,640,480};
AdjustWindowRect(&rect, GetWindowStyle(hwnd),
GetMenu(hwnd)!=NULL);
//윈도우를 묘화 하는 위치를 rect 구조체에 넣습니다.
//↓:윈도우 표시 좌표를
// Set(파라미터는 헬프 참조 (WindowsX.h))
SetWindowPos(hwnd, NULL, 0, 0,
(rect.right - rect.left),
(rect.bottom - rect.top),
SWP_NOMOVE|
SWP_NOZORDER|
SWP_NOACTIVATE);
SetWindowPos(hwnd, HWND_NOTOPMOST, 0,0,0,0,
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
}
//위에서 스크린 모드는 변경되었다. 그러나 서페이스는 없어졌다.
return TRUE;
}
윈도우 클래스의 재정의는 「SetWindowLong 함수」로 가능합니다.
주로 윈도우 클래스 정의에 「 제4 파라미터」···윈도우의 모습을 변경합니다.
(플래그에 대해서는 헬프 참조)
풀 스크린에서 윈도우로
이 때, 해상도를 원래대로 되돌린 것 만으로는 어플리케이션의 윈도우 폭은 원래대로
돌아오지 않습니다.
원래의 해상도····하지만 화면에는 하나의 윈도우로서 표시됩니다.
거기서 윈도우 모드로 전환할 때는 그 사이즈.위치도 설정해 주지 않으면 안됩니다
그것이 생성되는 것은 「SetWindowPos 함수」입니다.
위의 경우일때 디스플레이 모드는 교체되지만 서페이스의 내용은 파괴되고
「Blt 메소드」는 「DDERR_SURFACELOST」의 반환 묘화가 반영되지 않게 됩니다.
여기서 서페이스의 복원이 필요하게 되는 것입니다.
「Restore 메소드」를 사용하는 방법이 있으면(위에서도 다루었으나)
서페이스 로스트가 발생했을 때는 서페이스를 완전하게 파기해 다시 새롭게 만든다.
서페이스 로스트가 발생했을 때는 「initsurface 함수」를 호출합니다(강요는 아님).
이 함수는 새롭게 서페이스를 생성하는 함수로 준비되어 있습니다.
그리고, 그 선두에····
if(MainSurface){
//프라이머리 서페이스가 생성되어 있다면 해방
MainSurface->Release();
MainSurface=NULL;
}
if(mapsurface){
// 프라이머리 서페이스가 생성되어 있다면 해방
mapsurface->Release();
mapsurface=NULL;
}
if(sprite){ 프라이머리 서페이스가 생성되어 있다면 해방
sprite->Release();
sprite=NULL;
}
를 추가합니다.
보면 알 수 있듯이 서페이스가 존재하면 파기합니다.
어플리케이션을 기동해 1회째에 이 처리는 실행되지 않습니다.(당연 서페이스는 없으니까)
그러나 서페이스로스트가 발생해 이 함수를 부르면
현존 하는 서페이스를 완전 파기해 다시 새롭게 만들어 줍니다.
스크린 모드에 의해 서페이스의 속성이 바뀌므로 그 처리도 추가하고 있다.
프로그램이 커져 가므로 위의 해방 처리는 함수화 하는 것이 좋을지도 모른다
그리고, 함수의 마지막에····
if(!(sprite=loadbmp("mychr.bmp")))
return FALSE;
if(!(mapsurface=loadbmp("map.bmp")))//맵 표면 생성
return FALSE;
를 추가하는 것으로 서페이스를 완전하게 원래대로 되돌릴 수가 있습니다.
(덧붙여서 위의 처리는 지금까지 「CreateDraw 함수로 호출했었습니다만 부적절한 곳이 되므로 장소 변경)
서페이스의 파라미터
서페이스도 풀 스크린·윈도우에서는 그 성질이 달라 집니다.
풀 스크린일 때는 「attach 가능」으로 「플립 가능」
윈도우 모드일 때는 「attach 불가」,「플립 불가」입니다.
때문에 「initsurface 함수」도 변경하고 싶은 모드의 플래그에 의해
생성 또는 지우는 기능을 서페이스의 파라미터를 나누지 않으면 안됩니다.
윈도우 모드로 하고 싶으면·····
「attach 가능」 「백 버퍼 있음」 「플립 가능」의 서페이스를
만들어야 합니다.
지금까지의 「initsurface 함수」는 어느 쪽인지 다른 한쪽의 서페이스 생성만의 처리였습니다.
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
윈도우 모드의 묘화
지금까지의 윈도우 모드의 샘플은 화면이 의도하지 않는 형태로 확대되고 있지 않았습니까?
그것은 「flip 함수」에 문제가 있기 때문입니다.
void flip(void)
{
if(ncoop&DDSCL_FULLSCREEN){
MainSurface->Flip(NULL, DDFLIP_WAIT);
}
else{
MainSurface->Blt(NULL, BackSurface, NULL,
DDBLTFAST_WAIT, NULL);
}
}
이 부분이 플립(묘화의 화면 반영) 처리입니다.
풀 스크린 모드의 경우는 좌표가 변함없기 때문에 문제 없습니다.
윈도우 모드의 경우는 묘화할 곳의 좌표는 「화면 좌표」····
즉 디스플레이 단위의 좌표 값으로 묘화할 곳을 건네주지 않으면 안됩니다.
MainSurface->Blt(NULL, BackSurface, NULL, DDBLTFAST_WAIT, NULL);
위는 윈도우 모드일 때의 플립 처리입니다.
제1 파라미터는 전송할 곳의 네모를 나타내는 값입니다. 거기를 지금까지는 NULL로 했었습니다.
이것은 화면 전체에 묘화하라는 의미가 됩니다.
대부분의 사람은 디스플레이의 해상도를 「640×480」보다 높게 설정하고 있을 것이므로
클라이언트 영역에는 확대된 이미지가 표시되고 있을 것입니다.
(그래픽 카드에 따라서는 희미해지든지····)
확대하지 않고 제대로 된 사이즈로 묘화 하려면, 「전송할 곳」을 값을 올바르게 얻을 필요가 있습니다.
윈도우는 유저의 손에 의해 항상 이동이 가능해서 이동이 발생되면
「전송할 곳의 값」를 변경하도록 프로그래밍 하지 않으면 안됩니다.
윈도우가 작동되면·····
「WM_MOVE」메세지가 발생됩니다.
이 메세지를 취득해 전송할 곳의 값을 변경하는 처리를 추가합시다.
메세지 처리
LRESULT WINAPI WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message){
case WM_KEYDOWN:
switch(wParam)
{
case VK_ESCAPE:
releaseobject();
PostMessage(hwnd, WM_CLOSE, 0,0);
break;
case VK_RETURN:
//스크린 모드 변경
changescreen();
break;
}
break;
case WM_MOVE:
GetClientRect();
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
위와 같이 「WM_MOVE 메세지」에 대한 메세지를 추가합니다.
「GetClientRect 함수」는 현재의 클라이언트 영역의 위치를 얻는 함수입니다.
(추가했습니다)
void GetClientRect(void)
{
POINT clientpos={0,0}; //클라이언트 포지션
ClientToScreen(hwnd, &clientpos);
//윈도우의 좌상 좌표를 화면 좌표로 얻는다
//ScreenClientRect 는 글로벌 RECT 구조체
ScreenClientRect.left=clientpos.x;//시작점 X좌표
ScreenClientRect.top=clientpos.y;//시작점 Y좌표
ScreenClientRect.right=clientpos.x + _ScreenXsize;//끝점 X좌표
ScreenClientRect.bottom=clientpos.y + _ScreenYsize;//끝점 Y좌표
//시작점에 윈도우 사이즈의 매크로를 가산하는 것으로 //클라이언트의 크기를 알 수 있습니다.
}
현재의 윈도우의 좌표를 클라이언트 좌표로 취득하는 처리입니다.
처리는 간단합니다.
「ClientToScreen 함수」로 윈도우의 좌상의 좌표를 화면 좌표계로 얻을 수가 있으므로
그래서 얻은 값을 RECT 구조체에 건네줍니다.
시작점(좌상 좌표)과 끝점(우하 좌표)을 얻습니다.
그리고 RECT 구조체를 묘화 할 때, 「Blt 메소드」의 제1 파라미터에 건네줍니다.
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
Z소트
Z소트는 깊이를 표현할 때에 사용됩니다.
일정한 게임····2D지만 「깊이(Z축)」의 개념이 있는 게임을
만들 때에 사용하면 스프라이트의 묘화가 비교적 간단하게 됩니다.
지금까지의 샘플 프로그램은 화면에 묘화 할 때····
1.「배경의 묘화」
2.「캐릭터의 묘화」
etc···
등으로 안쪽에 있으면 있는 차례로 묘화하도록 프로그램 해 왔습니다.
묘화 순서가 반대라면,「캐릭터 이미지」는 배경에 가려지기 때문에
보이지 않을 것입니다.
2D에서는 「묘화 하는 차례」로 깊이를 표현합니다.
Z소트가 필요한 게임이 예를 들면(자)·····「디아블로」
등 맵에 「앞과 안쪽」의 개념이 있는 경우입니다.
캐릭터는 맵의 안쪽으로 가면 앞의 캐릭터의 뒤로 숨어 버립니다.
안쪽으로 갔으니까 당연합니다만,
이 코너의 지금까지 방법에서는 이것은 할 수 없습니다.
1.「배경의 묘화」
2.「캐릭터 A의 묘화」
2.「캐릭터 B의 묘화」
etc···
처리를 만들고 있으면 「캐릭터 B」는 항상 「캐릭터 A」의 앞에 묘화 되고.
맵의 안쪽으로 이동했다고 해도 맵의 앞에 있는 「캐릭터 A」의 뒤에 숨는 일은 없습니다.
는 그 예····「적색 화상」은 「청색 화상」보다 뒤로 묘화 하고 싶어도
할 수 없습니다.
이것은 「묘화의 차례」에 문제가 있기 때문입니다.
Z소트에서는 스프라이트에 「Z축의 값」을 각각 갖게하고 그 값에 의해 묘화 하는 차례를
변경하는 처리합니다.
「Z축의 값」은 「얼마나 안쪽에 있을까?」라고 하는 값으로 「0」이라면 제일 앞 「α」이라면 안쪽이라 생각합니다.
α값이 높을 수록 처리에 시간이 걸리지만 보다 정밀도가 높은 원근감을 표현할 수 있다
그리고 「Z축의 값」이 「큰(작은 순서로도 가능) 차례」로 묘화 해 나가면 OK입니다.
처리의 흐름
5개의 스프라이트를 Z소트에 의한 묘화를 실시한다
5개의 스프라이트는 모두 다른 이미지를 사용합니다.
그 이미지들은 모두 하나의 서페이스에 모을 수 있습니다.
표시하는 「5개」의 스프라이트에 구조체를 만듭시다.
스프라이트 구조체에도 추가해야 멤버는 이하와 같은 것이라 생각됩니다.
■스프라이트가 있는 서페이스의 주소
소스코드에서는 「하나의 서페이스」가 되고 있지만 나중에 확장할 때에
어느 서페이스로부터도 가능하도록 이것을 멤버에 넣습니다.
묘화 처리는 이 멤버가 지정하는 서페이스부터 묘화를 합니다.
■표시할 곳의 구역
「blt」로 묘화 할 때, 2개의 RECT 구조체로 전송할 곳과 전송해올 곳의 구역이
필요하므로 이것도 멤버에 넣도록 합시다.
X·Y의 위치만으로도 묘화를 처리하는 쪽에 처리를 추가하는 일로 가능
■Z값
그 스프라이트가 얼마나 안쪽으로 존재하는지를 나타내는 값입니다.
■앞과 다음의 구조체의 주소
양방향을 체인 리스트로 소트를 할 때에 이것이 필요합니다.
스프라이트가 증가할 때, 구조체를 동적으로 생성해 체인 안에 짜넣어 가는 것이
제일 좋다고 생각합니다.
그러나 이 소스에서는 체인 리스트에서도 소트는 하고 있지 않습니다.
다만 배열을 정의해 거기서 소트 하고 있습니다.
샘플을 그대로 사용하면····화면에 나오는 스프라이트의 최대분량의 구조체가
항상 메모리에 필요하기 때문에 최적이라고는 할 수 없습니다.
할 수 있으면 체인 리스트에서의 Z소트에 챌린지하는 편이 더욱 효율적입니다.
구조체의 정의
구조체를 정의합시다.
typedef struct{//Z소트 구조체 정의
DWORD LpSurface; //서페이스에의 포인터
RECT Trect; //전송할 곳의 네모
RECT Nrect; //전송해올 곳의 네모
int Zs; //Z값
}Zsort, *LpZsort;
이번 샘플은 캐릭터 5개와 맵 1개의 「6개」의 구조체 배열이 필요합니다.
구조체의 생성을 하면 초기의 소트 값을 격납하는 함수를 1회만 호출하도록 해 둡니다.
void ZsortInit(void)
{
//맵 이미지의 Z소트 초기화
sort[5].LpSurface = (DWORD) mapsurface;
sort[5].Zs = _ZLAST;//맵은 제일 안쪽에 Set한다
SetRect(&sort[5].Nrect, 0,0,256,240);
SetRect(&sort[5].Trect, 0,0,640,480);
//캐릭터 5개의 초기화
for(int cnt=0;cnt<=4;cnt++){
sort[cnt].LpSurface = (DWORD) sprite;
sort[cnt].Zs = cnt; //맵은 제일 안쪽에 Set한다
SetRect(&sort[cnt].Nrect, cnt*32, 0, cnt*32+32, 32);
//전송해올 곳 좌표
SetRect(&sort[cnt].Trect, cnt*32, 200, (cnt*32)+32, 232);
//전송할 곳 좌표
}
//캐릭터는 나중에 묘화 한다
//캐릭터 A만 움직임으로. . . Z값을 변경할 수가 있습니다.
}
초기 Z값은 맵의 제일 안쪽에서 캐릭터가 0·1·2·3·4로 Z 값을 할당하고 있습니다.
움직일 수 있는 캐릭터는 「A(초기 Z값:0)」만으로 기동했을 때는 캐릭터 A는
어느 캐릭터보다 앞에 있기 때문에 뒤로 숨는 일은 없습니다.
Z값의 초기화(캐릭터의 좌표 초기화)는 아무런 특색도 없는 값을 넣는 처리이므로
설명은 생략합니다.
다음에····「Z소트에 의한 묘화」입니다.
이 처리는 「void Zsortrender(void)」에서 처리됩니다.
for(cnt=_ZLAST;cnt>=0;cnt--) //안쪽부터 검색
{
for(x=0;x<=5;x++)//Z값이 높은 것부터 묘화 한다
{
if(sort[x].Zs == cnt) //Z심도와 동일하니까
BackSurface->Blt(&sort[x].Trect,
(IDirectDrawSurface*) sort[x].LpSurface,
&sort[x].Nrect,
DDBLTFAST_WAIT | DDBLT_KEYSRC, &ddbltfx);
}
}
기술적으로는 의외로 짧은 처리입니다.
루프 처리로 안쪽부터 차례로 묘화하는 처리가 되어 있습니다.
바깥쪽의 루프(for(cnt=_ZLAST;cnt>=0;cnt--)
「cnt」는 「Z값」을 나타내고 있어 「Z치의 최대값 매크로_ZLAST」부터 차례로 검색해 갈 것입니다.
「0」이 되면 차례로 묘화 다 한 것이 되므로 처리는 종료가 됩니다.
안쪽의 루프(for(x=0;x<=5;x++)
는 스프라이트 구조체의 검색입니다.
프로그램은 방향 키로 캐릭터 이동이 가능합니다.
Z키로 Z 값을 플러스 해···X키로 「Z값」을 마이너스 합니다
어디까지나 배열 테이블을 사용한 Z소트의 방법(보충)
새롭게 「스프라이트 구조체 포인터 배열」(구조체에의 포인터만을 모은 배열)을 만들어 둡니다.
프로그램 실행중에 「Z값」의 변경이 있었을 때 만 「스프라이트 구조체」에 데이터를 격납합니다.
「스프라이트 구조체」모두를 소트나 오름차순, 내림차순으로 「스프라이트 구조체 포인터 배열」의 테이블모두를 갱신합니다.
이미지의 묘화는 이 「스프라이트 구조체 포인터 배열」의 차례로 묘화 해 나갑니다.
소트는 배열의 내용을 순서에 맞추어 바꾸는 것을 말한다
이번 소스의 요점은「Z값을 스프라이트마다 갖게한다」를 알 수 있었다면 된다고 생각합니다.
다음 소스는 배열을 이용한 Z소트입니다.
//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////
Z소트이
전회에서 간단한(?) Z소트 같은 것을 했으므로 이번은 그것을 발전(?) 시켜
볼까하고 생각합니다.
목표는 스프라이트의 데이터를 메모리에 동적으로 생성해 그것을 체인으로 묶어
소트·묘화에 이용한다····입니다.
체인 리스트의 연습도 포함한다
체인 리스트체인 리스트의 개념을 모르는 분을 위해····
게임의 장르에 따라서는 화면에 표시되는 이미지의 순서가 정해져 있지 않은 경우가 있다고 합니다.
정해져 있어도 된다.
이 때, 이미지의 수만큼 그것을 관리하는 「좌표 변수」를 준비해야 합니다.
화면에 나타내는 이미지의 「최대수」의 변수를 「처음에 정의하면」····
즉···
int x[256];
int y[256];
라고 프로그램의 처음으로 필요한 최대양을 정의하는 것입니다.
위의 경우는 화면에 이미지 256개를 관리할 수 있는 좌표를 정의한 것이 되는군요.
그러나 화면에 이미지가 하나 밖에 표시되어 있지 않은 것을 생각해 보세요!
이 경우····256개의 관리용 변수를 보관 유지하는 메모리 영역중···
1개 밖에 사용하지 않기 때문에 나머지의 255개 분의 메모리 영역은 「낭비」가 되고 있습니다.
거기서····필요한 양만큼 그때 그때 생성해 거기에 데이터를 보관하자고 하는 것이
체인 리스트입니다.
이것이라면 필요한 양만큼 메모리를 소비하므로 낭비가 없습니다.
어느 함수(나중에 나올 함수)에서 메모리에 필요한 사이즈의 영역을 생성합니다.
(프로그램 루프내에서)
이 때 생성된 변수·구조체등의 메모리 위치는 어디가 될까 결정하는 일은 기본적으로
불가능해서 컴퓨터가 자동으로 적절한 위치에 영역을 확보해 줍니다.
위 그림과 같이 메모리에 뿔뿔이 흩어지게 데이터가 배치됩니다.
이 상태로는 필요한 데이터의 억세스를 할 수가 없습니다····거기서 동적으로 생성된
데이타에 「다음의 요소와 전의 요소」의 「주소(address)」를 가지는 변수를 갖게합니다.
메모리상의 체인의 개념은 그림에 나타난 대로
앞의 요소와 다음의 요소의 주소를 가져 오는 것으로 「체인(쇠사슬)」과 같이 연결되어 갑니다.
주소로 연결하므로 메모리상의 위치는 관계없다!
또 요소의 추가 삭제도 체인을 연결해서 바꾸는 것으로 간단하게 할 수 있고 테이블 처리에서는 하기 어려운(처리가 무거워진다) 「도중에 추가·삭제」등도 간단하게 고속으로 실시할 수 있습니다.
아무튼 주소의 앞을 변경하는 것만으로 가능하기때문에.
덧붙여서 체인 리스트는 「노벨 게임」이나 RPG에서의 이벤트 메세지 처리에
사용하면 좋은 알고리즘이라고 할 수 있습니다.
개념적인 물건
우선은 스프라이트 데이터의 구조체를 정의합니다.
이 구조체를 동적으로 생성해 이 구조체에 격납되고 있는 이미지를 묘화하도록 합니다.
우선 필요한 것은 「앞의 요소」와「다음의 요소」의 포인터 변수입니다.
샘플은 양방향의 체인이므로 이 두 개의 포인터가 필수입니다.
「체인 리스트」의 설명을 보면 알 수 있다고 생각합니다.
위의 포인터는 필수로서···나머지의 필요한 것을 생각하면.
이미지가 나오므로 「서페이스의 포인터」나 「표시를 나타내는 RECT 구조체」등으로
하나의 스프라이트 데이터 구조체로 하면 좋다고 생각합니다.
Z심도
깊이를 표현하므로 당연히 「Z심도」를 나타내는 「값」이 필요하게 됩니다.
이 데이터를 「스프라이트 구조체」마다 갖게하면····
위와 같이 됩니다.
일반적인 체인이라고 할 수 있습니다.
그리고 이미지 표시가 필요하게 되면 리스트에 모두 추가합니다.
추가·삭제 위치에 대해서는 검색 처리를 해야 합니다.
데이터는 내림차순에 줄지어 있으므로 「차례차례 검색」만으로
아닌 「순서대로의 검색」을 효율적이라고 생각됩니다.
메모리의 동적 생성의 방법
C언어 베이스라면 「malloc 함수」로 C++베이스라면 「new」가 있습니다.
여기는 C언어 베이스이므로···「malloc 함수」에 대해.
우선, 이 함수를 사용하려면 , stdlib.h 와 malloc.h를 인클루드해야 합니다.
void *malloc( size_t size );
이 함수의 파라미터는 1개로 「어느 메모리에 확보할까?」입니다.
리턴 값은 확보한 영역의 맨 앞의 주소입니다.
확보에 실패했을 경우는 NULL이 리턴 됩니다.
사용하는 경우는 구조체의 바이트 수를 판단하는 함수 「sizeof」등으로 병용해서 동적으로 확보합니다.
체인 리스트의 포인터 = malloc(sizeof(SpriteStruct));
영역이 불필요하게 되었을 경우는 프로그램쪽에서 명시적으로 해방 처리를 실시하지 않으면
메모리에 남아 있습니다.
「free 함수」에 의해 확보한 영역을 개방합니다.
void free( void *memblock );
파라미터는 해방하고 싶은 주소를 건네줍니다.
malloc로 확보한 선두 주소를 건네줍니다.
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
서페이스의 조작
이번은 「서페이스의 조작」을 해 봅시다.
간단하게 이미지가 보관되고 있는 메모리를 마구 주무른다는 의미입니다.
1회로서 「Blt」를 사용하지 않는 이미지의 묘화를 해 봅시다.
이「서페이스의 조작」의 대표적으로「반투명 처리」등을 들 수 있습니다.
또, 「선」을 긋거나 「점」을 묘화 한다면 꽤 좋은 방법이 아닐까요?
개념부터 우선 방법은 다음에 소개한다고 하고 개념부터 설명합니다.
우선, 이미지가 보관 유지되고 있는 메모리 영역을 DirectX에서는 「서페이스」라고
부르고 있었습니다.
무엇인가 어려울 것 같지만, 결국 메모리에 「이미지의 정보」가 격납되고 있는 것일뿐
입니다.
16비트 칼라 이상으로 설명합니다.
예를 들면 16비트 칼라는 이미지의 1 도트를 「16비트」로 표현하고 있습니다.
「2바이트」군요. (8비트=1바이트)
메모리의 「2바이트」에는 「R · G · B · 그 외(?)」의 정보가 격납되고
다음의 번지의 메모리에는 다음 도트의 정보가 격납되고 있습니다.
프로그램에서 어느 번지의 칼라정보의 처리(갱신등)가 어떻게 될까요?
당연히 서페이스의 이미지 그 자체가 변화됩니다.
반투명의 처리를 실시한다면 「전송할 곳의 이미지의 1 도트」 「전송해올 곳 이미지의 1 도트」의 칼라정보를 비교해 「전송할 곳의 메모리를 다시 기입하면」반투명 처리의 완성입니다.
(반투명도의 정도에 의해 계산식도 바뀔 것이다)
여기에서 「전송해올 곳의 서페이스」의 이미지를 「Blt 메소드」를 사용하지 않고
「전송할 곳」에 묘화 합니다.
흐름은 위의 같습니다.
아주 단순하게「전송해올 곳」의 정보를 읽어서···그것을 「전송할 곳」에 보내면 됩니다.
그럼 그 방법이란!
서페이스를 직접 조작하려면?
사실 이 기능을 DirectDraw는 가지고 있습니다.
간단히 말하면, 메모리라는 것을 그다지 의식하지 않아도 가능하다고 할 수 있습니다.
서페이스의 록
서페이스에 직접 억세스를 하려면 「DirectDraw 오브젝트」의 「Lock 메소드」
를 사용해서 서페이스를 「록」합니다.
잠그는 것으로 다른 어플리케이션이 「서페이스」에의 억세스가 불가능하게 되어
자신의 어플리케이션은 서페이스에 억세스 할 수 있도록 됩니다.
(메모리내의 데이타의 정합성을 유지하기 위해 록을 실시하겠지요.)
HRESULT Lock(
LPRECT lpDestRect,
LPDDSURFACEDESC lpDDSurfaceDesc,
DWORD dwFlags,
HANDLE hEvent
);
록 메소드는 위에 있는대로입니다.
여기서 지정하는 서페이스의 일부 영역(또는 전역)을 잠글 수 있습니다.
제1 파라미터:
이것은 잠그는 영역을 지정하는 RECT 구조체입니다.
서페이스의 내용을 변경하고 싶은 영역을 미리 RECT 구조체에 Set 하고 나서
건네줍니다.「null」를 건네주면 서페이스 전역이라는 의미가 됩니다.
제2 파라미터:
록하는 서페이스의 정보를 격납하는 구조체 「LPDDSURFACEDESC」입니다.
위의 설명 대로 록한 후 서페이스의 정보가 격납됩니다.
lpSurface 서페이스의 메모리상의 주소
dwWidth, dwHeight 서페이스의 폭과 높이
lPitch 서페이스의 가로라인의 바이트의 폭입니다.(중요)
ddpfPixelFormat 픽셀 포맷(이하 설명)
라고 위와 같은 멤버를 가지는 구조체에 데이타가 주어집니다.
제3 파라미터:
잠글 때의 동작을 결정하는 플래그, 이하의 플래그가 있습니다.
DDLOCK_EVENT
현재, 이 플래그는 실장되지 않았다.
DDLOCK_NOSYSLOCK
가능하면 Win16Lock 를 사용하지 않는다. 프라이머리 표면을 잠글 때 이 플래그는 무시된다.
DDLOCK_READONLY
잠그는 표면은 읽어내기 전용인 것을 나타내는 플래그.
DDLOCK_SURFACEMEMORYPTR
지정한 범위의 선두의 유효한 메모리포인터를 돌려주지 않으면 안 되는 것을 나타내는 플래그.
범위가 지정되지 않는 경우, 맨 위의 표면의 포인터가 리턴된다.
디폴트로는 이 플래그를 지정한다.
DDLOCK_WAIT
블록 전송 처리중이어서 록을 획득할 수 없는 경우, 이 메소드는 록을 획득할 수 있는지
또는 DDERR_SURFACEBUSY 등의 다른 에러가 생길 때까지 시도한다.
DDLOCK_WRITEONLY
잠그는 표면은 기입 전용인 것을 나타낸다.
제4 파라미터:
항상 NULL
프로그램은 「Blt」를 사용하지 않고 이미지 전송을 실시합니다.
「캐릭터」와「백 버퍼」를 잠그어 1 도트씩 메모리를 동일하게 해 갈 것입니다.
전송해올 곳의 서페이스는 32×32 도트입니다.
BYTE *pPixel; //픽셀(도트)의 데이타를 격납하는 주소를 얻는다
BYTE *pPixels; //스프라이트용 포인터
DDSURFACEDESC ddsd; //록할 때의 데이타를 격납하는 구조체
DDSURFACEDESC ddsds; //록할 때의 데이타를 격납하는 구조체
ZeroMemory(&ddsd, sizeof(ddsd)); //데이터의 보관, 유지구조체를 제로 클리어
ddsd.dwSize = sizeof(ddsd); //구조체의 사이즈를 건네준다
ZeroMemory(&ddsds, sizeof(ddsds)); //데이터의 보관, 유지 구조체를 제로 클리어
ddsds.dwSize = sizeof(ddsds); //구조체의 사이즈를 건네준다
if(BackSurface->Lock(NULL, &ddsd, DDLOCK_WAIT| DDLOCK_SURFACEMEMORYPTR, NULL) == DD_OK)
{
if(sprite->Lock(NULL, &ddsds, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR , NULL) == DD_OK)
{
pPixel = (BYTE*) ddsd.lpSurface; //주소를 취득(잠근 백 버퍼의)
pPixels = (BYTE*) ddsds.lpSurface;
//주소의취득(잠근 오프서페이스의)
for(int y=0;y<32;y++){
for(int x=0;x<32;x++){
pPixel[y*ddsd.dwWidth+x] =
pPixels[(y*ddsds.dwWidth) +x];
}
}
}
else
return FALSE;
}
else
return FALSE;
//서페이스의 록을 푼다
if(BackSurface->Unlock(NULL)!=DD_OK)
return FALSE;
if(sprite->Unlock(NULL)!=DD_OK)
return FALSE;
위의 처리는 백 버퍼와 스프라이트 서페이스를 록해서 스프라이트의 메모리의 내용을 백 버퍼의 메모리 공간에 복사하는 처리입니다.
BYTE형 변수에 서페이스의 주소를 격납해···
pPixel = (BYTE*) ddsd.lpSurface; //주소를 취득(잠근 백 버퍼의)
pPixels = (BYTE*) ddsds.lpSurface;
//주소의 취득(잠근 오프서페이스의)
메모리 공간은 연속하는 1 차원적인 메모리이므로····
for(int y=0;y<32;y++)
{
for(int x=0;x<32;x++)
{
pPixel[y*ddsd.dwWidth+x] =
pPixels[(y*ddsds.dwWidth) +x];
}
}
라고 하는 식으로 임의의 도트에 억세스 하는 것이 가능합니다.
예)
pPixel[10]
라면, 잠근 영역의 선두 도트부터 10 도트째의 주소를 가리킵니다.
pPixel[y*ddsd.dwWidth+x]
라고 소스는 기술하고 있습니다.
「dwWidth 멤버」는 서페이스의 가로 도트수를 가지고 있습니다.
y × 서페이스 가로 도트수 + x
로 함으로 임의의 사이즈를 분명하게 전송 할 수 있습니다.
for(int y=0;y<32;y++)
{
for(int x=0;x<32;x++)
{
덧붙여서 루프가 위와 같이 되고 있는 것은 스프라이트 서페이스 쪽의 사이즈가 32×32인 것과 전송 하고 싶은 사이즈가 32×32이기 때문입니다.
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
반투명 처리
「반투명」이라고 하는 묘화 처리는 「서페이스록」을 서페이스 메모리를
자유롭게 조작하는 일로 가능하게 됩니다.
이 장은 전장의 응용입니다.
게임에서는 「실체가 없는 · 숨어 있는 케렉터등」을 표현할 때에 사용될 것 같습니다.
예를 들면, 유령이라든지·····캐릭터의 잔상등으로서 사용되고 있네요.
알고리즘
그럼 반투명은 어떤 알고리즘일까?
서페이스의 록이 되고 비디오메모리의 주소의 내용(픽셀 데이타)을
취득할 수가 있으면 간단합니다.
배경이 되는 「맵서페이스」의 「픽셀 데이타」와
거기에 묘화되는 「켁릭터서페이스」의 「픽셀 데이타」를 얻습니다.
위의 그림과 같이 「반투명 묘화에 필요한 영역」의 픽셀 데이타를 취득합니다.
아마 「RGB」라고 하는 데이타가 그 주소에 격납되어 있을 것입니다.
(256 이하의 칼라의 화상을 제외)
취득한 「2개(이상)의 RGB 데이타」를 바탕으로 「계산」을 행한
계산 결과의 「RGB 데이타」를 전송할 서페이스의 주소에 전송합니다.
50% 50%인 반투명
이것은 가장 간단한 계산이므로 이것을 소개합니다.
「배경의 칼라」 「캐릭터의 칼라」 양쪽 모두 50%의 칼라를 화면에 표현하는 것입니다.
어느 픽셀 데이타의 내용이 이하와 같았다고 합시다.
RGB: 125 50 40 배경쪽
RGB: 10 80 255 캐릭터쪽
이 두 개의 데이타를 단순하게 플러스합니다.
RGB: 135 130 255
오버 플러그를 조심!
그리고 「2」로 나누어 봅시다.
RGB: 67 65 127
결과, 칼라는 이와 같이 되었습니다.
응~이럴꺼라 생각합니다 분명!
프로그램에서는 「50%브랜드」인 반투명 처리를 하고 있습니다.
처리 코드 소개
처리로 우선 주의해야 할 것은 칼라 모드에 의해 픽셀 포맷이 다른 것입니다.
16칼라의 경우····256칼라의 경우, 16비트 칼라의 경우, 32비트의 경우등
(요즘24비트는 잘 사용이 안 하므로 생략합니다. 3바이트이고 복잡한 처리가 된다)
칼라 모드에 의해 포맷이 다르므로 각각의 모드에 대해서 코드가 필요하게 됩니다.
그 때문에 우선 실시해야 하는 것은 「서페이스」의 「픽셀 포맷」을 취득하는 것입니다.
「픽셀 포맷」을 얻으려면 우선 DDPIXELFORMAT 구조체를 정의합니다.
이 구조체는 DirectDrawSurface의 「GetPixelFormat 메소드」를 사용해서 「픽셀 포맷 정보」를 얻을 때에 필요한 구조체입니다.
(DDPIXELFORMAT 구조체에 픽셀 포맷 정보가 격납됩니다. )
DDPIXELFORMAT pixel;
pixel.dwSize=sizeof(pixel);
if(DrawSurface->GetPixelFormat(&pixel)! =DD_OK) return FALSE;
위의 코드와 같이 기술하면 「DrawSurface(서페이스)」의 픽셀 포맷을 얻을 수가 있습니다.
「DDPIXELFORMAT 구조체」의 「dwRGBBitCount 멤버」에는 서페이스의 칼라 모드 정보
가 격납됩니다.
(4, 8, 16, 24, 또는 32)의 값이 이 멤버에 격납됩니다.
우선 여기서 칼라 모드에 의한 반투과처리(알파블렌딩)의 처리의 나누기를 실시합니다.
if(pixel.dwRGBBitCount == 4) Alpha_4();
//16칼라일 때의 알파 브랜드 처리 함수
if(pixel.dwRGBBitCount == 8) Alpha_8();
//256칼라일 때의 알파 브랜드 처리 함수
if(pixel.dwRGBBitCount == 16) Alpha_16();
//16비트 칼라일 때의 알파 브랜드 처리 함수
if(pixel.dwRGBBitCount == 24) Alpha_24();
//24비트 칼라일 때의 알파 브랜드 처리 함수
if(pixel.dwRGBBitCount == 32) Alpha_32();
//32비트 칼라일 때의 알파 브랜드 처리 함수
이렇게 나누어 처리하는 것이 효율적입니다.
보충으로 256칼라의 경우는 칼라가 팔레트에 관리되기때문에 위에서 설명하고 있는 것 같은 픽셀의 칼라 정보를 얻어 그것을 계산한다···약간 방법이 다릅니다.
그럼, 칼라 모드에 의한 처리 나누기로 「16비트 모드 처리」로 진행되었다고 합시다.
1 픽셀(1 도트)을 표현하는데 「16비트」의 메모리를 사용해서.
그 중에 「R·G·B」의 요소가 격납되고 있는 것입니다.
알파 브랜드이기 때문에 계산하려면····16비트 안에서 「R·G·B」를 개별적으로 뽑아낼
필요가 있습니다.
그 때문에「마스크 프로세싱」이라고 하는 비트 단위가 있는 처리를 실시합니다.
거기에 필요한「마스크」를 「DDPIXELFORMAT 구조체」의 「lBBitMask」 「lGBitMask」 「lRBitMask」멤버는 가지고 있습니다.
마스크 프로세싱의 하나의 예
어느 서페이스의 픽셀 포맷이 이하의 그림대로였다고 합시다.
그 포맷인 픽셀이 이하의 같은 칼라 데이타를 가지고 있었다고 합니다.
이 때, G(green)의 칼라 값만을 얻으려면? 어떻게 하면 좋을까요?
역시 제일 좋다고 생각되는 것이 「마스크 프로세싱」입니다.
「마스크 프로세싱」은 비트 단위로 「AND」 「OR」 「XOR」등을 취해 임의의 위치의
비트만을 조작할 수가 있습니다.
이 경우,「있는 부분만을 남긴다」라는 처리이므로.
「GetPixelFormat 메소드」는 「G정보」의 비트 위에만 「1」로 「마스크」를
준비하고 있을 것입니다.
준비된 마스크 데이타와 칼라 데이타를 「AND」하면 「G데이타」만이 비트에 남습니다.
「AND」는 양쪽 모두의 비트가 「1」이었을 경우, 비트의 결과가「1」이 된다.
그 이외는 「0」(true, false의 개념)
Green의 칼라 데이타만 비트가 있습니다.
그 후의 처리로서 비트 쉬프트등을 실시하면 올바른 값으로 해서 칼라 값을 얻을 수가 있습니다.
DWORD Rmask;
DWORD Bmask;
DWORD Gmask;
Rmask=pixel.dwRBitMask;
Bmask=pixel.dwBBitMask;
Gmask=pixel.dwGBitMask;
마스크 데이타를 보관한다면 보관해 둡시다.
■처리에 필요한 변수를 준비한다
WORD *LpBg; //배경의 맵(백 버퍼)의 포인터
WORD *LpCha; //배경에 거듭하는 케릭터 서페이스의 포인터
이것은 서페이스를 「록」할 때(잠글 때)에 필요합니다.
픽셀의 칼라 정보를 얻기 위해도 대상 서페이스의 주소가 필요하기 때문입니다.
이 포인터에는 「록」할 때에 이미지 데이타의 선두 주소가 격납됩니다.
BYTE *Chatop;
BYTE *Bgtop;
샘플에서는 32×32의 범위를 알파 처리합니다.
그 「행」의 선두 픽셀의 주소를 보관하는 변수입니다.
DDSURFACEDESC ddsd; //록할 때의 데이타를 격납하는 구조체
DDSURFACEDESC ddsds; //록할 때의 데이타를 격납하는 구조체
서페이스를 잠그려면, 「Lock 메소드」를 사용합니다.
그 때, 파라메타로서 「DDSURFACEDESC」가 필요하게 되므로 준비합니다.
DWORD red_1, red_2, red_3
DWORD green_1, green_2, green_3;
DWORD blue_1, blue_2, blue_3;
비트열 계산에 사용하는 변수입니다.
***_1에는···배경쪽 픽셀의 칼라 정보의 비트열
***_2에는···묘화쪽 픽셀의 칼라 정보의 비트열
***_3에는···계산 처리 후의 칼라 정보 비트열을 격납 합니다.
처리
--------------------------------------------------------------------------------
A
if(BackSurface->Lock(NULL, &ddsd, DDLOCK_WAIT |
DDLOCK_SURFACEMEMORYPTR, NULL) == DD_OK)
{
if(sprite->Lock(NULL, &ddsds, DDLOCK_WAIT |
DDLOCK_SURFACEMEMORYPTR , NULL) == DD_OK)
{
//양서페이스를 잠근다
LpBg = (WORD*) ddsd.lpSurface;
//주소를 취득(잠근 백 버퍼)
LpCha = (WORD*) ddsds.lpSurface;
//주소를 취득(잠근 오프서페이스)
// Ppixel에는 잠근 서페이스의 좌상 첫번째의
// 도트 정보의 주소가 들어간다
for(int y=0;y<32;y++)
{
Chatop = (BYTE*) LpCha;
Bgtop = (BYTE*) LpBg;
for(int x=0;x<32;x++)
{
//캐릭터 사이즈가 32*32(루프)
//알파 브랜드 처리
red_1 = *LpBg & Rmask;
red_2 = *LpCha & Rmask;
red_3 = ((red_1 + red_2)/2);
blue_1 = *LpBg & Bmask;
blue_2 = *LpCha & Bmask;
blue_3 = ((blue_1 + blue_2)/2);
green_1 = *LpBg & Gmask;
green_2 = *LpCha & Gmask;
green_3 = ((green_1 + green_2)/2);
*LpBg = (WORD)((red_3 & Rmask)|
(green_3 & Gmask)|(blue_3 & Bmask));
LpBg++;
LpCha++;
//마지막
}
LpBg = (WORD*)(Bgtop+ddsd.lPitch);
LpCha = (WORD*)(Chatop+ddsds.lPitch);
}
}
else
return FALSE;
}
else
return FALSE;
//서페이스의 Unlock
if(BackSurface->Unlock(NULL)! =DD_OK) return FALSE;
if(sprite->Unlock(NULL)! =DD_OK) return FALSE;
알파 브랜드의 처리는 위에 있는 대로입니다.
서페이스를 잠그고 그 선두 픽셀의 주소를 얻는 부분은 전장과 같아서 생략합니다.
루프가 위와 같이 되어 있는 것은 32×32 픽셀의 케릭터를 알파 처리하기 때문입니다
for(int y=0;y<32;y++)
{
Chatop = (BYTE*) LpCha;
Bgtop = (BYTE*) LpBg;
우선 위의 처리입니다.
Y축의 루프가 처리의 종료 조건이 되어 있습니다.
1열 마다
Chatop = (BYTE*) LpCha;
Bgtop = (BYTE*) LpBg;
하는 처리를 반복하는 것이 됩니다.
루프에 들어가기 전에 LpCha·LpBg의 포인터 변수(WORD형)에는 서페이스를 록할 때의 선두
픽셀의 주소가 대입되어 있습니다.
루프 처리의 1회째에 「Chatop·Bgtop」에는 선두 픽셀 주소가 격납됩니다.
그 후 LpCha·LpBg는 「++연산자」로 다음 주소로 내용이 변화합니다.
「Chatop·Bgtop」에는 각 열의 선두 픽셀 주소가 대입되어 간다
픽셀의 반투명 처리
//알파 브랜드 처리
red_1 = *LpBg & Rmask;
red_2 = *LpCha & Rmask;
red_3 = ((red_1 + red_2)/2);
blue_1 = *LpBg & Bmask;
blue_2 = *LpCha & Bmask;
blue_3 = ((blue_1 + blue_2)/2);
green_1 = *LpBg & Gmask;
green_2 = *LpCha & Gmask;
green_3 = ((green_1 + green_2)/2);
루프내에서 위의 처리를 반복해 실시하도록 되어 있습니다.
이것이 반투명 처리의 중심이라고 할 수 있는 연산 처리입니다.
위에서도 설명하고 있는 것처럼, 마스크와 픽셀의 비트 열을 「AND」연산해서 칼라정보만을 뽑아내서 「***_*」의 변수로 유지하고 있는 것입니다.
***_1 의 처리에는 배경(백 버퍼)의 대응 픽셀의 칼라정보를 알파 처리
***_2 의 처리에서는 스프라이트(캐릭터)의 대응 픽셀의 칼라정보를 알파 처리
***_3 의 처리에서는 1과 2로 얻을 수 있던 칼라 데이터를 더해서 2로 나누고 있습니다.
2로 나누면 50%투명도의 알파 브랜드가 됩니다.
백 버퍼·스프라이트의 양서페이스의 픽셀칼라요소를 반반이라고 하는 연산 결과가 되기 때문입니다
*LpBg = (WORD)((red_3 & Rmask) | (green_3 & Gmask) | (blue_3 & Bmask));
LpBg++;
LpCha++;
이 처리는 R·G·B의 칼라의 요소마다 계산된 칼라데이타를 하나에 모으고
「LpBg」(백 버퍼에의 포인터)가 가리키는 주소에 데이타를 전송 합니다.
여기까지의 처리로 알파 브랜드의 1 도트의 처리가 끝난 것이 됩니다.
다음 도트에 처리를 옮기기 위해서 「LpBg · LpCha」의 포인터 변수에 「++」합니다.
이렇게 함으로 포인터 변수는 1 주소로 나아갑니다. (오른쪽의 도트에)
LpBg = (WORD*)(Bgtop + ddsd.lPitch);
LpCha = (WORD*)(Chatop + ddsds.lPitch);
}
이 처리는 일행(옆 32개)의 처리가 끝났을 때에 실행되는 처리입니다.
이것은 「LpBg · LpCha」포인터 변수에 다음의 행의 주소를 건네주고 있습니다.
이것이 없으면 사각형에 전송 되지 않습니다.
ddsd.lPitch에는 그 서페이스의 가로의 픽셀수가 격납되고 있습니다.
「Bgtop · Chatop」에 그 값을 더하면····결과, 주소는 다음의 행의 선두와 동일해집니다.
샘플에서는, 32×32의 스프라이트 이미지를 백 버퍼의 0, 0 좌표에 전송하고 있습니다.
투과 처리를 하지 않기 때문에 스프라이트의 배경도 반투명 되고 있습니다.
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
스프라이트 반투명 처리
전번의 반투명 처리에서는 캐릭터에서 투명하게 만들고 싶은 색을 반투명 처리했습니다.
이번에는 그 점을 해소해 「스프라이트용 반투명 묘화」를 합니다.
전번에는 위의 캐릭터 이미지의 핑크의 부분까지 묘화 되었습니다.
■알고리즘
기본적인 부분은 전번과 큰 차이는 없습니다.
추가 처리로서 서페이스의 포인터가 가리키는 픽셀의 색이 「핑크」라면
그 색을 알파 브랜드 처리의 대상으로 하지 않고, 「백 버퍼(배경)」의 색을
100%반영시킵니다.
「투과 시키는 값과 비교」하는 처리가 추가 되므로 이 쪽의 스프라이트 알파 브랜드가
약간 무거운 처리가 됩니다.
■투과 색의 칼라 값(비트 열)
이것을 결정하려면 다양하게 방법이 있습니다.
일반적인 것이 「Black」을 투과색으로 하는 방법입니다(물론 반듯이 Black일 필요는 없다).
그 이유는 아마도 「Black」의 칼라 값은 「00000000...」이므로 결정하는 것이 간단합니다.
이 경우는 당연히 스프라이트 그 자체에 「00000...Black을 사용할 수 없습니다」
이 소스에서는 전송하는 스프라이트의 범위로···제일 좌상 픽셀로 사용되고 있는 색을
마스크 칼라로 합니다.
이 경우는 캐릭터 이미지의 제일 좌상의 픽셀은 마스크 칼라여야 하는 제약이
따릅니다. ··하지만, 거기까지 케릭터 이미지가 될 일은 적기 때문에 우선 괜찮습니다.
■제일 좌상 픽셀의 칼라 값을 얻으려면?
이것은 간단합니다.
서페이스를 잠그고 「DDSURFACEDESC 구조체 lpSurface 멤버」의 주소의 내용을 취득합니다.
DDSURFACEDESC ddsd;
memset(&ddsd, 0, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
//서페이스를 잠급니다
surface->Lock(NULL, &ddsd, DDLOCK_WAIT |
DDLOCK_SURFACEMEMORYPTR, NULL);
char *p=(char *) ddsd.lpSurface; //좌상각의 색을 투과 색으로 합니다
DWORD transparent=*(DWORD *) p;
소스는 이렇게 되겠지요.
■처리의 흐름
LPBgp =(WORD*) ddsds.lpSurface;//좌상각의 색을 투과색으로 합니다
for(int y=0;y<32;y++)
{
Chatop = (BYTE*) LpCha;
Bgtop = (BYTE*) LpBg;
for(int x=0;x<32;x++){//캐릭터 사이즈가 32*32이므로 그 루프
if(*LPBgp != *LpCha)
{
//알파 브랜드 처리
red_1 = *LpBg & Rmask;
red_2 = *LpCha & Rmask;
red_3 = ((red_1 + red_2)/2);
blue_1 = *LpBg & Bmask;
blue_2 = *LpCha & Bmask;
blue_3 = ((blue_1 + blue_2)/2);
green_1 = *LpBg & Gmask;
green_2 = *LpCha & Gmask;
green_3 = ((green_1 + green_2)/2);
*LpBg = (WORD)((red_3 & Rmask) |
(green_3 & Gmask)|(blue_3 & Bmask));
}
LpBg++;
LpCha++;
//마지막
}
LpBg = (WORD*)(Bgtop+ddsd.lPitch);
LpCha = (WORD*)(Chatop+ddsds.lPitch);
}
적색 문자의 부분이 추가되었습니다.
if(transparent != *LpCha)
{
의 처리는 투과 하는 칼라 값(transparent)과 캐릭터의 픽셀 칼라 값을
비교해서 「같지 않을 때」if안을 처리합니다.
동일할 때는 그대로 포인터가 나갑니다.
결론적으로 백 버퍼는 아무것도 변화가 없고 그 부분은 투과 된 형태가 됩니다.
코드
내용은 전회의 소스의 알파 브랜드 함수와 아래의 처리를 바꿔 준다면 아무런 문제가 없을 것입니다.
//이 함수는 서페이스를 잠근(록) 메모리를 하나씩 참조하면서
//대상의 서페이스에 묘화 해 나간다.
//Blt를 사용하는 방법과 비교하면···스피드는 불명
BOOL LockBlt(void)
{
DWORD Rmask;//마스크 데이터
DWORD Bmask;
DWORD Gmask;
DDPIXELFORMAT pixel;
WORD *LpBg;//배경의 맵(백 버퍼)의 포인터
WORD *LpCha;//배경에 거듭하는 캐릭터 서페이스의 포인터
BYTE *Chatop;//투과 대상의 첫번째 픽셀의 주소
BYTE *Bgtop;
WORD *LPBgp;
DDSURFACEDESC2 ddsd; //lock할 때의 데이터를 격납하는 구조체
DDSURFACEDESC2 ddsds; //lock할 때의 데이터를 격납하는 구조체
DWORD red_1, red_2, red_3; //red
DWORD green_1, green_2, green_3; //green
DWORD blue_1, blue_2, blue_3; //blue
ZeroMemory(&ddsd, sizeof(ddsd)); //데이터 구조체를 제로 클리어
ddsd.dwSize = sizeof(ddsd); //구조체의 사이즈를 건네준다
ZeroMemory(&ddsds, sizeof(ddsds)); //데이터 구조체를 제로 클리어
ddsds.dwSize = sizeof(ddsds); //구조체의 사이즈를 건네준다
pixel.dwSize=sizeof(pixel);
if(sprite->GetPixelFormat(&pixel)!=DD_OK) return FALSE;
Rmask=pixel.dwRBitMask;
Bmask=pixel.dwBBitMask;
Gmask=pixel.dwGBitMask;
//초기 처리
if(pixel.dwRGBBitCount!=16) return false;
//칼라 모드가 16비트 이외일 때 처리를 중지
if(BackSurface->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL) == DD_OK)
{
if(sprite->Lock(NULL, &ddsds, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR , NULL) == DD_OK)
{
//양서페이스를 잠근다
LpBg = (WORD*) ddsd.lpSurface;
//주소를 취득(잠근 백 버퍼)
LpCha = (WORD*) ddsds.lpSurface;
//주소를 취득(잠근 오프서페이스)
LPBgp =(WORD*) ddsds.lpSurface;
//좌상각의 색을 투과색으로
//DWORD *transparent=(DWORD *) p;
//Ppixel에는 잠근 서페이스의 좌상첫번째 도트의 정보에 주소가
// 들어간다
for(int y=0;y<32;y++)
{
Chatop = (BYTE*) LpCha;
Bgtop = (BYTE*) LpBg;
for(int x=0;x<32;x++)
{
//캐릭터 사이즈가 32*32이므로 그 루프
if(*LPBgp != *LpCha)
{
//알파 브랜드 처리
red_1 = *LpBg & Rmask;
red_2 = *LpCha & Rmask;
red_3 = ((red_1 + red_2)/2);
blue_1 = *LpBg & Bmask;
blue_2 = *LpCha & Bmask;
blue_3 = ((blue_1 + blue_2)/2);
green_1 = *LpBg & Gmask;
green_2 = *LpCha & Gmask;
green_3 = ((green_1 + green_2)/2);
*LpBg = (WORD)((red_3 & Rmask) | (green_3 & Gmask)|(blue_3 & Bmask));
}
LpBg++;
LpCha++;
}
LpBg = (WORD*)(Bgtop+ddsd.lPitch);
LpCha = (WORD*)(Chatop+ddsds.lPitch);
}
}
else
return FALSE;
}
else
return FALSE;
//서페이스의 Unlock
if(BackSurface->Unlock(NULL)!=DD_OK) return FALSE;
if(sprite->Unlock(NULL)!=DD_OK) return FALSE;
return TRUE;
}
"OpenGL / DirectX" 카테고리의 다른 글
- Introduction to Direct 3D (0)2007/04/29
- Direct X (Direct Draw3) (0)2007/04/05
- Direct X (Direct Draw2) (0)2007/04/05
- Direct X (Direct Draw) (0)2007/04/05
- 얼굴인식의 구현과 유사도 판단-5 (0)2005/09/14

수안이의 컴퓨터 연구실



Leave your greetings.