Animation (Sprites)

[Home]   [Puzzles & Projects]    [Delphi Techniques]   [Math topics]   [Library]   [Utilities]

 

 

Search

Search WWW

Search DelphiForFun.org

As of October, 2016, Embarcadero is offering a free release of Delphi (Delphi 10.1 Berlin Starter Edition ).     There are a few restrictions, but it is a welcome step toward making more programmers aware of the joys of Delphi.  They do say "Offer may be withdrawn at any time", so don't delay if you want to check it out.  Please use the feedback link to let me know if the link stops working.

 

Support DFF - Shop

 If you shop at Amazon anyway,  consider using this link. 

     

We receive a few cents from each purchase.  Thanks

 


Support DFF - Donate

 If you benefit from the website,  in terms of knowledge, entertainment value, or something otherwise useful, consider making a donation via PayPal  to help defray the costs.  (No PayPal account necessary to donate via credit card.)  Transaction is secure.

Mensa® Daily Puzzlers

For over 15 years Mensa Page-A-Day calendars have provided several puzzles a year for my programming pleasure.  Coding "solvers" is most fun, but many programs also allow user solving, convenient for "fill in the blanks" type.  Below are Amazon  links to the two most recent years.

Mensa® 365 Puzzlers  Calendar 2017

Mensa® 365 Puzzlers Calendar 2018

(Hint: If you can wait, current year calendars are usually on sale in January.)

Contact

Feedback:  Send an e-mail with your comments about this program (or anything else).

Search DelphiForFun.org only

 

 

 

Writing code to move things around on screen  always seems more difficult than it should be.    Windows creates the illusion that displayed objects  are  layered with closer things sitting on top of more distant things.  We "grab" and "drag" on-screen objects every day.   Objectively, we know that there is only one layer of pixels on the screen and making something "move" around must require  some behind the scene tricks.  And that's the topic of this page. 

 I do not claim much expertise in this area.  I do like to experiment and animation provides lots of opportunities for that.   

Making objects move on the screen is similar to making one of those "Claymation" movies created by taking a series of still pictures, moving  the figure a small amount between shots.   All of  the techniques tried here use the same loop for the animation:  
bulletCompute new locations for the sprites. 
bulletErase the sprites at their old locations. 
bulletDraw the new sprites at their new locations.   

The programs below include some ellipses and letters that float randomly around the screen.  I don't have an "official" definition for the term "sprite" - they definitely move about the screen and  are usually non-rectangular.  Must they include multiple images to imitate some internal motion (walking,  flying, etc.)?    Not sure.  Just to be safe, I've included  a "walking man" sprite that uses six different images to give the illusion that his legs are moving.    

Masking and Transparency

If we try drawing sprites without using masking,  the visual aspect of all of your sprites had better be rectangles.  Windows drawing routines only know rectangles, so the trick is to let the background show through those areas of of our rectangular sprite that are not supposed to be there.    Masking is the technique that accomplishes this.   Delphi has simplified masking by including the mechanics into several of it's visual controls, so if you find a Draw method and a Transparent Color property, you're in business.    I may do a "behind the scenes look"  at this magic trick one of these days.   (Sept 11:  Done! See Masking Unmasked! for more information.)

Drawing techniques

In these programs I tried  combinations  of several variables to draw each frame:

 
bulletDrawing surface - Timage or TPaintBox
bulletBuild each new frame on-screen or in an off-screen work bitmap.
bulletImage storage - Array of TBitmaps  or TImageList
bulletRedraw strategy - 
bulletRedraw full background and redraw all sprites 
bulletRedraw  the background only over the rectangles that contain sprites in their previous location, then redraw sprites in their new locations.
bulletBuild a large enough rectangle to enclose old and new sprite locations and redraw that.

The Programs

There are five source programs are available  below for downloading.    They are all similar with variations of the parameters described above.  
bulletSprites1 uses the simplest possible approach - for each "frame" we  redraw the entire background on a TImage component and then redraw the individual sprites.  Information is held in a TSpritesRec record that contains the bitmap of the image together with information about where it is now and where it will be displayed next. 
bulletSprites2 speeds drawing somewhat by copying only pieces of the background over old sprites to erase them and then copying the sprite rectangles in new positions.  
bulletSprites3 uses Sprites2 strategy but moves the sprite image information to a TImageList and uses the imagelist Draw method to put the sprite in the image canvas.  These first three programs all include a "Double Buffer" checkbox to turn on the form's DoubleBuffer  option.   In theory, drawing directly on the TImage canvas should cause an annoying flicker as it is redrawn.   In Delphi version 6 it does, in D5 it does not.  There must be some code in D5's TImage prevents it.  I guess it was a "bug" that Borland "fixed" in D6.      
bulletSprites4 switches the drawing surface from TImage to a TPaintBox control.  Now double buffering is definitely required. We will accomplish this by creating a  TBitmap, Work,  to hold the image as it being built. Once built we can use it to replace the currently displayed image without visible flicker.    In this version, I used the simplest strategy described above - copy the background to the work image, draw the new sprites on the work canvas, copy the work canvas to the paintbox canvas. 
bulletSprites5 is the fastest and takes us one step further.  Now we'll replace only the rectangles that contain the old sprite images with corresponding background  rectangles, then draw the sprites in their new location on the Work bitmap.  Now, since we cleverly saved the old image rectangle and the new image rectangle, we can define a new super rectangle that just encloses both Prevrect and NewRect and use that to copy pieces of the work canvas to the onscreen Paintbox canvas.  Especially when the number of sprites is small, this is very fast.   There is a Sprites6 not posted here that uses a TImageList to hold the sprites.  Sprite5 uses arrays to hold the sprite bitmaps and is slightly faster than Sprites6.    

Performance

I measured the  maximum measured frame rates for the five programs with 2 and 14 sprites displayed and on three of my computers.  Results are shown in the table below.  Upstairs is an 800mhz Celeron system with a 32mb AGP video car driving a monitor at 1024X768 resolution.  Downstairs is a 433mhz Celeron with a 2X AGP video card and driving 800X600 resolution,  Basement is a 300mhz AMD K6 with a S3 PCI video card driving a 100 year old 640X480 Dell VGA monitor.   The two numbers in each box are the maximum observed frames per second with  2 sprites and  14 sprites running.

  Sprites1 Sprites2 Sprites3 Sprites4 Sprites5
Upstairs 60,54 212,103 256,166 258,166 1662,322
Downstairs 68,45 94,77 130,77 148,88 682,114
Basement 67,34 245,48 222,91 324,108 518,70

 Frames per second for several programs and systems
 (2 sprites, 14 sprites)

 

Pixelformat and other anomalies

While taking these measurements, I noticed a few anomalies that deserve more study.  The Aopen PA3000 video card driver on Upstairs seems not very well behaved  for some operations,  Sprites3 and Sprites4 both start out with very high calculated frame rates even though the sprites are not moving exceptionally fast,  as if  the card is caching the requests in its own memory and returning to the program before the operation is complete.   Once that cache is full, the program becomes very non-responsive to button clicks (until the cache catches up I assume).    Further information - the problem is related to the pixel format of the background image.  When loaded from the resource file,  the bitmap type is DDB (device dependent bitmap) and the Pixelformat property is set to pfDevice.   When I change Pixelformat to the current screen Pixelformat, the response problems disappear, but frame rates  are somewhat slower.  Opting for usability over speed, I am setting Pixelformat to match the screen color depth in all five programs.   Comment out the statement    bg.pixelformat:=pixelformat;; in the FormCreate method to see the effect on your system.   

Also, on the old  Basement system,  the full redraw TPaintBox program, (Sprites4) outruns partial redraw TPaintBox program (Sprites5) for the 14 sprites case.  This probably reflects the relative power of the VGA card in drawing over the AMD processor in recalculating which pieces to redraw.   

Additional Features    

There are a number of other techniques used here that I may want to reference one day.  I'll mention them here so that Google can index them for me.  

bullet

Adding bitmaps to a resource and  loading bitmaps from a resource, 

bullet

 Using OnResize exit to adjust displayed  image sizes,

bullet

Controlling the speed of an animated sprite.  The walking man moves much too fast if images are changed for each frame,  I defined a ManRest variable to control how many frames pass before the  man image changes.

bullet

Getting the bits per pixel for the current display screen using GetDC and GetDeviceContext  API calls,  (See demo program Sprites3)

bullet

Use of QueryPerformanceCounter and QueryPerformanceFrequency API procedures to calculate frame rates.

bullet

The Tag property is used as a "Stop" flag to tell the animation to quit.  

bullet

Using OnCloseQuery exit to set the "Stop" flag to allow graceful program exits when the animation is running. 

bullet

Using the Sleep procedure to control animation speed. 

bullet

Saving a bitmap image to disk with a unique name. (Commented in Sprites1 after use to make the image at the top of this page.)
 image1.picture.bitmap.savetofile(extractfilepath(application.exename)
+ 'Spritepic'+ inttostr(trunc(now*secsperday)) + '.bmp');

Downloads  

Download source code for  programs Sprites1 - Sprites5 

Download bitmaps and files to build a resource.    (Not required to compile or run the programs above.  The image resource file, Sprites.res,  is included with the source download.)   

Areas for Further Exploration

I included a separate  speed control for the little man and spent too much time trying to keep him from getting hit by the flying sprites.   It could a good first game.  With a little more work, the guy could back up or jump.  And we would need some collision detection.  Difficulty levels could be controlled by the number and speed of the "attack" sprites.  Scoring could be based on time between hits (plus points) and hits (minus points).  

The letters included spell a word - it would be cool to have the letters gradually come to a halt forming the word across the center of the display after a minute or so.        

     

Created: September 8, 2002

Modified: May 15, 2018

 

  [Feedback]   [Newsletters (subscribe/view)] [About me]
Copyright © 2000-2018, Gary Darby    All rights reserved.