用Delphi + DirectX开发简单RPG游戏
作者: 来源:不详 添加时间:2006-5-25 21:23:25提到 RPG (角色扮演游戏,Role Play Game),在座各位恐怕没有不熟悉的。从古老经典的 DOS 版《仙剑奇侠传》到新潮花哨的《轩辕剑》系列,无不以曲折优美的故事情节,美丽可人的主角,悦耳动情的背景音乐,震撼了每一个玩家的心灵。而说到 RPG,就不能不提 DirectX,因为 PC 上大部分的 RPG 都是用这个冬冬开发的。早在《轩辕剑叁外传——天之痕》推出的时候,我就曾想过用 DirectX 写一个自己的 RPG,自己来安排故事情节的发展,却总是因为这样或那样的事情,一直没有能够实现这个心愿。在耗费了宝贵的几年青春,搞定了诸如考试、恋爱、出国等琐碎杂事之后,我终于可以在这个 SARS 肆虐的时代,坐在陪伴了我整个大学生涯的电脑前,听着颓废而又声嘶力竭的不知名歌曲,写一些一直想写却没有写的东西。
DirectX 简介
DirectX 对于大多数游戏爱好者来说都不陌生(当然,那些只在DOS下艰苦作战的朋友例外),在安装一个游戏前,系统总是会提示你是否需要同时升级 DirectX。简单地说,DirectX 就是一系列的 DLL (动态连接库),通过这些 DLL,开发者可以在无视于设备差异的情况下访问底层的硬件。DirectX 封装了一些 COM(Component Object Model)对象,这些 COM 对象为访问系统硬件提供了一个主要的接口。首先,我们先来看一下 DirectX 的结构:

DirectX 目前主要由以下七个主要部分组成:
DirectDraw – 为程序直接访问显存提供接口,同时和其它的Windows应用程序保持兼容。
Direct3D – 为访问3D加速设备提供接口。
DirectInput – 为各种输入设备提供接口,比如鼠标,键盘,力反馈游戏手柄和操纵杆等。
DirectPlay – 为游戏提供网络功能接口,比如支持通过 TCP/I、IPX 等协议进行游戏中的数据传输。
DirectSound – 为访问声卡提供接口,支持WAV、MIDI 等文件的直接播放。
DirectSound3D –通过此接口,可以模拟出某一个声音在三维空间中任何一个位置的播放所产生的效果,从而达到逼真的环绕立体声。
DirectMusic – 此接口主要是生成一系列的原始声音采样反馈给相应的用户事件。
开发工具(Delphi & DelphiX)
下一步,我们来介绍开发工具。我们通常所安装的其实只有 DirectX 的运行库(一系列封装好的DLL文件),其内部函数结构非常复杂,所以我们还需要 DirectX 的开发工具。所谓工欲善其事,必先利其器,虽然微软公布了 DirectX SDK,但是由于所有的头文件都是用 C/C++ 写成的,作为 Delphi 的热情拥护者,我们还是无从下手。把 C/C++ 写成的代码转换成 Pascal 可不是一件容易的事,但是不必担心,这项工作已经有人做好了。日本人 Hiroyuki Hori 为 Delphi 写了一个免费的组件包,称作 DelphiX。这些组件可以使得开发者可以轻松地访问 DirectX 的 DirectDraw、Direct3D、DirectSound、DirectInput(支持力反馈手柄)和 DirectPlay 对象。目前的 DelphiX 包支持 Borland Delphi 3/4/5/6/7 和 DirectX 7.0 以上版本(见图2)。安装了 DelphiX 之后,我们将不需要再安装微软的 DirectX SDK。在这篇文章里我们将使用的就是 DelphiX。
提到 RPG (角色扮演游戏,Role Play Game),在座各位恐怕没有不熟悉的。从古老经典的 DOS 版《仙剑奇侠传》到新潮花哨的《轩辕剑》系列,无不以曲折优美的故事情节,美丽可人的主角,悦耳动情的背景音乐,震撼了每一个玩家的心灵。而说到 RPG,就不能不提 DirectX,因为 PC 上大部分的 RPG 都是用这个冬冬开发的。早在《轩辕剑叁外传——天之痕》推出的时候,我就曾想过用 DirectX 写一个自己的 RPG,自己来安排故事情节的发展,却总是因为这样或那样的事情,一直没有能够实现这个心愿。在耗费了宝贵的几年青春,搞定了诸如考试、恋爱、出国等琐碎杂事之后,我终于可以在这个 SARS 肆虐的时代,坐在陪伴了我整个大学生涯的电脑前,听着颓废而又声嘶力竭的不知名歌曲,写一些一直想写却没有写的东西。
DirectX 简介
DirectX 对于大多数游戏爱好者来说都不陌生(当然,那些只在DOS下艰苦作战的朋友例外),在安装一个游戏前,系统总是会提示你是否需要同时升级 DirectX。简单地说,DirectX 就是一系列的 DLL (动态连接库),通过这些 DLL,开发者可以在无视于设备差异的情况下访问底层的硬件。DirectX 封装了一些 COM(Component Object Model)对象,这些 COM 对象为访问系统硬件提供了一个主要的接口。首先,我们先来看一下 DirectX 的结构:

DirectX 目前主要由以下七个主要部分组成:
DirectDraw – 为程序直接访问显存提供接口,同时和其它的Windows应用程序保持兼容。
Direct3D – 为访问3D加速设备提供接口。
DirectInput – 为各种输入设备提供接口,比如鼠标,键盘,力反馈游戏手柄和操纵杆等。
DirectPlay – 为游戏提供网络功能接口,比如支持通过 TCP/I、IPX 等协议进行游戏中的数据传输。
DirectSound – 为访问声卡提供接口,支持WAV、MIDI 等文件的直接播放。
DirectSound3D –通过此接口,可以模拟出某一个声音在三维空间中任何一个位置的播放所产生的效果,从而达到逼真的环绕立体声。
DirectMusic – 此接口主要是生成一系列的原始声音采样反馈给相应的用户事件。
开发工具(Delphi & DelphiX)
下一步,我们来介绍开发工具。我们通常所安装的其实只有 DirectX 的运行库(一系列封装好的DLL文件),其内部函数结构非常复杂,所以我们还需要 DirectX 的开发工具。所谓工欲善其事,必先利其器,虽然微软公布了 DirectX SDK,但是由于所有的头文件都是用 C/C++ 写成的,作为 Delphi 的热情拥护者,我们还是无从下手。把 C/C++ 写成的代码转换成 Pascal 可不是一件容易的事,但是不必担心,这项工作已经有人做好了。日本人 Hiroyuki Hori 为 Delphi 写了一个免费的组件包,称作 DelphiX。这些组件可以使得开发者可以轻松地访问 DirectX 的 DirectDraw、Direct3D、DirectSound、DirectInput(支持力反馈手柄)和 DirectPlay 对象。目前的 DelphiX 包支持 Borland Delphi 3/4/5/6/7 和 DirectX 7.0 以上版本(见图2)。安装了 DelphiX 之后,我们将不需要再安装微软的 DirectX SDK。在这篇文章里我们将使用的就是 DelphiX。

开发过程
说了许多废话以后,下面我们脱离纸上谈兵,开始正式的开发。在本例中,我们主要实现用鼠标来控制精灵往八个方向行走。所有图片均来自大宇公司《轩辕剑叁——天之痕》,其中精灵采用故事中陈靖仇的形象特此致谢。同时,请各位读者勿把这些图片用于商业用途,否则后果自负。[page]


打开 Delphi 并新建一个应用程序,依次选中 DelphiX 组件栏的 TDXDraw、TDXImageList、TDXInput、TDXTimer、TDXSpriteEngine 组件,添加到用户区,分别命名为 DXDraw、DXImageList、DXInput、DXTimer、DXSpriteEngine,按照下表设置其各项属性。对于 DXImageList,点击 Object Inspector 中的 Items,在其中加入两张位图(background.bmp和player.bmp),分别命名为 background 和 player,设置 player 的 PatternHeight 和 PatternWidth 均为120象素,设置其 transparentcolor 为粉红色(clFuchsia)。
控件 属性 值
DXDraw Align alClient
Display.Width 800
Display.Height 600
Display.BitCount 24
Options [doAllowReboot, doWaitVBlank, doCenter, doFlip]
AutoInitialize True
DXTimer Enabled True [page]
Interval 0
DXInput Mouse.BindInputState True
Mouse.Enabled True
DXSpriteEngine DXDraw DXDraw
下面就是全部的源程序,请先在 Delphi 中产生相应事件然后填入代码,最后按下F9运行就可以运行程序了。用鼠标点击你的目的地,陈靖仇就会自动跑到指定地点。尝试一下开发一些简单的游戏吧,用 DelphiX 这把牛刀!所有程序在 Delphi 4.0 + DirectX 8.0 环境下测试通过。本文所需控件可以在 这里 下载。
Unit Main;
Interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, Menus, DXClass, DXSprite, DXInput, DXDraws;
type
TDirection = (DrUp, DrDown, DrLeft, DrRight, DrUpLeft, DrUpRight, DrDownLeft, DrDownRight);
{自定义游戏中所用到的方向}
TPlayerSprite = class (TImageSprite)
CanMove: Boolean;
protected
procedure DoMove(MoveCount: Integer); override;
procedure MoveTo(MoveCount:Integer; Direction: TDirection);
procedure DoCollision(Sprite: TSprite; var Done: Boolean); override;
end;
TMainForm = class(TDXForm) {此处使用优化过的TDXForm来代替TForm}
DXTimer: TDXTimer;
DXDraw: TDXDraw;
DXSpriteEngine: TDXSpriteEngine;
DXInput: TDXInput;
ImageList: TDXImageList;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure DXDrawFinalize(Sender: TObject);
procedure DXDrawInitialize(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure DXTimerTimer(Sender: TObject; LagCount: Integer);
procedure DXDrawMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure DXDrawMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure DXDrawMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
AnchorX: Integer;
AnchorY: Integer; {鼠标点击发生的位置}
MouseX: Integer;
MouseY: Integer; {鼠标当前位置}
PlayerSprite: TPlayerSprite; {游戏中我们所用鼠标控制的人物}
BackSprite: TBackGroundSprite; {游戏的背景图}
end;
const
speed=5; {游戏人物向各个方向运动时的动画播放速度}
var
MainForm: TMainForm;
Steps: Integer; {用于控制切换精灵动画图片的参数}
implementation
{$R *.DFM}
procedure TPlayerSprite.DoCollision(Sprite: TSprite; var Done: Boolean);
begin
Done:=False; {已经侦测到碰撞,不再重复检测碰撞}
{检测游戏人物是否与其它精灵发生了碰撞,此处可以扩展为对话等情节}
end;
procedure TPlayerSprite.DoMove(MoveCount: Integer);
var
l,r,d,u: Boolean;
absX,absY: Integer; {游戏人物的当前位置与目的地的绝对距离}
begin
inherited DoMove(MoveCount);
MoveCount:=Trunc(MoveCount*1.5);
l:=false; r:=false; u:=false; d:=false;
if (Trunc(X)-MainForm.AnchorX>0) then l:=true else r:=true;
if (Trunc(Y)-MainForm.AnchorY>0) then u:=true else d:=true;
absX:=abs(Trunc(X)-MainForm.AnchorX);
absY:=abs(Trunc(Y)-MainForm.AnchorY);
if absX<4 then begin l:=false; r:=false; end;
if absY<4 then begin u:=false; d:=false; end;
{如果绝对距离已经小于四个象素,则认为已经到达目的地}
if u and l and not d and not r then MoveTo(MoveCount,DrUpLeft);
if u and r and not l and not d then MoveTo(MoveCount,DrUpRight);
if d and l and not r and not u then MoveTo(MoveCount,DrDownLeft);
if d and r and not u and not l then MoveTo(MoveCount,DrDownRight);
if d and not l and not r and not u then MoveTo(MoveCount,DrDown);
if u and not l and not r and not d then MoveTo(MoveCount,DrUp);
if l and not u and not r and not d then MoveTo(MoveCount,DrLeft);
if r and not l and not u and not d then MoveTo(MoveCount,DrRight);
{根据目的地来判断运动的方向,从而播放相应方向运动的动画}
Collision; {检测碰撞}
Engine.X := -X+Engine.Width div 2 - Width div 2;
Engine.Y := -Y+Engine.Height div 2 - Height div 2;
{移动引擎,从而是游戏人物处于舞台的正中央}
end;
procedure TMainForm.DXTimerTimer(Sender: TObject; LagCount: Integer);
begin
if not DXDraw.CanDraw then exit;
{检测DXDraw是否可以画,否则退出}
DXInput.Update;
{捕捉各类设备输入,这里我们用来检测鼠标的输入}
LagCount := 1000 div 60;
{用来控制整个游戏运行速度的参数}
DXSpriteEngine.Move(LagCount);
DXSpriteEngine.Dead;
DXDraw.Surface.Fill(0);
{将整个屏幕填充为黑色}
DXSpriteEngine.Draw;
with DXDraw.Surface.Canvas do
begin
brush.style:=bsclear;
pen.style:=psclear;
pen.color:=clwhite;
Font.Color:=clWhite;
Font.Size:=10;
textout(10,10,'Press ESC to Quit');
textout(100,100,'X: '+IntToStr(AnchorX)+'Y: '+IntToStr(AnchorY));
{鼠标点击的位置经转换后在游戏世界中的坐标}
textout(100,200,'Sprit x:'+IntToStr(Trunc(PlayerSprite.x))+'Y: ' +IntToStr(Trunc(PlayerSprite.y)));
{精灵在游戏世界中的坐标}
textout(100,300,'Relative x:'+IntToStr(AnchorX-Trunc(PlayerSprite.x))+'Y: ' +IntToStr(AnchorY-Trunc (PlayerSprite.y)));
{精灵当前位置与目的地之间的绝对距离}
textout(200,100,'Mouse x:'+IntToStr(MainForm.MouseX)+'Y: ' +IntToStr(MainForm.MouseY));
{鼠标当前位置,相对于窗口左上角,未转换为游戏世界坐标}
Release;
end;
{在字母上输出相应参数,用于程序调试}
DXDraw.Flip;
{将内存中的后台表面翻转到当前并且显示}
end;
procedure TMainForm.DXDrawFinalize(Sender: TObject);
begin
DXTimer.Enabled := False;
{关闭定时器}
end;
procedure TMainForm.DXDrawInitialize(Sender: TObject);
begin
DXTimer.Enabled := True;
{启动定时器}
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
Steps:=0;
AnchorX:=0;
AnchorY:=0;
MouseX:=320;
MouseY:=240;
{默认使鼠标处于屏幕的中央}
ImageList.Items.MakeColorTable;
DXDraw.ColorTable := ImageList.Items.ColorTable;
DXDraw.DefColorTable := ImageList.Items.ColorTable;
DXDraw.UpdatePalette;
{更新系统调色板}
with TBackgroundSprite(BackSprite) do
begin
SetMapSize(1, 1);{设定背景显示样式为1×1}
Image := ImageList.Items.Find('background'); {载入背景图片}
Z := -2; {设定背景层次}
Tile := True; {设定背景填充样式为平铺}
end;
PlayerSprite := TPlayerSprite.Create(DXSpriteEngine.Engine);
with TPlayerSprite(PlayerSprite) do
begin
Image := ImageList.Items.Find('player');
Z := 2;
Width := Image.Width;
Height := Image.Height;
end;
{载入游戏人物}
end;
procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
{如果按了Esc,则退出}
if Key=VK_ESCAPE then
Close;
{全屏模式和窗口模式的切换}
if (ssAlt in Shift) and (Key=VK_RETURN) then
begin
DXDraw.Finalize;
if doFullScreen in DXDraw.Options then
begin
RestoreWindow;
DXDraw.Cursor := crNone;
BorderStyle := bsSizeable;
DXDraw.Options := DXDraw.Options - [doFullScreen];
end else
begin
StoreWindow;
DXDraw.Cursor := crNone;
BorderStyle := bsNone;
DXDraw.Options := DXDraw.Options + [doFullScreen];
end;
end;
end;
procedure TMainForm.DXDrawMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
AnchorX := x + Trunc(PlayerSprite.x)-320;
AnchorY := y + Trunc(PlayerSprite.y)-240;
{将鼠标在屏幕上点击的位置转换到游戏世界中}
PlayerSprite.CanMove:=True;
{此参数允许鼠标拖动}
end;
procedure TMainForm.DXDrawMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if PlayerSprite.CanMove then
begin
AnchorX := x + Trunc(PlayerSprite.x)-320;
AnchorY := y + Trunc(PlayerSprite.y)-240;
{在鼠标拖动过程中将鼠标在屏幕上点击的位置转换到游戏世界中}
end;
MouseX:=X;
MouseY:=Y;
{鼠标当前位置}
end;
procedure TMainForm.DXDrawMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
PlayerSprite.CanMove:=False;
end;
procedure TMainForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
DXSpriteEngine.Free;
end;
procedure TPlayerSprite.MoveTo(MoveCount: Integer; Direction: TDirection);
begin
{控制精灵往各个方向移动}
case Direction of
DrUp:
begin
Y := Y-(150/1000)*MoveCount;
Inc(steps);
AnimPos:=steps div speed+20+1;
{当前动画中播放的图片序号}
if steps>4*speed-2 then steps:=0;
end;
DrDown:
begin
Y := Y+(150/1000)*MoveCount;
Inc(steps);
AnimPos:=steps div speed+1;
if steps> 4*speed-2 then steps:=0;
end;
DrLeft:
begin
X := X-(150/1000)*MoveCount;
Inc(steps);
AnimPos:=steps div speed+10+1;
if steps>4*speed-2 then steps:=0;
end;
DrRight:
begin
X := X+(150/1000)*MoveCount;
Inc(steps);
AnimPos:=steps div speed+30+1;
if steps>4*speed-2 then steps:=0;
end;
DrUpLeft:
begin
X := X-(150/1000)*MoveCount;
Y := Y-(150/1000)*MoveCount;
Inc(steps);
AnimPos:=steps div speed+15+1;
if steps>4*speed-2 then steps:=0;
end;
DrUpRight:
begin
X := X+(150/1000)*MoveCount;
Y := Y-(150/1000)*MoveCount;
Inc(steps);
AnimPos:=steps div speed+25+1;
if steps>4*speed-2 then steps:=0;
end;
DrDownLeft:
begin
X := X-(150/1000)*MoveCount;
Y := Y+(150/1000)*MoveCount;
Inc(steps);
AnimPos:=steps div speed+5+1;
if steps>4*speed-2 then steps:=0;
end;
DrDownRight:
begin
X := X + (150/1000)*MoveCount;
Y := Y + (150/1000)*MoveCount;
Inc(steps);
AnimPos:=steps div speed+35+1;
if steps>4*speed-2 then steps:=0;
end;
end;
end;
end.
后记
通过以上的讲解和例子,相信大家已经对 Delphi 下的 DirectX 游戏开发有了初步的概念。国内讲解开发 DirectX 游戏的权威资料很少,如果这篇讲解可以起到抛砖引玉的效果的话,我将再高兴不过了。本文部分内容借鉴了国外一些游戏开发网站的技术文档,在此一并致谢。如果各位有什么问题需要和我交流,请发信至yywx1314@163.com
源程序下载地址:http://www.cfan.com.cn/11PROGRAM/200312/0312yde.zip。