我们继续上次的话题。
上一讲中,我们介绍了一下具体的步骤。下面的清单 1.0 包含了所有的功能以及一个展示如何操作显存和键盘的例子。我们简单地将它作为一个演示。
// LISTING 1.0 - DIRECT X 5.0 GAME CONSOLE ////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////
#define WIN32_LEAN_AND_MEAN // make sure certain headers are included correctly
#include <windows.h> // include the standard windows stuff #include <windowsx.h> // include the 32 bit stuff #include <mmsystem.h> // include the multi media stuff // note you need winmm.lib also #include <ddraw.h> // include direct draw components
#include <stdlib.h> // include all the good stuff #include <stdio.h> #include <math.h> #include <float.h>
// DEFINES ////////////////////////////////////////////////////////////////////
#define WINDOW_CLASS_NAME "WINDOW_CLASS" // this is the name of the window class
// defines for screen parameters
#define SCREEN_WIDTH 640 // the width of the viewing surface #define SCREEN_HEIGHT 480 // the height of the viewing surface #define SCREEN_BPP 8 // the bits per pixel #define MAX_COLORS 256 // the maximum number of colors
// TYPES //////////////////////////////////////////////////////////////////////
typedef unsigned char UCHAR;
// MACROS /////////////////////////////////////////////////////////////////////
// these query the keyboard in real-time
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0) #define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)
// PROTOTYPES /////////////////////////////////////////////////////////////////
int DD_Init(HWND hwnd); int DD_Shutdown(void); int Set_Pal_Entry(int index, int red, int green, int blue);
void Game_Init(void); void Game_Main(void); void Game_Shutdown(void);
// DIRECTDRAW GLOBALS ////////////////////////////////////////////////////////
LPDIRECTDRAW lpdd = NULL; // dd object LPDIRECTDRAWSURFACE lpddsprimary = NULL; // dd primary surface LPDIRECTDRAWPALETTE lpddpal = NULL; // a pointer to the created dd palette PALETTEENTRY color_palette[256]; // holds the shadow palette entries DDSURFACEDESC ddsd; // a direct draw surface description struct DDSCAPS ddscaps; // a direct draw surface capabilities struct HRESULT ddrval; // result back from dd calls HWND main_window_handle = NULL; // used to store the window handle UCHAR *video_buffer = NULL; // pointer to video ram
// GAME GLOBALS GO HERE /////////////////////////////////////////////////////
// DIRECT X FUNCTIONS /////////////////////////////////////////////////////////
int DD_Init(HWND hwnd) { // this function is responsible for initializing direct draw, it creates a // primary surface
int index; // looping index
// now that the windows portion is complete, start up direct draw if (DirectDrawCreate(NULL,&lpdd,NULL)!=DD_OK) { // shutdown any other dd objects and kill window DD_Shutdown(); return(0); } // end if
// now set the coop level to exclusive and set for full screen and mode x if (lpdd->SetCooperativeLevel(hwnd, DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX)!=DD_OK) { // shutdown any other dd objects and kill window DD_Shutdown(); return(0); } // end if
// now set the display mode if (lpdd->SetDisplayMode(SCREEN_WIDTH,SCREEN_HEIGHT,SCREEN_BPP)!=DD_OK) { // shutdown any other dd objects and kill window DD_Shutdown(); return(0); } // end if
// Create the primary surface ddsd.dwSize = sizeof(ddsd); ddsd.dwFlags = DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
if (lpdd->CreateSurface(&ddsd,&lpddsprimary,NULL)!=DD_OK) { // shutdown any other dd objects and kill window DD_Shutdown(); return(0); } // end if
// create the palette and attach it to the primary surface
// clear all the palette entries to RGB 0,0,0 memset(color_palette,0,256*sizeof(PALETTEENTRY)); // set all of the flags to the correct value for (index=0; index<256; index++) { // create the GRAY/RED/GREEN/BLUE palette, 64 shades of each if ((index / 64)==0) { color_palette[index].peRed = index*4; color_palette[index].peGreen = index*4; color_palette[index].peBlue = index*4; } // end if else if ((index / 64)==1) color_palette[index].peRed = (index%64)*4; else if ((index / 64)==2) color_palette[index].peGreen = (index%64)*4; else if ((index / 64)==3) color_palette[index].peBlue = (index%64)*4;
// set the no collapse flag color_palette[index].peFlags = PC_NOCOLLAPSE;
} // end for index
// now create the palette object, note that it is a member of the dd object itself if (lpdd->CreatePalette((DDPCAPS_8BIT | DDPCAPS_INITIALIZE),color_palette,&lpddpal,NULL)!=DD_OK) { // shutdown any other dd objects and kill window DD_Shutdown(); return(0); } // end if
// now attach the palette to the primary surface lpddsprimary->SetPalette(lpddpal);
// return success if we got this far return(1);
} // end DD_Init
///////////////////////////////////////////////////////////////////////////////
int DD_Shutdown(void) { // this function tests for dd components that have been created and releases // them back to the operating system
// test if the dd object exists if (lpdd) { // test if there is a primary surface if(lpddsprimary) { // release the memory and set pointer to NULL lpddsprimary->Release(); lpddsprimary = NULL; } // end if
// now release the dd object itself lpdd->Release(); lpdd = NULL;
// return success return(1);
} // end if else return(0);
} // end DD_Shutdown
//////////////////////////////////////////////////////////////////////////////
int Set_Pal_Entry(int index, int red, int green, int blue) { // this function sets a palette entry with the sent color
PALETTEENTRY color; // used to build up color
// set RGB value in structure color.peRed = (BYTE)red; color.peGreen = (BYTE)green; color.peBlue = (BYTE)blue; color.peFlags = PC_NOCOLLAPSE;
// set the color palette entry lpddpal->SetEntries(0,index,1,&color);
// make copy in shadow palette memcpy(&color_palette[index], &color, sizeof(PALETTEENTRY));
// return success return(1);
} // end Set_Pal_Entry
// WINDOWS CALLBACK FUNCTION //////////////////////////////////////////////////
LRESULT CALLBACK WindowProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { // this is the main message handler of the system
HDC hdc; // handle to graphics context PAINTSTRUCT ps; // used to hold the paint info
// what is the message?
switch(msg) { case WM_CREATE: { // do windows inits here return(0); } break;
case WM_PAINT: { // this message occurs when your window needs repainting hdc = BeginPaint(hwnd,&ps); EndPaint(hwnd,&ps);
return(0); } break;
case WM_DESTROY: { // this message is sent when your window is destroyed PostQuitMessage(0); return(0); } break;
default:break;
} // end switch
// let windows 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) { WNDCLASSEX winclass; // this holds the windows class info HWND hwnd; // this holds the handle of our new window MSG msg; // this holds a generic message
// first fill in the window class stucture
winclass.cbSize = sizeof(WNDCLASSEX); 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.hIconSm = 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 (!RegisterClassEx(&winclass)) return(0);
// create the window if (!(hwnd = CreateWindowEx(WS_EX_TOPMOST, WINDOW_CLASS_NAME, // class "You can't See This!", // title WS_VISIBLE | WS_POPUP, 0,0, // x,y GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN), NULL, // parent NULL, // menu hinstance, // instance NULL))) // creation parms return(0);
// hide the mouse cursor ShowCursor(0);
// save the window handle main_window_handle = hwnd;
// initialize direct draw if (!DD_Init(hwnd)) { DestroyWindow(hwnd); return(0); } // end if
// initialize game 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 else { // do asynchronous processing here
// acquire pointer to video ram, note it is always linear memset(&ddsd,0,sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); lpddsprimary->Lock(NULL,&ddsd,DDLOCK_SURFACEMEMORYPTR,NULL); video_buffer = (UCHAR *)ddsd.lpSurface;
// call main logic module Game_Main();
// release pointer to video ram lpddsprimary->Unlock(ddsd.lpSurface);
} // end else
} // end while
// shut down direct draw DD_Shutdown();
// shutdown game Game_Shutdown();
// return to Windows return(msg.wParam);
} // end WinMain
// HERE ARE OUR GAME CONSOLE FUNCTIONS ///////////////////////////////////////////////////////
void Game_Init(void) { // do any initialization here } // end Game_Init
/////////////////////////////////////////////////////////////////////////////////////////////
void Game_Shutdown(void) { // cleanup and release all resources here } // end Game_Shutdown
/////////////////////////////////////////////////////////////////////////////////////////////
void Game_Main(void) { // process your game logic and draw next frame // this function must exit each frame and return back to windows // also this function is responsible for controlling the frame rate // therefore, if you want the game to run at 30fps, then the function should // take 1/30th of a second before returning
static int px = SCREEN_WIDTH/2, // position of player py = SCREEN_HEIGHT/2, color = 0; // color of blob 0..3, gray, red, green, blue
// on entry video_buffer is valid
// get input, note how keyboard is accessed with constants //"VK_" // these are defined in "winuser.h" are basically the //scan codes // for letters just use capital ASCII codes
if (KEY_DOWN(VK_ESCAPE)) PostMessage(main_window_handle,WM_CLOSE,0,0); // this is how you exit you game
// which way is player moving if (KEY_DOWN(VK_RIGHT)) if (++px>SCREEN_WIDTH-8) px=8;
if (KEY_DOWN(VK_LEFT)) if (--px<8) px=SCREEN_WIDTH-8;
if (KEY_DOWN(VK_UP)) if (--py<8) py=SCREEN_HEIGHT-8;
if (KEY_DOWN(VK_DOWN)) if (++py>SCREEN_HEIGHT-8) py=8;
// adjust color, notice if (KEY_DOWN('C')) { if (++color>=4) color=0; Sleep(100); } // end if
// draw graphics for (int pixels=0; pixels<32; pixels++) video_buffer[(px-4+rand()%8)+(py-4+rand()%8)*SCREEN_WIDTH] = (color*64)+rand()%64;
// sync time (optional)
// use sleep to slow system down to 70ish fps Sleep(14); // note that Sleep is extremly inaccurate, IRL you would use something more robust
// return and let windows have some time
} // end Game_Main
要编译这个程序并且运行它,你需要在工程中包括库文件 DDRAW.LIB ,和头文件 DDRAW.H 。他们通过设置搜索路径存在于你的 IDE 中或者是直接加到工程中去。你的目标程序,应该是一个标准的 Windows 程序。在建立了程序后,运行它,你应该可以看到一个炽热的水滴,并且可以通过方向键来移动它,<C> 键可以更改色彩,按 <ESC> 可以退出程序。现在你拥有了开始体验 DirectX 基础的所有东西了。你可以通过常量 SCREEN_WIDTH 和 SCREEN_HEIGHT 来尝试双缓冲、不同分辨率,甚至是调色板颜色变换。
现在,只剩下一个可能引起的问题了。如果你的显卡不能简单地为 DirectX 创建一个线形的显存模式的话,你必须直接仔细得操作显卡了。举个简单的例子,如果你创建了一个 640 象素每行,即在 640x480x256 色模式中,操作下一行的显存的话,你必须加上 640,或者是一般地操作一个象素点,你可以这样写:
video_buffer[x + y*640] = pixel;
无论如何,如果你的显卡无法支持线形内存的话,那么,你必须去修改一下,以便能直接真正地操作显存的“线形内存跨度”。这个值会比象素值大。举个例子,它在 640 个象素每行的模式中甚至可能是 1024 个象素的值。这个线形跨度能够通过锁定一个 DirectDraw 表面的描述来访问,像这样:
// 我们可以这样锁定 lpddsprimary->Lock(NULL,&ddsd,DDLOCK_SURFACEMEMORYPTR,NULL); video_buffer = (UCHAR *)ddsd.lpSurface;
// 这里就是我们可以用于操作的值 int lpitch = ddsd.lPitch;
那么,我们可以通过下面的方法来直接操作显存了:
video_buffer[x + y*lpitch] = pixel;
因此,最安全的方法是去经常使用我们修改后得到的值。但是,这个方法牺牲了极大的性能。因此,你可能会去测试他们是否会是一个相等的值,然后使用一个不同的基于此原理的渲染函数。
[本文例子的源代码下载]
|