先说说如果用Delphi进行游戏编程要些什么,要注意什么。
1
、到网上查找下载 DirectX
7.0
for
Delphi 声明档或更高版本(本人源码用的是
7.0
)。查找时最好用DirectDraw
.
pas,否则DelphiX控件信息会占满你前
100
页的搜索结果。
2
、如果你是用D7或更高版本,DirectX
7.0
for
Delphi 声明档的 DirectDraw
.
pas 第
145
行要改为:
{$IFDEF VER150}
否则你无法编译。
8.0
版要更改更多地方。
3
、如果是全屏模式,千万不要用单步执行的方式运行。否则会死的很难看。
4
、推荐一本书《Delphi Graphics
and
Game Programming Exposed
with
DirectX
7.0
》,网上能找到英文版下载。
5
、中文翻译表达不准备。如表面、界面等。源码很多是参看推荐书中,但也有很多改进。
先来二段最简单的代码。
实现功能一:将一张图片用全屏模式显示出来(更多注解请看后面笔记)
uese DirectDraw;
var
FDirectDraw: IDirectDraw7;
FPrimarySurface: IDirectDrawSurface7;
FBitmap:IDirectDrawSurface7;
procedure
TForm1
.
FormCreate(Sender: TObject);
var
TempDirectDraw: IDirectDraw;
DDSurface: TDDSurfaceDesc2;
begin
DirectDrawCreate(
Nil
,TempDirectDraw,
Nil
);
try
TempDirectDraw
.
QueryInterface(IID_IDirectDraw4, FDirectDraw);
finally
TempDirectDraw :=
nil
;
end
;
FDirectDraw
.
SetCooperativeLevel(handle,DDSCL_EXCLUSIVE
or
DDSCL_FULLSCREEN);
FDirectDraw
.
SetDisplayMode(
800
,
600
,
32
,
0
,
0
);
FillChar(DDSurface, SizeOf(TDDSurfaceDesc2),
0
);
DDSurface
.
dwSize := SizeOf(TDDSurfaceDesc2);
DDSurface
.
dwFlags := DDSD_CAPS;
DDSurface
.
ddsCaps
.
dwCaps := DDSCAPS_PRIMARYSURFACE;
if
FDirectDraw
.
CreateSurface(DDSurface,FPrimarySurface,
Nil
)<>DD_OK
then
Close;
FillChar(DDSurface, SizeOf(TDDSurfaceDesc2),
0
);
DDSurface
.
dwSize := SizeOf(TDDSurfaceDesc2);
DDSurface
.
dwFlags := DDSD_CAPS
or
DDSD_HEIGHT
or
DDSD_WIDTH;
DDSurface
.
dwWidth :=
800
;
DDSurface
.
dwHeight :=
600
;
DDSurface
.
ddsCaps
.
dwCaps := DDSCAPS_OFFSCREENPLAIN;
FDirectDraw
.
CreateSurface(DDSurface, FBitmap,
nil
);
end
;
procedure
TForm1
.
FormShow(Sender: TObject);
var
BMP:TBitMap;
SrcRect:TRect;
begin
BMP:=TBitMap
.
Create;
BMP
.
loadfromfile(
'./desk.bmp'
);
if
FBitmap
.
GetDC(l_DC)<>DD_OK
then
close;
BitBlt(l_DC,
0
,
0
,
800
,
600
,
BMP
.
Canvas
.
Handle,
0
,
0
, SrcCopy);
SrcRect := Rect(
0
,
0
,
800
,
600
);
FBitmap
.
ReleaseDC(l_DC);
FPrimarySurface
.
BltFast(
0
,
0
, FBitmap, @SrcRect, DDBLTFAST_NOCOLORKEY
OR
DDBLTFAST_WAIT);
BMP
.
Free;
end
;
实现功能二:继上面显示图形后,显示几个文字;
procedure
TForm1
.
FormShow(Sender: TObject);
var
BMP:TBitMap;
SrcRect:TRect;
TempCanvas: TCanvas;
SrfcDC: HDC;
begin
BMP:=TBitMap
.
Create;
BMP
.
loadfromfile(
'./desk.bmp'
);
if
FBitmap
.
GetDC(l_DC)<>DD_OK
then
close;
BitBlt(l_DC,
0
,
0
,
800
,
600
,
BMP
.
Canvas
.
Handle,
0
,
0
, SrcCopy);
SrcRect := Rect(
0
,
0
,
800
,
600
);
FBitmap
.
ReleaseDC(l_DC);
FPrimarySurface
.
BltFast(
0
,
0
, FBitmap, @SrcRect, DDBLTFAST_NOCOLORKEY
OR
DDBLTFAST_WAIT);
BMP
.
Free;
TempCanvas := TCanvas
.
Create;
FPrimarySurface
.
GetDC(SrfcDC);
with
TempCanvas
do
begin
Handle := SrfcDC;
Brush
.
Color := clBlack;
FillRect(Rect(
0
,
0
,
640
,
480
));
Font
.
Color := clLime;
TextOut(
100
,
100
,
'显示文字'
)
end
;
TempCanvas
.
Handle :=
0
;
FPrimarySurface
.
ReleaseDC(SrfcDC);
TempCanvas
.
Free;
end
;
游戏的的最基本要素:图形文字,用两段简单的化码就实现了。接下来是游戏最得要的要素了:动起来。
不过,千成不要急。这里我们还不能急于求成。因为这里还有很多东西要补充,结出说明。如果你运行了前两段代码,就会发现,这是全屏方式的,并且,如果弹出桌面后再返回程序,图形就完全变了。即使把画图写字的代码放到OnActive事件中也一样。如果显卡不支持
800
X
600
X
32
色模式,这程序就弹出出去了。所以先解释(一)中的代码。
var
FDirectDraw: IDirectDraw7;
FPrimarySurface: IDirectDrawSurface7;
有时也会说IDirectDrawSurface7代表的是显存,不一定正确,每个人理解不一样。
FBitmap:IDirectDrawSurface7;
离屏表面。这个变量不是多余的,虽然也是IDirectDrawSurface7,也可以看成是显存,但它的创建和上面那个变量不一样。可以看成是缓冲。
OnCreate事件中代码:
DirectDrawCreate(
nil
, TempDirectDraw,
nil
);
创建一个临时的 DirectDraw 对象。第一、三个参用
nil
,将游戏界面创建到主显示器上。如果有多个显示器,并且要创建到非主显示器上,你要先查找到所有显示器,然后将相应GUID的指针值,代替第一个参数。这里不讨论。(具体可参看DirectX控件源码是如何实现的)
TempDirectDraw
.
QueryInterface(IID_IDirectDraw7, FDirectDraw);
我们只能通过查询 DirectDraw 对象界面的方法,来取得一个 DirectDraw7 界面
TempDirectDraw :=
nil
;
现在我们有了 DirectDraw7 界面,临时 DirectDraw 对象不再需要它了
DX对象会在程序关闭时自动释放,也可以不赋
nil
值。前面例子中IDirectDraw7、IDirectDrawSurface7 在程序关闭时都未处理。没有必要。
FDirectDraw
.
SetCooperativeLevel(handle,DDSCL_EXCLUSIVE
or
DDSCL_FULLSCREEN);
设置合作水平:全屏模式。第二参数可以是多种组合,具体意义网上能找到,不重复。
FDirectDraw
.
SetDisplayMode(
800
,
600
,
32
,
0
,
0
);
设置屏幕大小、颜色数:
800
X
600
,
32
真彩色。现在显卡一般都支持:
8
bit、
16
bit、
32
bit,而
24
色比较特殊,很多显卡都不支持了。我主要讲的是
16
色和
32
色。
24
色除了占的字节数和
32
色不同之外,其它的都和
32
色一样(目前如此)。
8
bit(
256
色)在《Delphi Graphics
and
Game Programming Exposed
with
DirectX
7.0
》书中有详细说明。希望你已下载到此书。
如果想以此入门转而进入3D游戏编程的朋友,应主攻
16
色。
FillChar(DDSurface, SizeOf(TDDSurfaceDesc2),
0
);
DDSurface
.
dwSize := SizeOf(TDDSurfaceDesc2);
DDSurface
.
dwFlags := DDSD_CAPS;
DDSurface
.
ddsCaps
.
dwCaps := DDSCAPS_PRIMARYSURFACE;
FDirectDraw
.
CreateSurface(DDSurface,FPrimarySurface,
Nil
)<>DD_OK
then
Close;
这一段代码是创建主页面。这也DX的一大特点,创建对象时是跟据一个数据结构的要求来创建的。因此首先要初始化数据结构,然后再调用DX相应的函数。
FillChar:先对数据结构清零;
DDSurface
.
dwSize := SizeOf(TDDSurfaceDesc2); 这也是DX的一个特点,每个数据结构都有数据结构的大小,网上说是为了区别DX版本。这里只要按这种方式写就可以了
DDSurface
.
dwFlags := DDSD_CAPS; 标志,网上有所以参数说明,不重复
DDSurface
.
ddsCaps
.
dwCaps := DDSCAPS_PRIMARYSURFACE;设为主页面(PRIMARYSURFACE)
if
FDirectDraw
.
CreateSurface(DDSurface,FPrimarySurface,
Nil
)<>DD_OK
then
Close;
创建主页面,如果创建失败,就关闭程序。
接下来的代码和上面的创建过程一样,就是初始化数据结构时几个参数不同
DDSurface
.
dwFlags := DDSD_CAPS
or
DDSD_HEIGHT
or
DDSD_WIDTH;
DDSurface
.
ddsCaps
.
dwCaps := DDSCAPS_OFFSCREENPLAIN; 告诉程序,下面创建的是一个离屏表面,即不显示的页面。
OnShow 代码
这里三个函数要说一下:
GetDC、ReleaseDC,这两个没什么好说的,只要成双出现就行了。
BltFast。这是DX图形拷贝最快的一个函数。说是这么说,但好角Blt速度也差不了多少。
显示文字的代码就不用多说了,参看TCanvas就行了。
----------------------------------------------------------------
以上对DX代码作了简单说明总结。下面再说一下跟图形有关的东东,和几个非常简单特效。
一、抠图。
这个是非常实用,并且用的非常多。 实现起来也非常简单。
**接(一)程序**
var
ColorKey: TDDColorKey;
BMP:=TBitMap
.
Create;
BMP
.
loadfromfile(
'./aaa.bmp'
);
if
FBitmap
.
GetDC(l_DC)<>DD_OK
then
close;
BitBlt(l_DC,
0
,
0
, bmp
.
width, bmp
.
heigh,
BMP
.
Canvas
.
Handle,
0
,
0
, SrcCopy);
FBitmap
.
ReleaseDC(l_DC);
ColorKey
.
dwColorSpaceLowValue :=
0
;
ColorKey
.
dwColorSpaceHighValue :=
0
;
FBitmap
.
SetColorKey(DDCKEY_SRCBLT, @ColorKey);
SrcRect := Rect(
0
,
0
, bmp
.
width, bmp
.
heigh);
FPrimarySurface
.
BltFast(
200
,
100
, FBitmap2, @SrcRect, DDBLTFAST_SRCCOLORKEY
OR
DDBLTFAST_WAIT);
BMP
.
Free;
end
;
注意最后一个参数:DDBLTFAST_SRCCOLORKEY ,(一)中的是DDBLTFAST_NOCOLORKEY。
还应意,DX的透明色一般都用纯黑色。这是因为黑色,在8bit、16bit、24bit、32bit中的值都是零,而其他的颜色就不一定了。如:RGB(
0
,
0
,
255
)在16bit就不是纯蓝了。
二、BLT函数和它实现的几个特效
var
MaskRect:TRect;
PDD:TDDBltFX;
MaskRect := Rect(
100
,
100
,
200
,
200
);
FillChar(PDD, SizeOf(TDDBltFX),
0
);
PDD
.
dwSize := Sizeof(TDDBltFX) ;
PDD
.
dwFillColor:=RGB(
200
,
200
,
255
);
FPrimarySurface
.
Blt(@MaskRect,
nil
,@MaskRect,DDBLT_COLORFILL
or
DDBLT_WAIT,@PDD);
MaskRect := Rect(
200
,
200
,
220
,
216
);
FillChar(PDD, SizeOf(TDDBltFX),
0
);
PDD
.
dwSize := Sizeof(TDDBltFX);
PDD
.
dwDDFX := DDBLTFX_MIRRORUPDOWN;
FPrimarySurface
.
Blt(@MaskRect, FBitmap, @SrcRect,
DDBLT_DDFX
or
DDBLT_WAIT,@PDD);
注:Blt通过两个TRect就可以直接实现缩放。更多特效可参看TDDBltFX的数据结构中dwDDFX的变量值
------------------------------------------------------------
虽然现在几乎找不到不支持
800
X
600
X
32
的显卡。但还是有必要给出下面这段代码:
uses
DirectDraw;
var
FDirectDraw: IDirectDraw4;
implementation
function
EnumModesCallback(
const
lpDDSurfaceDesc: TDDSurfaceDesc2;
lpContext:
Pointer
): HResult; stdcall;
begin
TStringList(lpContext).Add(IntToStr(lpDDSurfaceDesc
.
dwWidth)+
' X '
+
IntToStr(lpDDSurfaceDesc
.
dwHeight)+
', '
+
IntToStr(lpDDSurfaceDesc
.
ddpfPixelFormat
.
dwRGBBitCount)+
'bits/pixel'
);
Result := DDENUMRET_OK;
end
;
procedure
TForm1
.
FormCreate(Sender: TObject);
var
TempDirectDraw: IDirectDraw;
begin
DirectDrawCreate(
nil
, TempDirectDraw,
nil
);
try
TempDirectDraw
.
QueryInterface(IID_IDirectDraw4, FDirectDraw);
finally
TempDirectDraw :=
nil
;
end
;
FDirectDraw
.
EnumDisplayModes(
0
,
nil
, ListBox1
.
Items, EnumModesCallback);
end
;
跟据这段代码,你就可以在显示模式不被支持时,让用户选择另一个模式,或给出一个提示对话框,而不用象我前面给出的例子,强制退出了。
在前面代码中,除列举显式模式程序之外,都是在全屏模式下实现的。如何实现窗口模式呢?又或者,还有更高的要求,只显式在一个Panle控件中呢?这个问题《Delphi Graphics
and
Game Programming Exposed
with
DirectX
7.0
》中没有给出答案。在解决非全屏模式的同时我们可以解决另一个问题,前面的显示文字图形的代码,是不能显示控件的。你放的任何控件都会被DX覆盖掉,这给调试带来一定麻烦,特别是全屏模式。
下面将给出非全模式、显示在控件中的方法。之后给出改进(英文书提供)的游戏框架代码。
一、窗口模式
运用窗口模式,首先设置合作水平时必须指明是窗口模式:
FDirectDraw
.
SetCooperativeLevel(handle,DDSCL_NORMAL); DDSCL_NORMAL 普通模式
然后建立Clipper——修剪者。指定DX显示范围。并依附在主表面上。如果不指定,你会看到一个很怪异的图中图的情形。
var
Clipper:IDirectDrawClipper;
FDirectDraw
.
CreateClipper(
0
,Clipper,
nil
);
Clipper
.
SetHWnd(
0
,Handle);
FPrimarySurface
.
SetClipper(Clipper) ;
Clipper
.
SetHWnd(
0
,Handle); 这里是指定的是窗口handle。如果你想DX只画在一个控件上,而窗口上其它位置留作它用,如画在panel1上,那么,这一句必须这么写:
Clipper
.
SetHWnd(
0
,panel1
.
Handle);
好了。下面全出一个比较全的代码,实现非全屏模式。
Uses
DirectDraw;
const
WM_DIRECTXACTIVATE = WM_USER +
200
;
private
procedure
DrawSurfaces;
procedure
FlipSurfaces;
procedure
AppIdle(Sender: TObject;
var
Done:
Boolean
);
procedure
AppMessage(
var
Msg: TMsg;
var
Handled:
Boolean
);
procedure
RestoreSurfaces;
var
FDirectDraw: IDirectDraw7;
FPrimarySurface: IDirectDrawSurface7;
oneSurface: IDirectDrawSurface7;
procedure
TForm1
.
FormActivate(Sender: TObject);
var
TempDirectDraw: IDirectDraw;
DDSurface: TDDSurfaceDesc2;
DDSCaps: TDDSCaps2;
Clipper:IDirectDrawClipper;
L_DC:HDC;
BMP:TBitMap;
begin
DirectDrawCreate(
nil
,TempDirectDraw,
Nil
);
try
TempDirectDraw
.
QueryInterface(IID_IDirectDraw7, FDirectDraw);
finally
TempDirectDraw :=
nil
;
end
;
Application
.
OnMessage := AppMessage;
FDirectDraw
.
SetCooperativeLevel(handle,DDSCL_NORMAL);
FillChar(DDSurface, SizeOf(TDDSurfaceDesc2),
0
);
DDSurface
.
dwSize := SizeOf(TDDSurfaceDesc2);
DDSurface
.
dwFlags := DDSD_CAPS;
DDSurface
.
ddsCaps
.
dwCaps := DDSCAPS_PRIMARYSURFACE;
DDSurface
.
dwBackBufferCount :=
1
;
if
FDirectDraw
.
CreateSurface(DDSurface,FPrimarySurface,
Nil
)<>DD_OK
then
Close;
FillChar(DDSurface, SizeOf(TDDSurfaceDesc2),
0
);
DDSurface
.
dwSize := SizeOf(TDDSurfaceDesc2);
DDSurface
.
dwFlags:=DDSD_WIDTH
or
DDSD_HEIGHT
or
DDSD_CAPS;
DDSurface
.
dwWidth :=
800
;
DDSurface
.
dwHeight :=
600
;
DDSurface
.
ddsCaps
.
dwCaps := DDSCAPS_OFFSCREENPLAIN ;
FDirectDraw
.
CreateSurface(DDSurface,oneSurface,
nil
);
FDirectDraw
.
CreateClipper(
0
,Clipper,
nil
);
Clipper
.
SetHWnd(
0
,Handle);
FPrimarySurface
.
SetClipper(Clipper) ;
PostMessage(Handle, WM_ACTIVATEAPP,
1
,
0
);
end
;
procedure
TForm1
.
AppMessage(
var
Msg: TMsg;
var
Handled:
Boolean
);
begin
case
Msg
.
Message
of
WM_ACTIVATEAPP:
if
not
Boolean
(Msg
.
wParam)
then
Application
.
OnIdle :=
nil
else
PostMessage(Application
.
Handle, WM_DIRECTXACTIVATE,
0
,
0
);
WM_DIRECTXACTIVATE:
begin
RestoreSurfaces;
Application
.
OnIdle := AppIdle;
DrawSurfaces;
end
;
WM_SYSCOMMAND:
begin
Handled := (Msg
.
wParam = SC_SCREENSAVE);
end
;
end
;
end
;
procedure
TForm1
.
AppIdle(Sender: TObject;
var
Done:
Boolean
);
begin
DrawSurfaces;
FlipSurfaces;
end
;
procedure
TForm1
.
DrawSurfaces;
var
BMP:TBitmap;
L_DC:HDC;
begin
BMP:=TBitMap
.
Create;
BMP
.
LoadFromFile(
'Desk.bmp'
);
oneSurface
.
GetDC(L_DC);
BitBlt(l_DC,
0
,
0
,
800
,
600
,
BMP
.
Canvas
.
Handle,
0
,
0
, SrcCopy);
oneSurface
.
ReleaseDC(L_DC);
end
;
procedure
TForm1
.
FlipSurfaces;
var
rcSrc,rcDest:TRECT;
P:TPoint;
begin
P
.
X:=
0
;P
.
y:=
0
;
P:=ClientToScreen(P);
rcDest:=GetClientRect;
OffsetRect(rcDest,P
.
x,P
.
y);
SetRect(rcSrc,
0
,
0
,
800
,
600
);
FPrimarySurface
.
Blt(@rcDest, oneSurface, @rcSrc, DDBLT_WAIT,
nil
);
end
;
procedure
TForm1
.
RestoreSurfaces;
begin
FPrimarySurface
.
_Restore;
oneSurface
.
_Restore;
end
;
好了,这时你放几个控件到窗口上,如:按钮、Panel、Memo,这时。它们是显示的DX图形上面了。
如果你没有设置窗口的BorderStyle:=bsSizeable; 那你可以试试改变窗口的大小,看看Blt的自动缩放动能。
如果看过DirectX
7.0
for
Delphi声明档例子,你这时会产生一个疑惑:因为它用的方法是:FPrimarySurface
.
Flip(
nil
,
0
); 非全屏模式下,不能用Flip。虽然FLip很快,非常快。但窗口模式不能用,这也是为什么很多游戏不能在在非全屏模式下运行的原因。我的一个程序,在窗口模式下只有
7
~
8
个FPS,而全屏模式下能达到
20
多。鱼和熊掌不能兼得。
关于表面恢复:
这也是全屏模式下的概念,非全屏模式下不会产生这个(我是没发现,正确与否还得论证)。在全屏模式是,DX会把所有的显存空出来给你的程序。当你程序最小化,显示出桌面时,内存中的数据就被破坏了,回到你的程序时,就必须进行恢复。恢复很简单,把丢失了的表面_Restore一下就行了。要命的是内存的的图片数据,你得一个个的恢复。你会说我上面怎么没有恢复图片的代码呀?你认真看一下就知道了,每次Blt之前都装入一次(DrawSurfaces事件),所以在表面恢复后,就没有必要。
玩过全屏游戏的朋友都知道,弹出桌后再回到游戏,
100
%的游戏都不能立刻显示游戏画面,都是黑屏,然后听到硬盘一阵狂响,这是在装入图片数据。然后才能让你继续游戏。
二、游戏框架代码
上面的代码已经有一点框架的雏形了。下面给出的是比较全面的框架,包括全屏和非全屏模式,还有比较详细的说明和心得体会。说明是翻译那本英文书上的,不是很准确,请多包涵。全屏模式下,是采用Blt、还是Flip,自己修改相应代码。看完这个框架,你对DX游戏编程就会有更深的理解了。
这只是一个框架,千万别试着运行它,在你还没有加入必要的代码之前。否则会死得很难看。
uses
DirectDraw;
const
CooperAtiveLevel = DDSCL_FULLSCREEN
or
DDSCL_ALLOWREBOOT
or
DDSCL_ALLOWMODEX
or
DDSCL_EXCLUSIVE;
SurfaceType = DDSCAPS_COMPLEX
or
DDSCAPS_FLIP
or
DDSCAPS_PRIMARYSURFACE;
const
WM_DIRECTXACTIVATE = WM_USER +
200
;
type
TForm_main =
class
(TForm)
procedure
FormCreate(Sender: TObject);
procedure
FormDestroy(Sender: TObject);
procedure
FormActivate(Sender: TObject);
procedure
FormKeyDown(Sender: TObject;
var
Key:
Word
;
Shift: TShiftState);
private
procedure
ExceptionHandler(Sender: TObject; ExceptionObj: Exception);
procedure
AppIdle(Sender: TObject;
var
Done:
Boolean
);
procedure
DrawSurfaces;
procedure
FlipSurfaces;
procedure
RestoreSurfaces;
procedure
AppMessage(
var
Msg: TMsg;
var
Handled:
Boolean
);
procedure
InitDirectDraw;
procedure
InitDirectDrawWindows;
public
end
;
var
Form_main: TForm_main;
DXWidth:
integer
=
800
;
DXHeight:
integer
=
600
;
DXCOLORDEPTH:
integer
=
8
;
BufferCount:
integer
=
1
;
WindowMode:
boolean
=
false
;
FDirectDraw : IDirectDraw7;
MainSurface : IDirectDrawSurface7;
FlipSurFace : IDirectDrawSurface7;
implementation
{$R *.dfm}
function
EnumModesCallback(
const
EnumSurfaceDesc: TDDSurfaceDesc2;
Information:
Pointer
): HResult; stdcall;
begin
if
(EnumSurfaceDesc
.
dwHeight = DXHEIGHT)
and
(EnumSurfaceDesc
.
dwWidth = DXWIDTH)
and
(EnumSurfaceDesc
.
ddpfPixelFormat
.
dwRGBBitCount = DXCOLORDEPTH)
then
Boolean
(Information^) :=
TRUE
;
Result := DDENUMRET_OK;
end
;
procedure
TForm_main
.
ExceptionHandler(Sender: TObject;
ExceptionObj: Exception);
begin
Application
.
OnIdle :=
nil
;
if
Assigned(FDirectDraw)
then
FDirectDraw
.
FlipToGDISurface;
MessageDlg(ExceptionObj
.
Message, mtError, [mbOK],
0
);
Application
.
OnIdle := AppIdle;
end
;
procedure
TForm_main
.
AppIdle(Sender: TObject;
var
Done:
Boolean
);
begin
Done :=
FALSE
;
if
not
Assigned(FDirectDraw)
then
Exit;
DrawSurfaces;
FlipSurfaces;
end
;
procedure
TForm_main
.
DrawSurfaces;
begin
end
;
procedure
TForm_main
.
FlipSurfaces;
var
DXResult: HResult;
rcSrc,rcDest:TRECT;
P:TPoint;
begin
if
WindowMode
then
begin
P
.
X:=
0
;P
.
y:=
0
;
P:=ClientToScreen(P);
rcDest:=GetClientRect;
OffsetRect(rcDest,P
.
x,P
.
y);
SetRect(rcSrc,
0
,
0
,DXWidth,DXHeight);
DXResult := MainSurface
.
Blt(@rcDest, FlipSurFace, @rcSrc, DDBLT_WAIT,
nil
);
end
else
DXResult := MainSurface
.
Flip(
nil
, DDFLIP_WAIT);
if
DXResult = DDERR_SURFACELOST
then
RestoreSurfaces
end
;
procedure
TForm_main
.
RestoreSurfaces;
begin
MainSurface
.
_Restore;
FlipSurFace
.
_Restore;
end
;
procedure
TForm_main
.
AppMessage(
var
Msg: TMsg;
var
Handled:
Boolean
);
begin
case
Msg
.
Message
of
WM_ACTIVATEAPP:
if
not
Boolean
(Msg
.
wParam)
then
Application
.
OnIdle :=
nil
else
PostMessage(Application
.
Handle, WM_DIRECTXACTIVATE,
0
,
0
);
WM_DIRECTXACTIVATE:
begin
RestoreSurfaces;
Application
.
OnIdle := AppIdle;
DrawSurfaces;
end
;
WM_SYSCOMMAND:
begin
Handled := (Msg
.
wParam = SC_SCREENSAVE);
end
;
end
;
end
;
procedure
TForm_main
.
InitDirectDraw;
var
TempDirectDraw: IDirectDraw;
DDSurface: TDDSurfaceDesc2;
DDSCaps: TDDSCaps2;
SupportedMode:
Boolean
;
begin
if
Assigned(FDirectDraw)
then
exit;
DirectDrawCreate(
nil
, TempDirectDraw,
nil
);
try
TempDirectDraw
.
QueryInterface(IID_IDirectDraw4, FDirectDraw);
finally
TempDirectDraw :=
nil
;
end
;
FillChar(DDSurface, SizeOf(TDDSurfaceDesc2),
0
);
DDSurface
.
dwSize := SizeOf(TDDSurfaceDesc2);
DDSurface
.
dwFlags := DDSD_HEIGHT
or
DDSD_WIDTH
or
DDSD_PIXELFORMAT;
DDSurface
.
dwHeight := DXHEIGHT;
DDSurface
.
dwWidth := DXWIDTH;
DDSurface
.
ddpfPixelFormat
.
dwSize := SizeOf(TDDPixelFormat_DX6);
DDSurface
.
ddpfPixelFormat
.
dwRGBBitCount := DXCOLORDEPTH;
SupportedMode :=
FALSE
;
FDirectDraw
.
EnumDisplayModes(
0
, @DDSurface, @SupportedMode,
EnumModesCallback );
if
not
SupportedMode
then
begin
MessageBox(Handle,
PChar
(
'The installed DirectX drivers do not support a '
+
'display mode of: '
+IntToStr(DXWIDTH)+
' X '
+
IntToStr(DXHEIGHT)+
', '
+IntToStr(DXCOLORDEPTH)+
' bit color'
),
'Unsupported Display Mode Error'
, MB_ICONERROR
or
MB_OK);
Close;
Exit;
end
;
FDirectDraw
.
SetCooperativeLevel(Handle, COOPERATIVELEVEL);
FDirectDraw
.
SetDisplayMode(DXWIDTH, DXHEIGHT, DXCOLORDEPTH,
0
,
0
);
FillChar(DDSurface, SizeOf(TDDSurfaceDesc2),
0
);
DDSurface
.
dwSize := SizeOf(TDDSurfaceDesc2);
DDSurface
.
dwFlags := DDSD_CAPS
or
DDSD_BACKBUFFERCOUNT;
DDSurface
.
ddsCaps
.
dwCaps := SURFACETYPE;
DDSurface
.
dwBackBufferCount := BUFFERCOUNT;
FDirectDraw
.
CreateSurface(DDSurface, MainSurface,
nil
);
FillChar(DDSCaps, SizeOf(TDDSCaps2),
0
);
DDSCaps
.
dwCaps := DDSCAPS_BACKBUFFER;
MainSurface
.
GetAttachedSurface(DDSCaps, FlipSurFace) ;
end
;
procedure
TForm_main
.
InitDirectDrawWindows;
var
TempDirectDraw: IDirectDraw;
DDSurface: TDDSurfaceDesc2;
Clipper:IDirectDrawClipper;
SupportedMode:
Boolean
;
begin
if
Assigned(FDirectDraw)
then
exit;
DirectDrawCreate(
nil
, TempDirectDraw,
nil
);
try
TempDirectDraw
.
QueryInterface(IID_IDirectDraw4, FDirectDraw);
finally
TempDirectDraw :=
nil
;
end
;
FillChar(DDSurface, SizeOf(TDDSurfaceDesc2),
0
);
DDSurface
.
dwSize := SizeOf(TDDSurfaceDesc2);
DDSurface
.
dwFlags := DDSD_HEIGHT
or
DDSD_WIDTH
or
DDSD_PIXELFORMAT;
DDSurface
.
dwHeight := DXHEIGHT;
DDSurface
.
dwWidth := DXWIDTH;
DDSurface
.
ddpfPixelFormat
.
dwSize := SizeOf(TDDPixelFormat_DX6);
DDSurface
.
ddpfPixelFormat
.
dwRGBBitCount := DXCOLORDEPTH;
SupportedMode :=
FALSE
;
FDirectDraw
.
EnumDisplayModes(
0
, @DDSurface, @SupportedMode,
EnumModesCallback );
if
not
SupportedMode
then
begin
MessageBox(Handle,
PChar
(
'The installed DirectX drivers do not support a '
+
'display mode of: '
+IntToStr(DXWIDTH)+
' X '
+
IntToStr(DXHEIGHT)+
', '
+IntToStr(DXCOLORDEPTH)+
' bit color'
),
'Unsupported Display Mode Error'
, MB_ICONERROR
or
MB_OK);
Close;
Exit;
end
;
FDirectDraw
.
SetCooperativeLevel(Handle, DDSCL_NORMAL);
FillChar(DDSurface, SizeOf(TDDSurfaceDesc2),
0
);
DDSurface
.
dwSize := SizeOf(TDDSurfaceDesc2);
DDSurface
.
dwFlags := DDSD_CAPS;
DDSurface
.
ddsCaps
.
dwCaps := DDSCAPS_PRIMARYSURFACE;
FDirectDraw
.
CreateSurface(DDSurface,MainSurface,
Nil
);
FillChar(DDSurface, SizeOf(TDDSurfaceDesc2),
0
);
DDSurface
.
dwSize := SizeOf(TDDSurfaceDesc2);
DDSurface
.
dwFlags:=DDSD_WIDTH
or
DDSD_HEIGHT
or
DDSD_CAPS;
DDSurface
.
dwWidth :=
800
;
DDSurface
.
dwHeight :=
600
;
DDSurface
.
ddsCaps
.
dwCaps := DDSCAPS_OFFSCREENPLAIN ;
FDirectDraw
.
CreateSurface(DDSurface,FlipSurFace,
nil
);
FDirectDraw
.
CreateClipper(
0
,Clipper,
nil
);
Clipper
.
SetHWnd(
0
,Handle);
MainSurface
.
SetClipper(Clipper) ;
end
;
procedure
TForm_main
.
FormCreate(Sender: TObject);
begin
Application
.
OnException := ExceptionHandler;
if
WindowMode
then
begin
BorderStyle := bsSingle;
BorderIcons := [biSystemMenu,biMinimize];
end
else
begin
BorderStyle := bsNone;
ShowCursor(
FALSE
);
FormStyle := fsStayOnTop;
Cursor := crNone;
BorderIcons := [];
end
;
end
;
procedure
TForm_main
.
FormDestroy(Sender: TObject);
begin
Application
.
OnException :=
nil
;
ShowCursor(
TRUE
);
end
;
procedure
TForm_main
.
FormActivate(Sender: TObject);
begin
if
WindowMode
then
InitDirectDrawWindows
else
InitDirectDraw;
Application
.
OnMessage := AppMessage;
PostMessage(Handle, WM_ACTIVATEAPP,
1
,
0
);
end
;
procedure
TForm_main
.
FormKeyDown(Sender: TObject;
var
Key:
Word
;
Shift: TShiftState);
begin
Case
Key
of
VK_ESCAPE:Close;
end
;
end
;
end
.
至此,你可以尝试写一点小小的动画了,如DX7
.0
例子中的小动画。
另,如果你想在全屏幕式下显示控件,比如按钮什么的,那么,你必须进行以下处理: 建立Clipper,依附在主表面上;然后改Flip为Blt。用了Clipper后,就不能用Flip了。
对于图形编程,就应该精通到对每一个象素点的操作。并且可以对每一个象素的R,G,B颜色分量进行操作。DX也应如何。
下面要说的就是,DX如何可以做到对每一个象素点的R、G、B分量进行操作。
var
SourSf: IDirectDrawSurface7;
SourSrfcInfo: TDDSurfaceDesc2;
Sour:
pointer
;
SourSrfcInfo
.
dwSize := SizeOf(TDDSurfaceDesc2);
SourSf
.
Lock(
nil
, SourSrfcInfo, DDLOCK_SURFACEMEMORYPTR
or
DDLOCK_WAIT,
0
);
Sour:=SourSrfcInfo
.
lpSurface;
SourSf
.
Unlock(
nil
);
Sour就是SourSf的颜色值的起始地址。如果锁定的是矩形,返回的是矩形第一个点的地址。暂时放下这个Sour,先说说SourSrfcInfo结构。
这个数据结构在Lock之后,取得当前屏幕各种返回值,有些返回值非常重要。
ddpfPixelFormat
.
dwRGBBitCount 返回当前屏幕的颜色数,如
8
、
16
、
24
、
32
。这对于同时使用全屏非全屏的游戏来说很重要,因为非全屏时,你设置的屏幕大小颜色数是没用的,颜色数是跟据桌面的颜色数来定的,只有通这个值,你才能确定颜色数是多少。
ddpfPixelFormat
.
dwRBitMask
ddpfPixelFormat
.
dwGBitMask
ddpfPixelFormat
.
dwBBitMask
这三个值是返回如何取得颜色RGB的分量。颜色值
and
上面其中一个值就可以取得其中一个分量。也许你会认为这是多此一举。象32bit:
00
RR GG BB,这分的很清楚呀。但对于16bit来说,它就有
565
和
555
两种方法表示。虽然现在的显卡几乎
100
%是
565
,但是作为程序员不能心存侥幸,用返回值来处理是
100
%正确(对于图形格式
555
和
565
不在讨论范围,请到网上找资料) 。《轩辕剑
5
》在联想的某种机型上采用窗口模式时不能正确抠图,其中原由未深究,程序员心存侥幸是一定的了。
这个数据结构还有很多值,请自己去查资料、慢慢体会了。
回过头来讲Sour这个变量。取得了首址,我们就可计算得到任何一点的地址值。DX中用的最多的是TRECT,一般也是跟据TRECT来找对应的地址,然后读取它的值。
为了使各种颜色数能通用。这里要增加一个变量:
RGBByteCount:= SourSrfcInfo
.
ddpfPixelFormat
.
dwRGBBitCount
div
8
;
这是因为颜色数为
8
bit 时,颜色值占一个
Byte
,
16
bit 占
2
个
Byte
,
24
bit 占
3
个
byte
, 32bit占
4
个
Byte
。而指针加
1
只加一个
Byte
。
下面是锁定整个屏幕后,矩形srRect的首址计算方法。
var
Sour,SrMem:
pointer
;
Sour:=SourSrfcInfo
.
lpSurface;
SrMem:=
Pointer
(
Longint
(Sour)+ srRect
.
Left *RGBByteCount+ SrRect
.
Top *SourSrfcInfo
.
lPitch );
第Y行的第一个象素的地址值:Sour :=
Pointer
(
Longint
(SrMem)+(SourSrfcInfo
.
lPitch )*Y );
讲了一大堆,取出来的还是地址,颜色值还是没有取出来,那颜色值怎么取呢?可以参照《Delphi Graphics
and
Game Programming Exposed
with
DirectX
7.0
》中
256
色的读法:
TBytePtr =
array
[
0..0
]
of
Byte
;
PBytePtr = ^TBytePtr;
var
BackMem: PBytePtr;
begin
BackMem := PBytePtr(Sour);
注,
256
色读取的不是颜色值,是索引值。要跟据索引值和颜色表找出对应RGB值。以下不再讨论
256
色。
同样对于
16
色 :
TWordPtr =
array
[
0..0
]
of
word
;
PWordPtr = ^TWordPtr;
16
色每个象素点占
2
个
Byte
,而
word
正好是
2
Byte
;
32
色每个象素占
4
个
Byte
,用Dword:
TDWordPtr =
array
[
0..0
]
of
Dword;
PDWordPtr = ^TDWordPtr;
麻烦来了,
24
色每个象素占
3
Byte
,而没有变量对应它。先定义一个3byte的数据结构,然后再如上定义。我想应该可以,但没做过试验。现在很多显卡不支持
24
色,可能也是这个原因吧。
另一种方法,用汇编通杀。强烈建议使用这种方法。
asm
push esi
mov esi,Sour
mov eax,[esi]
……
end
;
这时,eax的值就是Sour指针对应的值。注意:这时读取的是一个Dword,然后再分吧,
1
个,
2
个,
3
个。用汇编能读多少就读多少,不要因为是
16
色,而写成这样 movzx edx,
word
ptr [esi],如果用MMX能用 movq mm0,[esi] 一次读入一个8byte,就不要用movd mm0,[esi] 读入4byte。边界例外,要注意别读出界哦~~~(更多MMX请看我的另一篇笔记,网上有更多相关资料可供你查阅)。原因很简单,我在回答“我爱Pascal”的一个问题回答过:mov eax,[esi] 这样的东东是耗时大户,能少读一次是一次,而汇编其他的代码耗时很少,读入后再细分,代码是长点,但耗时绝对少。
值读出来了,进行一系列操作之后,要怎么写回去?这个应该不是问题了。只接把值写回到地址就行了。
BackMem:=
$0000FFFF
;
对于汇编
asm
……
mov [esi],eax
……
end
;
给出一个旋转图形特效代码,原理请看那本英文书或网上找资料。因为采用的反算法,每次只能读取计算一个点,因此无法运用上面说的能读几个点读几个点的美好想法。
var
SinTable:
array
[
0..359
]
of
single
;
CosTable:
array
[
0..359
]
of
single
;
function
ReadColor(Sour:
pointer
;ByteCount:
integer
):
integer
;
begin
case
ByteCount
of
2
:
asm
mov eax,sour
xor
edx,edx
mov dx,[eax]
mov @result,edx
end
;
3
:
asm
mov eax,sour
xor
edx,edx
movzx dx,
byte
ptr [eax+
2
]
shl
edx,
16
mov dx,
word
ptr [eax]
mov @result,edx
end
;
4
:
asm
mov eax,sour
mov edx,[eax]
mov @result,edx
end
;
end
;
end
;
procedure
writeColor(Dest:
pointer
;sour:
integer
;ByteCount:
integer
);
begin
case
ByteCount
of
2
:
asm
mov eax,Dest
mov edx,sour
mov [eax],dx
end
;
3
:
asm
mov eax,Dest
mov edx,sour
mov
word
ptr [eax],dx
bswap edx
mov
byte
ptr [eax+
2
],dh
end
;
4
:
asm
mov eax,Dest
mov edx,sour
mov [eax],edx
end
;
end
;
end
;
function
RotateDraw(DestSf: IDirectDrawSurface7;
DsRect:TRect;
SourSf: IDirectDrawSurface7;
SrRect:TRect;
ColorKey:
integer
;
UseColorKey:
boolean
;
Theta:
integer
):HResult;
var
DestSrfcInfo, SourSrfcInfo: TDDSurfaceDesc2;
DestX, DestY, SrcX, SrcY:
Integer
;
SinTheta, CosTheta:
Single
;
CenterX, CenterY:
Single
;
Dest, Sour, DsMem, SrMem:
pointer
;
Width,Height:
integer
;
aa:
integer
;
begin
if
Theta<
0
then
Theta:=
360
+(Theta
mod
360
);
Theta:=Theta
mod
360
;
SinTheta := SinTable[Theta];
CosTheta := CosTable[Theta];
try
SourSrfcInfo
.
dwSize := SizeOf(TDDSurfaceDesc2);
Result:=SourSf
.
Lock(
nil
, SourSrfcInfo, DDLOCK_SURFACEMEMORYPTR
or
DDLOCK_WAIT,
0
);
DestSrfcInfo
.
dwSize := SizeOf(TDDSurfaceDesc2);
Result:=DestSf
.
Lock(@DsRect, DestSrfcInfo, DDLOCK_SURFACEMEMORYPTR
or
DDLOCK_WAIT,
0
);
Sour:=
pointer
(
Longint
(SourSrfcInfo
.
lpSurface)
+ srRect
.
Left * RGBByteCount
+ SrRect
.
Top * SourSrfcInfo
.
lPitch );
Dest:= DestSrfcInfo
.
lpSurface;
Width:=SrRect
.
Right-srRect
.
Left;
Height:=SrRect
.
Bottom-SrRect
.
Top;
CenterX := (Width /
2
);
CenterY := (Height /
2
);
for
DestY :=
0
to
Height-
1
do
begin
for
DestX :=
0
to
Width-
1
do
begin
SrcX := Trunc(CenterX + (DestX - CenterX)*CosTheta -
(DestY - CenterY)*SinTheta);
SrcY := Trunc(CenterY + (DestX - CenterX)*SinTheta +
(DestY - CenterY)*CosTheta);
if
(SrcX >
0
)
and
(SrcX < Width)
and
(SrcY >
0
)
and
(SrcY < Height)
then
begin
SrMem :=
pointer
(
Longint
(Sour)+ SrcY * SourSrfcInfo
.
lPitch
+ SrcX * RGBByteCount);
aa:=ReadColor(SrMem,RGBByteCount);
DsMem :=
pointer
(
Longint
(Dest)+ DestY * DestSrfcInfo
.
lPitch
+ DestX * RGBByteCount);
if
UseColorKey
then
begin
if
aa<>ColorKey
then
writeColor(DsMem,aa,RGBByteCount);
end
else
writeColor(DsMem,aa,RGBByteCount);
end
;
end
;
end
;
finally
DestSf
.
Unlock(
nil
);
SourSf
.
Unlock(
nil
);
end
;
end
;
initialization
for
iAngle :=
0
to
359
do
begin
SinTable[iAngle] := Sin(iAngle*(PI/
180
));
CosTable[iAngle] := Cos(iAngle*(PI/
180
));
end
;
end
.
调用:借用(三)中的框架代码,这里不再重复。注意恢复表面时,要重载图形。
var
Theta:
integer
=
0
;
TickCount:Dword;
procedure
TForm_main
.
DrawSurfaces;
var
SrcRect:TRect;
sRect,aRect:TRect;
thisTickCount : DWORD;
begin
thisTickCount := GetTickCount;
if
(thisTickCount - TickCount) >
50
then
begin
TickCount:=thisTickCount;
dec(Theta,
2
);
end
;
sRect:=Rect(
0
,
0
,
110
,
110
);
SrcRect:=Rect(
0
,
0
,
110
,
110
);
offsetRect(SrcRect,
260
,
30
);
RotateDraw(FlipSurFace,SrcRect ,TempSurFace,sRect,
0
,
true
,Theta);
end
;
最后说说FPS,这是一个很重要的东西,编写游戏的水平就看这个指标,每秒画多少帧画面。
放一个TTimer控件在窗体上,Interval 设为
100
。
var
FrameRate:
integer
;
FOldTime2:Dword;
FPS:
integer
;
procedure
TForm_main
.
Timer1Timer(Sender: TObject);
var
t: DWORD;
i:
integer
;
begin
t := TimeGetTime;
i := Max(t-FOldTime2,
1
);
if
i>
1000
then
begin
FPS := Round(FrameRate*
1000
/i);
FrameRate:=
0
;
FOldTime2 := t;
end
;
end
;
在DrawSurfaces中用文字写出来FPS 值就行了。
-----------------------------------------------------
对于游戏来说,还有声音,输入设备,网络连接,力反馈等等。这些东西就不多讲了。请见谅。
声音处理跟图象处理差不多,建立过程也相似;键盘、鼠标的操作也不是难事。知道DX原理之后,就一通百通。力反馈没有研究,因为没有条件。至于网络,没写过即时战斗游戏也就用不上IPX连网了,也没研究,无权发言。以上所写仅给致力于游戏编程而苦于无法入门的朋友。
只是游戏开发远没有这么简单。首先美工关不是我们程序员能处理的,声音资源也不是我们能把握的。我们能处理的只是代码。就代码而言:
1
、至少要知道汇编,否则你无法优化你的程序。可以参看日本人写的DelphiX控件里面的汇编,你会受益非浅;写个简短的程序,然后调出CPU窗口,只接看汇编代码。
2
、能看懂C语言,必竟游戏是C的天下,源代码N多;
3
、还必须知道一些计算机图形原理,这是因为要进行图形特效处理,你不可能指望美工把各种可能的图形都画好供你调用,甚至不用抠图。FASTBMP控件是个很好的参考,它带有许多图形特效。如果你不想用DX写游戏,甚至可以用它来开发出不错的游戏。而代码竟然跟DX也差不多(初始化除外)。