Win32游戏制作之---FreakOut

来源:互联网 发布:库存软件免费版 编辑:程序博客网 时间:2024/05/22 11:24

       记得自己小时候曾经玩过这个小游戏,如今长大了,有了一定的知识就可以实现一些小时候未曾想过的事情,或者说梦想去做的事情!虽然这次实现的小游戏比较简单,但是也算游戏吧,比较自己还不是这方面的大神。

       如果想要用Win32项目制作一个小游戏,那么首先你要对对C/C++语言熟悉,对Windows编程有一定的了解。其实还有一点就是你在开始制作小游戏之前,你要知道你的游戏的逻辑结构。对于一般的小游戏结构可以用下图的结构表示。



       Game_main()是游戏的主体函数,不过它在主事件循环每次处理windows消息之后就会调用一次,要注意每次进入和终止该函数的时候,自动变量是瞬间变化的,如果想要一直使用的数据,建议使用全局变量,或者也可以设置为局部静态变量。

       恰好最近正在学Windows编程,想试试Windows是否真的适合制作游戏编程。最后在看了《Windows游戏编程大师技巧》之后,决定将里面的FreakOut游戏实现一下。

       由于平时用习惯了C/C++编程可能开始会有些不习惯,但是用多了就会发现其实最重要的就是要理解Windows编程中的消息驱动原理,简单的说其实就是Windows有其自己的基于事件的中枢神经系统。当我们按下一个键,就会有消息从按键事件中创建并且在系统中散播,直到有程序检出这个消息并使用它。当然Windows编程中的要注意的点还是挺多的,字符编码就是其中一个,不过解决起来的很简单(例如著名的“L”字符和TCHAR)。还有一点就是Windows编程中的主函数WinMain(),如果第一眼看到他,觉得很复杂,但是WinMain并不需要是一个又大又复杂的应用代码大杂烩。


相信大家都玩过FreakOut游戏(俗称打砖块游戏),游戏主要是利用小球消灭所有的砖块,所有的砖块大小相等,小球每次弹到砖块上都会发生反弹,按照物理上的说法就是发生弹性碰撞,当然,小球只能在屏幕(其实说窗口更准确)内运动,所以当小球碰到窗口的边界的时候就会发生反弹,假设小球做的是直线运动,根据速度的分解可知可以将小球的速度正交分解,分成一个X方向上的运动和一个Y方向上的运动。再碰到边界或者是砖块或者你跳板之后都会发生反弹,所以只要保证某一个方向上的速度不变,另一个方向上的速度反向即可。(不过我在具体实现的过程中没有完全按照物理学规律来制作,在碰到砖块或者跳板的时候让他们的速度有一点变化,这样有时候可以避免小球一直做同样的运动)。

下面就通过代码来解释:

首先来看下头文件FreakOut.h:

#pragma once#include "resource.h"//define for Windows                   窗口大小定义#define WINDOW_WIDTH                    640                #define WINDOW_HEIGHT                   480//state 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                   2#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(222,200,125)//ball defines                          有关小球的定义#define BALL_COLOR                       RGB(255,0,0)#define BALL_START_Y                     (WINDOW_HEIGHT/2)#define BALL_SIZE                        14//paddle defines                         跳板的定义#define PADDLE_START_X                   (WINDOW_WIDTH/2 - 16)#define PADDLE_START_Y                   (WINDOW_HEIGHT - 32 )#define PADDLE_WIDTH                     50#define PADDLE_HEIGHT                    8#define PADDLE_COLOR                     RGB(0,0,255)//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)//set bounds         设置关卡数#define Game_Count                        3//basic unsigned typestypedef unsigned short USHORT;typedef unsigned short WORD;typedef unsigned char UCHAR;typedef unsigned char BYTE;
头文件主要是一些游戏中一些对象的初始值的有关定义。可以按照个人喜好设置。

下面来看一下内部的实现逻辑,先贴上我的代码FreakOut.cpp:

// FreakOut.cpp : 定义应用程序的入口点。//#include "stdafx.h"#include "FreakOut.h"#define MAX_LOADSTRING 100// 全局变量: HINSTANCE hInst;// 当前实例TCHAR szTitle[MAX_LOADSTRING];// 标题栏文本TCHAR szWindowClass[MAX_LOADSTRING];// 主窗口类名HWND main_window_handle = NULL;                 //save the window handleHINSTANCE main_instance = NULL;                 //save the instanceint game_state = GAME_STATE_INIT;               //starting stateint paddle_x = 0, paddle_y = 0;                 //tracks position of handleint ball_x = 0, ball_y = 0;                     //tracks position of ballint ball_dx = 0, ball_dy = 0;                   //velocity of ballint score = 0;                                  //the scoreint level = 1;                                  //the Current levelint block_hit = 0;                              //tracks number of block hitDWORD start_clock_count = 0, now_clock = 0;     //used for timingUCHAR blocks[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS];//this contains the game grid data// 此代码模块中包含的函数的前向声明: ATOMMyRegisterClass(HINSTANCE hInstance);BOOLInitInstance(HINSTANCE, int);LRESULT CALLBACKWndProc(HWND, UINT, WPARAM, LPARAM);INT_PTR CALLBACKAbout(HWND, UINT, WPARAM, LPARAM);int                 Game_Init(void *parms = NULL);int                 Game_Shutdown(void *parms = NULL);int                 Game_Main(void *parms = NULL);DWORD               Start_Clock();DWORD               Get_Clock(void);DWORD               Wait_Clock(DWORD );void                Change_Mode(HWND, DWORD, int);int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,                     _In_opt_ HINSTANCE hPrevInstance,                     _In_ LPTSTR    lpCmdLine,                     _In_ int       nCmdShow){UNREFERENCED_PARAMETER(hPrevInstance);UNREFERENCED_PARAMETER(lpCmdLine); // TODO:  在此放置代码。MSG msg;HACCEL hAccelTable;WNDCLASSEX winclass;HWND hwnd;HDC hdc;PAINTSTRUCT ps;// 初始化全局字符串LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);LoadString(hInstance, IDC_FREAKOUT, szWindowClass, MAX_LOADSTRING);/*MyRegisterClass(hInstance);*/                  //注册窗口类winclass.cbSize = sizeof(WNDCLASSEX);winclass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;winclass.lpfnWndProc = WndProc;winclass.cbClsExtra = 0;winclass.cbWndExtra = 0;winclass.hInstance = hInstance;winclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);winclass.hCursor = LoadCursor(NULL, IDC_ARROW);winclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);winclass.lpszMenuName = MAKEINTRESOURCE(IDC_FREAKOUT);winclass.lpszClassName = szWindowClass;winclass.hIconSm = LoadIcon(winclass.hInstance, MAKEINTRESOURCE(IDI_SMALL));if (!RegisterClassEx(&winclass))                  //注册窗口类是否成功{return FALSE;}int cxNonClient = GetSystemMetrics(SM_CXBORDER) * 2 + 10;int cyNonClient = GetSystemMetrics(SM_CYBORDER) + GetSystemMetrics(SM_CYCAPTION) + 10;//Create the window, note the use of WS_POPUPhwnd = CreateWindowEx(NULL,szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, 0, 0, WINDOW_WIDTH + cxNonClient, WINDOW_HEIGHT + cyNonClient, NULL, NULL, hInstance, NULL);// 执行应用程序初始化if (!hwnd){return FALSE;}ShowWindow(hwnd, nCmdShow);      // WM_SIZE消息是由 ShowWindow函数发出的UpdateWindow(hwnd);       //hide mouse//ShowCursor(FALSE);//save the window handle and instance in a globalmain_window_handle = hwnd;main_instance = hInstance;hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_FREAKOUT));//perform all game console specific initializationGame_Init();// 主消息循环: while (true){if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)){//test if this is a quit msgif (msg.message == WM_QUIT){break;}//translate any accelerator keysTranslateMessage(&msg);//send the message to the window procDispatchMessage(&msg);}//main game processing goes hereGame_Main();Sleep(10);}return (int) msg.wParam;}/*DRAW Function ********** */int Draw_Rectangle(int x1, int y1, int x2, int y2, int color){//this function users Win32 API to draw a filled rectangleHBRUSH hbrush;HDC hdc;RECT rect;SetRect(&rect, x1, y1, x2, y2);hbrush = CreateSolidBrush(color);hdc = GetDC(main_window_handle);//SelectObject(hdc, hbrush);FillRect(hdc, &rect, hbrush);ReleaseDC(main_window_handle, hdc);DeleteObject(hbrush);return 1;}int Draw_Ball(int x1, int y1, int x2, int y2, int color){HDC hdc;HBRUSH hbrush;hbrush = CreateSolidBrush(color);hdc = GetDC(main_window_handle);SelectObject(hdc, hbrush);Ellipse(hdc, x1, y1, x2, y2);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 color for the text upSetTextColor(hdc, color);//set background mode to transparent so black isn't copied           SetBkMode(hdc, TRANSPARENT);                              //设置为透明的//draw the textTextOut(hdc, x, y, text, lstrlen(text));//release the dcReleaseDC(main_window_handle, hdc);return 1;}/*GAME PROGRAMMING CONSOLE FUNCTIONS *********************************/void Init_Blocks(void){//Initialize the block fieldint row = 0, col = 0;for (row = 0; row < level*NUM_BLOCK_ROWS; row++){for (col = 0; col < level*NUM_BLOCK_COLUMNS; col++){blocks[row][col] = 1;                       //初始化}}}void Draw_Blocks(void){//this function draws all the blocks in row major formint x1 = BLOCK_ORIGIN_X;int y1 = BLOCK_ORIGIN_Y;int row = 0, col = 0;//draw all the blocksfor ( row = 0; row < level*NUM_BLOCK_ROWS; row++){x1 = BLOCK_ORIGIN_X;for (col = 0; col < level*NUM_BLOCK_COLUMNS; col++){if (blocks[row][col] != 0){Draw_Rectangle(x1, y1, x1 + BLOCK_WIDTH, y1 + BLOCK_HEIGHT, BLOCK_COLOR);}//advance  column positionx1 += BLOCK_X_GAP;}//advance to next row positiony1 += 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 chessy collision algorithm:)//first test for ball block collisions//the algorith basically test the ball against each black's bounding box this is inefficient.//but easy to implement,later we'll see a better way//current rendering positionint x1 = BLOCK_ORIGIN_X;int y1 = BLOCK_ORIGIN_Y;//computer center of ballint ball_cx = ball_x + (BALL_SIZE / 2);int ball_cy = ball_y + (BALL_SIZE / 2);//test the ball has hit the paddleif (ball_y>(WINDOW_HEIGHT/2)&&ball_dy>0){int x = ball_x + (BALL_SIZE / 2);int y = ball_y + (BALL_SIZE / 2);//test for collision with paddleif ((x <= (paddle_x + PADDLE_WIDTH )) && (x >= paddle_x ) && (y >= paddle_y )&& (y <= paddle_y + PADDLE_HEIGHT/2)){if ( ((ball_dx)<0 && ((-ball_dx)>ball_dy))|| ((ball_dx>0) && (ball_dx>ball_dy)) ) {//test if there are no blocks, if so send a message to game loop to start another levelif (block_hit >= (level*NUM_BLOCK_ROWS)*(level*NUM_BLOCK_COLUMNS)){level++;if (level>Game_Count){MessageBox(main_window_handle, L"Congraculation, you pass all customs!", L"FreakOut", MB_OK);game_state = GAME_STATE_INIT;}else{game_state = GAME_STATE_START_LEVEL;}}//make a little noiseMessageBeep(MB_OK);return;}else{//relect ballball_dy = -ball_dy;//puch ball out of paddle since it made contactball_y = ball_y + ball_dy;//add a little english to ball based on mation of paddleif (KEY_DOWN(VK_RIGHT)){ball_dx -= rand() % 3;                          //change speed}else if (KEY_UP(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 levelif (block_hit >= (level*NUM_BLOCK_ROWS)*(level*NUM_BLOCK_COLUMNS)){level++;if (level>Game_Count){MessageBox(main_window_handle, L"Congraculation, you pass all customs!", L"FreakOut", MB_OK);game_state = GAME_STATE_INIT;}else{game_state = GAME_STATE_START_LEVEL;}}//make a little noiseMessageBeep(MB_OK);return;}}}//now scan thru all the blocks and see of ball hit blocksint row, col;for (row = 0; row < level*NUM_BLOCK_ROWS; row++){x1 = BLOCK_ORIGIN_X;for (col = 0; col < level*NUM_BLOCK_COLUMNS; col++){if (blocks[row][col]){//test ball against bounding box of blockif ((ball_cx >= x1) && (ball_cx <= x1 + BLOCK_WIDTH) && (ball_cy >= y1) && (ball_cy <= y1 + BLOCK_HEIGHT)){//removeblocks[row][col] = 0;//increment global block counter, so we know when to start another level upblock_hit++;//bounce the ballball_dy = -ball_dy;//add a little englishball_dx += (-1 + rand() % 3);//make a little noiseMessageBeep(MB_OK);//add some pointsscore += 5 * (level + (abs)(ball_dx));return;}}//advance column positionx1 +=  BLOCK_X_GAP;}//advance row positiony1 +=  BLOCK_Y_GAP;}}int Game_Init(void *parms){//this function is where you do all the initialization for your gamereturn 1;}int Game_Shutdown(void *parms){//this function is where you shutdown your game and release all resources//that you allocatedreturn 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 ge here!TCHAR buffer[80];//what state is the game in?if (game_state == GAME_STATE_INIT){//send the random number generator so game is different each playsrand((unsigned)time(NULL));//send the paddle position here to the middle buttompaddle_x = PADDLE_START_X;paddle_y = PADDLE_START_Y;//set ball position and velocityball_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 stategame_state = GAME_STATE_START_LEVEL;}else if (game_state == GAME_STATE_START_LEVEL){//get a new level ready to run//initialize the blocksInit_Blocks();//reset block counterblock_hit = 0;//transition to run stategame_state = GAME_STATE_RUN;}else if (game_state == GAME_STATE_RUN){//start the timing clockStart_Clock();//clear drawing surface for next frame of animationDraw_Rectangle(0, 0, WINDOW_WIDTH - 1, WINDOW_HEIGHT - 1, RGB(255,255,255));//move to paddleif (KEY_DOWN(VK_RIGHT)){//move paddle to rightpaddle_x += 8;//make sure that the paddle doesn't go off screenif (paddle_x > WINDOW_WIDTH - PADDLE_WIDTH){paddle_x = WINDOW_WIDTH - PADDLE_WIDTH;}}if (KEY_DOWN(VK_LEFT)){//move paddle to leftpaddle_x -= 8;//make sure that the paddle doesn't go off screenif (paddle_x < 0){paddle_x = 0;}}//draw blocksDraw_Blocks();//move the ballball_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 velocityif ((ball_x < 0)||(ball_x > WINDOW_WIDTH - BALL_SIZE/2)){//reflect x-axis velocityball_dx = -ball_dx;//update positionball_x += ball_dx;}//now y-axisif (ball_y < 0){//relect y-axis velocityball_dy = -ball_dy;//update positionball_y += ball_dy;}else if (ball_y >WINDOW_HEIGHT - BALL_SIZE/2){//reflect y-axis velocityball_dy = -ball_dy;//update positionball_y += ball_dy;//minus the scorescore -= 100;}//now watch out for ball velocity getting out of handif (ball_dx > 8){ball_dx = 8;}else if (ball_dx < -8){ball_dx = -8;}//test if ball hit any blocks or the paddleProcess_Ball();//draw the paddleDraw_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_Ball(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, BALL_COLOR);//check if it has reword//now_clock = Get_Clock();//Change_Mode(main_window_handle, 15000, score);//draw the infowsprintf(buffer, TEXT("Game Scroe %d       Level %d"), score, level);DrawText_GUI(buffer, 8, WINDOW_HEIGHT - 50, 127);//sync to 30 fpsWait_Clock(30);//Sleep(30);//check if user is trying to exitif (KEY_DOWN(VK_ESCAPE)){//send to message to windows to exitPostMessage(main_window_handle, /*WM_QUIT*/WM_DESTROY, 0, 0);//send exit stategame_state = GAME_STATE_SHUTDOWN;}}else if (game_state == GAME_STATE_SHUTDOWN){//in this state shut everything down and release resources//switch to exit stategame_state = GAME_STATE_EXIT;}else{;}return 1;}//CLOCK FUNCTIONSDWORD Get_Clock(void){//this function returns the current tickcount//return time return GetTickCount();}DWORD Start_Clock(){//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_Clockwhile (Get_Clock() - start_clock_count < count){;}return Get_Clock();}/*void Change_Mode(HWND hWnd, DWORD Times, int score)                //奖励模式{HDC hdc;hdc = GetDC(hWnd);RECT rect, wnd;if (score >= Reword_Score){while (Get_Clock() - now_clock < Times){rect = { paddle_x - PADDLE_WIDTH, paddle_y, paddle_x + PADDLE_WIDTH, paddle_y + PADDLE_HEIGHT };FillRect(hdc, &rect, CreateSolidBrush(RGB(0, 0, 255)));wnd = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT - 2 * PADDLE_HEIGHT };InvalidateRect(hWnd, &wnd, false);}}score -= 50;InvalidateRect(hWnd, NULL, false);ReleaseDC(hWnd, hdc);}*///  函数:  WndProc(HWND, UINT, WPARAM, LPARAM)////  目的:    处理主窗口的消息。////  WM_COMMAND- 处理应用程序菜单//  WM_PAINT- 绘制主窗口//  WM_DESTROY- 发送退出消息并返回////LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){int wmId, wmEvent;PAINTSTRUCT ps;HDC hdc;switch (message){case WM_COMMAND:wmId    = LOWORD(wParam);wmEvent = HIWORD(wParam);// 分析菜单选择: switch (wmId){case IDM_ABOUT:DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);break;case IDM_EXIT:DestroyWindow(hWnd);break;default:return DefWindowProc(hWnd, message, wParam, lParam);}break;case WM_PAINT:hdc = BeginPaint(hWnd, &ps);// TODO:  在此添加任意绘图代码.../*//创建内存HDCHDC memHDC = CreateCompatibleDC(hdc);//获取客户区大小RECT rectClient;GetClientRect(hWnd,&rectClient);//创建位图HBITMAP bmpBuff = CreateCompatibleBitmap(hdc, RECT_WIDTH(rectClient), RECT_HEIGHT(rectClient));HBITMAP pOldBMP = (HBITMAP)SelectObject(memHDC, bmpBuff);// draw somethingDrawBackGround(memHDC);//拷贝内存HDC内容到实际HDCBOOL tt = BitBlt(hdc, rectClient.left, rectClient.top, RECT_WIDTH(rectClient),RECT_HEIGHT(rectClient), memHDC, rectClient.left, rectClient.top, SRCCOPY);//内存回收SelectObject(memHDC, pOldBMP);DeleteObject(bmpBuff);DeleteDC(memHDC);*/EndPaint(hWnd, &ps);break;case WM_DESTROY:PostQuitMessage(0);break;case WM_ERASEBKGND://防止清除背景造成的白屏//什么也不做,返回0使默认窗口回调不再处理这个消息return 0;default:return DefWindowProc(hWnd, message, wParam, lParam);}return 0;}// “关于”框的消息处理程序。INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam){UNREFERENCED_PARAMETER(lParam);switch (message){case WM_INITDIALOG:return (INT_PTR)TRUE;case WM_COMMAND:if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL){EndDialog(hDlg, LOWORD(wParam));return (INT_PTR)TRUE;}break;}return (INT_PTR)FALSE;}
下面是游戏的截图:

初始状态:


游戏状态:



       游戏设计了好几关,每一关的砖块数量都不同。

       一些比较关键的地方都有注释,这个游戏的基本框架可以在《Windows游戏编程大师技巧》之中找到。游戏的美工方面做的挺差的,不过逻辑基本实现了。其实为了更美观你可以添加背景。这样看起来就会更不错一些。

1 0
原创粉丝点击