DelphiX Tutorial

Made by Martin Jochems, september 2001

 

Tools used for this tutorial:

-         DelphiX (v2000_0717-2)

-         Delphi4 (Client Server Ed.)

-         Windows 98 (2nd Ed.)

 

------------------------------------------------------------------------------------------------------------

 

Contents

. 2

Introduction. 3

1.   DXDraw.. 13

1.1   Most usual configurations. 17

1.2   Screen modus. 34

1.2.1   Window mode. 38

1.2.2   FullScreen mode. 56. 82

2.   DXImageList 84

Component to load pictures with. 85

2.1   Loading pictures at runtime. 87

2.2   Palettes. 114

2.3   Background. 161

3.  SpriteEngine. 170

3.1   Coordinate Systems. 176

3.2   Game scenario. 205

3.3  Background sprites. 213

3.3.1   Static Background. 215

3.3.2   Scrolling Background. 236

3.3.3   Tiled background  (background assembled by tiles) 249

3.3.4   Background collisions. 311

3.4   Isometric Tile Engine. 340

3.5   Sprite Animation. 352

3.6   Sprite collisions & pixelcheck. 372

4.   The Timer 394

5.   DXInput 435

6.   Hints & tips. 488

6.1   GetDXVersion. 491

6.2   Game Scenario. 512

6.3   STD Sprite. 517

6.4  Optimal sound quality. 522

6.5   Text in DXDraw.. 549

6.6   Menu's. 561

7.   Links DelphiX.. 572

 

------------------------------------------------------------------------------------------------------------

 

Introduction

DelphiX is a game-library for Delphi based on Microsofts’ DirectX (multimedia layer in Windows). At the moment, DelphiX supports functions from DirectX 1.0 to 7.0. But because of the DirectX concepts, games made with an older DirectX version will still work on newer versions of DirectX. Below is a picture which shows the component palette after installation.

 

To read this tutorial, it is assumed you have a little knowledge of DirectX and DelphiX. If you first want to know more about these subjects, I suggest you take a look at these 2 websites:

http://www.savagesoftware.com.au/delphi/articles/article1.html

http://cerebral-bicycle.co.uk/

 

 

 

Component-palette DelphiX

 

 

DelphiX

Copyright  Copyright© 1996-2000 Hiroyuki Hori

http://www.ingjapan.ne.jp/hori/

 

------------------------------------------------------------------------------------------------------------

 

1.   DXDraw

 

Component for drawing your graphics on. The surface of the DXDraw component is the fundament for all graphical goals.

 

1.1   Most usual configurations

Align                            allClient           

AutoSize                      True                

Display

   FixedBitCount          True                

   Fixedratio                 True                

   Fixedsize                  False

Options

   DoFullScreen            True                

   doFlip                       True

   Rest                          False

 

When both DoFullScreen and DoFlip are True, you can’t see any Window-controls anymore (No GDI anymore). When you want to use buttons and menu’s etc.

you could make these as sprites. When DoFlip is False, you are in Windowed mode. Using both Fullscreen and DoFlip makes a game very fast compared with a game in Windowed mode.You can show graphics by describing a DirectDrawSurface. DXDrawSurface is of type TDirectDrawSurface. This surface is also called ‘secondary surface’ or offscreen surface because it’s invisible.

The surface becomes visible after calling the method DXDraw.Flip . The (secondary) surface becomes primary surface at that moment. DXDraw.Primary is the primary (visible) surface.

 

 

1.2   Screen modus

Current configurations of the Windows Control Panel don’t affect the configuration of the game’s creen configuration. These tasks are all controlled by DXDraw. The Form on which the DXDraw component stays, should not be of type TForm, but of type TDXForm. This is a manual adjustment you should make yourself by changing TForm into TDXForm and add DXClass to the uses clausule.

 

 

1.2.1   Window mode

procedure TForm1.WindowMode;

begin

  DXDraw1.Finalize;

  RestoreWindow;

  DXDraw1.Options := DXDraw1.Options - [doFullScreen];

  DXDraw1.Display.Width := 640;

  DXDraw1.Display.Height := 480;

  DXDraw1.Display.BitCount := 8;

  DXDraw1.Initialize;

end;

 

 

DXDraw.Align := alClient;

Form.ClientWidth := …                       // Set width of Form/DXDraw Display

Form.ClientHeight := …                      // Set height of Form/DXDraw Display

 

 

1.2.2   FullScreen mode

 

procedure TForm1.FullScreenMode;

begin

  DXDraw1.Finalize;

  StoreWindow;      // Store Window in Form

  DXDraw1.Options := DXDraw1.Options + [doFullScreen];

  DXDraw1.Display.Width := 640;

  DXDraw1.Display.Height := 480;

  DXDraw1.Display.BitCount := 8;

  DXDraw1.Initialize;

End;

 

DXDraw.Options := DXDraw.Options + [doFlip];                  // Set mode in

// non-Windows GUI

                                                                                              // Disable windows-controls

                                                                                              // -> purely graphics screen

// (very fast)

 

DXDraw.Options := DXDraw.Options - [doFlip];                  // Enable Windows controls    

 

Screen.Cursor := CrNone;                                                      // No cursor visible

Form1.BorderStyle := bsSingle;                                              // Hide border of form

 

 

I’ve made some tests with screen resolutions. What I noticed in these tests is that the resolutions 640x480 and 800x600 work well at a framerate of 55-60 FPS, at a simple 2 MB video card. Though, a resolution of 1024x768 is too much for this video card. Some better videocards (8MB or more) will probably have no problems with this resolution.

----------------------------------------------------------------------------------------------------

 

2.   DXImageList

Component to load pictures with.

 

2.1   Loading pictures at runtime

 

// Load a normal raster-image into DXImageList (BMP, DIB etc.)

// Picture becomes an item of DXImageList

procedure LoadImage (Filename, NameImage : String; PatWidth, PatHeight : Integer;

  Transp : Boolean; TranspColor : TColor);

begin

 DXImageList.Items.Add;

 with DXImageList.Items[DXImageList.Items.Count-1] do

 begin

Picture.LoadFromFile (FileName);

Name := NameImage;

PatternWidth := PatWidth;

PatternHeight := PatHeight;

SkipHeight := 0;

SkipWidth := 0;

SystemMemory := False;

Transparent := Transp;

TransparentColor := TranspColor;

Restore;

  end;

end;

 

With this method, I can load about 200 pictures at a time with no problems. More pictures give problems, just because of the limits of my

physical memory J. Loading jpeg-pictures however, always give problems!  Only BMP and DIB pictures work well.

 

 

2.2   Palettes

A palette is a colortable. It’s actually an array of numbers where each number refers to a certain color. Each color is actually also just a number. This number depends on the amount of red, green and blue in the color (RGB-value). In DelphiX, the colorpalette is accessible with:  DXDraw.ColorTable.

 

To make sure DXDraw will use the same colorpalette as the pictures in the DXImageList:

ImageList.Items.MakeColorTable;

DXDraw.ColorTable := ImageList.Items.ColorTable;

DXDraw.DefColorTable := ImageList.Items.ColorTable;

DXDraw.UpdatePalette;

 

 

Below is a piece of code to create a color (a TRGBQuad). A standard palette containts 256 of these RGBQuads.

 

Var Pal : TRGBQuad;

 

Pal.rgbRed := 16;

Pal.rgbGreen := 0;

Pal.rgbBlue := 0;

Pal.rgbReserved := 0;

DXDraw1.ColorTable[i] := Pal;

 

Of course it is possible to make a function for this purpose, like the function ‘ComposeColor’ below:

 

functionComposeColor(Dest,Src:TRGBQuad;Percent:Integer):TRGBQuad;

begin

  with Result do

  begin

    rgbRed :=Src.rgbRed+((Dest.rgbRed-Src.rgbRed)*Percent div256);

    rgbGreen :=Src.rgbGreen+((Dest.rgbGreen-Src.rgbGreen)*Percent div256);

    rgbBlue :=Src.rgbBlue+((Dest.rgbBlue-Src.rgbBlue)*Percent div256);

    rgbReserved :=0;

  end;

end;

 

Below you see how you can fill a palette with colors: 

 

Var Col : Integer;

 

for i:=0 to255 do

  DXDraw1.ColorTable[i] :=  ComposeColor (RGBQuad(GetRValue(Col), GetGValue(Col),  

                                                  GetBValue(Col) ), DXDraw1.DefColorTable[i], p);

DXDraw1.UpdatePalette;

 

 

Palette animation is a technique which changes palette colors very fast in a short time. This is often used for

getting a light flash effect.

 

 

2.3   Background

 

When you want only 1 picture to fill the whole background (map), you have to make a picture with the exact height and width to fill the screen with (e.g. 640x480). The height and width of a background image are determined by the properties patternwidth and patternheight (of TpictureCollectionitem) where the image is contained in (in DXImageList.Items[…].Picture). When the background is respresented by 1 picture, then patternheight and patternwidth are both set to 0 (so not at e.g. 640x480!).

 

 

One picture as persistent background

 

------------------------------------------------------------------------------------------------------------

3.  SpriteEngine

 

The SpriteEngine is a component which controls all sprites. Below are some coordinate-systems explained which are related to the SpriteEngine.

The SpriteEngine is not a ‘pure’ DelphiX component because it does not represent a part of DirectX, in contrary to other DelphiX components. Some game-makers using DelphiX, make their own spriteengine with their own objects or records (see for example the DelphiX game ‘Joffra’ at: http://www.joffra.com/).

 

 

3.1   Coordinate Systems

 

 

Sprite.WorldX             = Sprite.X + Sprite.Engine.X               (sprite.x. related to spriteengine)

Sprite.WorldY             = Sprite.Y + Sprite.Engine.Y               (sprite.y. related to spriteengine)

 

The upper left corner respresents coordinate (0,0). Sprite.Engine.X and Sprite.Engine.Y are default 0. In that case both the X- and Y values of a sprite are equal to the WorldX and World Y values. When you modify the X- and Y values of the SpriteEngine, all X- and Y values contained by the spriteengine are modified too.

 

Sprite.Moved                          = Whether to enable moving; yes or no

Sprite.Collisioned                    = Whether do detect collisions; yes or no

 

 

The method DXSpriteEngine.Move makes sure that all sprites in the SpriteEngine call their method DoMove. Normally this occurs after eacht timer interval of the Timer component). In this DoMove method, collision detection is done by calling the sprite’s method Collision. Only when a collision is detected, the DoCollision method of the sprite is called.

 

To make the background scroll when moving a sprite, there are a number of solutions. One of the solutions is to change the X- and Y values of the DXSpriteEngine in the DoMove method of the moving sprite.

So:       DXSpriteEngine.X := ...

            DXSpriteEngine.Y := ...

 

An often used solution:

 

X := ...     // New x-value sprite

Y := ...     // New y-value sprite

DXSpriteEngine.X := -X + (DXSpriteEngine.Width div 2) - (Width div 2);

DXSpriteEngine.Y := -Y + (DXSpriteEngine.Height div 2) - (Height div 2);

 

Another solution to make the background scroll, is to make some backgroundsprites scroll.

 

 

3.2   Game scenario

 

Below is a standard game-scenario for a game (incl. SpriteEngine).

 

 

 

 

3.3  Background sprites

 

3.3.1   Static Background

 

TBackground = class (TBackgroundsprite)

public

   procedure DoMove (MoveCount : Integer); override;

end;

 

SBackground := TBackground.Create (DXSpriteEngine1.Engine);

with SBackground do

  begin

    image := DXImageList1.Items.Find('image1');

    image.Transparent := false;

    SetMapSize(1,1);

    Z := -10;

    Tile := false;

end;

 

var SBackground : TBackground;

 

 

 

3.3.2   Scrolling Background

 

TBackground = class (TBackgroundsprite)

public

procedure DoMove (MoveCount : Integer); override;

end;

procedure TBackground.DoMove (MoveCount : Integer);

begin

  inherited DoMove (MoveCount);

  X := X-1;

end;

 

 

3.3.3   Tiled background  (background assembled by tiles)

 

Tile := True;                 // Background is fully filled with patterns

                                   // (When scrolling, the pattern will be repeated)

                                   // Value of Setmapsize will be ignored; the number of patterns

                                   // in in both width as height depends on the area-size of the

// screen (complete background).

// Setmapsize has to be called!

// else it won’t work... just take for example: SetMapSize (1,1)

                                  

Tile := False;                // The number of patterns in both height and width is determined

                                    // by ‘Setmapsize (w,h) ‘

SetMapsize (20, 10)    <-> mapwidth :=20; mapheight :=10;

 

When a background-picture doesn’t cover the whole screen, the picture will be repeated as a pattern. Such a pattern is called a ‘Tile’. In the case a picture has to represent a Tile, the patternwidth and patternheight should be smaller than the total area of the screen. These values should not be too large (e.g. 70x50).

When the value for the pattern is too large, no picture will be visible!

 

 

 

 

   Tile 1                  Tile 2                   both from 1 image (see image below)

Tiled background 4x4  (random pattern)

MapWidth := 4; MapHeight := 4;

SBackground.Chips[0,0] to [3,3]

 

 

 

 

 

 

 

Randomize;

SBackground := TBackground.Create (DXSpriteEngine1.Engine);

with SBackground do

begin

image := DXImageList1.Items.Find(‘backgr’);

image.Transparent := false;

SetMapSize(4,4);

     Z := -10;

for i := 0 to MapHeight-1 do

for j := 0 to MapWidth-1 do

begin

Chips[j,i] := random(2);

end;

Tile := false;

end;

 

Randomize;

SBackground := TBackground.Create (DXSpriteEngine1.Engine);

with SBackground do

begin

image := DXImageList1.Items.Find(‘backgr’);

image.Transparent := false;

SetMapSize(4,4);

     Z := -10;

for i := 0 to MapHeight-1 do

for j := 0 to MapWidth-1 do

begin

Chips[j,i] := random(2);

end;

Tile := true;

end;

 

 

 

Chips[j,i] := -1;            // No picture for pattern-tile [j,i]

 

There is a limit for the number of patterns when you use tiles backgrounds (chips). This number depends on the computers’ memory, but a large amount is possible.

I did a test on this with a pattern of 100x100 pixels repeated in a map of 1000x1000. Nice thing is that each of these patterns can refer to a different picture. This is possible because each pattern is saved as a number (integer). A negative pattern number means no picture for that pattern.

 

 

3.3.4   Background collisions

 

Randomize;

SBackground := TBackground.Create (DXSpriteEngine1.Engine);

with SBackground do

begin

image := DXImageList1.Items.Find(‘backgr’);

image.Transparent := false;

SetMapSize(5,5);

    Collisioned := True;                                                 // Don’t forget this one!   Else

    Z := -10;                                                                 // the background won’t detect collisions!

for i := 0 to MapHeight-1 do                                 

for j := 0 to MapWidth-1 do

begin

        Chips[j,i] := random(2);                                      // Tiles get a random patternnumber

        CollisionMap [j,i] := false;                                   // All Tiles must ignore collisions

end;

Tile := false;

end;

SBackground.CollisionMap [3,3] := true;                    // Tile [3,3] detects collisions now

 

 

à Caution: By default, all tiles of the background allow collision detection, but

only when Collisioned is set at  True. Though, Collisioned is always default set at False

at a Tbackgroundsprite!. A TimageSprite has default collisioned at True.

 

 

Sprite collides against a Tile from the background, namely Chip [3,3].

 

 

3.4   Isometric Tile Engine

 

 

 

 

 

 

 

NatureBackgroundSprite  Z := -10;             GrassBackgroundSprite   Z := -11;

(3 tiles in 1 image)                                       (1 tile in 1 image)

 

 

Exactly the same concept as with the previous red-yellow tiled background. But now they are only 2 backgroundsprites instead of 1 and the NaturebackgroundSprite has an isometric shape. In this way it is possible to make multiple backgroundsprites consisting of different layers,

building complex and impressive backgrounds. There are several articles on the Internet about creating complete Isometric Engines.

 

 

3.5   Sprite Animation

 

A frame is a picture that belongs to a serie pictures used for animation. Below is a picture consisting of 7 patterns.

 

 

 

 

FPS = Frames per second.

AnimPos                     is the current patternnumber of the animation

AnimStart                    is the start patternnumber

AnimCount                  is het number of patterns of the total animation  (take patterncount)

AnimLooped               Whether to repeat the animation (in a loop)

AnimSpeed   = (DXTimer.Interval / 1000) * FPS

(Bijv.: interval=1 en FPS=2 à 2/1000)

 

For an animation you’d only set AnimStart, not AnimpPos. Animpos is automatically changed during the animation.

 

 

3.6   Sprite collisions & pixelcheck

 

 

 

 

 

 

 

 

 

 

Flegel will collide against the rock

 

Pixelcheck on:  see if the pixels of the 2 sprites hit each other

Pixelcheck off:   see if the sprite-frames hit each other  (a frame is determined by the height and width of the sprite. This is not always the same as the height and width of the sprite-image!)

·        Pixelcheck does not work at all when the colliding sprites both animate.

·        Pixelcheck doesn’t work well when 1 of the 2 colliding sprites animates.

·        Pixelcheck only works well when none of the 2 sprites animates.

·        Collisions without pixelcheck always work well (even when both sprites animate). In this case, only the frames of the sprites are important (the height and width). When these 2 frames hit each other, a collision has occured.

 

In a DoMove procedure of a sprite, the Collsion method is always called. This method detects if a sprite has collided against another sprite.

If so, then the DoCollision method is called. 

 
The header of a collisionprocedure always looks like this:

procedure TDummySprite.DoCollision (Sprite: TSprite; var Done: Boolean);

Here Sprite is the sprite which collided with the current sprite. You can explicit mention that a collision just has occured. This can be done by setting the (var)parameter Done to True. This parameter is False by default.

In a DoMove method you should always call the original DoMove method with:

 

Inherited DoMove (MoveCount);      (You don’t need to call this in a similar way when using DoCollision)

------------------------------------------------------------------------------------------------------------

 

4.   The Timer

 

In fact, the timer controls the whole game; alle game-actions are handled within 1 big game-loop.

The body of this loop can be put under the ‘OnTimer’ event of the Timer.

DXTimer is always trapped every millisecond. Though this doesn’t mean every that millisecond something happens.

This is determined by the ‘Interval’ property. This is the number of milliseconds between all actions.

Suppose this value is 100, then every 0.1 second the event ‘OnTimer’ is executed (the main loop of the game).

 

procedure TForm1.DXTimer1Timer(Sender: TObject; LagCount: Integer);

begin

  if not DXDraw1.CanDraw then Exit;

  DXDraw1.Surface.Fill (0);

 

  DXSpriteEngine1.Move (1);

  DXSpriteEngine1.Draw;

  DXDraw1.Flip;

end;

 

 

Interval                        Time in milliseconds between the execution of 2 sequential

OnTimer events (with a maximum of e.g. 60 of 85). 

Framerate (=FPS)       Number of times the OnTimer event is executed each second

(frequency). à Interval = 1000 / FrameRate

 

CAUTION:

When you use a very small interval (less than 10 millisec.) the relation between Interval and framerate is not correct anymore, since a computer can only handle a certain maximum framerate (for example 82 FPS) and thus also only a maximum interval. How much the interval can be, totally depends on the computer.

Normally spoken, an interval between 0 and 1 is chosen so that a maximum framerate can be acquired. The framerate should be read directly because it cannot be calculated anymore by using the Interval. The minimum framerate for a normal animation is about 25, but bigger is better of course. 60 is a nice framerate (DXTimer.Interval = 1000 / 60). Because FrameRate is a read-only property, the interval always has to be explicitely set to acquire a certain frequency.

 

 

DXTimer1.Interval := 1000 div 60;                       // 60 times each seconde calling the OnTimer event

LagCount is de laatste frequentie +1                     // frequency = Number of times the 'OnTimer' event is executed

                                                                                                                                                                               

You can also not set the ‘LagCount’ parameter yourself. This is done automatically. The value of LagCount depends of the chosen framerate (interval). LagCount is the true time-interval which is used (so it can differ from chosen the timer interval). When you choose 0 or 1, LagCount will determine the minimal time interval, so that the biggest framerate can be realised.

 

DXSpriteEngine.Move(LagCount);

X := X+ 0.7*MoveCount;                                        // You can set MoveCount 

                                                                                // by making this the same value as the LagCount-value

                                                                               // You can send this value through method-parameters

                                                                               // but you don’t have to (you can give your own value to MoveCount)

------------------------------------------------------------------------------------------------------------                

 

5.   DXInput

 

Component to Handle input by keyboard, joystick or mouse.

 

Get current Input-state

DXInput1.Update;

 

Current Joystick-state

DXInput1.Joystick.X;                                         

DXInput1.Joystick.Y;

 

Determine if joystick has moved:

if ((DXInput1.Joystick.X<>0) or (DXInput1.Joystick.Y<>0)) then

  

 

Range of axes

DXInput1.Joystick.RangeX := ...                                            // RangeX =10 ->  Range: -10 to +10

DXInput1.Joystick.RangeY := ...

DXInput1.Joystick.RangeZ := ...

 

Determine keyboard-state

if isLeft in DXInput1.States then                                              // The left key is pushed.

      .

if isRight in DXInput1.States then

   

if isUp in DXInput1.States then

      ….

if isDown in DXInput1.States then

     

 

It’s possible that DXInput1.States contains both IsUp and IsRight.

Disable Button1:            DXInput1.States := DXInput1.States - [isButton1];

Enable Button2:             DXInput1.States := DXInput1.States + [isButton2];

 

Keys

procedure TForm1. FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);

begin

  if Key = VK_ESCAPE then

   Close;

  if key = ord('B')  then

  

end;

 

VK_ESCAPE

Ord (Ascii)

VK_F1  t/m  VK_F10

VK_SPACE

VK_UP

VK_DOWN

VK_LEFT

VK_RIGJHT

-------------------------------------------------------------------------------------------------------------

 

6.   Hints & tips

 

 

6.1   GetDXVersion

 

function GetDXVersion : String;

begin

  with tregistry.Create do

  try

    rootkey:=HKEY_LOCAL_MACHINE;

    if OpenKey( '\SOFTWARE\Microsoft\DirectX', False) then

    begin

      result:= copy(Readstring('Version'),4,3);

    end

    else result:='Not installed';

  finally

    free;

  end;

end;

 

See all DirectX configurations from Windows (including hardware):

->  C:\Windows\System\DXDiag.EXE

 

 

6.2   Game Scenario

 

 

 

6.3   STD Sprite

 

 

 

6.4  Optimal sound quality

 

procedure Tform1.DXSound1Initialize(Sender: TObject);

var fmt:TWaveFormatEx;

begin

 

try

  // Set up the DXSound object to use high quality samples

  with Fmt do

  begin

    wFormatTag := WAVE_FORMAT_PCM;

    nSamplesPerSec := 44100;

    nChannels := 2;

    wBitsPerSample := 16;

    nBlockAlign := wBitsPerSample div 8 * nChannels;

    nAvgBytesPerSec := nSamplesPerSec * nBlockAlign;

  end;

  // must be in exclusive to set the format

  DXSound1.Options := DXSound1.Options + [soExclusive];

  DXSound1.Primary.SetFormat(Fmt);

except

  // Error

end;

 

end;

 

 

6.5   Text in DXDraw

 

with DXDraw1.Surface.Canvas do

begin

  Brush.Style := bsClear;

  Font.Size := 12;

  Font.Color := clRed;

  TextOut(0,0, ’Some Text’);

  Release;

end;

 

 

6.6   Menu's

For making menu-items you can make a different picture (sprite) for each menu-item.

To show if the menu-item is on or off, you’s just show a different picture (when you move your mouse over the

Option).

 

procedure TForm1.DXDraw1MouseMove(Sender: TObject; Shift: TShiftState; X,Y: Integer);

Sound Sound
 

 

 

 

 


ON                                                     OFF

------------------------------------------------------------------------------------------------------------

 

7.   Links DelphiX

 

Official DelphiX homepage (Hori) + components

http://www.ingjapan.ne.jp/hori

 

Tutorials

http://www.savagesoftware.com.au/delphi/articles/article1.html

http://cerebral-bicycle.co.uk/

http://turbo.gamedev.net/delphix.asp

http://www.savagesoftware.com.au/delphi

 

DelphiX 3D

http://www.multimania.com/dxplus/index1.htm