Windows游戏设计(二)- 打砖块游戏 - 使用Win32 SDK

注:以下内容为学习笔记,多数是从书本、资料中得来,只为加深印象,及日后参考。然而本人表达能力较差,写的不好。因非翻译、非转载,只好选原创,但多数乃摘抄,实为惭愧。但若能帮助一二访客,幸甚!


前一阵子学习Win32 SDK 时自己写过一个拼图游戏作为练习,基本功能是都完成了,但到后来随着代码量的增多,代码的组织上有点混乱。考虑到是第一次独立用Win32 SDK编写程序,就先放过了。但总觉的不爽。

最近看到一本名为《Windows 游戏编程大师技巧》的牛书,看到第一章打砖块的示例,欲效仿其组织代码的技巧,故将其源代码分拆成容易理解的若干部分,修改抄录于下,供他日重读或供无缘见到此书者查阅。

程序版权归原作者所有,吾仅是学习模仿,不敢掠美。

但源程序使用到了DirectX SDK,由于暂时不想涉及D3D,故改为用Win32 API 绘图。当然以后有空,或会学习一下DirectX SDK~


我们需要的是下面几个功能:

1)切换至任意图像模式

2)在屏幕上绘制各种颜色的矩形

3)获取键盘输入

4)使用一些定时函数同步游戏循环

5)在屏幕上画彩色字符串


1. 程序主框架(原程序为全屏隐藏鼠标,为调试简单,暂时改为非全屏不隐藏鼠标)

/*
 * FreakOut.cpp (改编自《Windows 游戏编程大师技巧》第一章示例程序)
 * [email protected]
 * 2012-11-29
 */

/* INCLUDES *****************************************************************************/
#include 

/* DEFINES ******************************************************************************/
// defines for windows
#define WINDOW_CLASS_NAME	TEXT("WIN32CLASS")
#define WINDOW_WIDTH		640
#define WINDOW_HEIGHT		480

// states for game loop
#define GAME_STATE_INIT         0
#define GAME_STATE_START_LEVEL  1
#define GAME_STATE_RUN          2
#define GAME_STATE_SHUTDOWN     3
#define GAME_STATE_EXIT         4


/* GLOBALS *******************************************************************************/
HWND		main_window_handle	= NULL;				// save the window handle
HINSTANCE	main_instance		= NULL;				// save the instance
int			game_state			= GAME_STATE_INIT;	// starting state


/* WINDPROC ******************************************************************************/
LRESULT CALLBACK WindowProc(HWND	hwnd,
							UINT	msg,
							WPARAM	wparam,
							LPARAM	lparam)
{
	// this is the main message handler of the system
	PAINTSTRUCT	ps;
	HDC			hdc;

	switch (msg)
	{
	case WM_CREATE:
		{
			return 0;
		}

	case WM_PAINT:
		{
			hdc = BeginPaint(hwnd, &ps);
			EndPaint(hwnd, &ps);
			return 0;
		}

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

	default:
		break;
	}

	return DefWindowProc(hwnd, msg, wparam, lparam);
}


/* WINMAIN *******************************************************************************/
int WINAPI WinMain(HINSTANCE hinstance,
				   HINSTANCE hprevinstance,
				   LPSTR lpcmdline,
				   int ncmdshow)
{
	WNDCLASS	winclass;
	HWND		hwnd;
	MSG			msg;
	HDC			hdc;
	PAINTSTRUCT	ps;

	/* CS_DBLCLKS Specifies that the window should be notified of double clicks with 
	 * WM_xBUTTONDBLCLK messages
	 * CS_OWNDC标志,属于此窗口类的窗口实例都有自己的DC(称为私有DC),私有DC仅属于该窗口实例,
	 * 所以程序只需要调用一次GetDC或BeginPaint获取DC,系统就为窗口初始化一个DC,并且保存程序
	 * 对其进行的改变。ReleaseDC和EndPaint函数不再需要了,因为其他程序无法访问和改变私有DC。
	 */
	winclass.style			= CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	winclass.lpfnWndProc	= WindowProc;
	winclass.cbClsExtra		= 0;
	winclass.cbWndExtra		= 0;
	winclass.hInstance		= hinstance;
	winclass.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
	winclass.hCursor		= LoadCursor(NULL, IDC_ARROW);
	winclass.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
	winclass.lpszMenuName	= NULL;
	winclass.lpszClassName	= WINDOW_CLASS_NAME;

	// register the window class
	if (!RegisterClass(&winclass))
		return 0;

	// Create the window, note the use of WS_POPUP
	hwnd = CreateWindow(
		WINDOW_CLASS_NAME,			// class
		TEXT("WIN32 Game Console"),	// title
		WS_OVERLAPPEDWINDOW,		// style
		0, 0,						// initial x, y	
		WINDOW_WIDTH,				// initial width
		WINDOW_HEIGHT,				// initial height
		NULL,						// handle to parent
		NULL,						// handle to menu
		hinstance,					// instance
		NULL);						// creation parms

	if (! hwnd)
		return 0;

	ShowWindow(hwnd, ncmdshow);
	UpdateWindow(hwnd);

	// hide mouse
	//ShowCursor(FALSE);

	// save the window handle and instance in a global
	main_window_handle	= hwnd;
	main_instance		= hinstance;

	// perform all game console specific initialization
	//Game_Init();

	// enter main event loop
	while (1)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			// test if this is a quit msg
			if (msg.message == WM_QUIT)
				break;

			// translate any accelerator keys
			TranslateMessage(&msg);

			// send the message to the window proc
			DispatchMessage(&msg);
		}

		// main game processing goes here
		//Game_Main();
	}

	// shutdown game and release all resources
	//Game_Shutdown();

	// show mouse
	//ShowCursor(TRUE);

	// return to windows like this
	return (msg.wParam);
}


2.  游戏控制流程

int Game_Init(void *parms)
{
	// this function is where you do all the initialization for your game
	
	return 1;
}

int Game_Shutdown(void *parms)
{
	// this function is where you shutdown your game and release all resources 
	// that you allocated
	
	return 1;
}

int Game_Main(void *parms)
{
	// this is the workhorse of your game it will be called continuously in real-time
	// this is like main() in C all the calls for you game go here!

	TCHAR buffer[80];

	// what state is the game in?
	if (game_state == GAME_STATE_INIT)
	{
		// seed the random number generator so game is different each play
		srand((unsigned int)time(0));

		// set the paddle position here to the middle bottom
		paddle_x = PADDLE_START_X;
		paddle_y = PADDLE_START_Y;

		// set ball position and velocity
		ball_x = 8 + rand() % (WINDOW_WIDTH-16);
		ball_y = BALL_START_Y;
		ball_dx = -4 + rand() % (8+1);
		ball_dy =  6 + rand() % 2;

		// transition to start level state
		game_state = GAME_STATE_START_LEVEL;
	}
	else if (game_state == GAME_STATE_START_LEVEL)
	{
		// get a new level ready to run

		// initialize the blocks
		Init_Blocks();

		// reset block counter
		blocks_hit = 0;

		// transition to run state
		game_state = GAME_STATE_RUN;
	}
	else if (game_state == GAME_STATE_RUN)
	{
		// start the timing clock
		Start_Clock();

		// clear drawing surface for next frame of animation
		Draw_Rectangle(0, 0, WINDOW_WIDTH-1, WINDOW_HEIGHT-1, 0);

		// move the paddle
		if (KEY_DOWN(VK_RIGHT))
		{
			// move paddle to right
			paddle_x += 8;

			// make sure paddle doesn't go off screen
			if (paddle_x > (WINDOW_WIDTH - PADDLE_WIDTH))
				paddle_x = WINDOW_WIDTH - PADDLE_WIDTH;
		}
		else if (KEY_DOWN(VK_LEFT))
		{
			// move paddle to left
			paddle_x += 8;

			// make sure paddle doesn't go off screen
			if (paddle_x < 0)
				paddle_x = 0;
		}

		// draw blocks
		Draw_Blocks();

		// move the ball
		ball_x += ball_dx;
		ball_y += ball_dy;

		// keep ball on screen, if the ball hits the edge of screen then
		// bounce it by reflecting its velocity
		if (ball_x > (WINDOW_WIDTH - BALL_SIZE) || ball_x < 0)
		{
			// reflect x-axis velocity
			ball_dx = -ball_dx;

			// update position
			ball_x += ball_dx;
		}

		// now y-axis
		if (ball_y < 0)
		{
			// reflect y-axis velocity
			ball_dy = -ball_dy;

			// update position
			ball_y += ball_dy;
		}
		else if (ball_y > (WINDOW_HEIGHT - BALL_SIZE))
		{
			// reflect y-axis velocity
			ball_dy = -ball_dy;

			// update position
			ball_y += ball_dy;

			// minus the score
			score -= 100;
		}

		// next watch out for ball velocity getting out of hand
		if (ball_dx > 8) 
			ball_dx = 8;
		else if (ball_dx < -8)
			ball_dx = -8;

		// test if ball hit any blocks or the paddle
		Process_Ball();


		// draw the paddle
		Draw_Rectangle(paddle_x, paddle_y, paddle_x+PADDLE_WIDTH, paddle_y+PADDLE_HEIGHT, PADDLE_COLOR);


		// draw the ball
		Draw_Rectangle(ball_x, ball_y, ball_x+BALL_SIZE, ball_y+BALL_SIZE, 255);


		// draw the info
		wsprintf(buffer, TEXT("FREAKOUT			Score %d				Level %d"), score, level);
		DrawText_GUI(buffer, 8, WINDOW_HEIGHT-16, 127);

		// sync to 30 fps
		Wait_Clock(30);

		// check if user is trying to exit
		if (KEY_DOWN(VK_ESCAPE))
		{
			// send message to windows to exit
			PostMessage(main_window_handle, WM_DESTROY, 0, 0);

			// set exit state
			game_state = GAME_STATE_SHUTDOWN;
		}
	}
	else if (game_state == GAME_STATE_SHUTDOWN)
	{
		// in this state shut everything down and release resources

		// switch to exit state
		game_state = GAME_STATE_EXIT;
	}

	return 1;
}

3.小球的控制和绘制(原程序为directDraw绘制,此处改为Win32 API绘制)

/* DRAW FUNCTION **************************************************************************/
int Draw_Rectangle(int x1, int y1, int x2, int y2, int color)
{
	// this function uses Win32 API to draw a filled rectangle
	HBRUSH		hbrush;
	HDC			hdc;
	RECT		rect;

	SetRect(&rect, x1, y1, x2, y2);

	hbrush = CreateSolidBrush(color);
	hdc = GetDC(main_window_handle);

	FillRect(hdc, &rect, hbrush);
	ReleaseDC(main_window_handle, hdc);
	DeleteObject(hbrush);

	return 1;
}

int DrawText_GUI(TCHAR *text, int x, int y, int color)
{
	HDC	hdc;

	hdc = GetDC(main_window_handle);

	// set the colors for the text up
	SetTextColor(hdc, color);

	// set background mode to transparent so black isn't copied
	SetBkMode(hdc, TRANSPARENT);

	// draw the text
	TextOut(hdc, x, y, text, lstrlen(text));

	// release the dc
	ReleaseDC(main_window_handle, hdc);

	return 1;
}


/* GAME PROGRAMMING CONSOLE FUNCTIONS *****************************************************/
void Init_Blocks(void)
{
	// initialize the block field
	for (int row = 0; row < NUM_BLOCK_ROWS; row++)
		for (int col = 0; col < NUM_BLOCK_COLUMNS; col++)
			blocks[row][col] = row*16 + col*3 + 16;
}

void Draw_Blocks(void)
{
	// this function draws all the blocks in row major form
	int x1 = BLOCK_ORIGIN_X;
	int y1 = BLOCK_ORIGIN_Y;

	// draw all the blocks
	for (int row = 0; row < NUM_BLOCK_ROWS; row++)
	{
		// reset column position
		x1 = BLOCK_ORIGIN_X;
		
		for (int col = 0; col < NUM_BLOCK_COLUMNS; col++)
		{
			if (blocks[row][col] != 0)
			{
				Draw_Rectangle(x1, y1, x1+BLOCK_WIDTH, y1+BLOCK_HEIGHT, blocks[row][col]);
			}

			// advance column position
			x1 += BLOCK_X_GAP;
		}

		// advance to next row position
		y1 += BLOCK_Y_GAP;
	}
}

void Process_Ball(void)
{
	// this function tests if the ball has hit a block or the paddle if so, the ball is bounced 
	// and the block is removed from  the playfield note: very cheesy collision algorithm :)

	// first test for ball block collisions

	// the algorithm basically tests the ball against each block's bounding box this is inefficient, 
	// but easy to implement, later we'll see a better way

	// current rendering position
	int x1 = BLOCK_ORIGIN_X;
	int y1 = BLOCK_ORIGIN_Y;

	// computer center of ball
	int ball_cx = ball_x + (BALL_SIZE/2);
	int ball_cy = ball_y + (BALL_SIZE/2);

	// test the ball has hit the paddle
	if (ball_y > (WINDOW_HEIGHT/2) && ball_dy > 0)
	{
		// extract leading edge of ball
		int x = ball_x + (BALL_SIZE/2);
		int y = ball_y + (BALL_SIZE/2);

		// test for collision with paddle
		if ((x >= paddle_x && x <= paddle_x + PADDLE_WIDTH) &&
			(y >= paddle_y && y <= paddle_y + PADDLE_HEIGHT))
		{
			// reflect ball
			ball_dy = -ball_dy;

			// push ball out of paddle since it made contact
			ball_y += ball_dy;

			// add a little english to ball based on motion of paddle
			if (KEY_DOWN(VK_RIGHT))
				ball_dx -= rand() % 3;
			else if (KEY_DOWN(VK_LEFT))
				ball_dx += rand() % 3;
			else
				ball_dx += (-1 + rand() % 3);

			// test if there are no blocks, if so send a message to game loop to start another level
			if (blocks_hit >= (NUM_BLOCK_ROWS * NUM_BLOCK_COLUMNS))
			{
				game_state = GAME_STATE_START_LEVEL;
				level++;
			}

			// make a little noise
			MessageBeep(MB_OK);

			return;
		}
	}

	// now scan thru all the blocks and see of ball hit blocks
	for (int row = 0; row < NUM_BLOCK_ROWS; row++)
	{
		x1 = BLOCK_ORIGIN_X;

		// scan this row of blocks
		for (int col = 0; col < NUM_BLOCK_COLUMNS; col++)
		{
			if (blocks[row][col] != 0)
			{
				// test ball against bounding box of block
				if ((ball_cx > x1) && (ball_cx < x1 + BLOCK_WIDTH) &&
					(ball_cy > y1) && (ball_cy < y1 + BLOCK_HEIGHT))
				{
					// remove the block
					blocks[row][col] = 0;

					// increment global block counter, so we know when to start another level up
					blocks_hit++;

					// bounce the ball
					ball_dy = -ball_dy;

					// add a little english
					ball_dx += (-1 + rand() % 3);

					// make a little noise
					MessageBeep(MB_OK);

					// add some points
					score += 5 * (level + (abs)(ball_dx));

					return;
				}
			}

			// advance column position
			x1 += BLOCK_X_GAP;
		}

		// advance row position
		y1 += BLOCK_Y_GAP;
	}
}

4.时间控制

/* CLOCK FUNCTIONS ************************************************************************/
DWORD Get_Clock(void)
{
	// this function returns the current tick count

	// return time
	return GetTickCount();
}

DWORD Start_Clock(void)
{
	// this function starts the block, that is, saves the current count,
	// use in conjunction with Wait_Clock()

	return (start_clock_count = Get_Clock());
}

DWORD Wait_Clock(DWORD count)
{
	// this function is used to wait for a specific number of clicks since
	// the call to Start_Clock

	while (Get_Clock() - start_clock_count < count)
		;

	return Get_Clock();
}



5. 发现的问题及解决方案

1)颜色太暗:

原因可能是int 到RGB转换的问题,解决办法是传参数时直接用RGB构造。


2)挡板和分数都看不到

原因是原程序没有标题栏,是全屏显示的,现在有了标题栏,占了一定的空间,导致位置计算的不对,修改窗口style 为WS_POPUP即可。


3)界面闪烁及耗费CPU

原因是每次都先绘制黑色屏幕清除全屏,然后重新绘制,较为浪费,且一直在循环,耗费CPU。

解决办法是减少重绘区域,每次只绘制需要绘制的地方,而不是重绘全屏。修改使用死循环来耗费时间,改为Sleep。


完整代码:

/*
 * FreakOut.cpp (改编自《Windows 游戏编程大师技巧》第一章示例程序)
 * [email protected]
 * 2012-11-29
 */

/* INCLUDES *******************************************************************************/
#include 
#include 
#include 
#include 


/* DEFINES ********************************************************************************/
// defines for windows
#define WINDOW_CLASS_NAME	TEXT("WIN32CLASS")
#define WINDOW_WIDTH		640
#define WINDOW_HEIGHT		480

// states for game loop
#define GAME_STATE_INIT         0
#define GAME_STATE_START_LEVEL  1
#define GAME_STATE_RUN          2
#define GAME_STATE_SHUTDOWN     3
#define GAME_STATE_EXIT         4

// block defines
#define NUM_BLOCK_ROWS          6
#define NUM_BLOCK_COLUMNS       8

#define BLOCK_WIDTH             64
#define BLOCK_HEIGHT            16
#define BLOCK_ORIGIN_X          8
#define BLOCK_ORIGIN_Y          8
#define BLOCK_X_GAP             80
#define BLOCK_Y_GAP             32


// paddle defines
#define PADDLE_START_X          (WINDOW_WIDTH/2 - 16)
#define PADDLE_START_Y          (WINDOW_HEIGHT - 32);
#define PADDLE_WIDTH            32
#define PADDLE_HEIGHT           8
#define PADDLE_COLOR            RGB(0, 0, 255)

// ball defines
#define BALL_START_Y            (WINDOW_HEIGHT/2)
#define BALL_SIZE                4

// color defines
#define BACKGROUND_COLOR		RGB(0, 0, 0)
#define BLOCK_COLOR				RGB(125, 0, 0)
#define BALL_COLOR				RGB(222, 0, 222)

// these read the keyboard asynchronously
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code)   ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)


/* basic unsigned types *******************************************************************/
typedef unsigned short USHORT;
typedef unsigned short WORD;
typedef unsigned char  UCHAR;
typedef unsigned char  BYTE;


/* FUNCTION DECLARATION *******************************************************************/
int Game_Init(void *parms = NULL);
int Game_Shutdown(void *parms = NULL);
int Game_Main(void *parms = NULL);

DWORD Start_Clock(void);
DWORD Wait_Clock(DWORD count);


/* GLOBALS *********************************************************************************/
HWND		main_window_handle	= NULL;				// save the window handle
HINSTANCE	main_instance		= NULL;				// save the instance
int			game_state			= GAME_STATE_INIT;	// starting state

int			paddle_x = 0, paddle_y = 0;				// tracks position of paddle
int			ball_x   = 0, ball_y   = 0;				// tracks position of ball
int			ball_dx  = 0, ball_dy  = 0;				// velocity of ball
int			score    = 0;							// the score
int			level    = 1;							// the current level
int			blocks_hit = 0;							// tracks number of blocks hit

DWORD		start_clock_count	= 0;				// used for timing

// this contains the game grid data
UCHAR blocks[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS];    


/* WINDPROC ********************************************************************************/
LRESULT CALLBACK WindowProc(HWND	hwnd,
							UINT	msg,
							WPARAM	wparam,
							LPARAM	lparam)
{
	// this is the main message handler of the system
	PAINTSTRUCT	ps;
	HDC			hdc;

	switch (msg)
	{
	case WM_CREATE:
		return 0;

	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;
	}

	return DefWindowProc(hwnd, msg, wparam, lparam);
}


/* WINMAIN ********************************************************************************/
int WINAPI WinMain(HINSTANCE hinstance,
				   HINSTANCE hprevinstance,
				   LPSTR lpcmdline,
				   int ncmdshow)
{
	WNDCLASS	winclass;
	HWND		hwnd;
	MSG			msg;
	HDC			hdc;
	PAINTSTRUCT	ps;

	/* CS_DBLCLKS Specifies that the window should be notified of double clicks with 
	 * WM_xBUTTONDBLCLK messages
	 * CS_OWNDC标志,属于此窗口类的窗口实例都有自己的DC(称为私有DC),私有DC仅属于该窗口实例,
	 * 所以程序只需要调用一次GetDC或BeginPaint获取DC,系统就为窗口初始化一个DC,并且保存程序
	 * 对其进行的改变。ReleaseDC和EndPaint函数不再需要了,因为其他程序无法访问和改变私有DC。
	 */
	winclass.style			= CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
	winclass.lpfnWndProc	= WindowProc;
	winclass.cbClsExtra		= 0;
	winclass.cbWndExtra		= 0;
	winclass.hInstance		= hinstance;
	winclass.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
	winclass.hCursor		= LoadCursor(NULL, IDC_ARROW);
	winclass.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
	winclass.lpszMenuName	= NULL;
	winclass.lpszClassName	= WINDOW_CLASS_NAME;

	// register the window class
	if (!RegisterClass(&winclass))
		return 0;

	// Create the window, note the use of WS_POPUP
	hwnd = CreateWindow(
		WINDOW_CLASS_NAME,			// class
		TEXT("WIN32 Game Console"),	// title
		WS_POPUP,					// style
		200,						// initial x
		100,						// initial y	
		WINDOW_WIDTH,				// initial width
		WINDOW_HEIGHT,				// initial height
		NULL,						// handle to parent
		NULL,						// handle to menu
		hinstance,					// instance
		NULL);						// creation parms

	if (! hwnd)
		return 0;

	ShowWindow(hwnd, ncmdshow);
	UpdateWindow(hwnd);

	// hide mouse
	//ShowCursor(FALSE);

	// save the window handle and instance in a global
	main_window_handle	= hwnd;
	main_instance		= hinstance;

	// perform all game console specific initialization
	Game_Init();

	// enter main event loop
	while (1)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			// test if this is a quit msg
			if (msg.message == WM_QUIT)
				break;

			// translate any accelerator keys
			TranslateMessage(&msg);

			// send the message to the window proc
			DispatchMessage(&msg);
		}

		// main game processing goes here
		Game_Main();
		Sleep(30);
	}

	// shutdown game and release all resources
	Game_Shutdown();

	// show mouse
	//ShowCursor(TRUE);

	// return to windows like this
	return (msg.wParam);
}


/* DRAW FUNCTION **************************************************************************/
int Draw_Rectangle(int x1, int y1, int x2, int y2, int color)
{
	// this function uses Win32 API to draw a filled rectangle
	HBRUSH		hbrush;
	HDC			hdc;
	RECT		rect;

	SetRect(&rect, x1, y1, x2, y2);

	hbrush = CreateSolidBrush(color);
	hdc = GetDC(main_window_handle);

	FillRect(hdc, &rect, hbrush);
	ReleaseDC(main_window_handle, hdc);
	DeleteObject(hbrush);

	return 1;
}

int DrawText_GUI(TCHAR *text, int x, int y, int color)
{
	HDC	hdc;

	hdc = GetDC(main_window_handle);

	// set the colors for the text up
	SetTextColor(hdc, color);

	// set background mode to transparent so black isn't copied
	SetBkMode(hdc, TRANSPARENT);

	// draw the text
	TextOut(hdc, x, y, text, lstrlen(text));

	// release the dc
	ReleaseDC(main_window_handle, hdc);

	return 1;
}


/* GAME PROGRAMMING CONSOLE FUNCTIONS *****************************************************/
void Init_Blocks(void)
{
	// initialize the block field
	for (int row = 0; row < NUM_BLOCK_ROWS; row++)
		for (int col = 0; col < NUM_BLOCK_COLUMNS; col++)
			blocks[row][col] = 1;
}

void Draw_Blocks(void)
{
	// this function draws all the blocks in row major form
	int x1 = BLOCK_ORIGIN_X;
	int y1 = BLOCK_ORIGIN_Y;

	// draw all the blocks
	for (int row = 0; row < NUM_BLOCK_ROWS; row++)
	{
		// reset column position
		x1 = BLOCK_ORIGIN_X;
		
		for (int col = 0; col < NUM_BLOCK_COLUMNS; col++)
		{
			if (blocks[row][col] != 0)
			{
				Draw_Rectangle(x1, y1, x1+BLOCK_WIDTH, y1+BLOCK_HEIGHT, BLOCK_COLOR);
			}
			else
			{
				Draw_Rectangle(x1, y1, x1+BLOCK_WIDTH, y1+BLOCK_HEIGHT, BACKGROUND_COLOR);
			}
			x1 += BLOCK_X_GAP;		// advance column position
		}
		y1 += BLOCK_Y_GAP;			// advance to next row position
	}
}

int Process_Ball(void)
{
	// this function tests if the ball has hit a block or the paddle if so, the ball is bounced 
	// and the block is removed from  the playfield note: very cheesy collision algorithm :)

	// first test for ball block collisions

	// the algorithm basically tests the ball against each block's bounding box this is inefficient, 
	// but easy to implement, later we'll see a better way

	// current rendering position
	int x1 = BLOCK_ORIGIN_X;
	int y1 = BLOCK_ORIGIN_Y;

	// computer center of ball
	int ball_cx = ball_x + (BALL_SIZE/2);
	int ball_cy = ball_y + (BALL_SIZE/2);

	// test the ball has hit the paddle
	if (ball_y > (WINDOW_HEIGHT/2) && ball_dy > 0)
	{
		// extract leading edge of ball
		int x = ball_x + (BALL_SIZE/2);
		int y = ball_y + (BALL_SIZE/2);

		// test for collision with paddle
		if ((x >= paddle_x && x <= paddle_x + PADDLE_WIDTH) &&
			(y >= paddle_y && y <= paddle_y + PADDLE_HEIGHT))
		{
			ball_dy = -ball_dy;		// reflect ball
			ball_y += ball_dy;		// push ball out of paddle since it made contact

			// add a little english to ball based on motion of paddle
			if (KEY_DOWN(VK_RIGHT))
				ball_dx -= rand() % 3;
			else if (KEY_DOWN(VK_LEFT))
				ball_dx += rand() % 3;
			else
				ball_dx += (-1 + rand() % 3);

			// make a little noise
			MessageBeep(MB_OK);

			return 0;
		}
	}

	// now scan thru all the blocks and see of ball hit blocks
	for (int row = 0; row < NUM_BLOCK_ROWS; row++)
	{
		x1 = BLOCK_ORIGIN_X;

		// scan this row of blocks
		for (int col = 0; col < NUM_BLOCK_COLUMNS; col++)
		{
			if (blocks[row][col] != 0)
			{
				// test ball against bounding box of block
				if ((ball_cx > x1) && (ball_cx < x1 + BLOCK_WIDTH) &&
					(ball_cy > y1) && (ball_cy < y1 + BLOCK_HEIGHT))
				{
					blocks[row][col] = 0;			// remove the block

					// increment global block counter, so we know when to start another level up
					blocks_hit++;				
					ball_dy = -ball_dy;				// bounce the ball				
					ball_dx += (-1 + rand() % 3);	// add a little english
					MessageBeep(MB_OK);				// make a little noise

					// test if there are no blocks, if so send a message to game loop to start another level
					if (blocks_hit >= (NUM_BLOCK_ROWS * NUM_BLOCK_COLUMNS))
					{
						game_state = GAME_STATE_START_LEVEL;
						level++;
					}

					// add some points
					score += 5 * (level + (abs)(ball_dx));

					return 1;
				}
			}
			x1 += BLOCK_X_GAP;		// advance column position
		}
		y1 += BLOCK_Y_GAP;			// advance row position
	}

	return 0;
}

int Game_Init(void *parms)
{
	// this function is where you do all the initialization for your game
	return 1;
}

int Game_Shutdown(void *parms)
{
	// this function is where you shutdown your game and release all resources 
	// that you allocated
	return 1;
}

int Game_Main(void *parms)
{
	// this is the workhorse of your game it will be called continuously in real-time
	// this is like main() in C all the calls for you game go here!
	TCHAR	buffer[80];
	BOOL	bPaddleMoved = FALSE;
	int		old_paddle_x, old_paddle_y;
	int		old_ball_x, old_ball_y;

	// what state is the game in?
	if (game_state == GAME_STATE_INIT)
	{
		// seed the random number generator so game is different each play
		srand((unsigned int)time(0));

		// set the paddle position here to the middle bottom
		paddle_x = PADDLE_START_X;
		paddle_y = PADDLE_START_Y;

		// set ball position and velocity
		ball_x =   8 + rand() % (WINDOW_WIDTH-16);
		ball_y = BALL_START_Y;
		ball_dx = -4 + rand() % (8+1);
		ball_dy =  6 + rand() % 2;

		// transition to start level state
		game_state = GAME_STATE_START_LEVEL;
	}
	else if (game_state == GAME_STATE_START_LEVEL)
	{
		// get a new level ready to run
		Init_Blocks();				// initialize the blocks
		blocks_hit = 0;				// reset block counter
		Draw_Blocks();				// draw blocks
		// draw the paddle
		Draw_Rectangle(paddle_x, paddle_y, paddle_x+PADDLE_WIDTH, paddle_y+PADDLE_HEIGHT, PADDLE_COLOR);
		game_state = GAME_STATE_RUN;// transition to run state
	}
	else if (game_state == GAME_STATE_RUN)
	{
		// move the paddle
		if (KEY_DOWN(VK_RIGHT))
		{
			old_paddle_x = paddle_x;
			old_paddle_y = paddle_y;
			
			paddle_x += 8;			// move paddle to right

			// make sure paddle doesn't go off screen
			if (paddle_x > (WINDOW_WIDTH - PADDLE_WIDTH))
				paddle_x = WINDOW_WIDTH - PADDLE_WIDTH;

			bPaddleMoved = TRUE;
		}
		else if (KEY_DOWN(VK_LEFT))
		{
			old_paddle_x = paddle_x;
			old_paddle_y = paddle_y;
			
			paddle_x -= 8;			// move paddle to left

			// make sure paddle doesn't go off screen
			if (paddle_x < 0)
				paddle_x = 0;

			bPaddleMoved = TRUE;
		}

		old_ball_x = ball_x;
		old_ball_y = ball_y;

		// move the ball
		ball_x += ball_dx;
		ball_y += ball_dy;

		// keep ball on screen, if the ball hits the edge of screen then
		// bounce it by reflecting its velocity
		if (ball_x > (WINDOW_WIDTH - BALL_SIZE) || ball_x < 0)
		{
			ball_dx = -ball_dx;		// reflect x-axis velocity
			ball_x += ball_dx;		// update position
		}

		// now y-axis
		if (ball_y < 0)
		{
			ball_dy = -ball_dy;		// reflect y-axis velocity
			ball_y += ball_dy;		// update position
		}
		else if (ball_y > (WINDOW_HEIGHT - BALL_SIZE))
		{
			ball_dy = -ball_dy;		// reflect y-axis velocity
			ball_y += ball_dy;		// update position		
			score -= 100;			// minus the score
		}

		// next watch out for ball velocity getting out of hand
		if (ball_dx > 8) 
			ball_dx = 8;
		else if (ball_dx < -8)
			ball_dx = -8;

		// test if ball hit any blocks or the paddle
		if (Process_Ball())
		{	
			Draw_Blocks();			// draw blocks
		}

		if (bPaddleMoved)
		{
			// 覆盖旧的paddle
			Draw_Rectangle(old_paddle_x, old_paddle_y, old_paddle_x+PADDLE_WIDTH, old_paddle_y+PADDLE_HEIGHT, 
				BACKGROUND_COLOR);
			// draw the paddle
			Draw_Rectangle(paddle_x, paddle_y, paddle_x+PADDLE_WIDTH, paddle_y+PADDLE_HEIGHT, PADDLE_COLOR);
		}

		// 覆盖旧的ball
		Draw_Rectangle(old_ball_x, old_ball_y, old_ball_x+BALL_SIZE, old_ball_y+BALL_SIZE, BACKGROUND_COLOR);
		// draw the ball
		Draw_Rectangle(ball_x, ball_y, ball_x+BALL_SIZE, ball_y+BALL_SIZE, BALL_COLOR);

		// draw the info
		wsprintf(buffer, TEXT("FREAKOUT            Score %d              Level %d"), score, level);
		Draw_Rectangle(8, WINDOW_HEIGHT-26, WINDOW_WIDTH, WINDOW_HEIGHT, BACKGROUND_COLOR);
		DrawText_GUI(buffer, 8, WINDOW_HEIGHT-26, RGB(255, 255, 128));

		// check if user is trying to exit
		if (KEY_DOWN(VK_ESCAPE))
		{
			// send message to windows to exit
			PostMessage(main_window_handle, WM_DESTROY, 0, 0);

			// set exit state
			game_state = GAME_STATE_SHUTDOWN;
		}
	}
	else if (game_state == GAME_STATE_SHUTDOWN)
	{
		// in this state shut everything down and release resources
		// switch to exit state
		game_state = GAME_STATE_EXIT;
	}

	return 1;
}



/* CLOCK FUNCTIONS ************************************************************************/
DWORD Get_Clock(void)
{
	// this function returns the current tick count
	// return time
	return GetTickCount();
}

DWORD Start_Clock(void)
{
	// this function starts the block, that is, saves the current count,
	// use in conjunction with Wait_Clock()

	return (start_clock_count = Get_Clock());
}

DWORD Wait_Clock(DWORD count)
{
	// this function is used to wait for a specific number of clicks since
	// the call to Start_Clock

	while (Get_Clock() - start_clock_count < count)
		;

	return Get_Clock();
}




不足之处:因为之前经常在linux下编程,用的是game_state类型的变量风格,最近在windows下想学所谓的匈牙利表示法,但这段代码作者用了game_state类型的变量风格,我又被俘获了。。。

编程风格要统一,不能总是变来变去的!切记。


你可能感兴趣的:(Win32,SDK)