天天看點

《Windows遊戲程式設計大師技巧》(第二版)第1章(下)

執行個體:FreakOut

在沉溺于所讨論的有關Windows、DirectX 和3D 圖形之前,應當暫停一下,先給你看一個完整的遊戲——雖然簡單了一點,但毫無疑問是一個完整的遊戲。你會看到一個實際的遊戲循環和一些圖形功能調用,最後一霎那就可以通過編譯。不錯吧?跟我來吧!

問題是我們現在才講到第一章。我不應該使用後面章節中的内容……這有點像作弊,對吧?是以,我決定要做的是讓你習慣于使用黑黑(black box)API來進行遊戲程式設計。基于這個要求,我要提一個問題“要制作一個類似Breakout(打磚塊)的2D遊戲,其最低要求是什麼?”我們真正所需要的是下面的功能:

? 在任意圖像模式中切換

? 在螢幕上畫各種顔色的矩形

? 擷取鍵盤輸入

? 使用一些定時函數同步遊戲循環

? 在螢幕上畫彩色文字串

是以我建了一個名為BLACKBOX.CPP|H的庫。它封裝了一套DirectX函數集(限于DirectDraw),并且包含實作所需功能的支援代碼。妙處是,讀者根本不需要看這些代碼,隻需依照函數原型來使用這些函數就可以了,并與BLACKBOX.CPP|H連接配接來産生.EXE可執行檔案。

以BLACKBOX庫為基礎,我編寫了一個名字為FreakOut的遊戲,這個遊戲示範了本章中所讨論的許多概念。FreakOut 遊戲包含真正遊戲的全部主要組成部分,包括:遊戲循環、計分、關卡,甚至還有為球而寫的迷你實體模型。真是可愛。圖1.9 是一幅遊戲運作中的螢幕畫面。顯然它比不上Arkanoid(經典的打磚塊類遊戲),但4 個小時的工作有此成果也不賴!

圖1.9 FreakOut遊戲的截屏

在閱讀遊戲源代碼之前,我希望讀者能看一下工程和遊戲各組成部分是如何協調一緻的。參見圖1.10。

圖1.10 FreakOut 的結構

從圖中可以看到,遊戲由下面檔案構成:

FREAKOUT.CPP——遊戲的主要邏輯,使用BLACKBOX.CPP,建立一個最小化的Win32應用程式。

BLACKBOX.CPP——遊戲庫(請不要偷看:)。

BLACKBOX.H——遊戲庫的頭檔案。

DDRAW.LIB——用于生成應用程式的DirectDraw輸入庫。其中并不含有真正的DirectX代碼。它主要是用作讓使用者調用的中間庫,然後輪流調用進行實際工作的DDRAW.DLL動态連結庫。它可以在DirectX SDK 安裝目錄下的LIB子目錄内被找到。

DDRAW.DLL——運作時(Runtime)的DirectDraw 庫,實際上含有通過DDRAW.LIB 輸入庫調用DirectDraw 接口函數的COM 執行程式。不必為此擔心;隻要确認已經安裝了DirectX運作時檔案即可。

為了通過編譯,需要将BLACKBOX.CPP和FREAKOUT.CPP加入工程裡面,連接配接上DDRAW.LIB庫檔案,并確定BLACKBOX.H在頭檔案搜尋路徑或工作目錄裡,以便編譯器可以正确地找到它。

現在我們已大緻了解了FreakOut的結構。讓我們看一下BLACKOUT.H頭檔案,看看它包含了哪些函數。

程式清單1.2 BLACKOUT.H 頭檔案

// BLACKBOX.H - Header file for demo game engine library

// watch for multiple inclusions

#ifndef BLACKBOX

#define BLACKBOX

// DEFINES

// default screen size

#define SCREEN_WIDTH    640  // size of screen

#define SCREEN_HEIGHT   480

#define SCREEN_BPP      8    // bits per pixel

#define MAX_COLORS      256  // maximum colors

// MACROS /

// 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)

// initializes a direct draw struct

#define DD_INIT_STRUCT(ddstruct) {memset(&ddstruct,0,sizeof(ddstruct));

ddstruct.dwSize=sizeof(ddstruct); }

// TYPES //

// basic unsigned types

typedef unsigned short USHORT;

typedef unsigned short WORD;

typedef unsigned char  UCHAR;

typedef unsigned char  BYTE;

// EXTERNALS //

extern LPDIRECTDRAW7         lpdd;             // dd object

extern LPDIRECTDRAWSURFACE7  lpddsprimary;     // dd primary surface

extern LPDIRECTDRAWSURFACE7  lpddsback;        // dd back surface

extern LPDIRECTDRAWPALETTE   lpddpal;          // a pointer dd palette

extern LPDIRECTDRAWCLIPPER   lpddclipper;      // dd clipper

extern PALETTEENTRY          palette[256];     // color palette

extern PALETTEENTRY          save_palette[256]; // used to save palettes

extern DDSURFACEDESC2        ddsd;    // a ddraw surface description struct

extern DDBLTFX               ddbltfx;           // used to fill

extern DDSCAPS2              ddscaps; // a ddraw surface capabilities struct

extern HRESULT               ddrval;            // result back from dd calls

extern DWORD                 start_clock_count; // used for timing

// these defined the general clipping rectangle

extern int min_clip_x,                             // clipping rectangle

           max_clip_x,

           min_clip_y,

           max_clip_y;

// these are overwritten globally by DD_Init()

extern int screen_width,                            // width of screen

           screen_height,                           // height of screen

           screen_bpp;                              // bits per pixel

// PROTOTYPES /

// DirectDraw functions

int DD_Init(int width, int height, int bpp);

int DD_Shutdown(void);

LPDIRECTDRAWCLIPPER DD_Attach_Clipper(LPDIRECTDRAWSURFACE7 lpdds,

                                      int num_rects, LPRECT clip_list);

int DD_Flip(void);

int DD_Fill_Surface(LPDIRECTDRAWSURFACE7 lpdds,int color);

// general utility functions

DWORD Start_Clock(void);

DWORD Get_Clock(void);

DWORD Wait_Clock(DWORD count);

// graphics functions

int Draw_Rectangle(int x1, int y1, int x2, int y2,

                   int color,LPDIRECTDRAWSURFACE7 lpdds=lpddsback);

// gdi functions

int Draw_Text_GDI(char *text, int x,int y,COLORREF color,

                  LPDIRECTDRAWSURFACE7 lpdds=lpddsback);

int Draw_Text_GDI(char *text, int x,int y,int color,

                  LPDIRECTDRAWSURFACE7 lpdds=lpddsback);

#endif

現在,不要花費太多時間絞盡腦汁研究這裡的程式代碼,搞清楚那些神秘的全局變量究竟表示什麼并不重要。讓我們來看一看這些函數。如你所想,這裡有實作我們的簡單圖形界面所需的全部函數。基于這個圖形界面和最小化的Win32 應用程式(我們要做的Windows 程式設計工作越少越好)的基礎上,我建立了遊戲FREAKOUT.CPP,如清單1.3 所示。請認真地看一看,尤其是遊戲主循環和對遊戲處理功能的調用。

程式清單1.3 FREAKOUT.CPP 源檔案

// INCLUDES ///

#define WIN32_LEAN_AND_MEAN // include all macros

#define INITGUID            // include all GUIDs

#include         // include important windows stuff

#include

#include

#include        // include important C/C++ stuff

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include           // directX includes

#include "blackbox.h"       // game library includes

// DEFINES

// defines for windows

#define WINDOW_CLASS_NAME "WIN3DCLASS"  // class name

#define WINDOW_WIDTH            640     // size of window

#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          (SCREEN_WIDTH/2 - 16)

#define PADDLE_START_Y          (SCREEN_HEIGHT - 32);

#define PADDLE_WIDTH            32

#define PADDLE_HEIGHT           8

#define PADDLE_COLOR            191

// ball defines

#define BALL_START_Y            (SCREEN_HEIGHT/2)

#define BALL_SIZE                4

// PROTOTYPES /

// game console

int Game_Init(void *parms=NULL);

int Game_Shutdown(void *parms=NULL);

int Game_Main(void *parms=NULL);

// 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

// this contains the game grid data

UCHAR blocks[NUM_BLOCK_ROWS][NUM_BLOCK_COLUMNS];

// FUNCTIONS //

LRESULT CALLBACK WindowProc(HWND hwnd,

                      UINT msg,

                            WPARAM wparam,

                            LPARAM lparam)

{

// this is the main message handler of the system

PAINTSTRUCT    ps;           // used in WM_PAINT

HDC            hdc;       // handle to a device context

// what is the message

switch(msg)

    {

    case WM_CREATE:

        {

    // do initialization stuff here

    return(0);

    }  break;

    case WM_PAINT:

         {

         // start painting

         hdc = BeginPaint(hwnd,&ps);

         // the window is now validated

         // end painting

         EndPaint(hwnd,&ps);

         return(0);

        }  break;

    case WM_DESTROY:

         {

           // kill the application

         PostQuitMessage(0);

         return(0);

           }  break;

    default:break;

    }  // end switch

// process any messages that we didn't take care of

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

}  // end WinProc

// WINMAIN

int WINAPI WinMain(HINSTANCE hinstance,

            HINSTANCE hprevinstance,

            LPSTR lpcmdline,

            int ncmdshow)

{

// this is the winmain function

WNDCLASS winclass;  // this will hold the class we create

HWND     hwnd;         // generic window handle

MSG     msg;         // generic message

HDC      hdc;       // generic dc

PAINTSTRUCT ps;     // generic paintstruct

// first fill in the window class structure

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

if (!(hwnd = CreateWindow(WINDOW_CLASS_NAME,    // class

        "WIN3D Game Console",    // title

        WS_POPUP | WS_VISIBLE,

        0,0,                    // initial x,y

        GetSystemMetrics(SM_CXSCREEN),  // initial width

        GetSystemMetrics(SM_CYSCREEN),  // initial height

        NULL,        // handle to parent

        NULL,        // handle to menu

        hinstance,   // instance

        NULL)))      // creation parms

return(0);

// 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

        if (msg.message == WM_QUIT)

           break;

    // translate any accelerator keys

    TranslateMessage(&msg);

    // send the message to the window proc

    DispatchMessage(&msg);

    } // end if

       // main game processing goes here

       Game_Main();

    }  // end while

// shutdown game and release all resources

Game_Shutdown();

// show mouse

ShowCursor(TRUE);

// return to Windows like this

return(msg.wParam);

}  // end WinMain

// T3DX GAME PROGRAMMING CONSOLE FUNCTIONS

int Game_Init(void *parms)

{

// this function is where you do all the initialization

// for your game

// return success

return(1);

}  // end Game_Init

///

int Game_Shutdown(void *parms)

{

// this function is where you shutdown your game and

// release all resources that you allocated

// return success

return(1);

}  // end Game_Shutdown

///

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;

}  // end Init_Blocks

///

void Draw_Blocks(void)

{

// this function draws all the blocks in row major form

int x1 = BLOCK_ORIGIN_X, // used to track current position

    y1 = BLOCK_ORIGIN_Y;

// draw all the blocks

for (int row=0; row < NUM_BLOCK_ROWS; row++)

    {

    // reset column position

    x1 = BLOCK_ORIGIN_X;

    // draw this row of blocks

    for (int col=0; col < NUM_BLOCK_COLUMNS; col++)

        {

        // draw next block (if there is one)

        if (blocks[row][col]!=0)

            {

            // draw block

            Draw_Rectangle(x1-4,y1+4,

                 x1+BLOCK_WIDTH-4,y1+BLOCK_HEIGHT+4,0);

            Draw_Rectangle(x1,y1,x1+BLOCK_WIDTH,

                 y1+BLOCK_HEIGHT,blocks[row][col]);

            }  // end if

        // advance column position

        x1+=BLOCK_X_GAP;

        }  // end for col

    // advance to next row position

    y1+=BLOCK_Y_GAP;

    }  // end for row

}  // end Draw_Blocks

///

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

int x1 = BLOCK_ORIGIN_X, // current rendering position

    y1 = BLOCK_ORIGIN_Y;

int ball_cx = ball_x+(BALL_SIZE/2),  // computer center of ball

    ball_cy = ball_y+(BALL_SIZE/2);

// test of the ball has hit the paddle

if (ball_y > (SCREEN_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++;

          }  // end if

       // make a little noise

       MessageBeep(MB_OK);

       // return

       return;

       }  // end if

   }  // end if

// now scan thru all the blocks and see if ball hit blocks

for (int row=0; row < NUM_BLOCK_ROWS; row++)

    {

    // reset column position

    x1 = BLOCK_ORIGIN_X;

    // scan this row of blocks

    for (int col=0; col < NUM_BLOCK_COLUMNS; col++)

        {

        // if there is a block here then test it against ball

        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)));

               // that's it -- no more block

               return;

               }  // end if

           }  // end if

        // advance column position

        x1+=BLOCK_X_GAP;

        }  // end for col

    // advance to next row position

    y1+=BLOCK_Y_GAP;

    }  // end for row

}  // end Process_Ball

///

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 your game go here!

char buffer[80]; // used to print text

// what state is the game in?

if (game_state == GAME_STATE_INIT)

    {

    // initialize everything here graphics

    DD_Init(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP);

    // seed the random number generator

    // so game is different each play

    srand(Start_Clock());

    // 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()%(SCREEN_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;

    }  // end if

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;

    }  // end if

///

else

if (game_state == GAME_STATE_RUN)

    {

    // start the timing clock

    Start_Clock();

    // clear drawing surface for the next frame of animation

    Draw_Rectangle(0,0,SCREEN_WIDTH-1, SCREEN_HEIGHT-1,200);

    // 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 > (SCREEN_WIDTH-PADDLE_WIDTH))

          paddle_x = SCREEN_WIDTH-PADDLE_WIDTH;

       }  // end if

    else

    if (KEY_DOWN(VK_LEFT))

       {

       // move paddle to right

       paddle_x-=8;

       // make sure paddle doesn't go off screen

       if (paddle_x < 0)

          paddle_x = 0;

       }  // end if

    // 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 > (SCREEN_WIDTH - BALL_SIZE) || ball_x < 0)

       {

       // reflect x-axis velocity

       ball_dx=-ball_dx;

       // update position

       ball_x+=ball_dx;

       }  // end if

    // now y-axis

    if (ball_y < 0)

       {

       // reflect y-axis velocity

       ball_dy=-ball_dy;

       // update position

       ball_y+=ball_dy;

       }  // end if

   else

   // penalize player for missing the ball

   if (ball_y > (SCREEN_HEIGHT - BALL_SIZE))

       {

       // reflect y-axis velocity

       ball_dy=-ball_dy;

       // update position

       ball_y+=ball_dy;

       // minus the score

       score-=100;

       }  // end if

    // 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 and shadow

    Draw_Rectangle(paddle_x-8, paddle_y+8,

                   paddle_x+PADDLE_WIDTH-8,

                   paddle_y+PADDLE_HEIGHT+8,0);

    Draw_Rectangle(paddle_x, paddle_y,

                   paddle_x+PADDLE_WIDTH,

                   paddle_y+PADDLE_HEIGHT,PADDLE_COLOR);

    // draw the ball

    Draw_Rectangle(ball_x-4, ball_y+4, ball_x+BALL_SIZE-4,

                   ball_y+BALL_SIZE+4, 0);

    Draw_Rectangle(ball_x, ball_y, ball_x+BALL_SIZE,

                   ball_y+BALL_SIZE, 255);

    // draw the info

    sprintf(buffer,"F R E A K O U T           Score %d   //

          Level %d",score,level);

    Draw_Text_GDI(buffer, 8,SCREEN_HEIGHT-16, 127);

    // flip the surfaces

    DD_Flip();

    // sync to 33ish 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;

       }  // end if

    }  // end if

///

else

if (game_state == GAME_STATE_SHUTDOWN)

   {

   // in this state shut everything down and release resources

   DD_Shutdown();

   // switch to exit state

   game_state = GAME_STATE_EXIT;

   }  // end if

// return success

return(1);

}  // end Game_Main

哈哈,酷吧?這就是一個完整的Win32/DirectX遊戲了,至少幾乎是完整的了。BLACKOUT.CPP源檔案中有好幾百行代碼,但是我們可以将其視為某人(我!)編寫的DirectX的一部分。不管怎樣說,還是讓我們迅速浏覽一下程式清單1.3的内容吧。

首先,Windows 需要一個事件循環。這是所有Windows程式的标準結構,因為Windows幾乎完全是事件驅動的。但是遊戲卻不是事件驅動的,無論使用者在幹什麼,它們都在一直運作。是以,我們至少需要支援小型事件循環以配合Windows。執行這項功能的代碼位于WinMain()中。WinMain() 是所有Windows 程式的主要入口點,就好比main()是所有DOS/UNIX 程式中的入口點一樣。FreakOut 的WinMain()建立一個視窗并進入事件循環。當Windows需要作某些工作時,就随它去。當所有的基本事件處理都結束時,調用Game_Main()。Game_Main是實際運作遊戲程式的部分。

如果願意的話,你可以不停地在Game_Main()中循環,而不釋放回到WinMain()主事件循環體中。但這樣做不是件好事,因為Windows會得不到任何資訊。哎,我們該做的是讓遊戲在運作一幀時間的動畫和邏輯之後,傳回到WinMain()。這樣的話,Windows可以繼續響應和處理資訊。如果所有這些聽起來像是幻術的話,請不要擔心——在下一章中情況還會更糟。

進入Game_Main()後,FreakOut的遊戲邏輯開始被執行。遊戲圖像被渲染到一個不直接顯示出來的工作緩沖區,爾後通過調用DD_FLIP()而在循環結束時在顯示屏上顯示出來。是以我希望你閱讀一下全部的遊戲狀态,一行一行地過一遍一遍遊戲循環的每一部分,了解工作原理。要啟動遊戲,隻須輕按兩下FREAKOUT.EXE,遊戲程式會立即啟動。遊戲控制方式如下:

右箭頭鍵——向右移動擋闆。

左箭頭鍵——向左移動擋闆。

Esc鍵——退回Windows。

還有,如果你錯過一個球的話,将被罰掉100分,可要仔細盯緊啊!

如果你已經明白了遊戲代碼和玩法,不妨試着修改一下遊戲。你可以增加不同的背景顔色(0~255 是有效的顔色)、增加更多的球、可以改變擋闆的大小以及加上更多的聲音效果(目前我隻用到了Win32 API 中的MessageBeep()函數)

總結

這大概是我所寫的最快的一章遊戲程式設計入門教程了!我們提及了大量的基礎内容,但是還隻能算作是本書的縮略版本(就像印在封底的那樣)。我隻想讓讀者對本書中我們将學習和讨論的内容有一個感性認識。另外,閱讀一個完整的遊戲總是有益的,因為這帶來許多需要讀者思考的問題。

在進入第二章之前,請先確定你能夠輕松編譯FreakOut遊戲。如果還不行的話,請立即翻開編譯器的書并且RTFM(閱讀那惱人的使用手冊!)。我等着你們。