본문 바로가기
Lecture

Brick Breaker 게임 제작하기 - 1

by 작은별하나 2019. 12. 24.
반응형

벽돌깨기 게임 개요

 

벽돌깨기 게임은 아케이드 게임으로 오랜 역사를 가지고 있다. 초창기 컴퓨터 게임은 단순한 로직을 사용합니다벽돌깨기 게임은 그만큼 단순하기 때문에, 처음 게임을 개발하는 사람이면 한번쯤 개발을 시도해볼만한 좋은 게임입니다.  여기서는 기존에 나왔던 게임의 모티브를 따라서 게임을 제작했습니다.

 

알카노이드 게임

 

알카노이드(ARKANOID)은 1986년 타이토라는 게임 소프트웨어 회사가 만든 게임입니다.  당시 컴퓨터나 콘솔 게임기의 성능이 대단하지 않았지만, 벽돌깨기 게임을 즐기기에는 충분했었던 듯 합니다.  워낙 스테이지도 많고, 스테이지 격파 방식이었고, 벽돌을 깰때마다 떨어지는 특수 아이템으로 부가적인 기능을 적용할 수 있었지만, 여기에서는 본연의 벽돌깨기 기능만 구현토록 합니다.

 

 

목표하는 모습

 

벽돌깨기 게임에서는 움직이는 물체가 공(ball)과 패들입니다. 공은 자신이 움직이는 방향으로 움직이다가 벽, 블록, 패들 등을 만나면 반사된다. 또한 블록에 공이 부딪치게되면 해당 블록은 깨지게 된다. 패들을 움직여서 공이 아래로 빠지지 않게 조절하면서 가능한 많은 블록을 깨는 것이 이 게임의 목적이다.

 

여기에서는 키보드 커서키를 이용하여 패들을 좌우로 이동하여 볼을 받게 한다. 공의 충돌 모델을 간단하게 하기 위해서 패들에 부딪치는 부분에 상관없이 항상 들어온 각도로 반사되어 나가도록 한다.

 

Direct3D 개요

 

이 게임을 제작할 때 사용할 수 있는 플랫폼은 다양하게 존재한다. 그래픽이 들어가는 관계로 텍스트모드로의 개발은 불가능하다고 볼 수 있다. 일반적으로 Windows API를 사용하는 방법과 게임 전용 라이브러리라 할 수 있는 Direct3D를 사용하는 방법이 있다. 본 교재에서는 Direct3D를 이용해서 게임을 제작하도록 한다.

 

Direct3D는 마이크로소프트사에서 게임을 제작하기 위해서 만들어진 윈도우용 라이브러리이다. 게임은 빠른 그래픽 처리 속도를 요구하기 때문에 기존의 Windows API를 이용하는데에는 한계가 따른다. 블록격파 게임은 2D 게임이기 때문에 Direct3D 중에 2D와 관련된 부분을 알아보고, 이를 이용하여 게임을 제작토록 한다.

 

Direct3D 초기화

 

Direct3D는 기본적으로 인터페이스 객체(Interface Object)라는 개념을 이용한다. 이 중 중요한 인터페이스 객체는 IDirect3D9IDirect3DDevice9이 있다. 이 두개의 객체를 생성하는 것이 Direct3D를 초기화하는 부분이 된다.

 

Direct3D를 초기화하기 위해서는 기본적으로 윈도우가 생성되어 있어야 한다. 윈도우 생성은 Visual C/C++ 에서 제공하는 템플릿을 이용토록 한다.

 

Direct3D 인터페이스 객체 중 가장 먼저 생성해야 하는 것은 IDirect3D9 이다. IDirect3D9 인터페이스 객체는 Direct3D 컨셉 레이어 객체로 Direct3D 라이브러리를 검사하고 로딩하는 역할을 한다. IDirect3D9 객체는 다음과 같은 코드로 생성이 가능하다. 그리고 소멸을 할 때에는 Release() 메소드 함수를 사용한다.

 

//	Direct3D를사용하기위한헤더파일포함
#include <d3d9.h>
#include <d3dx9.h>

//	Direct3D 인터페이스객체
IDirect3D9 *pD3D;

//	Direct3D 인터페이스객체생성
pD3D = Direct3DCreate9(D3D_SDK_VERSION);

//	Direct3D 인터페이스객체소멸
pD3D->Release();

 

IDirect3D9 인터페이스 객체가 Direct3D 라이브러에 관한 것이라면, 그래픽 카드에 연결되는 것은 IDirect3DDevice9 인터페이스 객체이다. IDirect3DDevice9 인터페이스 객체는 그래픽 카드 설정과 관련되어 있기 때문에 사용 방법이 매우 복잡하다. 화면을 설정하기 위해서 화면 설정 구조체인 D3DPRESENT_PARAMETER 구조체의 내용을 올바르게 채워주어야 한다. 여기서는 일반적인 2D 게임에서 사용되는 초기화 방법을 소개한다.

 

//	Direct3D Device 인터페이스객체
IDirect3DDevice9 *pD3DDev;

//	화면설정파라미터
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));		//	기본값으로초기화
d3dpp.Windowed = TRUE;				//	윈도우모드
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;	//	스웝이펙트

//	Direct3D Device 인터페이스객체생성
pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, 
	D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pD3DDev);

//	Direct3D Device 인터페이스객체소멸
pD3DDev->Release();

 

Direct3D 폴리곤

 

폴리곤을 이루는 구성요소는 꼭지점(vertex), 모서리(edge), (surface)이다. 삼각형은 면을 이루는 최소의 꼭지점을 가지고 있다. 또한 삼각형만이 일직선상에 있지 않는 어떤 꼭지점 좌표가 주어지더라도 평면(plain)을 이룰 수 있다. 이러한 이유로 Direct3D에서는 삼각형을 면을 표현하기 위한 기본 단위로 사용하고 있다. 세 개의 꼭지점과 세 개의 모서리가 있으면 한 개의 면이 생성된다.

 

(1) 꼭지점(vertex)

 

Direct3D에서는 꼭지점에 대한 정보를 프로그래머가 지정할 수 있다. 2D 어플리케이션의 경우에는 꼭지점의 좌표, 꼭지점의 색깔, 그리고 텍스처의 좌표 값을 지정하는 것이 일반적이다. 꼭지점의 정보를 구성하기 위해서 다음과 같이 프로그램에서 설정해주어야 한다.

 

#define	D3DFVF_CUSTOM	(D3DFVF_XYZRHW | D3DFVF_TEX1)
struct CustomVertex
{
	D3DXVECTOR4 pos;
	D3DXVECTOR2 tex1;
};

 

XYZRHW는 꼭지점의 좌표값을 나타내며, 가장 필수적인 정보이다. 2차원의 경우 위치 지정을 위한 x, y2차원 좌표만 필요하다고 할 수 있겠지만, 실제로는 4개의 좌표로 이루어진다. rhw는 일반적으로 1.0f로 설정된다. TEX1은 첫 번째 텍스처에 대한 좌표값으로 텍스처를 사용할 때에는 반드시 사용되어야 한다.

 

꼭지점 정보는 그래픽 카드에 꼭지점 버퍼를 생성하여 저장한다. 그림을 그릴 때에는 꼭지점 버퍼의 핸들 값을 넘겨줌으로써 꼭지점 정보를 매번 보내주지 않아도 된다. 꼭지점 정보를 저장할 버퍼는 다음과 같이 생성한다.

 

//	정점 버퍼의 생성 
IDIRECT3DVERTEXBUFFER9 *pVB;
pD3DDev->CreateVertexBuffer( 4*sizeof(CustomVertex), 0,D3DFVF_CUSTOM, D3DPOOL_DEFAULT, &pVB, NULL);

 

2D 어플리케이션은 사각형을 가지고 대부분의 그래픽을 처리하게 된다. 그래서 4개의 꼭지점 정보가 필요하기 때문에 4*sizeof(CustomVertex) 바이트만큼의 버퍼를 생성토록 한다.

 

정점 버퍼를 생성하였으면, 정점 버퍼에 꼭지점 정보를 기록해야 한다. 가로축과 세로축에 평행한 직사각형은 left, top, right, bottom의 네 개의 정보만 있으면 가능하다. 정점 버퍼에 정보를 기록하기 위해서는 버퍼 잠금(lock)과 버퍼 해제(unlock)이라는 과정이 필요하다.

 

CustomVertex *pVerts;
//	정점버퍼에내용을기록하기위하여버퍼잠그기
pVB->Lock(0, 4*sizeof(CustomVertex), (void **)&pVerts, 0);
pVerts[0].pos = D3DXVECTOR4(left, bottom, 0, 1);
pVerts[0].tex = D3DXVECTOR2(0, 1);
pVerts[1].pos = D3DXVECTOR4(left, top, 0, 1);
pVerts[1].tex = D3DXVECTOR2(0, 0);
pVerts[2].pos = D3DXVECTOR4(right, bottom, 0, 1);
pVerts[2].tex = D3DXVECTOR2(1, 1);
pVerts[3].pos = D3DXVECTOR4(right, top, 0, 1);
pVerts[3].tex = D3DXVECTOR2(1, 0);
pVB->Unlock();

 

(2) 모서리(edge)

 

모서리를 만드는 방법은 여러 가지가 있다. 그 중에서 삼각형과 같이 면 단위로 모서리를 만드는 경우 일반적으로 세 가지 방식이 많이 사용된다.

 

첫 번째는 세 개의 꼭지점을 지정하여 매번 삼각형을 만들어주는 방식이다. 이 경우 n개의 삼각형을 표시하기 위해서 3n 개의 꼭지점을 지정해주어야 한다. 이 방식을 TRIANGLELIST 라고 부른다.

 

두 번째는 이웃한 삼각형끼리는 두 개의 꼭지점을 공유하고 있기 때문에 띠(strip) 형태의 경우에는 n개의 삼각형을 표시하기 위해서 n+2개의 꼭지점을 지정하면 된다. 앞의 경우보다 훨씬 적은 꼭지점의 수가 필요하다. 이 방식을 TRIANGLESTRIP 이라고 부른다. 블복격파 게임에서 이 방식을 사용하도록 한다.

 

Triangle Strip

세 번째는 두 번째와 비슷하지만, 원이나 부채꼴(fan)과 같이 하나의 꼭지점을 모든 삼각형들이 공유하는 경우이다. 띠 형태와 마찬가지로 n개의 삼각형을 표시하기 위하여 n+2개의 꼭지점을 지정하면 된다. 이 방식을 TRIANGLEFAN 이라고 한다.

 

Triangle Fan

삼각형띠(triangle strip)을 이용하여 직사각형을 그리는 방법은 다음과 같다.

 

pD3DDev->SetFVF(D3DFVF_CUSTOM);
pD3DDev->SetStreamSource(0, pVB, 0, sizeof(CustomVertex));
pD3DDev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

 

SetStreamSource는 사용할 꼭지점 버퍼를 설정한다. DrawPrimitive에는 어떤 방식으로 꼭지점 버퍼 인덱스 위치부터 몇 개의 삼각형을 그릴 것인가를 지정한다.

 

Direct3D 텍스처

 

Direct3D 텍스처는 서피스(surface)라는 것을 이용하고 있다. 먼저 서피스에 그림 정보를 쓰고 그것을 폴리곤에 입히게 된다. 텍스처의 해상도와 상관없이 텍스처 위치 정보는 0-1사이로 정규화된다.

 

(1) 텍스처 생성

 

텍스처를 생성하는 방법은 여러 가지가 있을 수 있겠지만, 가장 많이 이용하는 방법은 그림 파일로부터 데이터를 읽어서 저장하는 방법이다. Direct3D 자체에는 그림 파일을 읽는 기능이 없고, Direct3D 유틸리티 라이브러리 함수를 이용해야 한다.

 

IDirect3DTexture9 은 텍스처 핸들로 다음과 같은 방법으로 파일로부터 텍스처 핸들을 생성할 수 있다. 텍스처 핸들은 다른 Direct3D 핸들과 마찬가지로 Release 함수를 통하여 반환해준다.

 

//	텍스처생성
IDirect3DTexture9 *pTex;
D3DXCreateTextureFromFile(pD3DDev, filename, &pTex);

 

D3DXCreateTextureFromFile 함수를 이용해서 filename에 그림 파일 이름을 넣으면 텍스처를 로딩하고 그 결과값을 pTex에 저장하게 된다.

 

(2) 텍스처 설정

 

폴리곤을 그리기 전에 텍스처 설정을 해줌으로써, 그려지는 폴리곤에 해당 텍스처가 입혀지게 된다.

 

pD3DDev->SetTexture(0, pTex);	//	텍스처설정

 

SetTexture 함수에 의해서 설정된 텍스처는 이 후 그리는 모든 폴리곤에 적용된다.

 

2차원 폴리곤 설정 및 표시 함수

 

Direct3D에서 2차원 폴리곤은 일반적으로 가로축과 세로축에 평행한 직사각형을 만든다. 직사각형을 표현하는데 필요한 정보는 left, top, right, bottom 이렇게 4가지만 있으면 된다. 4개의 값을 가지고 정점정보를 설정하고 그려주는 함수를 만들어보도록 한다.

MakeVertices

 

직사각형은 4개의 꼭지점을 가지고 있기 때문에 총 4개의 정점이 필요하다. 정점버퍼와 left, top, right, bottom 정보를 설정하는 MakeVertices 함수를 다음과 같이 작성하도록 한다.

 

void MakeVertices(IDirect3DVertexBuffer9 *pVB, int left, int top, int right, int bottom)
{
	CustomVertex *pVerts;
	pVB->Lock(0, 4*sizeof(CustomVertex), (void **)&pVerts, 0);
	pVerts[0].pos = D3DXVECTOR4(left, top, 0, 1);
	pVerts[0].tex1 = D3DXVECTOR2(0, 0);
	pVerts[1].pos = D3DXVECTOR4(right, top, 0, 1);
	pVerts[1].tex1 = D3DXVECTOR2(1, 0);
	pVerts[2].pos = D3DXVECTOR4(left, bottom, 0, 1);
	pVerts[2].tex1 = D3DXVECTOR2(0, 1);
	pVerts[3].pos = D3DXVECTOR4(right, bottom, 0, 1);
	pVerts[3].tex1 = D3DXVECTOR2(1, 1);
	pVB->Unlock();
}

 

DrawVertices

 

정점버퍼를 이용하여 그림을 그릴 때에는 여러가지 설정이 필요하다. 정점의 형식, 정점 버퍼, 텍스처를 설정한 후 DrawPrimitive 함수를 이용하여 직사각형 폴리곤을 그리게 된다.

 

void DrawVertices(IDirect3DDevice9 *pD3DDev, IDirect3DVertexBuffer9 *pVB, IDirect3DTexture9 *pTex)
{
	pD3DDev->SetFVF(D3DFVF_CUSTOM);
	pD3DDev->SetTexture(0, pTex);
	pD3DDev->SetStreamSource(0, pVB, 0, sizeof(CustomVertex));
	pD3DDev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
}

 

WinMain 함수 수정

 

블록격파 게임은 가로 450, 세로 520 해상도로 동작하게 만들게 된다. 그렇게 하기 위해서 윈도우 해상도를 수정해야 한다. 아울러 CreateWindow에서 생성한 윈도우 핸들을 WinMain 함수에서 사용해야 하므로 InitInstance 함수에 선언된 HWND hWnd; 부분을 전역 변수 영역에 옮겨야 한다.

 

// Global Variables:
HINSTANCE hInst;								// current instance
TCHAR szTitle[MAX_LOADSTRING];		// The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];	// the main window class name
HWND hWnd;				// main window handle
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	hInst = hInstance; // Store instance handle in our global variable
	RECT rt = { 0, 0, 450, 520 };
	AdjustWindowRect(&rt, WS_OVERLAPPEDWINDOW, FALSE);
	hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
	   CW_USEDEFAULT, 0, rt.right-rt.left, rt.bottom-rt.top, 
	   NULL, NULL, hInstance, NULL);
	if (!hWnd)
	{
		return FALSE;
	}
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	return TRUE;
}

 

다음으로 앞에서 기술한 Direct3D 초기화부와 종료부를 추가하도록 한다.

 

//	Direct3D 초기화
IDirect3D9 *pD3D;
IDirect3DDevice9 *pD3DDev;
pD3D = Direct3DCreate9(D3D_SDK_VERSION);

D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;

pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, 
	D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &pD3DDev);
pD3DDev->Release();
pD3D->Release();

윈도우는 이벤트 기반에서 동작하기 때문에 이벤트를 받고 그에 대한 처리를 하는 부분이 존재한다. 그런데 GetMessage 함수는 이벤트가 발생할 때까지 기다려주는 함수이므로 계속 움직여야하는 게임에서는 부적합하다. 여기서는 PeekMessage 함수를 사용토록 바꾼다.

 

// Main message loop:
msg.message = 0;
while (msg.message != WM_QUIT)
{
	if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	else
	{
		//	키보드입력처리
		//	업데이트부분
		//	렌더링상태설정
		pD3DDev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
		pD3DDev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
		pD3DDev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
		//	렌더링시작
		pD3DDev->Clear(0, NULL, D3DCLEAR_TARGET, 0, 1.0f, 0);
		pD3DDev->BeginScene();
		//	그림그리기
		//	레더링종료및스웝
		pD3DDev->EndScene();
		pD3DDev->Present(NULL, NULL, NULL, NULL);
	}
}

 

기본적으로 이와같이 작성하면 검정색 화면이 뜨게 된다. 이제 필요한 그래픽 요소를 위 소스에 추가하면 게임이 완성되게 된다.

 

728x90

'Lecture' 카테고리의 다른 글

슈팅게임 제작하기  (0) 2020.01.02
Brick Breaker 게임 제작하기 - 2  (0) 2019.12.26
Reversi 게임 제작하기 - 3  (1) 2019.12.22
Reversi 게임 제작하기 - 2  (1) 2019.12.19
Reversi 게임 제작하기 - 1  (0) 2019.12.19

댓글