Setting up a simple DirectX screen using the DirectX Component
To start with we are going to set up a simple test app.
First of all, create a new Delphi project and view the form.
Go to the DelphiX tab in the component pallette and select the DirectX Component.
Resize the component to be either the size that you want or use the Align property and set it to Client. This will make the component resize to fit the form. Take a moment to look at the properties
Set the properties for the component to the following:
Align - alClient
Display - 640x480x8 ( 640 pixels x 480 pixels ) with 8 bit colour.
Now place a DXImageList onto the form.
Go to the Items property and press the [...] button. This will bring up the Edit dialog. Press the Add New toolbar button. This will create a new item in the list. Click on this item to select it if it's not already and change the Picture property. You will need to give it a picture.
Make sure you set the DXDraw property to the DXDraw component on your form or you won't see anything.
You may even want to turn the Transparency property to False for the background image or you may get strange results.
Now the image list has a picture, we can draw it to the screen.
The process for drawing to the screen is as follows.
1. Draw image to the back buffer
2. Flip the surfaces to display the image
We'lldo
this now.
Place a DXTimer onto the form and disable it.
( We need to disable it to make sure that no DirectX related objects try to access the main DirectX object before it's been properly initialized )
In the OnTimer event place the following code
DXImageList1.Items[0].Draw(DXDraw1.Surface,0,0,0);
DXDraw1.Flip;
In the OnInitialize event for the DirectX component, place code to enable to timer
and the OnFinalize event place code to disable the timer.
If wedo
n'tdo
this, we will get errors.
Run the project and you should see your image drawn onto the form.
Click here todo
wnload the project for Tutorial 1
OK, so it's not quite a game yet, But if you thought you could write a game in a few lines of code, Go and buy Klik &
Play instead
DelphiX Tutorial2 by: Jason Farmer
Basic Sprite Handling using the Sprite Engine component
The Sprite engine component is quite badlydo
cumented, but depending on what you want todo
, very simple to use.
It requires a greater understanding of Delphi that the simple DirectX component tutorial.
Basically here's what youdo
.
• Place a DX Sprite engine component on your form
• Place a DirectX component on you form and set it up as per the previous tutorial
• Place an DXImage list on your form
• Place a DXTimer on your form
Set the DXDraw property of the DXSprite engine to the DXDraw1 component ( Rememebr todo
it for the DXImage list too )
Set each of the above components up as specified in the above tutorial. Only we're going to add an extra image to the DXImageList for our sprite and call it Ball.
Make sure that the Transparent flag is set to True and the Transparent colour is set to Black.
• Create a New Class based on TImageSprite ( I generally prefer to use a seperate unit for this )
• Override thedo
Move method ( We place our own code in here.. you'll see why later )
• Create an instance of your new class in your form unit
The code to create a new Class should look something like this.
unit Sprites;
interface
uses DXSprite,DXCLass,DXDraws,Windows,DirectX ;
type
//This is the new class
TMySprite = class(TImageSprite)
public
proceduredo
Move(MoveCount: Integer);
override;
end;
implementation
procedure TMySprite.DoMove(MoveCount: Integer);
var
T : Integer;
begin
Place movement code in here
end;
end.
A couple of pointers about the Sprite class.
The X and Y positions of the Sprite are stored asdo
ubles so you either have to work withdo
ubles or convert them to integers.
Todo
this, use code similar to this.
TempX := round(X);
TempY := round(Y);
OK Now we're almost ready to go
We've just got a few loose ends to tie up.
1st thing, Did you create new unit for your class? If so, make sure you reference it in your uses section in your forms code
Create an object of type TMySprite in your Var section of your forms code
then
in the DXDraw1Initialize section add the following code
MySprite := TMySprite.Create(DXSpriteEngine1.Engine);
MySprite.Image := DXImageList1.Items.Find('Ball');
//Set it's image to the new Ball image as descried earlier
MySprite.Width := MySprite.Image.Width;
Mysprite.Height := MySprite.Image.Height;
// Set the width and the Height so that the Engine knows what size the sprite is for drawing and collision
MySprite.X := 30;
//Set the Position
MySprite.Y := 30;
Now you need to alter the timer code so it looks like this
procedure TDirectXForm.DXTimer1Timer(Sender: TObject;
LagCount: Integer);
begin
DXImageList1.Items[0].Draw(DXDraw1.Surface,0,0,0);
// Draw the Background image
//Move the sprites, this method executes thedo
Move method of all it's sprites..
DXSpriteEngine1.Move(1);
DXSpriteEngine1.Draw();
// Draw the sprites onto the back buffer
DXDraw1.Flip;
end;
What you should get now is a pretty unspectacular ball sat on the screen, notdo
ing anything really interesting, Just sat there.
Try adding some code to the TMySprate Class to bounce the ball around
Click here todo
wnload the full DelphiX Turorial 2 ( With Bouncing Ball solution )
There are a couple of notes about the Sprite class, you may not be aware of.
To control the order in which things are drawn on the screen, you set the Z property of the sprite.
Thisdo
esn't perform any 3D transformations on the sprite. It simply adjusts the Z-Order for the sprite.
You will have to manage this yourself. Basically, the higher the number, the closer to the screen it is. The lower, the further away it is.
In this example, we're letting DelphiX handle the sprite drawing for us. This is all very well for the simplest of tasks. But what if you want todo
more? Well there's good news, there's great news and there's some not so great news.
The good news is that DelphiX wraps up a lot of drawing functionallity for you. You simply override thedo
Draw method for the sprite anddo
your own drawing with the functions provided.
The great news is that DelphiX also exposes the full directX surface interface for you to tinker with.
The not so great news is that you're going to have todo
a lot more work to get these other features working. Well, maybe not that much more but there's some theory to learn.
All of that is covered in tutorial 7, Advanced Sprite handling - Drawing But I suggest that you follow all of the other tutorials first to get a more rounded feel for the capabilities of DelphiX before you go off and code yourself into a hole. ;-)
DelphiX Tutorial3 by: Jason Farmer
Getting Input from using the Direct Input component
The DXInput component is a great way to capture information from an input device. Itdo
es a great job of encapsulating DirectInput whilst giving extra functionallity such as Key binding.
In this tutorial, we will learn how to get data from an analogue joystick and how to use the keys. It also builds on the previous tutorials to draw some graphics onto the screen to represent the input.
Lets get cracking then
.
We're going to create a simple app to draw images onto the screen based on the previous tutorials, although we won't be using the DXSprite Engine this time.
We will have an image in the center of the screen to show what directional keys have been pressed, there will be a ball on the screen that moves to the Joysticks axis and a ball on the left of the screen to move to the throttle control ( if available )
DirectX Component list.
Create an new project and place a DXscreen control onto your form. Place a DXImagelist and a DXTimer onto your form also. Set these up in the same way as before. Make sure that you set the timer interval to a low value or you will get very steppy results.
Add some images to your DXImage list.
You will need
• A Background bitmap
• 8 Directional images ( Arrows N S E W + NE NW SE SW )
• A Joystick position image ( Anything willdo
really, the ball from the last tutorial is ideal )
Now place a DXInput component onto your form and take a look at it's properties.
As you can see, the properties are quite simple. You have a Joystick section, a Keyboard section and a Mouse section.
To set up a Joystick youdo
the following
• Set the DXinput.Joystick.RangeX , RangeY and RangeZ properties from 0 to 1000.
This tells the DXInput component that you are expecting values to be no higher than X and no lower than the -X where X is the value that you set.
• Set the joystick sensetivity by using the DXinput.Joystick.DeadzoneX , DeadzoneY and DeadzoneZ properties from 0 to 100.
The higher the number, the more you will need to move the joystick to regester a movement. a setting of 0 will be very sensetive.
Once the joystick is set up, we can read values from it.
The values for the joystick are found in the DXinput.Joystick.X , Y and Z properties.
Values X and Y represent the joysticks Axis and the Z value is used if the joystick has a throttle stick attached.
To find out if any buttons have been pressed, we run through the buttons list from 0 to Buttoncount and check if the value is True or False.
for i := 0 to DXInput1.Joystick.ButtonCountdo
begin
if DXInput1.Joystick.Buttons
= True then
begin
//Button Pressed,do
some action.
end;
end;
Each time you ask the DXInput component for a new set of values, you must call the DXInput.Update method.
This queries the controls and sets the properties to the current values. The values will then
be retained until you call Update again.
To get information from the Keyboard, you simply query the DXInput.Keyboard.States property. The property is a list of the currently pressed keys. We just check to see if the key that we want is in the list.
Common keys have been assigned constants e.g. isUp , isDown , isLeft , isRight
You can also bind up to 32 other keys using the Editor (do
uble click on the DXinput control to access this ) You can have up to 3 keys bound to a single action.
To check the user definable keys you use the constants isButton1 throught to isButton32
e.g
If isLeft in dxinput1.Keyboard.States then
begin
//Do Stuff
end;
We now have everything that we need to use the DXInput component. Try it out for yourself. Make something move on the screen.
You will need variables to store the following positions.
1. the Star to show the keys pressed
2. The Ball or Orb to show the Joystick position
When we press a key on the keyboard, we index the correct image in the list and display it
// Draw the background
DXImageList1.Items[0].Draw( DXDraw1.Surface,0,0,0);
// Draw the pointy thing using the Image indicated by the
// key Image variable
Dximagelist1.Items[KeyImage].Draw( DXDraw1.Surface,StarX - round(Dximagelist1.Items[KeyImage].Width /2), StarY - round(Dximagelist1.Items[KeyImage].Height /2) ,0);
// Draw the ball
Dximagelist1.Items[1].Draw( DXDraw1.Surface,OrbX - round(Dximagelist1.Items[1].Width /2), OrbY - round(Dximagelist1.Items[1].Height /2) ,0);
// Draw another ball to show a throttle control
DXimagelist1.Items[1].Draw( DXDraw1.Surface,0,(200-round(Dximagelist1.Items[1].Height /2)) + DXinput1.Joystick.Z,0);
// Flip the surfaces to disaply the changes
DXDraw1.Flip;
Click here todo
wnload the full DelphiX Turorial 3 project
Other considerations for the DXInput component.
Although we've created an app that uses the DXInput component to move things around, there are still a couple of points that you may want to investigate further.
• Force Feedback using the Effects properties
• Using Joystate to get extra information
DelphiX Tutorial4 by: Jason Farmer
Making noise using the Sound Component
DirectX Component list.
In this tutorial we will be making use of the DXWavelist component and the DXSound components.
The wav files used in this tutorial can bedo
wnloaded here. Sounds
In this tutorial we will accomplish 4 things, Playing a wave, Altering the frequency of a wave at run time, Alter the panning of a wave at run time and looping a wave.
Place a DXSound component onto the form.then
place a DXWavelist onto the form and set the DXSound property of the wavelist to the DXSound1 component on your form.
Place a Button onto the form. Give it a caption of 'Bell'. We will be playing a bell sound using this button.
Now we have the components on the form, we can add the sounds.
Click on the wavelist anddo
uble click on the items property in the object inspector.
Press Add New, You should see element 0 appear in the list. Select Element 0 and them click the [...] button next to the wave property in the object inspector. You'll be given a wave editor dialog box. Press Load and select the Bell wave youdo
wnloaded earlier. then
enter Bell as the name
Repeat the procedure for Thunder, Dive and Bang wavs. The wavelist can be edited at any time so if youdo
n't have all of these files, you can get them later and insert them.
Now we have all of the sounds that we'll be using, we can continue.
There are 2 ways of playing a wave from a wavelist.
//You can find the wave by index:
DXWaveList1.Items[0].Play(False);
//or by name:
DXWaveList1.Items.Find('bell').play(False);
In the OnClick event of the button, place the code:
DXWaveList1.Items[0].Play(False);
Save the project, compile it and run.
Press the button and .... You should hear a bell.
Add another button to the form with a caption of "Thunder"
In the OnClick event of the new button add the code
dxwavelist1.Items.Find('Thunder').play(False);
Save your work, compile and run again.
Press the Thunder button and you should hear... Thunder.
Now press the Bell button and the Thunder button in quick succession. They should be mixed as opposed to cancelling each other out. DelphiX plays waves by mixing them. If you need to stop a sound when another plays, you'll have todo
that manually but for now have some fun making noises.
So far we've just played different sounds and seen how they are mixed. All simple stuff, but it's not going to win you any awards. Now we're going to see how to loop sounds, stop sounds and change their pitch.
The example I've used is a dive bomber. If you've ever seen any movies with an airoplane diving, you can hear the pitch gradually increasing. This effect is really easy to accomplish.
Create 2 extra buttons, give them captions of 'Dive' and 'Stop Dive'
then
put a TrackBar in between them, the Dive and Stop Dive buttons should be a opposite ends of the form.
Go back to the wavelist and find the Dive wave. Set it's Looped property to true.
When we play the Dive wave now, it will loop. But it will loop indefinitely or until we tell it to stop.
Put the following code into the OnClick event of the Dive button:
dxwavelist1.Items.Find('Dive').play(False);
Put the following code into the OnClick event of the Stop Dive button:
dxwavelist1.Items.Find('Dive').Stop;
This will start and stop the sound. Save it and try it out.
As you can see, when you press Dive, it keeps playing. Pressing Stop Dive stops the sound.
The next bit is to alter the frequency at run time. On the Change event of the trackbar place this code:
dxwavelist1.Items.Find('Dive').Frequency := TrackBar1.Position;
Save and run again. Press Dive, the sound should play. then
move the track bar from the left to the right. Hear how the pitch changes.
This technique can be used for anything from racing car engines to a simple musical instrument.
Now we will see how to adjust the panning of a wave at run time.
Create 3 new buttons at the bottom of the form and place them next to each other. One with a caption of 'Left', one with 'Centre' and one with 'Right'.
In the OnClick event for the Left button, add the following code:
dxwavelist1.Items.Find('Bang').Pan := -10000;
dxwavelist1.Items.Find('Bang').play(False);
In the OnClick event for the Centre button, add the following code:
dxwavelist1.Items.Find('Bang').Pan := 0;
dxwavelist1.Items.Find('Bang').play(False);
In the OnClick event for the Right button, add the following code:
dxwavelist1.Items.Find('Bang').Pan := 10000;
dxwavelist1.Items.Find('Bang').play(False);
As you can see, by altering the values from -10000 to 10000 you can achieve the panning effect.
Well, that's it for this tutorial. Have a play with the sounds. I'm sure that by experimenting with the sound lists and DXSound component, you can come up with far more exciting uses that I have.
Click here todo
wnload the full DelphiX Turorial 4
DelphiX Tutorial5 by: Jason Farmer
More Advanced Sprite handling - Collision
DirectX Component list.
So we've got sprites. We have lots of sprites. There are sprites coming out of our ears but at the moment, they just look very pretty on the screen anddo
n'tdo
an awful lot at all.
To make a game work, we need to interact with the in game objects. This isdo
ne in most games by blowing the object to smithereens. But sometimes you just want to see if you're standing on the floor or a platform. You may want to know if your bullets have hit the enemy. Or you may be wondering if your little ball has hit the other little ball across the other side of the screen.
Each of these scenarios requires the use of collision detection.
There are many ways to check if an object touches/intersects another. Some of these ways are better than others for the task at hand. Some use complex formulas to work out the collision.
But this is DelphiX where nothing is complicated.
We have 2 mothods of detection available to us.
1. Bounding box collision: The position and dimensions of the sprite are tested against the position and dimensions of each sprite in the game engine and alerts the colliding sprite when the collision occurs.
2. Pixel testing collision: The position and dimensions of the sprite are still used but instead of simply checking to see if the 2 regions overlap, the regions are compared pixel by pixel. This leads to a more accurate detection but it can also be slower. Pixel level detection is best used if you have an uneven shapes to test.
Examples of the different collision detection types readily available to DelphiX
In the examples below, the blue section represents the transparent part of the sprite. But you probably guessed that already.
The bounding Box
The box tells DelphiX the dimentions for the sprite and how to work with other sprites. The box also tells DelphiX when to clip the image if it goes off screen. All in all, the bounding box is quite important.
If youdo
not specify a width and height, DelphiX will still draw your sprite but you will not be able to perform collision detection on the sprite.
Hitting a bounding box
As you can see by the example image, the collision between the two sprites has occurred. But the spritesdo
not appear to have collided on the screen. This is the big drawback to using bounding box collision. It is inaccurate for anything other than rectangular objects. But it is very fast. If you have lots of objects to test, bounding boxes would be your best bet. (especially if they're off screen)
Pixel checking
A more accurate method of detection is pixel checking. This method checks to see if the images themselves have touched by using the transparent colour as a test. This method is far more effective of irregular objects like our ships there. but at a cost.pixel checking can use up valuable processor time.
More accurate collision
The two pixel checking sprites have been involved in a collision. As we can see, the sprites overlapped a long time ago but only when the non transparent parts touched did the collision occur.
Mixing the detection types
Objects that use different collision methods can still collide with each other take this image for example. One ship is using bounding box checking and one is using pixel checking and unlike bounding box testing alone, it produces more realistic results.
A compromise -
(never the perfect solution, but then
it wouldn't be a comprimise if it was...)
But as you can see, it's not perfect. The game player can easily see that these ships havn't really collided but your game will think that they have because the bounding box has intersected the non transparent pixels of the other sprite.
Setting up the collision detection
Here are the general steps required to get it working
• Create your sprite class in the usual way
• Override thedo
Move Procedure
• Override thedo
Collision Procedure
• Set the width and height properties of your sprite object to the correct size.
• In thedo
Move procedure call the Collision method.
• In yourdo
Collision method, place any code you need to respond to the collision. This code will be called whenever an object touches the object regardless of it's type. You'll should test the type of the sprite object before you perform any action.
• Choose your collision detection method. When you create your sprite object in your game, you can choose the type of collision detection you require by setting the PixelCheck property to True or False.
You could alsodo
this in the Constructor for the sprite.
Once this isdo
ne, you have functioning collision detection.
Putting it all together.
Create a new project
Place a DXDraw component , a DXImageList component , a DXSprite engine and a DXTimer on the form as per the usual drill.. If your unsure how todo
, follow the previous tutorials and come back here.
Place a new image into the DXImagelist. Something small and round with a transparent background. This ball shoulddo
nicely.
Create a ball sprite class
TBallSprite = class( TImageSprite )
public
XSpeed :do
uble;
YSpeed :do
uble;
proceduredo
Move(MoveCount: Integer);
override;
proceduredo
Collision(Sprite: TSprite;
vardo
ne: Boolean);
override;
end;
Complete Yourdo
Move procedure
Yourdo
Move code should look like this
procedure TBallSprite.DoMove(MoveCount: Integer);
begin
// Very simple ball movement
X := X + XSpeed;
Y := Y + YSpeed;
if X < 0 then
begin
X := 800 - ABS(X);
end;
if X > 800 then
begin
X := X - 800;
end;
if Y < 0 then
begin
Y := 600 - ABS(Y);
end;
if Y > 600 then
begin
Y := Y - 600;
end;
// Test to see if this ball hits another sprite
Collision;
end;
then
yourdo
Collision code
procedure TBallSprite.DoCollision(Sprite: TSprite;
vardo
ne: Boolean);
begin
// What have we hit?
If Sprite is TBallSprite then
begin
// Bounce the ball away
if XSpeed >0 then
XSpeed := - XSpeed
else
XSpeed := ABS(XSpeed);
If YSpeed >0 then
YSpeed := - YSpeed
else
YSpeed := ABS(YSpeed);
// Bounce the other ball away too
if TBallSprite(Sprite).XSpeed >0 then
TBallSprite(Sprite).XSpeed := - TBallSprite(Sprite).XSpeed
else
TBallSprite(Sprite).XSpeed := ABS(TBallSprite(Sprite).XSpeed);
If TBallSprite(Sprite).YSpeed >0 then
TBallSprite(Sprite).YSpeed := - TBallSprite(Sprite).YSpeed
else
TBallSprite(Sprite).YSpeed := ABS(TBallSprite(Sprite).YSpeed);
end;
end;
Now to create the ball sprites when the form starts
procedure TForm1.FormActivate(Sender: TObject);
var
Index : Integer;
begin
// Create the Balls
for Index := 0 to 9do
begin
balls[Index] := TBallSprite.Create( DXSpriteEngine1.Engine );
// Set Random positions and Speed values
balls[Index].X := random(800);
balls[Index].Y := Random(600);
balls[Index].XSpeed := random(20) -10;
balls[Index].YSpeed := random(20) - 10;
balls[Index].Image := DXImageList1.Items.Find('ball') ;
balls[Index].Width := balls[Index].Image.width;
balls[Index].Height := balls[Index].Image.Height;
end;
end;
then
clean up when the form closes
procedure TForm1.FormDestroy(Sender: TObject);
var
Index : Integer;
begin
// Destroy the Balls
for Index := 0 to 9do
begin
FreeAndNil( balls[Index]);
end;
end;
To make things happen we put come code into the timer
procedure TForm1.DXTimer1Timer(Sender: TObject;
LagCount: Integer);
begin
// Move the Sprites
DXDraw1.Surface.Fill(0);
// This clears the back buffer quickly instead of blitting a background image.
DXSpriteEngine1.Move(1);
// Move our balls
DXSpriteEngine1.Draw;
// Draw the balls
DXDraw1.Flip;
// Flip the surfaces to display the screen
end;
What you should see when you run this code is a lot of balls bouncing pretty unconvincingly away from each other. But then
this is only a simple tutorial and I'm sure that you cando
much better.
A small problem...
There is a minor issue with this collision detection method that you may have spotted.
The collision test isdo
ne when a sprite moves. But sprites move one at a time even though you only make one call to the sprite engine, they're moved one at a time and so it is perfectly feasable that a sprite may be moved into a position where it will collide with another sprite before it has been moved..
The way to compensate for this is to check for collision after your DXSpriteEngine1.Move(1) call in the timer event. But this will only work if you loop through the sprites.
Code similar to this would be used.
DXSpriteEngine1.Move(1);
//Move the sprites
//Now test for collision by cycling through the list of sprites
for SpriteCount := 0 to DXSpriteEngine1.Engine.Count - 1do
begin
if DXSpriteEngine1.Engine.Items[SpriteCount].Visible = True then
begin
DXSpriteEngine1.Engine.Items[SpriteCount].Collision;
end;
end;
Well that's it for this tutorial. I hope it was useful. The methods you choose to use in your game will depend heavily on what you're trying todo
. The collision detection methods that DelphiX supplies are perfect for platform games but for other games, maybe not so useful.
There are lots of tutorials on collision detection available on the net if you want to know more, but be prepared todo
some digging and it may be advantagous to get those C++ books out for a lot of them.
If you want todo
wnload the project used in this tutorial instead of typing it all in, you can get it here.
Plugging Other types of collision detection into DelphiX.
It is possible to create your own collision detection routines and put them into DelphiX. All you need todo
is to itterate through the list of sprites in the sprite engine and test to see if they hit each other by your chosen method.
Maybe you're writing a pool game and you want todo
accurate bounding circle collision detection butdo
n't want to waste time with pixel checking. Well, there is a very simple formula to work out if 2 spheres have hit.
Given centerpoints for the 2 circles as x1,y1 and x2,y2 with radii of r1,r2, the calculation would look like this:
// calculate delta on each axis
delt_x := x2 - x1;
delt_y := y2 - y1;
// calculate distance
dist := sqrt( delt_x * delt_x + delt_y * delt_y );
// test for intersection
if dist < r1 + r2 then
begin
// circles interect....
end;
DelphiX Tutorial6 by: Jason Farmer
Playing with Others using the Direct Play component.
DirectX Component list.
Welcome to the DXPlay tutorial. This ones a bit more heavy going than the last ones because it has less todo
with the game programming concepts and more todo
with communication and networking concepts.
Direct Play is brokendo
wn into component objects in the same way that Direct Draw is.
At the Root is the Direct Play object. This Object has Providers, Sessions and Players
The Provider is the network type used by Direct Play to send its messages. This can be anything from TCPIP, IPX or a Direct cable connection. Each of the available providers are listed in the Providers list.
Sessions are the currently running message rooms or games. Basically, you can't send messages until you're in a session.
Once you're in a session you can send messages to anyone else
in the session or to everyone.
There are 2 ways of organising the communication structure. Peer to Peer and Client server.
The following diagram explains the connection process for a Peer to Peer connection.
In this diagram, Machine B has created a session. It is the Host for the game. Machine A and Machine C have already connected and are sending messages to each other and to the host. Machine D is attempting to connect. Once it has connected, it can send messages to any other machine in the session (the blue blob in the diagram). The host plays less of an active part once everyone is in. Messages are sent directly to the other players theydo
not go through the host.
For simplicitys sake, we will be using a Peer to Peer configuration.
Our application needs todo
the following.
If the application is hosting the game it needs to:
&#8226; Select a provider &
provide connection information
&#8226; Open a connection specifying the new session name and the host players name
&#8226; Send messages to inform other players of state changes
If the application is joining an existing session
&#8226; Select a provider &
provide connection information
&#8226; Get a list of existing sessions
&#8226; Open a connection specifying the session name and the players name
&#8226; Send messages to inform other players of state changes
Afterwards the same things aredo
ne in both cases.
&#8226; Keep sending messages, informing the other players about changes in state, text messages and so on
&#8226; Close the session to inform all other users that you have left.
Once players have joined the session, you can list them by reading from the players list.
OK, that's enough background and thoery, let's get on with some work.
We're going to create a simple application that sends actions and text messages to the other players in the session.
The example will go through selecting a provider, setting up a new session, joining an existing session and sending messages in an application. We're not going to worry too much at the moment about optimizing the messages to reduce network traffic or anything like that. We're just going to get it up and running.
The application will be capable of sending 2 types of message. Simple text and Action key presses. We'll be sending simple key presses similar to those indo
om style games.
The messages themselves are sent using pointers to variables.
The type declaration for the message is as follows.
type { Use this Input Specific Type to send specific action keypresses}
TDXActionMessage = record
dwType: DWORD;
//{ dwType is absolutely necessary. } DXPlay needs this but the rest of the type can contain anything you want
ActionCode: Integer;
end;
The actioncode part of the type is the variable that will store all of the keycodes. Those who havn'tdo
ne boolean operations before may be wondering how we store more than one value in a single variable.. We take advantage on the fact that numbers are made up of Bits in a Byte or many bytes depending on the variable type. So by accessing the individual bits, we can read up to 8 different boolean ( 1 or 0 ) values to be used as different flags by reading from one single byte.
The constants we will use to differentiate between the various bits are as follows.
DXKEY_LEFT = 1 ;
DXKEY_RIGHT = 2 ;
DXKEY_UP = 4;
DXKEY_DOWN = 8;
DXKEY_SHOOT = 16;
DXKEY_JUMP = 32;
DXKEY_DUCK = 64;
By ANDing the ActionCode in the message when we recieve it, we can work out which button the other player pressed.
if TDXActionMessage( Data^ ).actioncode AND DXKEY_LEFT = DXKEY_LEFT then
When a message is received by the DXPlay component, all we get is a pointer to some data.
Wedo
n't actually know what format this data is in so we run a function provided by DelphiX to determine this. This is why the dwType variable is so important as DelphiX needs it to figure out what type of message has been sent. The function call is DXPlayMessageType( Data ). We just pass it the pointer and it tells us what message type it is. Considering that we define the message types ourselves, it shouldn't be too hard to figure out.
We send the messages easily enough. Take the following snippet as an example.
var
Msg: ^TDXActionMessage;
//A pointer to our message type variable
msgSize: Integer;
//We need to know the size of the data so DXPlay can send our message
begin
msgSize := SizeOf( TDXActionMessage );// Gets the message size.
GetMem(Msg, MsgSize);
//All we declared was a pointer, we need to allocate enough memory to store the variable ourselves.
msg.dwType := DXACTION_MESSAGE;
//Heres the part where we tell DXPlay what type of message it is.
msg.ActionCode := DXKEY_UP;
//Our data goes here
DXPlay1.SendMessage(DPID_ALLPLAYERS,msg,msgsize);
//Send the message to all players
You may have noticed that I've included a constant that you didn't know about DXACTION_MESSAGE. Well this is used to tell DXPlay what message type it is.
DXCHAT_MESSAGE = 0;
{ Identifies the message type. Add as many as you need}
{ E.G. Chat, PlayerMoves, General Updates, etc.. }
DXACTION_MESSAGE = 1;
Sending text is a bit more complicated as we have to include the length of the text that we're sending in the msgsze variable.
We have a new type for text messages
type { Use this Type for String Messages }
TDXChatMessage = record
dwType: DWORD;
{ dwType is absolutely necessary. }
Len: Integer;
//Length of text that we're sending
C: array[0..0] of Char;
//Char array that is to be expanded to make enough room for the string
end;
When sending text messages we also adopt a slightly different tactic.
var
Msg: ^TDXChatMessage;
//We are declaring the Chatmessage variable type
msgSize: Integer;
begin
msgSize := SizeOf( TDXChatMessage ) + Length( txtTextMessage.Text);
//Include the length of the text
GetMem(Msg, MsgSize);
//Allocate the memory for the variable
msg.dwType := DXCHAT_MESSAGE;
//Set the type. Now a chat message
msg.Len := Length( txtTextMessage.Text);
//Note that we now have a len variable to be filled in. This is to tell us theamount the length of text we're sending
StrlCopy( @Msg^.c,PChar( txtTextMessage.Text),length(txtTextMessage.text));
//Copy the text into the variable.
DXPlay1.SendMessage(DPID_ALLPLAYERS,msg,msgsize);
//Send as usual
To deal with a text message when we get it
if TDXchatMessage( Data^ ).Len <= 0 then
//Check how much text was sent
begin
s := '';
//Just use an empty string
end
else
begin
SetLength(s,TDXChatMessage( Data^ ).len);
//Set the length of a string 'S' to the length of the text
StrLCopy(PChar(s), @TDXChatMessage(Data^).c, Length(s));
//Copy the message into the variable 'S'
end;
{do
something with the message here }
So far we know that we have to connect to a provider and select a service. We also know that messages can be created using any message format we wish. The next bit is to put it all together
OK, Here is a brief description of the DXPlay events
OnAddPlayer
This event occurs whenever a player joins the current session. It sends you a player objects. This object lets you read the name of the player and any player data. There is a special data section to the player object which lets you read extra information about a player when the player connects to a session.
OnClose
This event is a bit strange, it tells the connected users that the session is closed. Which itdo
es but only if the close event has been raised by the host. if the host's PC crashes, you're non the wiser.
OnDeletePlayer
This Event tells you that a player has left the current session. Once again, you get a player object.
OnMessage
Anytime a messge is received this message is fired. This time you get a player object ( the sender ) and a data pointer
(Note youdo
n't receive the message that you send unless you explicitly send it to yourself)
OnOpen
This event is fired off whenerver succesfully you Create or Join a session.
OnSendComplete
This event may seem Superfluous but it's really cool to find out if the message actually got delivered or not. Since you cannot rely on the OnClose or OnSessionLost events to tell you if the session still exists, it's handy to know if your message for through. If it didn't, you're either suffering from SERIOUS lag or the server connection got reset.
OnSessionLost
This event is fired to tell you that the session that you're connected to no longer exists. However, it seems that this event is not raised if the host PC crashes.
Building the Application
Create a new project.
On your form, place a DXPlay component.
You may want to lay out the components something like this. then
you will need:
Combo box ( name:cboProviders )
List box ( name:lstPlayers )
List box ( name: lstMessages )
List box ( name: lstSessions )
Edit box ( name: txtNewSession )
Edit box ( name: txtPlayer )
Edit box ( name: txtTextMessage )
Button ( name: btnConnect caption: Connect )
Button ( name: btnHost caption: Host )
Button ( name: btnJoin caption: Join )
Button ( name: btnForward caption: Forward )
Button ( name: btnBack caption: Backward )
Button ( name: btnLeft caption: Left )
Button ( name: btnRight caption: Right )
Button ( name: btnJump caption: Jump )
Button ( name: btnDuck caption: Duck )
Button ( name: btnShoot caption: Shoot )
Button ( name: btnSend caption: Send )
, some stickyback plastic and an empty washing up liquid bottle. oops, sorry ( Blue Peter moment there )
In the form create method, we'll be retrieving the list of available providers. This list comes back in the form of a string list. Which makes it nice and easy to put into a combo box.
{get the list of available providers and put them into the combo}
cboProviders.Items := DXPlay1.Providers;
Next, we need to connect to the provider. This is where most of the headaches happen. You have a list of providers but youdo
n't know what protocols the player has set up in their network settings. So you may choose IPX and find that theydo
n't have that particular protocol installed. The same goes for TCPIP. So it's best not to hard code the selection. Also if youdo
n't specify the correct settings for the provider, you may end up getting errors there too.
I've kept my connection code simple for this tutorial. But you can set up the TCPIP settings before hand. Things like Port number and Hostname but these are beyond the scope of this tutorial.
In the Click event for the connect button place the following code
DXPlay1.ProviderName := cboProviders.Items[cboProviders.itemindex];
DXPlay1.GetSessions;
lstSessions.Items := DXPLay1.Sessions;
{We can always host a game.. But you may not want the player to host a game}
{Some games allow multiplayer spawn copies but they cannot host games}
btnHost.Enabled := true;
If lstSessions.Items.Count <=0 then
begin
{No sessions, disable the join button}
btnJoin.Enabled := False;
end
else
begin
{Have a list of sessions, the player can join}
btnJoin.Enabled := True;
end
As you can see, I'm also enabling and disabling buttons. Youdo
n't need todo
this but I'vedo
ne it to make it obvious to the user that they need to create a session.
So the user selects a provider from the combo box and presses connect. This gets a list of available sessionsbut first, because we havn't specified any network settings,if the user chose TCPIP, they will be presented with a dialog box asking for an IP address. Pressing OK searches for it.
Now we have a choice. Host a session or Join a session.....
To Host / Create a new session we tell DXPLay that we're opening a session and we pass it the Session name and Hosting player name
{Create a New session}
DXPlay1.Open2( True, txtNewSession.text, txtPlayer.text);
The first parameter is a boolean value to say if we're creating a new session. We are so we set it to True.
The new session gets created, connected users can nowdo
a GetSessions and see the session on the network.
To Join a session wedo
a similar thing.
{Join an existing session}
DXPlay1.Open2 (False, lstSessions.Items[lstSessions.itemindex],txtPlayer.Text);
As you can see, We're passing False as the first parameter. Which means that we're not creating a new session. We then
pass it the name of the session that we want to join then
the Players name.
Now that we're connected to a session ( either hosting or participating ) we can send messages to the other players.
To send a message about a player action e.g Jumping. Place code in the Action buttons Click event code.
var
Msg: ^TDXActionMessage;
msgSize: Integer;
begin
{ We're sending a single keypress, but you may need more than one key press
to be sent at a time so you just add them together.
e.g
msg.ActionCode = DXKEY_UP + DXKEY_LEFT;
Because we're using a single bit in a byte for each key, we can send all
keypresses in one single byte. Depending on the number type you use,
You can have anything from 8 to 64 different keypresses to check }
msgSize := SizeOf( TDXActionMessage );
GetMem(Msg, MsgSize);
msg.dwType := DXACTION_MESSAGE;
msg.ActionCode := DXKEY_UP;
DXPlay1.SendMessage(DPID_ALLPLAYERS,msg,msgsize);
This code is for the Jump button. Use the same code for the other action buttons and change the msg.ActionCode to the relevant constant. (DXKEY_UP, DXKEY_DOWN.. etc )
Now we're going to send text. Place this code in the Send buttons click event.
var
Msg: ^TDXChatMessage;
msgSize: Integer;
begin
msgSize := SizeOf( TDXChatMessage ) + Length( txtTextMessage.Text);
GetMem(Msg, MsgSize);
msg.dwType := DXCHAT_MESSAGE;
msg.Len := Length( txtTextMessage.Text);
StrlCopy( @Msg^.c,PChar( txtTextMessage.Text),length(txtTextMessage.text));
DXPlay1.SendMessage(DPID_ALLPLAYERS,msg,msgsize);
Now we've got code to send the messages, we need to be able to read them. As discussed earlier, when a message is sent to a player, the DXPlay component raises a Message event.
In the DXPlay onMessage event, place this code.
var
s: String;
begin
{ Check the type of the message}
case DXPlayMessageType( Data ) of
DXCHAT_MESSAGE:
begin
if TDXchatMessage( Data^ ).Len <= 0 then
begin
s := '';
end
else
begin
SetLength(s,TDXChatMessage( Data^ ).len);
StrLCopy(PChar(s), @TDXChatMessage(Data^).c, Length(s));
end;
{do
something with the message here }
lstMessages.Items.Add(Format('%s> %s', [From.Name, s]));
end;
DXACTION_MESSAGE:
begin
s := 'Action Code: ';
if TDXActionMessage( Data^ ).actioncode AND DXKEY_LEFT = DXKEY_LEFT then
s := s + 'Left ' ;
if TDXActionMessage( Data^ ).actioncode AND DXKEY_RIGHT = DXKEY_RIGHT then
s := s + 'Right ' ;
if TDXActionMessage( Data^ ).actioncode AND DXKEY_UP = DXKEY_UP then
s := s + 'Up ' ;
if TDXActionMessage( Data^ ).actioncode AND DXKEY_DOWN = DXKEY_DOWN then
s := s + 'Down ' ;
if TDXActionMessage( Data^ ).actioncode AND DXKEY_JUMP = DXKEY_JUMP then
s := s + 'Jump ' ;
if TDXActionMessage( Data^ ).actioncode AND DXKEY_DUCK = DXKEY_DUCK then
s := s + 'Duck ' ;
if TDXActionMessage( Data^ ).actioncode AND DXKEY_SHOOT = DXKEY_SHOOT then
s := s + 'Shoot ' ;
lstMessages.Items.Add(Format('%s> %s', [From.Name, s]));
end;
end;
Now you should have enough code to put the application together.
Testing your Application.
You may be wondering how to test this code. After all, you may not have a network to work with. Luckily, there is a solution. If you go to the project folder after you have succesfully compiled and run the application, you should see an executable for the project. Run this exe so you have 2 instances of the application running. Make one of them the Host for a session and make the other one Join the session. You should be able to send messages between them.
Well that's about it for this tutorial. I hope you found it useful. Although we have set up a simple Direct Play application there are many more things to consider.
Network Traffic - The size and frequency of network transmissions can seriously affect the performance of the game. Anyone who's tried to play a game on line over a slow modem will understand this only too well. There are tools to help you to test for slow networks. In the DirectX 7 SDK, there is a dll which acts as a network simulator.
Different types of message - We we're just sending text and button press messages. In practice though, this is going to generate an awful lot of traffic. Other methods need to be used. Some games just send state changes to minimise the amount of traffic. Image that you have a player who is runningdo
wn a corridor. You wouldn't want to sent messages for every change in his/her XYZ co-ordinates. So you just send the speed changes and Direction changes and let the game figure out where the player is supposed to be. This method has a dissadvantage. If the network suffers from lag, the player could end up somewhere completely different on the different machines. So evey now and again, you should send co-ordinate updates to counter this. There are many other types of message sheme that you could try.
Pre-empting the player - As discussed in the previous point, sending direction and velocity changes has one major dissadvantage, Network lag can cause the player to be in different places on different PC's This can be countered by watching what the player isdo
ing. If the player is running towards a health item or a weapon, you can more or less say that they will be there in X number of cycles. if the network slowsdo
wn, you can move the player and correct the position when the network speeds up again.
Syncronised Gaming - Not all games allow a player to 'just Turn up'. In order to be fair, each player must start at the exact same time. Todo
this you would send a Game start message to each player. The other players would be waiting in a Lobby area until this message arrives. You may want 2 different Game start messages. 1, to tell the PC to load any level data and to initialise the game and another to say that all of the players are ready to begin
. You may want to look more into Client Server networking todo
this. You would have a server application controlling the whole game and client applications receiving messages from the server. The clientsdo
not send messages to the other players, They only send messages to the server. The server then
sends the messages to the other clients after resolving any conflicting messages. It can make a much smoother game but it is harder to code.
If you want todo
wnload the project used in this tutorial instead of typing it all in, you can get it here.
________________________________________
DelphiX Tutorial7 by: Jason Farmer
Advanced Sprite handling - Drawing
DirectX Component list.
So, you've gotten this far, good. Now you're ready for something a little more meaty than just bouncing balls? Well,do
n't get your hopes up too much. We're going to take the bouncing ball sample from earlier on. But we're going to make it real pretty.
There are a lot more things you cando
with DelphiX to draw an image than the simple blt function used by the defaultdo
Draw method.
&#8226; Draw : The standard drawing function used by the defaultdo
Draw method.do
es exactly what it says on the tin. It draws an image on the screen at the co-ordinated provided. Out of the DelphiX drawing methods, this is by far the fastest (unless you plan on digging into the DirectX layer itself)
&#8226; StretchDraw: This takes the source image and draws it to a new size on the destination surface. Good for scaling things
&#8226; DrawAlpha : This draws an image in a similar way to Draw, but it uses a slightly different positioning system and will draw translucent images based on an Alpha value.
&#8226; DrawAdd : This draws an image in a similar way to the DrawAlpha, but instead of blenging the image to thebackground, it adds it to it. This can produce some very nice effects. Lighting, explosions
&#8226; DrawSub : This works in the same way as DrawAdd but instead of adding the source image to the destination, it subtracts it. Very useful for casting shadows
&#8226; DrawRotate : This draws the image on the screen at an angle. The parameters it uses are not obvious at first glance so I'll explain in a little more detail
&#8226; DrawRotateAlpha : this works in a similar way to DrawRotate but it also gives you the ability to draw it semi transparent.
&#8226; DrawRotateAdd : Working in the same way as DrawRotateAlpha, You can create nice spinning fire effects.
&#8226; DrawRotateSub : As before only subtracting the final image from the destination surface.
These are some drawing methods that draw waves, but I'm not going to go into them as they are of limited use and I think there's plenty to be getting on with as it is.
As well as the drawing functions provided by DelphiX, you can also access any of DirectX's own drawing functions. This also opens DelphiX up for future implementations of DirectX with additional features. There is no reason why you couldn't use DelphiX to set everything up and then
get at the DirectX functions from DelphiX.
A bit of thoery
You may have noticed in the last section, I mentioned surfaces. If you're new to DelphiX you may not know what they are. Even if you've beendo
ing simple DelphiX stuff, you can be forgiven for not knowing as DelphiXdo
es a good job of hiding them from you.
This is not out of spite, Surfaces are the basis for all drawing methods and as such are very complicated and getting too close to them when you're learning might be off putting.
Everything (graphics wise anyway) youdo
in DirectX uses surfaces for one thing or another. Basically, a surface is a piece of memory either in system memory or on the graphics card used to store and manipulate images.
When your screen is set up, A primary surface is created to act as your screen. You then
draw to this and it appears on the display.
If you set thedo
Flip option to True on your DXDraw component, you create a second surface. This is called the back buffer. After you have finished drawing everything to the back buffer, you Flip them.
This makes the back buffer (and everything on it) the visible surface and the old primary surface, the back buffer.
This technique is incredibly useful for creating flicker free displays.
DelphiX also creates a back buffer whendo
Flip is set to False, but it simply copies the contents of the back buffer to the primary surface so it will be displayed when the flip method is called. This method is slower than flipping.
(relationship between back buffer, primary surface and display)
Each time we add an image to the DXImageList, we in fact create a surface. This surface stores the image that we wish to composite onto the back buffer. This surface has many attributes but one of the most important being the transparent colour.
Note: The transparent colour may not work on all systems if it is not set to clBlack RGB(0,0,0). This is todo
with the pixel format used on 16bit colour systems.
To be safe, stick to black as it will always work. Sometimes a different transparency colour may work in windowed mode but not in full screen mode.
(Note: if the transparent colour is set to black, you have to be careful when drawing your image that youdo
n't miss bits of your image by accident. To get aound this, use a very dark grey. DirectX knows the difference even if your eyedo
es not)
The image of your DelphiX Sprite class, has a surface.
In this example, we are blitting from a surface (a Imagelist item in this case) onto the back buffer. The transparent colour here is Red (see note)
Internal to a surface, there are methods to draw the contents of another surface. So in DirectX, you would say BackBuffer.Blt( DestinationRect, SourceRect, Flags, Effects;
SourceSurface );
As you will notice, this is the opposite way arround to the method we've been using in DelphiX.
So far we've been saying, SourceSurface.Draw();
In DelphiX, the drawing functions are Source based and in DirectX, the drawing functions are Destination Based.
This may cause some confusion so we're going to use just the DelphiX functions for now.
OK Enough thoery, let's get on with it.
Making a Prettier bouncy ball
If you load up the code from tutorial 5 More Advanced Sprite handling - Collision and save it as another project somewhere. We'll begin
the alterations.
First of all, we're going to make the ball translucent.
Taking over the drawing functionallity...
Override thedo
Draw method of the Ball Sprite class. This will enable us to use our own drawing code. This is very useful. It allows you to really get into the image and add bits that you wouldn't normally be able to. In fact, it's very rare that you would leave the normal draw method intact. it's quite possible to draw multiple images in your own drawing function.
Place this code in the sprite class declaration. It should go just before or just after thedo
Move declaration.
Proceduredo
Draw();
override;
Imagine a little man sprite with independant arms and legs, instead of creating a sprite for each, you could create one sprite object with several images (defined as variables and set up with images beforehand) and draw the lot in one go. Each arm could be rotated and maybe he's got translucent areas like a light sabre or something... All possible with DelphiX and alldo
ne in thedo
Draw method so one call from the sprite enginedo
es the lot.
Drawing with DrawAlpha, DrawAdd and DrawSub
First we're going to use the Image method DrawAlpha.
To use the DrawAlpha method, we have to use a TRect type variable to pass the co-ordinates. The DrawAdd and DrawSub methods also use a TRect.
Procedure TMySprite.DoDraw;
Var
ImageRect : TRect;
begin
ImageRect.Left := Round(X);
ImageRect.Top := Round(Y);
ImageRect.Right := ImageRect.Left + Image.Width;
ImageRect.Bottom := ImageRect.Top + Image.Height;
Image.DrawAlpha( Engine.Surface, ImageRect,0, 128 );
end;
This code will Draw the ball at 50% translucency. See how the balls look as if they're made of glass. The Translucency (alpha) values range from 0 - 255. 255 being the totally opaque.
Now save and run the project. You should see the ball is transparent. This is Alpha blitting.
Try Changing the code slightly. Change the DrawAlpha method to DrawAdd.
Save and run it again. You should see a strange, yet not unpleasant effect, they look a bit like search lights. The image is added to the background, causing it to light up. The more white in the image, the brighter it will be.
Now change the code to use the DrawSub method.
Save and run. The image is now darker. It also seems to be a negative. This is because the image is taken away from the background. The lighter the source, the more colour is removed from the background.
See how easy that was. By combining these effects effectively, you can create some spectacular graphics.
A word of caution before we go any further. Although these effects are really useful and pretty, use them sparingly. The code behind them is not DirectX code and therefore cannot be accellorated (Correct at time of going to press Feb 2001). This is not to say however that the code is badly written, far from it. Hori hasdo
ne a class A job in providing this kind of drawing functionallity.
If you want to draw lots of transparent effects, you should consider using Direct3D or OpenGL instead as they support hardware alpha blitting and so on.
But that is so far beyond the scope of this tutorial, you couldn't spot it with a telescope.
Putting a different spin on things
DelphiX also comes with some really cool rotation functions and compared to others I've seen, they're a piece of cake to use. You only need the bare minimum of parameters and they are fast.
The methods we'll use here are DrawRotate, DrawRotateAlpha, DrawRotateAdd and DrawRotateSub.
Image.DrawRotate( Surface, X , Y, Width, Height, PatternIndex, CenterX, CenterY, Angle);
Image.DrawRotateAlpha( Surface, X , Y, Width, Height, PatternIndex, CenterX, CenterY, Angle, Alpha);
Image.DrawRotateAdd( Surface, X , Y, Width, Height, PatternIndex, CenterX, CenterY, Angle, Alpha);
Image.DrawRotateSub( Surface, X , Y, Width, Height, PatternIndex, CenterX, CenterY, Angle, Alpha);
Using these drawing methods is fairly simple. All you need is the X and Y locations, CenterX and CenterY , the Width and Height and an Angle. These values represent the location of the pivot point and affect where the image will be drawn.
The Image is always drawn around the pivot point. The center of the pivot point resides at the X,Y location of the background. The CenterX and CenterY values are fractional values to represent the location of the pivot point within the image. Got that? well here's an example to help to explain.
In the example, we can see the aeroplane flying through the sky. The pink area represents the image area. In the center of the plane is a cross symbol. This represents the pivot point. This is where the image will be drawn on the background. The numbers up the side and across the bottom are the CenterX and CenterY values. at the moment these have a value of 0.5 which means halfway. So the pivot point can be found at half width and half height in the image.
Imagine pinning a drawing to a wall with a drawing pin and rotating it.
The next parameter, the angle causes the image to rotate. As you can see from the second image, the plane is now at an angle. The plane's X and Y position remains unchanges as far as the pivot point is concerned.
The Angle parameter is slightly strange in that it uses values from 0 to 256 for it's angles. It's simple to convert to degrees though
ConvertedAngle := 256 / 360 * DesiredAngle;
Moving the pivot point.
Now lets see what happens if we supply a different number for the CenterX parameter. If we use the value of 0.25, the pivot point can be found at 1/4 of the distance of the width. As you can see, the pivot point has moved to the left.
Now if we rotate the image, we get a slightly different result to the previous example. The image is no longer rotated around it's center.
So in short, if you want the pivot point to the left, use a CenterX of 0, if you want it in the center, use 0.5 if you want it to the right use 1.
It is perfectly possible to supply numbers less than 0 and greater than 1. If youdo
this the pivot point will move out of the image to the left or the right. So a value of -1 would position the pivot point to the left of the left hand side by the width of the image and if you supply a 2, the pivot point will be to the right of the right hand side by the width.
( the pivot point can be outside the image area by using numbers < 0 or > 1 )
DrawRotateAlpha, DrawRotateAdd and DrawRotateSub all use the same rotation and position system as DrawRotate but with the added parameter of Alpha. This value works in the same way as it did in DrawAlpha, DrawAdd and DrawSub. The value is between 0 and 256, 256 being the most opaque and 0 the most transparent.
Well, that should be enough to get you started. The next time we visit drawing, we'll take a look at using DelphiX to expose the DirectDraw surface and use the drawing functions there.
Normally around this point I offer a "Download the completed project" option. But come on you guys... 11 lines of code.....
Until next time......
________________________________________
DelphiX Tutorial8 by: Jason Farmer
Advanced Sprite handling - 3D or not 3D...
Using StretchDraw to simulate simple 3D
DirectX Component list.
In our previous tutorials, we discussed many of the tools available to you as a DelphiX programmer. You should by now be familiar with setting up DelphiX and have a relatively good understanding of the sprite engine. If youdo
n't think you know enough,do
n't worry. I'll be explaining everything as I go.
As you already know, DelphiX offers the programmer a very simple yet powerful set of tools to develop multimedia applications with. Most of these tools deal with DelphiX's very capable 2D features and some of them deal with 3D. However, the 3D features that DelphiX exposes are a little out of date. In fact most of them refer to DirectX's retained mode which dissapeared sometime around DirectX6. So we won't be using any of them. In fact, if you want to use 3D properly, I recommend that you use OpenGL.
Still not to worry, You can still use DelphiX for 3D even if you're on;y using the 2D features.
Here's how to create a star field using DelphiX's sprite engine and simple 2D commands.
Creating a 3D smiley star field
Start a new project and open the form designer.
Add the following components and set them up.
DXDraw
DXImageList
DXSpriteEngine
DXTimer
Set the align property of the DXDraw component to alClient and setdo
Flip to True
Set the DXDraw property of the ImageList and the SpriteEngine to the DXDraw component
Set the Interval of the DXTimer compoenent to 1 and the enabled property to False.
Add an image to the imagelist and call it 'smiley'. You can use any image you want. But feel free to use this one.
So far this looks like every other DelphiX tutorial you've ever seen right? Well, yes it is. And that's the beauty of it.
The next thing todo
is to create a new unit to store our sprite class in. Call it Sprite or something. Make sure you add the name of the unit to the uses clause of your forms unit.
In your new unit set up the uses clause.
uses DXSprite,DXCLass,DXDraws,Windows,DirectX ;
and create a new class that inherits from TImageSprite
TMySprite = class(TImageSprite)
public
SpriteX : integer;
SpriteY : integer;
SpriteZ : integer;
proceduredo
Move(MoveCount: Integer);
override;
proceduredo
Draw();
override;
end;
You've probably noticed the SpriteX, SpriteY and SpriteZ variables for this class and you may be wondering why we need them when a sprite already has its own co-ordinate system.
Well, the sprites own co-ordinate system can only deal with 2D screen co-ordinates so we need to give it a little help and store the sprites' 3D position in these new variables.
Moving the Sprite
Now then
, this sprite is going to move towards the screen each frame so thedo
Move procedure will be really simple.
procedure TMySprite.DoMove(MoveCount: Integer);
begin
// This simple sprite simply moves towards the screen.
// Just decriment the Z axis.
SpriteZ := SpriteZ - 1;
if SpriteZ <= 0 then
begin
SpriteZ := 100;
end;
end;
This routine decriments the Z position and then
tests to see if the sprite has passed the viewer (i.e. SpriteZ <= 0 )
If it has, we just give it a new value of 100 to put it back into the distance.
Drawing the Sprite
This is where things get a little more tricky, but not much.
To fool the eye into seeing 3D on a 2D screen we mustdo
2 things.
1. Shrink the sprite to give the illusion of distance
2. Adjust the position of the sprite on screen to make distant objects appear closer to the center of the screen .
All of the scaling and screen co-ordinate calculations will bedo
ne in thedo
Draw procedure.
procedure TMySprite.DoDraw;
Var
DestRect : TRect;
SpriteWidth, SpriteHeight : Integer;
begin
Now to calculate the width of the sprite based on the distance from the screen (Zaxis)
if (SpriteZ > 0) and (spriteZ < 200) then
begin
Make sure wedo
n't try to draw objects that are too far away or too close (avoiding any nasty Division by Zero errors)
// Work out the Width based on the Z axis
SpriteWidth := image.Width div SpriteZ;
SpriteHeight := image.Height div spriteZ;
then
we work out the screen co-ordinates
X := (engine.Surface.Width div 2) - (((engine.Surface.Width div 2) div SpriteZ)+(SpriteX div SpriteZ));
Y := (engine.surface.Height div 2) - (((engine.Surface.Height div 2) div SpriteZ)+(SpriteY div SpriteZ));
This works by working out where the X and Y positions would be if the screen had been divided by the Z value and then
centered.
Engine.Surface.Width and Engine.Surface.Height are the dimensions of the window we're rendering to.
All that's left todo
in the sprite is to scale our image and draw it in the correct location.
So, we define a destination rect. This is the area on the surface we'll be drawing to. StretchDraw takes this rect and compares it to the source image. It then
scales the image to fit into the destination rect.
DestRect.Left := Round(X) - (spritewidth div 2);
DestRect.Top := Round(Y) - (spriteheight div 2);
DestRect.Right := DestRect.Left + spritewidth;
DestRect.Bottom := DestRect.Top + spriteheight;
In this example, I've taken the X and Y position of the sprite to mean the center of the sprite. This is optional, you can if you wish keep the X,Y co-ordinates relating to the top left hand corner of the sprite.
then
we Draw to the screen.
Image.StretchDraw(engine.Surface,DestRect,0);
end;
end;
That's the end of the sprite class. Now to create some smileys.
Give me a smile
It's time to go back to your form unit because now we're going to create our sprites.
Create an array of sprites in the form level variables section
var
Form1: TForm1;
Sprites: array[1..1000] of TMySprite;
implementation
then
in your DXDraw Initialize method, add this code to populate the array with smileys
procedure TForm1.DXDraw1Initialize(Sender: TObject);
var
SmileySprite : TMySprite;
SpriteIndex : integer;
begin
for SpriteIndex := 1 to 1000do
begin
Sprites[SpriteIndex] := TMySprite.Create(DXSpriteEngine1.Engine);
SmileySprite := Sprites[SpriteIndex];
SmileySprite.Image := DXImageList1.Items.Find('smiley');
//Set it's image to the new Ball image as descried earlier
SmileySprite.Width := SmileySprite.Image.Width;
SmileySprite.Height := SmileySprite.Image.Height;
SmileySprite.SpriteX := random(8000)-4000;
//Set the Position
SmileySprite.SpriteY :=random(8000)-4000;
SmileySprite.SpriteZ := random(100);
end;
DXTimer1.Enabled := True;
end;
We create 1000 sprites and then
enable the timer.
As you can see, the X and Y values we're giving to the sprites can be quite large. Because we're no longer restricted to only seeing the dimentions of the screen, we can give our sprites massive X and Y values and we'll still be able to see them when they're far away.
Now we've created our smileys, we need to move them and draw them. So put the following code into the DXTimer Timer procedure
procedure TForm1.DXTimer1Timer(Sender: TObject;
LagCount: Integer);
var
SpriteIndex : integer;
begin
//Move the sprites, this method executes thedo
Move method of all it's sprites..
DXDraw1.Surface.Fill(0);
// We move each sprite in turn
for SpriteIndex := 1 to 1000do
begin
Sprites[SpriteIndex].DoMove(1);
Sprites[SpriteIndex].Dodraw;
end;
DXDraw1.Flip;
end;
The last thing todo
is to turn off the timer and destroy the sprites when the DXDraw component is destroyed.
So in your DXDraw Finalize routine place this code
procedure TForm1.DXDraw1Finalize(Sender: TObject);
var
SpriteIndex : integer;
begin
DXTimer1.Enabled := False;
for SpriteIndex := 1 to 1000do
begin
Sprites[SpriteIndex].Free;
Sprites[SpriteIndex] := nil;
end;
end;
If you've followed the tutorial carefully, you should get something looking like this.
Well, there you go. Probably the simplest 3D engine in the world. It's fast, but it's not going to win you any awards. Still, itdo
es go to show how much you can get out of DelphiX.
There are some improvements that can be made to this smiley field demo.
ZOrdering. If you look closely, you'll see that some of the distant smileys are drawn over the top of the closer smileys. This is because we're not forcing the distant smileys to be drawn first. In fact they're being drawn in any old order. A quick fix for this particular demo would be to sort the array by the SpriteZ value but we've notdo
ne that here to keep it simple.
How about mixing up different sprites.. you could include a star field. In fact, you could quickly turn this simple demo in to a 3D asteroids game.
I hope you enjoyed this tutorial.
Click here todo
wnload this example if youdo
n't want to type it all in.
DelphiX Component List by: Jason Farmer
The components and what theydo
.
DXDraw: The Direct X Component
Your entryway to DirectX Programming. Sets up the DirectX display modes andother options visually without extra coding. The directX component is placed onto a form like any other Delphi component.
DXDIB: Device Independant Bitmap
Store images for painting onto a DX surface
DXImageList: A collection of DIB's
A List of Images stored in a library. Very handy for keeping images together. The images are compiled into the Executable.
DX3d: Direct 3D Component ( Now superceded by DXDraw's 3D functionality )
Sets up Direct 3D. However, this functionallity is now included with the DXDraw component so this component is obsolete. Included for backwards compatability.
DXSound: The Direct Sound Component
Sets up Direct Sound on the form.
DXWave: A DirectX Sample
A Sample to be played using Direct Sound
DXWaveList: A Collection of DirectX Samples
Store sounds in the forms file to be compiled into the Executable
DXInput: Direct Input Component
Very handy for capturing input data from the user. Provides Keyboard, Mouse and Joystick access along with Force feedback support.
DXPlay: Direct Play Component
Playing with yourself gets dull after a while. So use this component to play with others using IPX or TCPIP.
DXSpriteEngine: Sprite Engine Component
Although not strictly a DirectX component, the Sprite engine is a very useful component. It provides a way of handling sprites. It makes setting up a simple game easier than coding the sprite routines manually. It also provides collision detection and a render loop.
DXTimer: Miltimedia Timer
An accurate timer
DXPaintBox: DIB Version of the TImage component.
The paint box is a DIB version of the TImage. If provides similar functionality to the normal Delphi TImage component.
The components and what theydo
.
DXDraw: The Direct X Component
Your entryway to DirectX Programming. Sets up the DirectX display modes andother options visually without extra coding. The directX component is placed onto a form like any other Delphi component.
DXDIB: Device Independant Bitmap
Store images for painting onto a DX surface
DXImageList: A collection of DIB's
A List of Images stored in a library. Very handy for keeping images together. The images are compiled into the Executable.
DX3d: Direct 3D Component ( Now superceded by DXDraw's 3D functionality )
Sets up Direct 3D. However, this functionallity is now included with the DXDraw component so this component is obsolete. Included for backwards compatability.
DXSound: The Direct Sound Component
Sets up Direct Sound on the form.
DXWave: A DirectX Sample
A Sample to be played using Direct Sound
DXWaveList: A Collection of DirectX Samples
Store sounds in the forms file to be compiled into the Executable
DXInput: Direct Input Component
Very handy for capturing input data from the user. Provides Keyboard, Mouse and Joystick access along with Force feedback support.
DXPlay: Direct Play Component
Playing with yourself gets dull after a while. So use this component to play with others using IPX or TCPIP.
DXSpriteEngine: Sprite Engine Component
Although not strictly a DirectX component, the Sprite engine is a very useful component. It provides a way of handling sprites. It makes setting up a simple game easier than coding the sprite routines manually. It also provides collision detection and a render loop.
DXTimer: Miltimedia Timer
An accurate timer
DXPaintBox: DIB Version of the TImage component.
The paint box is a DIB version of the TImage. If provides similar functionality to the normal Delphi TImage component.