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.)
------------------------------------------------------------------------------------------------------------
Component to load pictures with.
2.1
Loading pictures at runtime
3.3.3 Tiled background (background assembled by tiles)
3.6
Sprite collisions & pixelcheck
------------------------------------------------------------------------------------------------------------
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:
DelphiX
Copyright Copyright© 1996-2000 Hiroyuki Hori
http://www.ingjapan.ne.jp/hori/
------------------------------------------------------------------------------------------------------------
Component
for drawing your graphics on. The surface of the DXDraw component is the
fundament for all graphical goals.
Align allClient
AutoSize True
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.
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.
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
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.
// 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.
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),
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.
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
------------------------------------------------------------------------------------------------------------
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/).
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.
Below is a
standard game-scenario for a game (incl. SpriteEngine).
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;
TBackground =
class (TBackgroundsprite)
public
procedure DoMove (MoveCount :
Integer); override;
end;
procedure
TBackground.DoMove (MoveCount : Integer);
begin
inherited DoMove (MoveCount);
X := X-1;
end;
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.
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].
NatureBackgroundSprite Z := -10; GrassBackgroundSprite Z := -11;
(3 tiles in
1 image) (1 tile in 1 image)
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.
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.
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)
------------------------------------------------------------------------------------------------------------
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
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.
ON OFF