I wrote 2 codes to show the flickering effect and the solution to solve it following what molly said:
a) create a global bitmap (don't forget to assign it the proper dimensions)
b) whenever you need to draw your graphics you do that on the bitmap.canvas instead.
c) Whenever you want your graphics displayed on the Form you then copy the contents of that bitmap to your forms's canvas.
d) when your app is done, don't forget to free the bitmap again
Flickering and performance are the most basic things a game programmer need to pay attention about. Flickering issue can be solved easily.
For simple games (like snake), flickering rarely happens. It will become noticeable when lots of objects needed to paint on the screen. Below is the common things that may happen when running a game:
repeat
clear the screen
paint object #1
paint object #2
...
paint object #1000
endIf only few objects needed to paint on the screen (for example only 10 objects), flickering issue almost never happen. The time between clearing the screen and painting the last object is the key factor, the longer it is the more noticeable flickering effect. It is because the monitor need time to perform the update/refresh.
The solution is simple: "
don't paint every single object on the screen directly".
We paint all the objects on a memory buffer, and then we update the screen using a single command to show (or copy) the memory buffer to the screen.
TBitmap is good to use as the memory buffer for this case.
My Demo1 shows flickering on my Core2 Quad GeForce 430 Dell monitor if I set the
TotalParticles >= 1000. On different hardware, the value may be lower or higher.
The code of Demo2 is basically the same as Demo1, but it does not paint the screen directly so there will no noticeable flickering (I tried to set TotalParticles = 5000).
The differences between the 2 codes are:
- The declaration of variable "TempOutput: TBitmap;"
- Preparing TempOutput on FormCreate
- Freeing TempOutput on FormDestroy
- The procedure DrawGameWorld
procedure TForm1.DrawGameWorld;
const
Padding = 2;
var
X, Y: Integer;
ScreenX, ScreenY: Integer;
begin
// Clear the temporary output
TempOutput.Canvas.Brush.Color := clWhite;
TempOutput.Canvas.FillRect(0, 0, Width, Height);
// Draw on the canvas of the temporary output
for X := 1 to WorldWidth do
for Y := 1 to WorldHeight do
begin
ScreenX := (X-1) * Scale;
ScreenY := (Y-1) * Scale;
TempOutput.Canvas.Brush.Color := GameWorld[X,Y].Color;
case GameWorld[X, Y].Item of
Empty: ; // do nothing
Box: TempOutput.Canvas.Rectangle(ScreenX, ScreenY, ScreenX+Scale-Padding, ScreenY+Scale-Padding);
Circle: TempOutput.Canvas.Ellipse(ScreenX, ScreenY, ScreenX+Scale-Padding, ScreenY+Scale-Padding);
end;
end;
// Draw the temporary output to the active form's canvas
Canvas.Draw(0, 0, TempOutput);
end;