TDK_Man's Tutorial On
Dark Basic Clickable On-Screen Mouse Areas
(Or How To Make Menu Screens With Buttons On Them!)
02 Feb 08: Added Section On Implementing Single And Multiple Menus In Your Programs.
One question you see on the forums time after time, is the good old 'how do you make a menu screen'? So, in an attempt to save us all the hard work of repeating how to do it over and over again, I decided to do a tutorial on the subject.
First of all, the old saying 'there's more than one way to skin a cat' holds true. There are in fact a number of ways to create a screen of clickable areas - or buttons - some better than others. There are no 'wrong' ways to do it - if the method you use works then fine.
I'll attempt to go through all of them and you can choose which one is best for you.
Note: In order to save you time, I've created some images for this tutorial - OK, my middle name isn't Picasso, so they aren't very good. But, for this tutorial they don't have to be.
The required images will be placed at the start of the section for each of the three methods I will be covering, so all you need to do is right-click on each one and select Save Image to save it to your DB project folder.
This tutorial is for both Dark Basic Classic and Dark Basic Professional. The only difference I am aware of is that in DBPro, when you load an image to be used as a sprite or texture, you have an extra ',1' flag parameter at the end of the Load command. If your button/sprite images are a little 'fuzzy' add the ,1 on the end.
So let's get started...
Method 1 - Using The Mouse X,Y Position
This is the probably the simplest method, but can involve a lot of typing if you have lots of buttons.
Essentially, you check the X and Y position of the mouse in the main loop and use If..Endif to check if the mouse is in pre-defined areas when the mouse button is clicked.
The screen with the buttons on it can be created in a paint program, so you can make it as fancy as you wish, Here's one I made earlier...
As you can see it's just a plain screen, but there's nothing stopping you from adding nice graphics around the edges or pasting the text and buttons over a full screen image or photograph. Make it as fancy as you like.
The above image took me about 5 minutes to make in Paintshop Pro, so given time, I'm sure you could come up with something far better.
So, we have five buttons. What we need to know is the X and Y positions of the top left (TL) and bottom right (BR) corner of every button. In PSP, you can just keep the mouse still over the required pixel of the image and it tells you this information.
In the example image I did, the five X/Y pairs of 2D co-ordinates are:
TL: 245,186 BR: 566,242
TL: 245,262 BR: 566,318
TL: 245,338 BR: 566,394
TL: 245,414 BR: 566, 470
TL: 245,490 BR: 566, 546
In our program, we load the image and paste it onto the screen. Five If..Endif blocks check to see if the mouse is within the regions defined by the above co-ordinates.
So, if you have 20 buttons, then you need 20 If..Then conditions.
Let's see the code...
Rem Example 1 - Simple On-Screen Clickable Mouse Regions
Rem From TDK_Man's Tutorial On Menu Screens
Set Display Mode 800,600,32
Sync On: Sync Rate 60
Load Image "Screen1.jpg",1
Paste Image 1,0,0
Set Text Opaque
Do
Mx=MouseX(): My=MouseY(): Mc=MouseClick()
If Mc=1 Then Gosub CheckRegion
Sync
Loop
End
CheckRegion:
Rem First Button
If Mx > 245 and My > 186 And Mx < 566 And My < 242
Rem Add here, whatever code you want to be executed when you click button 1
Center Text 400,580,"You Clicked On Button 1!"
Endif
Rem Second Button
If Mx > 245 and My > 262 And Mx < 566 And My < 318
Rem Add here, whatever code you want to be executed when you click button 2
Center Text 400,580,"You Clicked On Button 2!"
Endif
Rem Third Button
If Mx > 245 and My > 338 And Mx < 566 And My < 394
Rem Add here, whatever code you want to be executed when you click button 3
Center Text 400,580,"You Clicked On Button 3!"
Endif
Rem Fourth Button
If Mx > 245 and My > 414 And Mx < 566 And My < 470
Rem Add here, whatever code you want to be executed when you click button 4
Center Text 400,580,"You Clicked On Button 4!"
Endif
Rem Fifth Button
If Mx > 245 and My > 490 And Mx < 566 And My < 546
Rem Add here, whatever code you want to be executed when you click button 5
Center Text 400,580,"You Clicked On Button 5!"
End
Endif
Return
So, what happens is that we load in the main screen into image 1 and then paste it at 0,0 on the screen.
In the main loop, we store the mouse X and Y position in Mx and My, along with the status of the mouse button in Mc.
To prevent wasteful testing of the mouse regions, the If Mc=1 Then Gosub CheckRegion line only tests the area of the screen the mouse is over only when the mouse button is clicked.
The CheckRegion procedure just contains the five If..Then blocks which test to see if the mouse X and Y position is inside the defined button areas.
Obviously, you need to put the required labels onto the buttons in the paint program and the relevant code inside the If..Then blocks to match what your buttons do.
Embellishment:
There are lots of things to do to make your menu screens look a little more flashy - including animating the background image. But for now we'll concentrate on the buttons....
Simple Mouse Over Highlighting:
This simple technique simply highlights the button the mouse is currently hovering over. No practical use, but it looks nice.
We already know the X and Y co-ordinates of the top left and bottom right corners of our buttons, so we can use them to make a simple rectangle which moves from one button to another when we move the mouse.
As the 2D Box command draws a filled box, we'll make a small box function of our own and modify the above code to use it.
Version 2 - With Button Highlighter:
Rem Example 2 - Highlighting On-Screen Clickable Mouse Regions
Rem From TDK_Man's Tutorial On Menu Screens
Set Display Mode 800,600,32
Sync On: Sync Rate 60
Load Image "Screen1.jpg",1
Paste Image 1,0,0
Set Text Opaque
Do
Mx=MouseX(): My=MouseY(): Mc=MouseClick()
Gosub CheckRegion
Sync
Loop
End
CheckRegion:
Rem First Button
If Mx > 245 and My > 186 And Mx < 566 And My < 242
If Mc=1
Rem Add here, whatever code you want to be executed when you click button 1
Center Text 400,580,"You Clicked On Button 1!"
Else
UnfilledBox(245,186,566,242,RGB(255,0,0)): Rem Highlight Button
Endif
Endif
Rem Second Button
If Mx > 245 and My > 262 And Mx < 566 And My < 318
If Mc=1
Rem Add here, whatever code you want to be executed when you click button 2
Center Text 400,580,"You Clicked On Button 2!"
Else
UnfilledBox(245,262,566,318,RGB(255,0,0)): Rem Highlight Button
Endif
Endif
Rem Third Button
If Mx > 245 and My > 338 And Mx < 566 And My < 394
If Mc=1
Rem Add here, whatever code you want to be executed when you click button 3
Center Text 400,580,"You Clicked On Button 3!"
Else
UnfilledBox(245,338,566,394,RGB(255,0,0)): Rem Highlight Button
Endif
Endif
Rem Fourth Button
If Mx > 245 and My > 414 And Mx < 566 And My < 470
If Mc=1
Rem Add here, whatever code you want to be executed when you click button 4
Center Text 400,580,"You Clicked On Button 4!"
Else
UnfilledBox(245,414,566,470,RGB(255,0,0)): Rem Highlight Button
Endif
Endif
Rem Fifth Button
If Mx > 245 and My > 490 And Mx < 566 And My < 546
If Mc=1
Rem Add here, whatever code you want to be executed when you click button 5
Center Text 400,580,"You Clicked On Button 5!"
End
Else
UnfilledBox(245,490,566,546,RGB(255,0,0)): Rem Highlight Button
Endif
Endif
Return
Function UnfilledBox(X1,Y1,X2,Y2,Colour)
Paste Image 1,0,0: Rem Restore Background Screen
Ink Colour,0
Line X1-1,Y1-1,X2+1,Y1-1
Line X2+1,Y1-1,X2+1,Y2+1
Line X2+1,Y2+1,X1-1,Y2+1
Line X1-1,Y2+1,X1-1,Y1-1
EndFunction
As you can see if you run code example 2, as you move the mouse over the buttons, the red highlighting line moves with it. A very simple addition, but quite effective.
The first change from example 1 is that we now use Gosub CheckRegion and lose the If Mc=1. This is because we need the routine to be called even when the mouse button is not being pressed - so we can detect which button the mouse is over.
The other change is the addition of If..Else..Endif blocks inside the region checking If..Endifs. This essentially does the following:
The region check If..Endif decides if the mouse is within a given button's region - regardless of the state of the mouse button.
The If Mc=1 checks to see if the mouse button has been pressed. If it has then the Center Text block of code is executed.
If not, then the block after the Else is executed and the UnfilledBox() function is called - which clears the screen with the background image and then draws a red box using the provided co-ordinates.
You could just as easily have created a second set of button images from your original background and coloured them slightly different. You would simply load them in and as you moved the mouse over the buttons, pasted the button from the other set - instead of adding a coloured rectangle.
Let's see how that's done next. First, right-click and save the following five buttons into the same folder as the first image:
Version 3 - With Alternate Image Highlighter
Rem Example 3 - Mouse-Over Button Highlighting With Images
Rem From TDK_Man's Tutorial On Menu Screens
Set Display Mode 800,600,32
Sync On: Sync Rate 60
Load Image "Screen1.jpg",10
Load Image "Button1.jpg",1
Load Image "Button2.jpg",2
Load Image "Button3.jpg",3
Load Image "Button4.jpg",4
Load Image "Button5.jpg",5
Paste Image 10,0,0
Set Text Opaque
Do
Mx=MouseX(): My=MouseY(): Mc=MouseClick()
Gosub CheckRegion
Sync
Loop
End
CheckRegion:
Rem First Button
If Mx > 245 and My > 186 And Mx < 566 And My < 242
If Mc=1
Rem Add here, whatever code you want to be executed when you click button 1
Center Text 400,580,"You Clicked On Button 1!"
Else
Paste Image 10,0,0: Rem Restore Background Screen
Paste Image 1,245,186: Rem Other Button 1 Image
Endif
Endif
Rem Second Button
If Mx > 245 and My > 262 And Mx < 566 And My < 318
If Mc=1
Rem Add here, whatever code you want to be executed when you click button 2
Center Text 400,580,"You Clicked On Button 2!"
Else
Paste Image 10,0,0: Rem Restore Background Screen
Paste Image 2,245,262: Rem Other Button 2 Image
Endif
Endif
Rem Third Button
If Mx > 245 and My > 338 And Mx < 566 And My < 394
If Mc=1
Rem Add here, whatever code you want to be executed when you click button 3
Center Text 400,580,"You Clicked On Button 3!"
Else
Paste Image 10,0,0: Rem Restore Background Screen
Paste Image 3,245,338: Rem Other Button 3 Image
Endif
Endif
Rem Fourth Button
If Mx > 245 and My > 414 And Mx < 566 And My < 470
If Mc=1
Rem Add here, whatever code you want to be executed when you click button 4
Center Text 400,580,"You Clicked On Button 4!"
Else
Paste Image 10,0,0: Rem Restore Background Screen
Paste Image 4,245,414: Rem Other Button 4 Image
Endif
Endif
Rem Fifth Button
If Mx > 245 and My > 490 And Mx < 566 And My < 546
If Mc=1
Rem Add here, whatever code you want to be executed when you click button 5
Center Text 400,580,"You Clicked On Button 5!"
End
Else
Paste Image 10,0,0: Rem Restore Background Screen
Paste Image 5,245,490: Rem Other Button 5 Image
Endif
Endif
Return
This example is basically the same as example number 2, but we've lost the function as it's no longer required.
This time, instead of calling the function, we paste image 10 onto the screen to clear what was there before, then simply paste the required button from the second set.
Note: I changed the main screen image from number 1 to 10 so I could use images 1 to 5 for the buttons - it just makes things easier to understand if you are using image number 1 for the first button and so on...
OK that's it. If you aren't artistically challenged - like me - you could give the second set of buttons some tasteful graphical effect like a glowing outer edge or something - it's up to you.
Method 2 - Hidden Button Zones
This method is quite clever - and is better than method one because it allows for none rectangular buttons and it eliminates the need for all that If X>20 And Y>100 And X<300 and Y<200 business.
The theory is that you have two images - one normal that you see (like Screen1 above) and the other contains only the parts (zones) of the screen that you can click on. The second screen is never seen.
Each of these zones is a different colour and when you click on the screen with the mouse, the pixel at the exact X and Y position on the hidden screen is tested. If that pixel is black, you haven't clicked in a zone.
If it's any colour other than black, then it's a zone. The colour of the pixel defines the zone you clicked in.
As mentioned previously, this means that the zones (or buttons) can be any shape you like - ovals, circles, stars, you name it! If you click anywhere inside the shape it returns a colour.
So, we need a couple of new screens for the next example, so usual job next - save these two images:
As you can see, the top image is what you see on the screen. The bottom image contains coloured zones identical in shape and position to the buttons - the rest of the screen is black. This image is created in your paint program from the main screen you design, only the buttons are filled in with solid colour and the rest just left black. It's then saved with a different name (see below).
When we click the mouse on the buttons on the visible screen, we test the hidden screen at the same position and the colour returned tells us what button has been clicked on.
And now the down side - you cannot use JPG files for the hidden zone image. You must use a BMP file because JPG's are compressed files. If you save it as a JPG, then load it back in and zoom in on one of the coloured zone edges you will see that they are pixellated.
As we are testing the colour of pixels, these different shades will be detected as non-zone colours - we won't have a solid zone boundary from one colour to black. With BMP's, there's no compression so this problem doesn't occur.
As uncompressed .BMP files can be quite large, a tip is to create the colour zone file as a 256 or 16 colour image. The resulting file is much smaller and even a 16 colour image allows for up to 15 differently coloured zones (buttons).
This whole idea relies on the fact that DB uses something called 'Bitmaps'. No, not Windows BMP bitmaps - screens which can be created when we need them.
Up to 32 other screens are available - not including bitmap 0 (zero) which is the one we can always see. If we create bitmaps 1, 2, 3 or 4, they are in memory and we can't see them. The Set Current Bitmap command selects which one we want to draw to -
not which one is actually seen - that is always bitmap 0!
So, we load Screen2 into an image and paste it onto bitmap 0 (the screen) then load Screen3 into bitmap 1 where it's hidden from view.
Want to see some code? OK...
Version 4 - Hidden Button Zones
Rem Example 4 - Mouse Buttons With Hidden Bitmap Zones
Rem From TDK_Man's Tutorial On Menu Screens
Gosub Setup
Do
Mx=MouseX(): My=MouseY(): Mc=MouseClick()
ColNum = CheckRegion(Mx,My)
If ColNum <> 0 And Mc=1 Then Gosub HandleButton
Sync
Rem Text 10,0,"Zone Colour: "+Str$(ColNum)+" "
Loop
End
HandleButton:
Rem If we are here then we clicked on a button and a colour was returned
Select ColNum
Case 16711680
Rem Zone (Button) 1
Center Text 400,580,"You Clicked On Button 1"
End
EndCase
Case 65280
Rem Zone (Button) 2
Center Text 400,580,"You Clicked On Button 2"
EndCase
Case 255
Rem Zone (Button) 3
Center Text 400,580,"You Clicked On Button 3"
EndCase
Case 16776960
Rem Zone (Button) 4
Center Text 400,580,"You Clicked On Button 4"
EndCase
Case 16711935
Rem Zone (Button) 5
Center Text 400,580,"You Clicked On Button 5"
EndCase
Case 65535
Rem Zone (Button) 6
Center Text 400,580,"You Clicked On Button 6"
EndCase
EndSelect
Return
Setup:
Set Display Mode 800,600,32
Sync On: Sync Rate 60
Load Image "Screen2.jpg",10
Paste Image 10,0,0
Sync
Load Bitmap "Screen3.bmp",1
Set Current Bitmap 0
Set Text Opaque
Return
Function CheckRegion(X,Y)
Set Current Bitmap 1
Retval = Point(X,Y)
Set Current Bitmap 0
EndFunction Retval
OK, as there's a bit more going on before the main program loop I've moved it all into a procedure called Setup and called it with a Gosub Setup right at the beginning. Much tidier now!
In the main loop, as usual, we get the mouse X, Y and button status into Mx, My and Mc.
We then call the CheckRegion() function, passing the mouse X and Y position. As we use:
ColNum = CheckRegion(Mx,My)
this means that the function's return value is placed into the variable ColNum. This will be a 0 if the mouse isn't on a coloured zone and a non-zero value if it is.
The function itself simply flips to the hidden screen and tests the colour of the pixel at X,Y using Point(X,Y). It then flips back to screen 0 and returns the pixel colour value it found there.
Back in the main loop, if ColNum isn't 0 (black) and Mc=1 (the mouse button has been pressed) then we Gosub HandleButton.
In the HandleButton procedure, we have a series of Select..Case items which are basically the same as If..Endifs but are a bit tidier to look at.
Select ColNum.. Case 65535 is basically the same as saying If ColNum = 65535. If it is, then everything between the Case and EndCase is executed in the same ways as the code between If and Endif - had we used it.
But, those strange numbers? Where are they from?
Well, those are called 'Colour Values' and are what the Ink and Color commands use. As they can be between 0 and around 60 or so million, no-one is expected to know them all, so we use the RGB function.
RGB(0,255,255) simply converts to the colour number 65535 without you having to do the maths yourself.
Obviously if you do know the colour value number you want already, you can speed up your programs by entering the colour value directly - therefore doing away with a calculation which takes time - admittedly only a fraction of a second, but they all add up...
If you want to know how I found out what numbers to use, remove the Rem from the start of the Text line at the end of the main program loop. Run the program and move the mouse over the buttons. You didn't think I knew all the colour values did you?
Notice also, how accurate the button detection is around the star shaped buttons - you could never do this using method 1 in a million years!
Embellishment:
There's no way to do a nice outline highlighting version with simple 2D commands like with method 1 - because of the button shapes. Rectangles around unusual shapes doesn't look too good either.
However, you can use the image mouse-over method exactly the same way as shown previously. You just need to make sure that you paste the mouse-over button images in exactly the right place.
Here's an example. I've just done the three star buttons at the bottom to show you the method. Don't forget to save the three button images below first!
Version 5 - Alternative Images And Zones
Rem Example 5 - Hidden Bitmap Zones And Alternate Images
Rem From TDK_Man's Tutorial On Menu Screens
Gosub Setup
Do
Mx=MouseX(): My=MouseY(): Mc=MouseClick()
ColNum = CheckRegion(Mx,My)
If ColNum <> 0
Gosub HandleButton
Else
Paste Image 10,0,0
Endif
Sync
Loop
End
HandleButton:
Rem If we are here then a colour was returned
Select ColNum
Case 16711680
Rem Zone (Button) 1
If Mc=1
Center Text 400,580,"You Clicked On Button 1"
End
Endif
EndCase
Case 65280
Rem Zone (Button) 2
If Mc=1
Center Text 400,580,"You Clicked On Button 2"
Endif
EndCase
Case 255
Rem Zone (Button) 3
If Mc=1
Center Text 400,580,"You Clicked On Button 3"
Endif
EndCase
Case 16776960
Rem Zone (Button) 4
If Mc=1
Center Text 400,580,"You Clicked On Button 4"
Else
Paste Image 4,21,373
Endif
EndCase
Case 16711935
Rem Zone (Button) 5
If Mc=1
Center Text 400,580,"You Clicked On Button 5"
Else
Paste Image 5,284,373
Endif
EndCase
Case 65535
Rem Zone (Button) 6
If Mc=1
Center Text 400,580,"You Clicked On Button 6"
Else
Paste Image 6,546,373
Endif
EndCase
EndSelect
Return
Setup:
Set Display Mode 800,600,32
Sync On: Sync Rate 60
Load Image "Screen2.jpg",10
Paste Image 10,0,0
Sync
Load Bitmap "Screen3.bmp",1
Set Current Bitmap 0
Load Image "M2B4.jpg",4
Load Image "M2B5.jpg",5
Load Image "M2B6.jpg",6
Set Text Opaque
Return
Function CheckRegion(X,Y)
Set Current Bitmap 1
Retval = Point(X,Y)
Set Current Bitmap 0
EndFunction Retval
As the examples are all roughly variations on a theme, by now you should have a good idea of what they are doing.
So briefly, example 5 loads the visible screen with the buttons into image 10 and puts it on the screen. It then loads the image with the colour zones on it into hidden bitmap 1 and sets the current bitmap to 0. (Whenever you load or create a bitmap, that bitmap becomes the current one, so you have to switch back to bitmap 0 afterwards or all future commands will be sent to bitmap 1 and you won't see anything happen).
We then load the three alternative button images into images 4, 5 and 6 respectively.
In the main loop, we call the CheckRegion() function as before and if the number returned isn't 0 (black) we gosub the HandleButton procedure. If it is 0 then the mouse isn't over a button, so we paste the background image.
In the HandleButton procedure, we use Select..Case to determine the zone colour the mouse is over and in each case we check to see if the mouse button has been pressed and act accordingly.
However, in the case of the last three buttons, (4, 5, and 6), the Else sections mean that if Mc doesn't equal 1 (the mouse button isn't being pressed), then we paste the respective alternative button image.
As you can see, this method really does give a precise mouse-over effect on non-rectangular buttons.
Method 3 - Buttons With Sprites
This method is also better than method one because you don't need to know where the buttons are on the screen. Instead, you use DB's sprite collision commands.
The theory this time is that you make all your buttons sprites and place them on the screen where you want them.
You have another very small hidden sprite (say sprite number 100) which you place at the mouse X,Y position and you simply test this sprite for collision. If you use:
OverButton = Sprite Collision(100,0)
...then the 0 parameter tells DB to return the number of any sprite that sprite 100 collides with. So, any number other than 0 returned is the actual number of the button the mouse is over.
OK, save the new background screen and the button images below, then run example 6:
Version 6 - Buttons Made Of Sprites
Rem Example 6 - Sprite Buttons
Rem From TDK_Man's Tutorial On Menu Screens
Gosub Setup
Do
Mx=MouseX(): My=MouseY(): Mc=MouseClick()
Sprite 100,Mx,My,100
OverButton = Sprite Collision(100,0)
If OverButton <> 0 Then Gosub HandleButton
Sync
Loop
End
HandleButton:
Rem If we are here then a collision was detected
Select OverButton
Case 1
Rem Button 1
If Mc=1
Center Text 400,580,"You Clicked On Button 1"
End
Endif
EndCase
Case 2
Rem Button 2
If Mc=1
Center Text 400,580,"You Clicked On Button 2"
Endif
EndCase
Case 3
Rem Button 3
If Mc=1
Center Text 400,580,"You Clicked On Button 3"
Endif
EndCase
Case 4
Rem Button 4
If Mc=1
Center Text 400,580,"You Clicked On Button 4"
Endif
EndCase
Case 5
Rem Button 5
If Mc=1
Center Text 400,580,"You Clicked On Button 5"
Endif
EndCase
Case 6
Rem Button 6
If Mc=1
Center Text 400,580,"You Clicked On Button 6"
Endif
EndCase
EndSelect
Return
Setup:
Set Display Mode 800,600,32
Sync On: Sync Rate 60
Load Image "Screen4.jpg",10
Load Image "M3B1.jpg",1
Load Image "M3B2.jpg",2
Load Image "M3B3.jpg",3
Load Image "M3B4.jpg",4
Load Image "M3B5.jpg",5
Load Image "M3B6.jpg",6
Set Text Opaque
Rem Create and place button sprites
SprNum = 1
For Ny=0 To 2
For Nx=0 To 1
Sprite SprNum,Nx*300+130,Ny*100+200,SprNum
Inc SprNum
Next Nx
Next Ny
Rem Get Image For Hidden Sprite
CLS 141241: Get Image 100,0,0,2,2
Rem Create Hidden Sprite
Sprite 100,400,300,100
Hide Sprite 100
Ink RGB(255,255,255),0
Paste Image 10,0,0
Sync
Return
So, a quick run-through on what's going on in this example...
As usual, in Setup, the screen background is loaded, followed by the six button images. We then create the six button sprites and place them on the screen. They don't have to be placed in any particular position - put them in an oval pattern if you like!
Next, we set the screen to a non-black colour and get a tiny 2 pixel square image for our hidden sprite (number 100) which we are going to place at the mouse position as we move it around.
Note: You have to change the screen colour because grabbing a bit of the default black screen will result in a black (transparent) sprite which obviously won't work as there are no non-black pixels in it to test for collision!
Finally, we create sprite 100 and paste the background screen in place. As sprites always appear over 2D, this doesn't wipe off the sprites we've just positioned.
In the main loop, we get the mouse details and then immediately position our hidden sprite 100 where the mouse is.
The line OverButton = Sprite Collision(100,0) will always return a 0 unless sprite 100 is colliding with another sprite. This will only happen when the mouse pointer is over one of the buttons.
And, as the value returned and stored in OverButton is the number of the sprite being collided with, we instantly know what button the mouse is over!
The next line gosubs the HandleButton procedure only if OverButton doesn't equal 0 (the mouse is actually over a button).
The HandleButton procedure is essentially the same as the previous examples - you just need to put your own handling routines for whatever your buttons are for.
Embellishment:
With this method there are a number of things you can do. As the buttons are sprites, you can jiggle them about, change the image they use or for a nice effect, scale or stretch them.
Changing the image is pretty much the same as in previous examples - load an alternative set in and just use the Sprite command with the alternative image value - setting it back to the default when the mouse moves off the button. I'll let you experiment with that yourself if that's the method you want to use.
I'll finish off with a version which scales the sprite buttons in a nice way...
Version 7 - Scaled Sprite Buttons
Rem Example 7 - Sprite Buttons With Scaling Mouse-Over
Rem From TDK_Man's Tutorial On Menu Screens
Gosub Setup
Do
Mx=MouseX(): My=MouseY(): Mc=MouseClick()
Sprite 100,Mx,My,100
OverButton = Sprite Collision(100,0)
If OverButton <> 0 Then Gosub HandleButton
Sync
Loop
End
HandleButton:
Rem If we are here then a collision was detected
Select OverButton
Case 1
Rem Button 1
If Mc=1
Center Text 400,580,"You Clicked On Button 1"
End
Else
Gosub ScaleSprite
Endif
EndCase
Case 2
Rem Button 2
If Mc=1
Center Text 400,580,"You Clicked On Button 2"
Else
Gosub ScaleSprite
Endif
EndCase
Case 3
Rem Button 3
If Mc=1
Center Text 400,580,"You Clicked On Button 3"
Else
Gosub ScaleSprite
Endif
EndCase
Case 4
Rem Button 4
If Mc=1
Center Text 400,580,"You Clicked On Button 4"
Else
Gosub ScaleSprite
Endif
EndCase
Case 5
Rem Button 5
If Mc=1
Center Text 400,580,"You Clicked On Button 5"
Else
Gosub ScaleSprite
Endif
EndCase
Case 6
Rem Button 6
If Mc=1
Center Text 400,580,"You Clicked On Button 6"
Else
Gosub ScaleSprite
Endif
EndCase
EndSelect
Return
ScaleSprite:
For N=1 To 10
Scale Sprite OverButton,N+99
Sprite OverButton,ButtonPos(OverButton,0)-N,ButtonPos(OverButton,1),OverButton
Sync
Next N
For N=10 To 1 Step -1
Scale Sprite OverButton,N+99
Sprite OverButton,ButtonPos(OverButton,0)-N,ButtonPos(OverButton,1),OverButton
Sync
Next N
Return
Setup:
Set Display Mode 800,600,32
Sync On: Sync Rate 60
Load Image "Screen4.jpg",10
Load Image "M3B1.jpg",1
Load Image "M3B2.jpg",2
Load Image "M3B3.jpg",3
Load Image "M3B4.jpg",4
Load Image "M3B5.jpg",5
Load Image "M3B6.jpg",6
Set Text Opaque
Rem Dimension an array to store the button positions
Dim ButtonPos(6,1)
Rem Create and place button sprites
SprNum = 1
For Ny=0 To 2
For Nx=0 To 1
Sprite SprNum,Nx*300+130,Ny*100+200,SprNum
ButtonPos(SprNum,0) = Nx*300+130: Rem Store Button's X Position
ButtonPos(SprNum,1) = Ny*100+200: Rem Store Button's Y Position
Inc SprNum
Next Nx
Next Ny
Rem Get Image For Hidden Sprite
CLS 141241: Get Image 100,0,0,2,2
Rem Create Hidden Sprite
Sprite 100,400,300,100
Hide Sprite 100
Ink RGB(255,255,255),0
Paste Image 10,0,0
Sync
Return
You should be able to figure out what this snippet is doing. The only exception possibly is the use of the array ButtonPos() created in the Setup procedure.
Quite simply, it's used to store the X and Y screen positions of each button. Why, I'll cover in a moment. It's dimensioned with:
ButtonPos(6,1)
The 6 means the array has 6 main elements - one for each button (ignoring element 0 for simplicity as there's no button 0). The second sub-element is dimensioned with a 1 - making two indexes - 0 and 1.
We'll use sub-element 0 to hold the X position and 1 to hold the Y position. So:
ButtonPos(3,0) - contains the screen X position of button 3 and
ButtonPos(3,1) - contains the screen Y position of button 3.
These values are stored at the point the six button sprites are created and positioned on the screen.
In the HandleButton procedure, if the mouse is over a button, but the mouse button isn't pressed, we now gosub a new procedure - ScaleSprite.
In ScaleSprite, we have a couple of small If...Then loops - one to scale the current sprite up and the other to scale it back down to it's original size. And this is where the array comes in...
When you scale a sprite, it is scaled with the top left corner locked into position - making it stretch down and to the right. To make it look right for our menu screen, it would look much nicer if it scaled with the centre of the button locked in place. To give this impression, you simply need to move the sprite up and to the left as it is scaled.
This is why each button's X and Y position is required and storing them in an array allows us to access the correct X and Y values by using the OverButton variable - which always contains the number of the button the mouse is over.
Adding Menus To Your Programs
Actually writing the code to add menu screens to your programs is quite straight forward - provided your program has a well-structured layout.
The menu is best placed in it's own procedure and should be contained within a Repeat..Until loop. So, if you need a main menu, create a procedure called Main_Menu and use something like the following pseudo-code:
Main_Menu:
Rem Main Menu Code Section
Paste Image 1,0,0: Rem Main menu image loaded earlier
MMExitFlag=0
Repeat
Rem Use required button testing method from tutorial here
If Button 1 clicked
Do whatever for button 1
Endif
If Button 2 clicked
Do whatever for button 2
Endif
Rem And so on for all buttons
If Exit Button clicked Then End
If Play Game Button clicked
Re-Initialse game variables for game restart
MMExitFlag=1
Endif
Until MMExitFlag=1
Return
Important: MMExitFlag MUST be set to 0 before dropping into the Repeat..Until loop. If you don't, on the very next visit to the main menu, the flag will still equal 1 and the Repeat..Until loop will immediately be exited.
In your program, you would call the Main_Menu procedure at the start of your program before dropping into your main program loop:
Gosub Setup
Gosub Main_Menu
Rem Main Program Loop
Do
Rem Gosub Game Procedures Here
If Player Loses All Lives Then Gosub Main_Menu
Sync
Loop
End
Rem ***** All Procedures Here *****
Main_Menu:
Rem Above Main_Menu procedure here
Return
Setup:
Rem Initialise all program and variables here
Return
Rem ***** All Functions Here *****
When the player clicks on the Play Game button, the exit flag is set to 1 and the loop is exited - dropping back into the main program loop and the game starts.
Multiple Menu Screens
New users often get confused when implementing multiple menu screens. But, if you use the program layout defined above, then adding more menu screens is a doddle!
OK, say your main menu screen has an Options button.
All you need to do is create a new procedure called Options_Menu with exactly the same layout as Main_Menu. (You can copy/paste the Main_Menu procedure and edit it if you like).
Once you have copied it, the first thing you need to do is change all occurences of the exit flag variable name in the new procedure. With the main menu it was called MMExitFlag so for the options menu it can be OMExitFlag.
Note: If you don't do this, and use the same variable name for all menu screens, when you return to the main menu from a sub-menu, the exit from the first menu will trigger the exit from the second.
Next, change the menu screen image number you paste and alter the blocks of code which check which buttons are clicked - so they suit the Options menu screen.
This time, there will be a 'Back' or 'Previous Menu' button and it is this button which sets the exit flag to 1 - at which point, the program drops back to the previous menu - Main Menu. All you have to do is paste the main menu image on the screen
before dropping out of the Options Menu procedure.
Adding yet more sub-menus is easily done by following the same procedure as many times as you require, so your Main Menu screen can have as many sub-menus running off it as you want. Your main program code would therefore look something like:
Gosub Setup
Gosub Main_Menu
Rem Main Program Loop
Do
Rem Gosub Game Procedures Here
If Player Loses All Lives Then Gosub Main_Menu
Sync
Loop
End
Rem ***** All Procedures Here *****
Options_Menu:
Rem Options Menu Code Section
Paste Image 2,0,0: Rem Options menu image loaded earlier
OMExitFlag=0
Repeat
Rem Use required button testing method from tutorial here
If Button 1 clicked
Do whatever for button 1
Endif
If Button 2 clicked
Do whatever for button 2
Endif
Rem And so on for all buttons...
If Back Button clicked
Paste Image 1,0,0: Rem Paste Main Menu Image To Screen
OMExitFlag=1
Endif
Until OMExitFlag=1
Return
Main_Menu:
Rem Main Menu Code Section
Paste Image 1,0,0: Rem Main menu image loaded earlier
MMExitFlag=0
Repeat
Rem Use required button testing method from tutorial here
If Button 1 clicked
Do whatever for button 1
Endif
Rem And so on for all buttons...
If Options button clicked
Gosub Options_Menu
Endif
If Exit Button clicked Then End
If Play Game Button clicked
Re-Initialse game variables for game restart
MMExitFlag=1
Endif
Until MMExitFlag=1
Return
Setup:
Rem Initialise all program and variables here
Return
Rem ***** All Functions Here *****
OK, that's the end of yet another epic tutorial and I hope it's made the subject of making in-game menus a little clearer.
The only problem now is deciding which of the three methods to use...
There are many more tutorials and example code snippets on TDKMan.com - my DBC game programming site. Click on the link below - it's free to join and everyone's welcome!
TDK_Man