September 25, 2022, 11:37:41 PM

News:

IonicWind Snippit Manager 2.xx Released!  Install it on a memory stick and take it with you!  With or without IWBasic!


Programming 7 - A Direct X Window Project.

Started by GWS, May 05, 2010, 03:56:50 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

GWS

May 05, 2010, 03:56:50 AM Last Edit: December 01, 2012, 08:41:46 PM by GWS
Hi folks,

Here's an example derived from a project I did a while ago. It illustrates some interesting things you can do using Direct X screens.

Programming 7.

In this section, we'll take a look at another type of window - a Direct X window.

These operate in a different way to a GUI window, and are where the fun games stuff can be found.

To demonstrate a DX window, I'll use a project I coded a while ago.  This will also demonstrate the use of User-defined variable types, subroutines, Windows API routines, and playing midi music files.

Rather than going through the instructions step-by-step as we did in Programming 6, we'll consider the whole program at one go, and I'll explain what each part is doing.

Close any currently open program and open a new editor window using 'File - New - Source File'.

Make a new folder to hold this example, and copy and paste the following code  ..
__________________________________


' Creative Basic - A Simple Direct X Window

def w:window
def style,n,n1,run:int

def fx,fy:float
def dx,dy,fxnew,fynew:float

def musicfile:string

type flake 
def fx:int 
def fy:int 
def fspr:int
endtype

declare Rn(low:INT,high:INT)
declare "WINMM",mciSendStringA(command:STRING,temp:STRING,length:INT,callBack:INT),INT

' specify the number of snowflakes ..
n1 = 150

def f[n1+1]:flake :' snowflakes

style = @NOAUTODRAW|@NOCAPTION

window w,-1200,0,1024,768,style,0,"Creative Basic Direct X",main
setwindowcolor w,0

' create a DirectX screen ...
createscreen(w,1024,768,32)

frontpen w,rgb(0,80,120)
setfont w,"Times New Roman",20,400,@SFITALIC
drawmode w, @TRANSPARENT

' play background music ..
musicfile = getstartpath + "tren.mid"
mciSendStringA("Play " + musicfile,"",0,0)

run = 1

' load background image ...
if dxnewmap(w,getstartpath + "back.jpg",1024,768,1) = 0
messagebox w, "Could not load the background image","Error"
run = 0
endif

centerwindow w

' create a blank map filled with tile#0 ...
dxcreatemap w,1,1,0
dxdrawmap w

' hide the cursor
setcursor w,@CSCUSTOM,0

for i = 0 to n1
f[i].fx = Rn(100,1000)
f[i].fy = Rn(1,768)
f[i].fspr = dxsprite(w,getstartpath + "flake.bmp",20,20,1,0)
DXSETSPRITEDATA f[i].fspr,@SDBLTTYPE,@BLTTRANSSCALED
DXSETSPRITEDATA f[i].fspr,@SDSCALE, 0.16 + Rnd(0.1) - rnd(0.1)
next i

waituntil run = 0
mciSendStringA("Close "+ musicfile,"",0,0)
closewindow w
END

main:
select @CLASS
case @IDDXUPDATE
update
case @IDCLOSEWINDOW
  run = 0
case @idchar
' pressing the 'ESC'(ape) key will abort the program ...
    key = @CODE
if (key = 27) then run = 0
endselect
return

sub update

'draw the background
dxdrawmap w

for i = 0 to n1
dx = rnd(15.0) - rnd(15.0) + 3
fxnew = f[i].fx + dx
f[i].fx = 0.9*f[i].fx + 0.1*fxnew
dy = rnd(5.0) - rnd(5.0) + 10
fynew = f[i].fy + dy
if (fynew > 850 + rnd(200) - rnd(200))
f[i].fx = Rn(100,1000)
f[i].fy = Rn(-100,1) :' done to get a random flow of flakes from the top of the screen
else
f[i].fy =  0.2*fynew + 0.8*f[i].fy
endif
DXMOVESPRITE f[i].fspr, f[i].fx, f[i].fy
DXDRAWSPRITE w, f[i].fspr
next i

move w,250,640
print w,"A Direct X Window"
move w,200,670
print "Press ESC to Exit"

'show the changes
dxflip w

return

sub Rn(low,high)
' generates a random number between Low and High ..
n = int((high - low + 1) * rnd(1.0) + low)
return n


___________________________________________________

This program requires some graphic and sound resources, so I've attached them to this post.

Download the 'DXWindow.zip' file and unzip it in the folder you set up for this project.

You should then have this program's source code  - 'simpleDX.cba', two graphics 'back.jpg' and 'flake.bmp', a midi music file 'tren.mid', and the executable program 'simpleDX.exe'.

You can run it to see how it looks ..

So now we can examine how it works.  Some of this program is nearing the limits of my knowledge, so if anyone wishes to jump in with any clarification please feel free.

The first block of statements are the usual variable definitions for quantities used in the program.

One new type of definition though is the UDT (User defined type).  This is ..


type flake 
def fx:int 
def fy:int 
def fspr:int
endtype


The purpose of this UDT type is to define properties for the variable 'flake'.

The program will display many snowflakes - and each has an 'x' position, a 'y' position, and a particular sprite image which varies in size.

So we are using an array 'f[ ]' to hold all this information for 150 snowflakes, each of type 'flake'.

An Array .. ? What is that ?

Arrays

An array is just a collection of variables of the same type.

You define an array thus:

DEF arrayname[5] as Int

This is an array which can hold 5 values. For example, if the array was named 'Speed[5]' you could hold values for the speed of 5 racing cars.

Why not just have 5 separate variables, Speed1, Speed2, Speed3, Speed4, and Speed5 ?

Well you could do that, but imagine all the statements you'd need to process speeds for 40 cars.

Using an array of speeds, you could simply use a FOR loop to do the job thus:


FOR i = 1 to 40
Speed[i] = Speed[i] * 1.05
NEXT i


.. which loops through all the speed values in the array, and increases them by 5%.

As you can see, using an array, each element is addressed by an index - so to change the value of the third car's speed, you would process the third element of the array - Speed[3].  And so on.

Another example would be if you wished to make the speeds of cars 1 and 5 equal, you could use:

Speed[1] = Speed[5]

In fact any array element can be used in any calculations that are physically sensible.

There is one thing about arrays that can be confusing - but on the other hand can be useful.

Arrays are 'zero-based'.

This means the actual first element of an array in the computer, is the zero'th element array[0].

In our car speeds example, there is always Speed[0] , which we've ignored so far.

The Speed[ ] array which we've defined, can indeed hold 5 car speeds - but Car 1's speed would have to be loaded in the array element Speed[0].  This is where the confusion can arise.

It is often simpler to allow an extra element when defining of the array, and then ignore the zero'th element.

Our definition  then becomes:

DEF Speed[6] as Int

.. and we can now use Speed[1] to Speed[5] as you would expect for the speeds of Cars 1 to 5.

Speed[0] still exists, but can either be ignored, or you can use that element to hold the number of values being used in the array - 5 in this example.

This method can be useful where you may be sorting an array of values, and the zero'th element can tell you how many values there are in the array to be sorted.

Anyway, back to our Direct X example.  Here we are processing the data for 150 snowflakes that we've specified on the statement

n1 = 150

So now you will understand the definition of the array 'def f[n1+1] : flake' in the program.

This tells the computer to reserve memory for 'n1' snowflakes, plus one extra to allow for the zero'th element.

So we are reserving working space for 151 snowflakes in total, but ignoring the zero'th one.  That is, we will be working with elements f[1] to f[150] of the array.

Note that the elements of the f1[ ] array, are not just single values like car speeds.

Each snowflake in the array, is defined to be of our User-defined type, comprising 'x' and 'y' location values, and a snowflake sprite value.

So each 'element' of array f[ ] contains values for each of these quantities.

Elements of a composite array like this, are referenced using a dot notation such as:

f[5].fx

which selects the 'x' location of snowflake 5.

Similarly, f[5].fy accesses the 'y' location.

It's a tricky idea - but you will see how easy it is to use.

Now we press on to open the window.  We specify the window style as:


style = @NOAUTODRAW|@NOCAPTION


The window used to hold the screen needs to be created with the @NOAUTODRAW flag to prevent Creative BASIC from overwriting any changes to the screen.

Since it is to be a 'windowless' screen to improve the 'look' of the program, we also specify @nocaption.

This means there will be no border and top caption, so there will be no way to close this window using the normal cross icon at the top right.

So we have to make some other provision to close it - in this example, by using the 'ESC' key.

Notice how we apply both stlye flags using the ' | ' separator.  You can apply any number of flags by logically 'OR'ing them together in this way. (See the logical OR in the help file).

You could choose to run the program in a normal window frame - in which case the ESC key exit would be just another option.

The statement to open the window is:


window w,-1200,0,1024,768,style,0,"Creative Basic Direct X",main


and we color it black (ie. RGB(0,0,0) which is the same as an integer color value of 0).

setwindowcolor w, 0

Notice also that the window is to be opened with a horizontal 'x' offset to -1200 pixels.  Why do this ?

If you don't, the start up of a new window often causes white flashes on the screen, which don't look very good until the window settles down.

Creating the window 'off-screen' and coloured black, minimises any unwanted flashes the user might otherwise see.

Now we have a more or less ordinary window 'w', we attach a Direct X screen to it using the command:


createscreen(w,1024,768,32)


This specifies the DX window size as 1024x768 pixels, and with a colour depth of 32 bits per pixel - that is, the highest colour quality.

For the text we are planning to display, we then set the text colour using the statement:


frontpen w, rgb(0,80,120)


The font is set using:


setfont w,"Times New Roman",20,400,@SFITALIC


That is, 'Times New Roman', size 20 points, normal weight, italic.

If we left things at that, the text would appear on a white backround, which looks awful - unless you want the text to be on a coloured background, in which case you would set the 'backpen' color to your requirements.

In this example, I wanted just the text to appear on top of the background image, so we need this statement:


drawmode w, @TRANSPARENT


which places the text on a transparent background.

Music.

This application is enhanced with a little background music.  So I found a midi tune to suit, and placed the file 'tren.mid' in the program folder.

Now Creative can play a .wav music file using it's inbuilt 'playwave' command.  But midi files are not so straightforward.

I've chosen to use a midi file, to demonstrate how easy it is to call on the vast Windows API resources to do additional things that wouldn't be possible otherwise.

So we place these instructions next :


' play background music ..
musicfile = getstartpath + "tren.mid"
mciSendStringA("Play " + musicfile,"",0,0)


Two strange looking commands.  The first one tells the computer that the music file you want to play, can be found using the fully qualified path, comprising the path from which the program is started, plus the name of the music file.

Musicfile is a string type variable, and in my case, the music file is located at  'F:\simpleDX\tren.mid'.  (Yours will almost certainly be different).

Then we call on an API routine to actually play it.  So the second strange statement, passes the command 'Play musicfile' to the API routine.

How do we know how to do this ?  Well notice there is the declaration of the API at the start of the program:


declare "WINMM",mciSendStringA(command:STRING,temp:STRING,length:INT,callBack:INT),INT


There are hundreds of these useful library functions, and they often look quite complicated because they can do many clever things.

In this case though, we can ignore most of the function values, and tell it to just get on with playing the music.

Notice that to stop the music, we place a corresponding 'Close' statement in the program closing statements:


mciSendStringA("Close "+ musicfile,"",0,0)


Now we load the backgound image, and centre the window.

The next part is where I'm not expert enough to explain these two commands:


' create a blank map filled with tile#0 ...
dxcreatemap w,1,1,0
dxdrawmap w


I've just used these instructions based on Pal's examples.

As I understand it, a DX program or game, uses maps (usually scrollable) as a background, over which game sprites move.

So in our program, the background image is a single 'tile', and the CreateMap instruction defines a map of 1 tile wide by 1 tile high, filled with tile 0, which is our background image.

So lo and behold, we now have a DX window with a nice background picture.

So that the picture and sprite actions are not obscured by the cursor, we arrange to hide it using the statement:


' hide the cursor
setcursor w,@CSCUSTOM, 0


That is, no cursor at all.

OK so far, but we now need to set up 150 snowflakes at random locations and with varying sizes.

The FOR loop


for i = 0 to n1
..
next i


.. sets the 'x' and 'y' co-ordinates, and loads a bitmap of a snowflake into each f[ ] array.

They are all the same image 'flake,bmp'.

Then two DX commands set the way the snowflakes will be displayed and scaled.  You will need to look these commands up in the Help files to understand DX commands more fully.

Next, we have more or less normal closing statements which will stop the music, and close the program.

The message handling subroutine is similar to those we've already seen, but with two new bits ..


main:
select @CLASS
case @IDDXUPDATE
update
case @IDCLOSEWINDOW
  run = 0
case @idchar
' pressing the 'ESC'(ape) key will abort the program ...
    key = @CODE
if (key = 27) then run = 0
endselect
return


The main new item is that we are looking for @IDDXUPDATE messages.

These are messages which start to flow as soon as the DX screen is opened.

Each time a @IDDXUPDATE message is received, the program will call another subroutine (in this example called 'Update'), which contains all the processing to calculate new speeds and directions, and move objects and sprites in the scene.

Each update, the entire screen background, text, and sprites are all re-drawn.  This happens very quickly, and you can acheive much more than 100 frames redrawn a second - so the action appears continuous.

In this simple example, the updates will occur as fast as the graphics card and computer will allow.

This will result in varying performance depending on the machine specifications.  Some will run faster than others.

Obviously, this can be improved by setting the update frequency to a desired number of frames per second, and using the system timer to time updating.

Also note the interception of key presses using the @idchar messages.  This is used to close the program by looking for the ESC key to be pressed.

Finally, we'll take a look at the Update subroutine.

At each update, we first redraw the background image:


'draw the background
dxdrawmap w


Then we loop through all the snowflakes and move them to the next position on screen.

The random 'x' position is calculated using two random numbers in the range +15 to -15 pixels and a small positive bias of 3 pixels.

The resulting random change is added to the current 'x' position.

Because this would result in rapid jerks of movement in the 'x' axis, the movement is smoothed using the weighting formula:


f[i].fx = 0.9*f[i].fx + 0.1*fxnew


That is, the new position is based on 90% of the previous position plus only 10% of the raw calculated position.

Obviously this formula can be tweeked to get the amount of random movement that looks best.

The same approach is used to caluclate a new 'y' position.  However, the smoothing gives greater effect to the raw new position, so you get more variation in the 'y' axis to simulate air movement in 'gusts'.

The next calculations try to reflect random points on the 'y' axis, at which snowflakes reach the ground, and are then re-positioned at the top of the screen to 'fall' again.

A new 'x' value is allocated, and the new 'y' value is randomly chosen in an off-screen band 100 pixels high.

If this is not done, and you start all snowflakes at y = 0, you rapidly get unnatural bands of snowflakes falling at the same level - which looks awful.

Once the new position calculations are done, the snowflakes are redrawn on top of the background.

You then perform this command:


'show the changes
dxflip w


The drawing we've done so far is actually in a buffer, and the DXFLIP command transfers this graphics buffer to the 'frontbuffer' which actually displays all the newly drawn items at one go.

The example also has a small subroutine for generating random numbers in a given range 'low value' to 'high value'.

So there you are, we have a Direct X window in which all kinds of 2D sprites, patterns,games and animated charts can be run.
It's a really fun part of Creative Basic - but there's more.

You can have not only a 2D screen, but also a 3D screen  .. and then many more impressive games and effects can be achieved.
The full complement of images,code and executable is included in the attached.zip file.

End of Programming 7
______________________________________________

























Tomorrow may be too late ..