









Because I am one of side scroller game fan such as Sega Sonic or Mario Bros.
Since version 8, Microsoft has announced that DirectDraw is now deprecated and no longer being developed. Microsoft will focus on developing DirectX Graphics (sometime refer as Direct3D). i.e. new API for graphics where DirectDraw and Direct3D (Direct X version 7 or older) is united. Near in the future, there are good chances that DirectDraw is no longer supported. In this tutorial, all code use DirectX 8 (because my development system uses DirectX 8 , you must install DirectX 8 to be able to run demo that we will develop in this tutorial. I will use term Direct3D to refer DirectX Graphics.
Of course! Download first DirectX header conversion units from SourceForge (DirectX 9) or here (DirectX 8.1). Source code is available to download here. Ok enough with the introduction. Let's get started..
Direct3DCreate8() function is available for instantiate IDirect3D8 interface declared in DirectXGraphics.pas unit. For example :
var d3d8:IDirect3D8; begin d3d8:=Direct3DCreate8(D3D_SDK_VERSION); ...
If it succeed, d3d8 will hold pointer to IDirect3D8 interface which later can be used to get IDirect3DDevice8 instance, interface thet represents graphics card installed on the system. IDirect3D8 also used for querying device capabilities.
During application runtime, we will use device quite often, i.e, to initialize graphic mode, doing transformation, creating textures, display rendering result to screen and etc. Device is represented by IDirect3DDevice8 interface. To get pointer to this interface we call CreateDevice() of IDirect3D8 .
function CreateDevice(const Adapter : Cardinal;
const DeviceType : TD3DDevType;
hFocusWindow : HWND;
BehaviorFlags : LongWord;
var pPresentationParameters:
TD3DPresent_Parameters;
out ppReturnedDeviceInterface :
IDirect3DDevice8) : HResult; stdcall;
For now, the most important fields are: BackBufferWidth and BackBufferHeight. You use it to setup screen resolution for example 800x600, 1024x768 etc. If a screen resolution is not supported, CreateDevice will fail. Tips: It is recomended to use standard graphic mode because they are likely supported by most graphic card available today such 800x600 or 1024x768. Windowed, you use it to determined whether application running in windowed mode or fullcreen. If it is false application runs in fullscreen and occupies entire screen. In fullscreen mode, application can use page flipping or blitting to do double buffering process. In windowed mode, double buffering use blitting only.
Note: Page flipping or blitting is double buffering techniques commonly used. Double buffering was developed to minimize flicker to occur, i.e, by use of two buffers (or triple buffering for three buffers), i.e primary buffer, or so called front buffer, which is visible to us, and an additional invisible buffer called back buffer. All drawing is done at back buffer. After drawing is finished, back buffer is transferred to front buffer. Page flipping is technique to transfer back buffer into front buffer by changing front buffer into back buffer and vice versa by means of changing starting address of scan of graphic card. Blitting, abbreviation of Block Binary transfer, copies content of the back buffer to the front buffer. Theoritically page flipping is more faster than blitting, because transfering back buffer to front buffer does not involve moving any data around.
BackBufferFormat, we use to decide color depth we want. For example, to run application with 32 bit color depth, we use D3DFMT_A8R8G8B8 where each pixels is consist of four components, Alpha(A) =8 bit, Red (R)= 8 bit, Green(G)= 8 bit and Blue(B)=8 bit. Alpha determines level of transparency of color if it is blended with other color. For 16 bit mode, we use D3DFMT_R5G6B5 with R=5 bit G=6 bit and B=5 bit without any alpha component. For 24 bit mode, you use constant D3DFMT_R8G8B8 without alpha.
SwapEffect determines how swap of back buffer and front buffer occur. If it is set with D3DSWAPEFFECT_DISCARD, then after back buffer and front buffer is swap, content of back buffer is discarded so we should not assume that content of back buffer is same before it is swapped. With D3DSWAPEFFECT_DISCARD, we tell DirectX to pick the most optimal swap mechanism for our buffer. If we set it with D3DSWAPEFFECT_FLIP, we tells DirectX to use page flipping mechanism and if we use D3DSWAPEFFECT_COPY or D3DSWAPEFFECT_VSYNC_COPY, DirectX uses blitting. VSYNC blits by synchronize it with vertical trace of monitor's electron gun to minimize tear-off effect.
hDeviceWindow, window handle of render target. If we set to 0, hFocusWindow is used. ppReturnedDeviceInterface, variable that will receive pointer to IDirect3DDevice8 interface.
If CreateDevice() failed, ppReturnedDeviceInterface is equal to nil. For example:
if accel3DSupported then
behaviour:=D3DCREATE_HARDWARE_VERTEX_PROCESSING
else
behaviour:=D3DCREATE_SOFTWARE_VERTEX_PROCESSING;
ZeroMemory(presentParam,sizeof(TD3DPresent_Parameters));
presentParam.hFocusWindow:=Form1.Handle;
presentParam.BackBufferCount:=1;
presentParam.BackBufferWidth:=800;
presentParam.BackBufferHeight:=600;
presentParam.SwapEffect:=D3DSWAPEFFECT_DISCARD;
presentparam.BackBufferFormat:=D3DFMT_A8R8G8B8;
d3d8.CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
behaviour,
presentParam,
d3d8Dev);
To be able to utilize graphic card capability optimally, we need to ensure that graphic card support 3D acceleration. To check graphic card capabilities, IDirect3D8 has GetDeviceCaps() method.
function GetDeviceCaps(const Adapter : Cardinal;
const DeviceType : TD3DDevType;
out pCaps : TD3DCaps8) : HResult; stdcall;
Adapter, ordinal value of graphic card, 0=first graphic card, 1=second,..etc. As usual, we can fill it with D3DADAPTER_DEFAULT to refer to primary adapter.
DeviceType, device type can be D3DDEVTYPE_HAL or D3DDEVTYPE_REF.
pCaps, graphic card capability information. To check whether 3D accelerator is available we need to check pCaps.DevCaps for D3DDEVCAPS_HWTRANSFORMANDLIGHT value. If it contains this value, then 3D acceleration is available. For example:
d3d8.GetDeviceCaps(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
aCaps);
accel3DSupported:=(aCaps.DevCaps AND
D3DDEVCAPS_HWTRANSFORMANDLIGHT
= D3DDEVCAPS_HWTRANSFORMANDLIGHT);
To clear buffer content, whether it is rendering buffer, depth buffer or stencil buffer, we use IDirect3DDevice8 method Clear().
function Clear(const Count : LongWord;
pRects : PD3DRect;
const Flags : LongWord;
const Color : TD3DColor;
const Z : Single;
const Stencil : LongWord) : HResult; stdcall;
Count holds number of rectangle in pRects. If pRects=nil then Count must be set to 0.
pRects holds rectangles to be cleared. If it is set to nil, then all buffer will be cleared.
Flags indicates type of buffer that will be cleared. D3DCLEAR_STENCIL causes stencil buffer to be cleared with value in Stencil parameter. D3DCLEAR_ZBUFFER causes depth buffer to be cleared with value in Z and D3DCLEAR_TARGET causes render target is cleared with color information stored in Color parameter. For now, depth buffer and stencil buffer are not being discussed.
To clear (or more precise, to fill) all render buffer with red color (ARGB=$FFFF0000) :
d3dDev.Clear(0,nil, D3DCLEAR_TARGET, $FFFF0000,0,0);
Before drawing 3D, we must call BeginScene() and end it with EndScene() call, where between both method callis where you put your drawing code.
d3ddev.BeginScene; //draw your objects here d3ddev.EndScene;
BeginScene/EndScene pair must be called even though we are not drawing anything.
To make all objects we have drawn visible, we must call Present(), a method of IDirect3DDevice8.
function Present(pSourceRect, pDestRect : PRect;
const hDestWindowOverride : HWND;
pDirtyRegion : PRgnData) : HResult; stdcall;
Example:
d3ddev.Present(nil,nil,nil,0,nil);
Texture is image that wrapped on polygon to enhance realism of 3D object. In Direct3D 8, texture is represented in IDirect3DTexture8 interface.
DirectX has utilities called D3DX, two of functions available in D3DX are D3DXCreateTextureFromFile() and D3DXCreateTextureFromFileEx(). Both are declared in d3dx8.pas unit. Later function is an extension to the previous one and we will use it to create textures.
Sprite is term in game programming to refer to image of characters in a game. Main property of sprite is transparency information where transparent color (color key) will not be drawn. Mouse cursor is an example of sprite.
D3DX provides interface to simplify sprite manupulation with ID3DXSprite.
We use D3DXCreateSprite() function to get pointer of ID3DXSprite instance.
function D3DXCreateSprite(const pDevice : IDirect3DDevice8;
out ppSprite : ID3DXSprite) : HResult; stdcall;
Example:
D3DXCreateSprite(d3dDev,aSprite);
To begin and to end drawing, we must call ID3DXSprite.Begin and ID3DXSprite.End. Because Begin and End is reserved words in Pascal language, so in conversion header, this function was renamed to _Begin and _End
aSprite._Begin; //do something with sprite aSprite._End;
Enough for Direct3D introduction, let us create framework implementation for 2D application. We are going to create an application where we can move Guile character (Street Fighter), control Guile to perform punching and kicking action.
We are going to create wrapper class for Direct3D initialization and rendering. Features that this class provides:
TD3DCollection is base class for collection of items in this 3D engine. This class has a property of type TEngine and is derived from TCollection.
Texture management encapsulation are consist of two classes: TTextureCollection and TTexture. TTexture handles initialization of IDirect3DTexture8 and image file loading. TTextureCollection handles TTexture management. TTextureCollection is derived from T3DCollection. TTexture is derived from TCollectionItem.
unit u_3D_engine;
interface
uses classes,windows,sysutils,directxGraphics;
type
E3DError=class(Exception);
TEngineParam=record
Handle:HWND;
Fullscreen:boolean;
ResolutionWidth:integer;
ResolutionHeight:integer;
TotalBackBuffer:integer;
ColorDepth:integer;
InitDeviceOnCreate:boolean;
end;
TEngine=class(TObject)
private
FDirect3DObj:IDirect3D8;
FDirect3DDevice:IDirect3DDevice8;
function CreateD3D:IDirect3D8;
function CreateDevice(const d3d:IDirect3D8):IDirect3DDevice8;
function HwVertexProcSupported:boolean;
protected
FEngineParam:TEngineParam;
FPresentParam:TD3DPresent_Parameters;
procedure BeginDraw;
procedure Draw;virtual;abstract;
procedure EndDraw;
public
constructor Create(Param:TEngineParam);virtual;
destructor Destroy;override;
procedure InitDevice;
procedure Clear(const ClearCol:TD3DColor);
procedure Render;
published
property Direct3DObj:IDirect3D8 read FDirect3DObj;
property Direct3DDevice:IDirect3DDevice8 read FDirect3DDevice;
end;
T3DCollection=class(TCollection)
private
FEngine: TEngine;
procedure SetEngine(const Value: TEngine);
published
property Engine:TEngine read FEngine write SetEngine;
end;
T3DObject=class(TCollectionItem)
private
protected
FVertexBuff:IDirect3DVertexBuffer8;
FIndexBuff:IDirect3DIndexBuffer8;
public
procedure Init;virtual;abstract;
procedure Draw;virtual;abstract;
end;
TTexture=class(TCollectionItem)
private
FTextureObj: IDirect3DTexture8;
FColorKey: TD3DColor;
procedure SetColorKey(const Value: TD3DColor);
public
destructor Destroy;override;
procedure LoadFromFile(const filename:string);
published
property TextureObj:IDirect3DTexture8 read FTextureObj;
property ColorKey:TD3DColor read FColorKey write SetColorKey;
end;
TTextureCollection=class(T3DCollection)
end;
implementation
uses d3dx8;
{ TEngine }
procedure TEngine.BeginDraw;
begin
if FDirect3DDevice<>nil then
FDirect3DDevice.BeginScene;
end;
procedure TEngine.Clear(const ClearCol:TD3DColor);
begin
if FDirect3DDevice<>nil then
FDirect3DDevice.Clear(0,nil,D3DCLEAR_TARGET,ClearCol,0,0);
end;
constructor TEngine.Create(Param: TEngineParam);
begin
FEngineParam:=Param;
FDirect3DObj:=CreateD3D;
if FEngineParam.InitDeviceOnCreate then
InitDevice;
end;
function TEngine.CreateD3D: IDirect3D8;
begin
result:=Direct3DCreate8(D3D_SDK_VERSION);
end;
function TEngine.CreateDevice(const d3d: IDirect3D8): IDirect3DDevice8;
var hr:HResult;
vertexProcessing:cardinal;
begin
if d3d<>nil then
begin
ZeroMemory(@FPresentParam,sizeof(TD3DPresent_Parameters));
FPresentParam.Windowed:=not FEngineParam.Fullscreen;
FPresentParam.BackBufferWidth:=FEngineParam.ResolutionWidth;
FPresentParam.BackBufferHeight:=FEngineParam.ResolutionHeight;
FPresentParam.BackBufferCount:=FEngineParam.TotalBackBuffer;
FPresentParam.BackBufferFormat:=FEngineParam.ColorDepth;
FPresentParam.SwapEffect:=D3DSWAPEFFECT_DISCARD;
//3D hardware acceleration tersedia?
if HwVertexProcSupported then
vertexProcessing:=D3DCREATE_HARDWARE_VERTEXPROCESSING
else
vertexProcessing:=D3DCREATE_SOFTWARE_VERTEXPROCESSING;
hr:=d3d.CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
FEngineParam.Handle,
vertexProcessing,
FPresentParam,
result);
if result=nil then
raise E3DError.Create(D3DXErrorString(hr));
end else
result:=nil;
end;
destructor TEngine.Destroy;
begin
FDirect3DDevice:=nil;
FDirect3DObj:=nil;
inherited;
end;
procedure TEngine.EndDraw;
begin
if FDirect3DDevice<>nil then
FDirect3DDevice.EndScene;
end;
function TEngine.HwVertexProcSupported: boolean;
var caps:TD3DCaps8;
begin
FDirect3DObj.GetDeviceCaps(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,caps);
result:=((caps.DevCaps and D3DDEVCAPS_HWTRANSFORMANDLIGHT)0);
end;
procedure TEngine.InitDevice;
begin
FDirect3DDevice:=CreateDevice(FDirect3DObj);
end;
procedure TEngine.Render;
begin
BeginDraw;
try
Draw;
if FDirect3DDevice<>nil then
FDirect3DDevice.Present(nil,nil,0,nil);
finally
EndDraw;
end;
end;
{ T3DCollection }
procedure T3DCollection.SetEngine(const Value: TEngine);
begin
FEngine := Value;
end;
{ TTexture }
destructor TTexture.Destroy;
begin
FTextureObj:=nil;
inherited;
end;
procedure TTexture.LoadFromFile(const filename: string);
var atexcollection:TTextureCollection;
hr:HResult;
begin
if Collection is TTextureCollection then
begin
aTexCollection:=Collection as TTextureCollection;
if (aTexCollection.Engine.Direct3DDevice<>nil) and
fileExists(filename) then
begin
hr:=D3DXCreateTextureFromFileEx(aTexCollection.Engine.Direct3DDevice,
PChar(filename),
D3DX_DEFAULT,
D3DX_DEFAULT,
D3DX_DEFAULT,
0,
D3DFMT_UNKNOWN,
D3DPOOL_MANAGED,
D3DX_FILTER_NONE,
D3DX_FILTER_NONE,
FColorKey,
nil,nil,
FTextureObj);
if FTextureObj=nil then
raise E3DError.Create(D3DXErrorString(hr));
end;
end;
end;
procedure TTexture.SetColorKey(const Value: TD3DColor);
begin
FColorKey := Value;
end;
end.
OK, next we create implementation to handle sprites. Sprite management will be encapsulated in TSprite class, while TSprite is managed by TSpriteCollection. Just like TTexture and TTextureCollection.
unit u2DSpr;
interface
uses classes,directxgraphics,d3dx8,u_3D_engine;
type
TSpriteCollection=class(T3DCollection)
private
FSprite:ID3DXSprite;
procedure Init;
public
destructor Destroy;override;
procedure BeginDraw;
procedure EndDraw;
published
property SpriteObj:ID3DXSprite read FSprite;
end;
TSprite=class(T3DObject)
private
FSprite:ID3DXSprite;
FTexture: TTexture;
FRotation: single;
FColor: TD3DColor;
FScaling: TD3DXVector2;
FRotationCenter: TD3DXVector2;
FTranslation: TD3DXVector2;
FAlpha: byte;
procedure SetTexture(const Value: TTexture);
procedure SetColor(const Value: TD3DColor);
procedure SetRotation(const Value: single);
procedure SetRotationCenter(const Value: TD3DXVector2);
procedure SetScaling(const Value: TD3DXVector2);
procedure SetTranslation(const Value: TD3DXVector2);
procedure SetAlpha(const Value: byte);
public
constructor Create(ACollection: TCollection);override;
destructor Destroy;override;
procedure Init;override;
procedure Draw;override;
procedure DrawEx(const translVect,
scaleVect,
rotCenter:PD3DXVector2;
const rot:single;
const Acolor:TD3DColor;
const alphaValue:byte);
published
property Texture:TTexture read FTexture write SetTexture;
property Translation:TD3DXVector2 read FTranslation write SetTranslation;
property X:single read FTranslation.x write FTranslation.x;
property Y:single read FTranslation.y write FTranslation.y;
property RotationCenter:TD3DXVector2 read FRotationCenter write SetRotationCenter;
property Scaling:TD3DXVector2 read FScaling write SetScaling;
property Rotation:single read FRotation write SetRotation;
property Color:TD3DColor read FColor write SetColor;
property Alpha:byte read FAlpha write SetAlpha;
end;
implementation
{ TSprite }
constructor TSprite.Create(ACollection: TCollection);
var asprCollect:TSpriteCollection;
begin
inherited;
FSprite:=nil;
if ACollection is TSpriteCollection then
begin
asprCollect:=ACollection as TSpriteCollection;
if asprCollect.SpriteObj=nil then
asprCollect.Init;
FSprite:=asprCollect.SpriteObj;
end;
FTranslation.x:=0;
FTranslation.Y:=0;
FRotationCenter.x:=0;
FRotationCenter.y:=0;
FRotation:=0;
FScaling.x:=1;
FScaling.y:=1;
FColor:=$FFFFFFFF;
end;
destructor TSprite.Destroy;
begin
FSprite:=nil;
inherited;
end;
procedure TSprite.Draw;
begin
DrawEx(@FTranslation,
@FScaling,
@FRotationCenter,
FRotation,
FColor,
FAlpha);
end;
procedure TSprite.DrawEx(const translVect, scaleVect,
rotCenter: PD3DXVector2; const rot: single;
const AColor:TD3DColor;const AlphaValue:byte);
begin
if (FSprite<>nil) and
(FTexture<>nil) and
(FTexture.TextureObj<>nil) then
begin
FSprite.Draw(FTexture.TextureObj,nil,
scaleVect,
rotCenter,rot,
translVect,
(AlphaValue shl 24) or AColor);
end;
end;
procedure TSprite.Init;
begin
end;
procedure TSprite.SetAlpha(const Value: byte);
begin
FAlpha := Value;
end;
procedure TSprite.SetColor(const Value: TD3DColor);
begin
FColor := Value;
end;
procedure TSprite.SetRotation(const Value: single);
begin
FRotation := Value;
end;
procedure TSprite.SetRotationCenter(const Value: TD3DXVector2);
begin
FRotationCenter := Value;
end;
procedure TSprite.SetScaling(const Value: TD3DXVector2);
begin
FScaling := Value;
end;
procedure TSprite.SetTexture(const Value: TTexture);
begin
FTexture := Value;
end;
procedure TSprite.SetTranslation(const Value: TD3DXVector2);
begin
FTranslation := Value;
end;
{ TSpriteCollection }
procedure TSpriteCollection.BeginDraw;
begin
if FSprite<>nil then
FSprite._Begin;
end;
destructor TSpriteCollection.Destroy;
begin
FSprite:=nil;
inherited;
end;
procedure TSpriteCollection.EndDraw;
begin
if FSprite<>nil then
FSprite._End;
end;
procedure TSpriteCollection.Init;
begin
if (Engine<>nil) and (Engine.Direct3DDevice<>nil) then
D3DXCreateSprite(Engine.Direct3DDevice,FSprite)
else
FSprite:=nil;
end;
end.
Below is screen shot of application.
To move Guile, use right/left arraw, for kicking and punching action press J, K or L. To exit press ESC or ALT+F4. Source code can be downloaded here
Sprite images can be downloaded from www.gsarchive.net (however this site is dead now). Guile character is property of Marvel Comic, used here for educational purpose only.
Do you like this article? Help this website improve by donating. Any amounts is appreciated.
Or you can help by bookmarking this page.
Bookmark this on Delicious