Create Your Own Animated Sprite Screen Saver
Its easy to create a screen saver with Visual Basic. All you need is a few forms
and a little imagination. Here I have created a screen saver that displays
several sprites which move around the screen glancing off its edges and each
other.
Once you have a Windows screen saver enabled, your Visual Basic application can
Instruct Windows to Run the Screen Saver.
Also discussed is how to work with Animated Sprites.
This part can get a little tricky and uses memory device
contexts and many of Window's GDI functions such as
BitBlk (Bit-Block Transfer), StretchBlt,
CreateCompatibleDC, CreateCompatibleBitmap, etc. As a side effect,
this program shows how to create transparent bitmaps. You may want to see my
Create a Transparent Bitmap sample for a more thorough discussion of
that topic.
First I will discuss screen saver basics then I will talk about sprites.
Download Source Code
Note: This is still a VB executable and requires the Visual Basic runtime
files. To use your screen saver on another PC, create installation disks and
install it on the target PC.
The screen saver program and its executable (.Scr file) were developed in VB6.
If you have an earlier version of VB, just open the program and recompile it.
What's Needed for a Screen Saver
|
-
A Main form to act as the screen saver itself -- let your imagination be your
guide.
-
A Configuration or settings form to let the user customize your screen saver.
The configuration form must save its settings either in an .Ini file or the
registry. I used the registry since you need to read a value from there anyway.
-
A Change Password form. Windows screen savers use ScrnSave.Lib to manage
passwords. However, this is unavailable to VB. You will need to display your
own dialog and save the password somewhere. Again, I use the registry. (I used
my Registry.bas module to read and write the
registry. It contains all the necessary functionality).
-
A Password Entry dialog. When the user attempts to disable the screen saver you
need to prompt them for a password.
I modeled all my password forms and message boxes to look exactly like Windows.
Right down to their size, shape and message text.
Here are the basics of what your screen saver should do. See the source code for
details.
Your Main form should have the following properties:
BorderStyle
|
None
|
Caption
|
""
|
ControlBox
|
False
|
Icon
|
None
|
ShowInTaskBar
|
False
|
WindowState
|
Maximized
|
The MouseDown, MouseMove, Click, Double Click, KeyPress
and KeyDown events must End the program.
Because the MouseMove event fires when the form is maximized, you must write
code to handle this. Otherwise on the initial load your screen saver will exit.
For example:
Private Sub Form_MouseMove(Button As Integer,
Shift As Integer, x As Single, Y As Single)
Static iCount As Long
If iCount > 2 Then
End
Else
iCount = iCount + 1
End If
End Sub
Use Sub Main to start your program.
Since when previewing your screen saver you must Load your main form then make
it a child of the Preview window prior to showing it.
Parse command line parameters.
Windows passes command switches to the screen saver to tell it what to do. The
following switches are used:
When You:
|
Windows Passes:
|
You Should:
|
Select a screen saver from the drop down
|
/p <hwnd>
|
Run your screen saver in the Preview window.
|
Click the Preview button
|
/s
|
Run your screen saver normally.
|
Stop previewing the screen saver
|
/p <hwnd>
|
Run your screen saver in the Preview window.>
|
Click the Settings button
|
/c:<hwnd>
|
Show your configuration dialog.
|
Close your Configuration form
|
/p <hwnd>>
|
Run your screen saver in the Preview window.>
|
Click the Change Password button
|
/a <hwnd>
|
Show your change password screen.
|
Click the Apply button
|
/p <hwnd>
|
Run your screen saver in the Preview window.
|
Pick a screen saver and leave the PC idle
|
/s
|
Run your screen saver normally.
|
Where <hwnd> is the handle of the Preview window. The Preview
window is the small window on the Screen Saver tab of the Display Properties
applet.
Run your screen saver in the small PreviewWindow.
Here is the cool part. When Windows sends a "/p<hwnd>" to your screen
saver it needs to run in the Preview window. To do this you must know the
preview window's size. Calling GetClientRect with
the Preview window's handle (the <hwnd> value Windows passed you)
populates a RECT structure with the window's
dimensions.
You then have to set the Preview window to be the parent of your main form. A
call to SetWindowLong with the GWL_STYLE
command retrieves your form's current window style. This must be done after you
Load your form. By OR-ing the style with the WS_CHILD
flag and calling SetWindowLong with the new style
your form is converted a child window.
Now you can set its parent with SetParent passing
it the handle of the Preview window. One last call to SetWindowLong
with the GWL_HWNDPARENT flag fills your form's
window structure with the handle of the Preview window. Your form is now a
child of the Preview window.
To show your form, call SetWindowPos passing it the
dimensions of the Preview window. When your screen saver is displayed, it will
appear within the boundaries of the Preview window.
Windows will automatically shut down your program when you close the Display
Properties dialog or select another screen saver.
Prevent multiple instances of your screen saver - sometimes.
When your PC is idle for the specified time period, Windows launches the screen
saver continually passing it the "/s" switch. Your program must check to see if
it is already running and, if so, terminate.
When the Display Properties dialog is shown and you click the Preview button,
Windows also starts your screen saver with the "/s" switch. This time, however,
an instance of your screen saver will already be running in the Preview Window.
You need a second instance to run full screen. A call to
FindWindow looking for a window with the title "Display Properties"
will distinguish between these two scenarios.
Public Sub Main()
. . .
sOption = Left$(Command, 2)
lHwnd = FindWindow(vbNullString, "Display Properties")
If App.PrevInstance And sOption = "/s" And lHwnd = 0 Then
End
Else
frmMain.show
End If
End Sub
Read the registry to see if password protection is enabled.
The HKCU\Control Panel\Desktop\ScreenSaveUsePassword value indicates if the
screen saver is password protected. You can use my Registry.bas
module to read and write the registry.
Display the Password form modally.
So users are forced to enter a password.
About Passwords.....
Screen savers written in C use the SCRNSAV.lib to process passwords. You cannot
call this from VB. However on Windows 9x you can use 2 undocumented functions:
Declare Sub PwdChangePassword Lib "mpr.dll" Alias
"PwdChangePasswordA" _
(ByVal lpcRegkeyname As String, ByVal hwnd As Long, ByVal uiReserved _
As Long)
Declare Function VerifyScreenSavePwd Lib
"password.cpl" (ByVal hwnd As Long) As Boolean
To change the password:
Private Sub cmdChange_Click()
Call PwdChangePassword("SCRSAVE",
Me.hwnd, 0, 0)
End Sub
Call VerifyScreenSavePwd on mouse or keyboard
activity:
Private Sub cmdTest_Click()
Dim bRes As Boolean
bRes = VerifyScreenSavePwd (Me.hwnd)
MsgBox bRes
End Sub
Disable Ctl-Alt-Delete and Alt-Tab task switching.
So users cannot switch to another application or kill the screen saver if it is
password protected.
Call SystemParametersInfo(SPI_SCREENSAVERRUNNING, True, lPrev, 0)
Make your screen saver always on top.
Set your main form to be always on top using:
Call SetWindowPos(frmMain.hWnd, HWND_TOPMOST, 0&, 0&, 0, 0, SWP_NOSIZE)
Hide the cursor.
Using the ShowCursor API so when the screen saver
is displayed you do not see it.
WARNING:
Disabling Ctl-Alt-Delete, making your screen saver the top most window which
covers the entire desktop and hiding the cursor makes debugging
impossible. If you lock up during development you will not be able to
kill your program with out pressing the reset button on your PC! I recommend
commenting out these features during development.
Set the Title of your application.
Go to Project | Properties | Make. Make sure the title begins with SCRNSAVE:
(the colon is required). For example, I used SCRNSAVE:TheScarms.
Compile your application with an extension of .SCR.
Control Panel looks in the Windows and Windows\System folders for *.SCR.
It also checks the header of these files for the SCRNSAVE: code.
Copy your .Scr file to the Windows folder.
Starting a Screen Saver from Visual Basic
|
You can start a screen saver from VB application (provided, of course, you have
a screen saver enabled).
Const WM_SYSCOMMAND = &H112&
Const SC_SCREENSAVE = &HF140&
Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long
Call SendMessage(frmMain.hWnd, WM_SYSCOMMAND, SC_SCREENSAVE, 0&)
What is an Animated Sprite?
|
A Sprite is a picture with an irregular shape,
possibly with transparent holes in it, that can be moved left, right, up, and
down on the screen, and that has depth, which is called z-order. So a sprite is
a picture with x, y, and z coordinates.
A sprite can be a set of images combined vertically or horizontally into a
single bitmap. You can then load the image and tell the sprite how many
frames or phases to
divide the image into. As the sprite moves, it changes the current phase
of the image. In this way, you can create moving images such as the
animated Gifs so popular on the web.
When moving a sprite, the location just vacated by the sprite is typically reset
back to the original image. As an added effect, this location can be left
to display the old sprite. The result is that the sprite leaves a trail
or tracer behind it showing the path it moved along.
Sprite Images.
The images for my sprite are stored as one bitmap. I created the
individual images and pasted them together vertically into one bitmap
file. Try to keep your bitmap small by using as few colors as you can and
storing it at a low resolution.
Once the bitmap is created it is stored in a Resource (.Res) file.
The Resource Editor is available as a Visual Basic 6.0 Add-In. Under
earlier versions of VB you have to use the resource compiler.
To create multiple copies of the sprite, it is implemented as a class.
Then, it can be instantiated as many times as desired. Using properties
of the class for the sprite's characteristics means each sprite is self
contained and can take on a different size, move with a different speed,
etc.
Sprites, or their handles, are stored in an array. Each sprite can then
be positioned by looping through the array and calling the sprite's move
method. A timer is used to continually invoke the sprites' move
method. In an ideal world, each sprite would reside on its own thread and
there would be another thread to handle the screen repaints...
Customizing Sprites.
The screen saver's configuration screen allows the user to select the number of
sprites, their size, speed, timer interval (refresh rate) and whether or not to
use tracers.
Displaying the Sprites.
The first thing the screen saver does is capture the current desktop and store
it in memory as a bitmap. Your sprite is then painted on this bitmap and
the bitmap is redisplayed on your main form. Your main form is sized
either to cover the entire desktop or to fit within the preview window.
In the later case, the desktop bitmap and your sprite must be resized
accordingly. Thus, we need a method to resize bitmaps.
When the timer fires your sprite is moved according the algorithm you
choose. Moving the sprite means determining its current position,
calculating its new position, getting the next sprite frame, drawing the sprite
in the new location on the bitmap, restoring the area the sprite moved from to
the original desktop image, and repainting the bitmap on your form.
As the number of sprites increases, more processing is required. In this
sample I allow each sprite to have a different size, speed, and refresh
rate. As you probably guessed, this must be done as efficiently as
possible if you want any semblance of animation. As a result, the
Window's GDI (Graphic Device Interface) functions are used extensively.
This section talks about each of the main routines used to manipulate and
display the sprites. See the source code for more details. Here are
the .bas module routines.
pInitDesktop
This procedure creates and returns a bitmap that looks like the current desktop
but that is stretched or compressed to a pre-defined width and height as
specified by its input parameter. The steps involved are:
-
Get a handle to the desktop window using the GetDesktopWindow
API. Use this handle to get the desktop window's Device
Context (DC) via GetWindowDC and write
the window's dimension into a RECT structure using
GetWindowRect.
-
Using CreateCompatibleBitmap, a desktop window
compatible bitmap is created based on the desktop window's device context and
dimensions.
-
Fill the output bitmap's structure with the width, height and color information
of the newly created compatible bitmap with a call to the GetObject
API function.
-
Create a memory device context compatible with the desktop window DC with a
call to CreateCompatibleDC. The memory DC's
display surface is one monochrome pixel wide and one monochrome pixel high.
-
The desktop compatible bitmap is copied into the new memory DC using
SelectObject.
-
Finally, using the StretchBlt function the desktop
compatible bitmap is copied to the output DC stretching or compressing it as
required by the specified dimensions.
pDrawTransparentBitmap
This procedure copies the foreground image of the sprite onto a bitmap
representing the desktop. The background of the sprite is removed
allowing the underlying desktop image to show through. Basically it masks
out the background of the sprite, masks out the portion of the bitmap where the
sprite's foreground will be placed, and merges the two images together.
The heart of this routine is the BitBlk (Bit-Block
transfer) function. For a more detailed explanation of this routine see
the source code or my Create a Transparent Bitmap
sample.
fShrinkBmp
Shrink bitmap takes a bitmap and scales it vertically and horizontally and
returns a handle to the new smaller bitmap.
It does this by creating two memory device contexts, compatible with the DC of
the original bitmap, via CreateCompatibleDC.
Attributes of the bitmap are retrieved using the GetObject
function and stored in a Bitmap structure. The structure is copied to a
second structure whose the bitmap dimensions are modified according to the x
and y percentages passed in. Based on this modified information, a new
bitmap is created by calling the CreateBitmapIndirect
API.
The new and original bitmaps are selected into the memory DCs with calls to
SelectObject. Using handles to these DCs, the
StretchBlt API copies the original bitmap to the new bitmap
compressing it to its new dimensions.
CreateSprite
As its name implies, this method creates a new sprite, initializes its
properties and returns its handle. For easy access the characteristics of
the sprite are implemented as properties (well, public variables of the
class).
The CreateSprite method performs three main functions:
-
Loads the bitmap image from the resource file if it is not already loaded.
-
Scales the sprite to the proper dimensions via a call to fShrinkBmp. The
dimensions to scale the sprite to are calculated based on the sprite size
option selected by the user on the configuration screen.
-
Calculates the width, height, number of vertical and horizontal frames, first
frame to display and other properties of the sprite.
CollisionTest
This method examines the coordinates of two sprites to see if they overlap and
returns true if they do.
ResolveCollision
Called when 2 sprites collide, this method moves them until the collision is
resolved. The logic to determine how to move the sprites relies on a bit
of physics.
UpdatePosition
Updates the x and y position of a sprite reversing its direction when it hits a
border.
DrawNext
This routine performs the bulk of the work when moving a sprite to a new
location. It first gets the location of the next sprite frame to display
from the bitmap. Then it draws the sprite frame in the new location via a
call to pDrawTransparentBitmap.
If the user did not select the "Use Tracers" option, DrawNext calculates the
area just vacated by the sprite. This area must be reset to the image
originally displayed in that location. Again, this is done with a call to
pDrawTransparentBitmap. This time passing it the handle to the device
context of the original image.
AutoMove
This method calls DrawNext to draw the sprite in a new position. It then
calls ResolveCollision to see if the sprite has collided with another sprite
and subsequently calls UpdatePosition if it has.
Download the sample and copy the .SCR file to your Windows folder. Right click
on the desktop, select properties, pick the screen saver tab and choose
TheScarms Screen Saver from the screen saver drop down.
Click the Preview button to see a demo. Click Settings to display the
configuration dialog. Finally, check the Password Protected box and click
on Change to bring up the change password dialog.
|