前一陣子學習Win32 SDK 時自己寫過一個拼圖游戲作為練習,基本功能是都完成了,但到後來隨著代碼量的增多,代碼的組織上有點混亂。考慮到是第一次獨立用Win32 SDK編寫程序,就先放過了。但總覺的不爽。
最近看到一本名為《Windows 游戲編程大師技巧》的牛書,看到第一章打磚塊的示例,欲效仿其組織代碼的技巧,故將其源代碼分拆成容易理解的若干部分,修改抄錄於下,供他日重讀或供無緣見到此書者查閱。
程序版權歸原作者所有,吾僅是學習模仿,不敢掠美。
但源程序使用到了DirectX SDK,由於暫時不想涉及D3D,故改為用Win32 API 繪圖。當然以後有空,或會學習一下DirectX SDK~
我們需要的是下面幾個功能:
1)切換至任意圖像模式
2)在屏幕上繪制各種顏色的矩形
3)獲取鍵盤輸入
4)使用一些定時函數同步游戲循環
5)在屏幕上畫彩色字符串
1. 程序主框架(原程序為全屏隱藏鼠標,為調試簡單,暫時改為非全屏不隱藏鼠標)
[cpp]
/*
* FreakOut.cpp (改編自《Windows 游戲編程大師技巧》第一章示例程序)
* 2012-11-29
*/
/* INCLUDES *****************************************************************************/
#include <windows.h>
/* 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. 游戲控制流程
[cpp]
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繪制)
[cpp]
/* 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.時間控制
[cpp]
/* 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)擋板和分數都看不到
原因是原程序沒有標題欄,是全屏顯示的,現在有了標題欄,占了一定的空間,導致位置計算的不對,解決辦法是設置窗口大小時增加邊框和標題欄。
3)界面閃爍及耗費CPU
原因是每次都先繪制黑色屏幕清除全屏,然後重新繪制,較為浪費,且一直在循環,耗費CPU。
解決辦法是減少重繪區域,每次只繪制需要繪制的地方,而不是重繪全屏。
但此次只是為了學習這個框架,所以暫時只是每次循環取消息後睡眠10ms,這只能緩解閃爍和耗費CUP,並不能消除。
完整代碼:
[cpp]
/*
* FreakOut.cpp (改編自《Windows 游戲編程大師技巧》第一章示例程序)
* 2012-11-29
*/
/* INCLUDES *******************************************************************************/
#include <windows.h>
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
/* 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
#define BLOCK_COLOR RGB(125, 125, 0)
#define BALL_COLOR RGB(222, 222, 222)
// 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
// 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;
int cxNonClient = GetSystemMetrics(SM_CXBORDER)*2 + 10;
int cyNonClient = GetSystemMetrics(SM_CYBORDER) + GetSystemMetrics(SM_CYCAPTION) + 10;
// 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 + cxNonClient, // initial width
WINDOW_HEIGHT+ cyNonClient, // 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(10);
}
// 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);
}
// 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;
}
}
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, BALL_COLOR);
// draw the info
wsprintf(buffer, TEXT("FREAKOUT Score %d Level %d"), score, level);
DrawText_GUI(buffer, 8, WINDOW_HEIGHT-26, 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;
}
/* 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();
}