mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-08-13 13:59:07 +02:00
started initial animation explanation
This commit is contained in:
parent
902514047d
commit
ad353b52cd
362
docs/gtk5.tex
362
docs/gtk5.tex
@ -171,29 +171,36 @@ functions for points, lines, segments, rectangles, polygons, circles, text
|
|||||||
and bitmaps.
|
and bitmaps.
|
||||||
|
|
||||||
All functions accept as the first two parameters a \var{PGDKdrawable}, which
|
All functions accept as the first two parameters a \var{PGDKdrawable}, which
|
||||||
can be a \var{TGDKWindow} or \var{TGDkPixmap}, and a \var{PGdkGC}, a pointer
|
can be a pointer to a \var{TGDKWindow} or a \var{TGDkPixmap}, and a
|
||||||
to a graphics context. These parameters are omitted from the following
|
\var{PGdkGC}, a pointer to a graphics context.
|
||||||
declarations:
|
|
||||||
|
These parameters are omitted from the following declarations:
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
procedure gdk_draw_point(x,y:gint);
|
procedure gdk_draw_point(x,y:gint);
|
||||||
procedure gdk_draw_line(x1,y1,x2,y2:gint);
|
procedure gdk_draw_line(x1,y1,x2,y2:gint);
|
||||||
procedure gdk_draw_rectangle(filled,x,y,width,height:gint);
|
procedure gdk_draw_rectangle(filled,x,y,width,height:gint);
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
|
The above functions draw respectively a dot, a line and a rectangle.
|
||||||
The meaning of the parameters for these functions is obvious.
|
The meaning of the parameters for these functions is obvious.
|
||||||
For the rectangle, care must be taken. If \var{Filled} is false (-1) then
|
For the rectangle, care must be taken. If the parameter \var{Filled} is
|
||||||
the drawn rectangle is actually \var{Width+1}, \var{Height+1}. If it is
|
False (-1) then the drawn rectangle has actually a width and height of
|
||||||
filled, then the width are height are as specified.
|
\var{Width+1}, \var{Height+1}. If it is filled, then the width and
|
||||||
|
height are as specified in the call to \var{gdk\_draw\_rectangle}.
|
||||||
|
|
||||||
The following functions can be used to draw a series of lines:
|
The following functions can be used to draw a series of lines:
|
||||||
\begin{verbatim}
|
\begin{verbatim}
|
||||||
procedure gdk_draw_polygon(filled:gint;points:PGdkPoint; npoints:gint);
|
procedure gdk_draw_polygon(filled:gint;points:PGdkPoint; npoints:gint);
|
||||||
procedure gdk_draw_segments(segs:PGdkSegment; nsegs:gint);
|
|
||||||
procedure gdk_draw_lines(points:PGdkPoint; npoints:gint);
|
procedure gdk_draw_lines(points:PGdkPoint; npoints:gint);
|
||||||
|
procedure gdk_draw_segments(segs:PGdkSegment; nsegs:gint);
|
||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
The \var{gdk\_draw\_polygon} polygon takes a series of dots and connects
|
The \var{gdk\_draw\_polygon} polygon takes a series of dots and connects
|
||||||
them using lines, optionally filling them. A \var{TGDKPoint} record contains
|
them using lines, optionally filling them. The points are specified by a
|
||||||
2 fields \var{X,Y} which specify the location of a point. If needed, the
|
pointer to an array of \var{TGDKPoint} records (there should be \var{npoint}
|
||||||
first and last points are also connected using a line.
|
such records in the array).
|
||||||
|
A \var{TGDKPoint} record contains 2 fields: \var{X,Y} which specify the
|
||||||
|
location of a point.
|
||||||
|
If needed, the first and last points are also connected using a line.
|
||||||
|
|
||||||
The \var{gdk\_draw\_lines} does the same, only it cannot be filled, and it
|
The \var{gdk\_draw\_lines} does the same, only it cannot be filled, and it
|
||||||
will not connect the first and last points.
|
will not connect the first and last points.
|
||||||
The \var{gdk\_draw\_segments} requires a series of \var{TGDKSegment}
|
The \var{gdk\_draw\_segments} requires a series of \var{TGDKSegment}
|
||||||
@ -225,6 +232,13 @@ procedure gdk_draw_string(drawable:PGdkDrawable; font:PGdkFont; gc:PGdkGC;
|
|||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
The meaning of the parameters for this functions should be obvious.
|
The meaning of the parameters for this functions should be obvious.
|
||||||
|
|
||||||
|
The font for the \var{gdk\_draw\_string} can be obtained using the
|
||||||
|
\var{gdk\_font\_load} function:
|
||||||
|
\begin{verbatim}
|
||||||
|
function gdk_font_load(font_name:Pgchar):PGdkFont;
|
||||||
|
\end{verbatim}
|
||||||
|
The font name should be specified as an X font path.
|
||||||
|
|
||||||
All this is demonstrated in the following program:
|
All this is demonstrated in the following program:
|
||||||
\begin{lstlisting}{}
|
\begin{lstlisting}{}
|
||||||
program graphics;
|
program graphics;
|
||||||
@ -357,12 +371,338 @@ and adding a \var{TGTKDrawingArea} to it. It then connects 2 event handlers,
|
|||||||
one to stop the application if the window is closed (\var{CloseApp}),
|
one to stop the application if the window is closed (\var{CloseApp}),
|
||||||
the other to draw the \var{TGTKDrawingArea} when it is exposed
|
the other to draw the \var{TGTKDrawingArea} when it is exposed
|
||||||
(\var{Exposed}). This latter contains the actual drawing routines, and is
|
(\var{Exposed}). This latter contains the actual drawing routines, and is
|
||||||
pretty self-explaining.
|
pretty self-explaining. It simply demonstrates the use of the drawing
|
||||||
|
primitives explained above.
|
||||||
|
|
||||||
Note that the allocated colors are not freed again, so this program does
|
Note that the allocated colors are not freed again, so this program does
|
||||||
contain a memory leak.
|
contain a memory leak.
|
||||||
|
|
||||||
\section{Animation}
|
\section{Animation}
|
||||||
|
The GDK drawing functions can be used to draw directly on a window visible
|
||||||
|
on the screen. This is OK for normal applications, but applications that
|
||||||
|
have a lot of (changing) graphics will soon see a flickering screen.
|
||||||
|
|
||||||
|
Luckily, GDK provides a means to cope with this: Instead of drawing directly
|
||||||
|
on the screen, one can draw on a bitmap which exists in memory, and copy
|
||||||
|
parts of the bitmap to the screen on an as-need basis.
|
||||||
|
|
||||||
|
This is the reason why the GDK drawing functions generally accept a
|
||||||
|
\var{PGDKdrawable} parameter: This can be of the type \var{PgdkWindow} or
|
||||||
|
\var{PGDKPixmap}: The \var{TGDKPixmap} can be used to do the drawing in the
|
||||||
|
background, and then copy the pixmap to the actual window.
|
||||||
|
|
||||||
|
This technique, known as double buffering, will be demonstrated in a small
|
||||||
|
arcade game: BreakOut. The game is quite simple: at the top of the screen,
|
||||||
|
there are a series of bricks. At the bottom of the screen is a small pad,
|
||||||
|
which can be move left or right using the cursor keys. A ball bounces on the
|
||||||
|
screen. When the ball hits a brick, the brick dissappears. When the ball
|
||||||
|
hits the bottom of the window, the ball is lost. The pad can be used to
|
||||||
|
prevent the ball from hitting the bottom window.
|
||||||
|
|
||||||
|
When the pad hits the ball, the ball is accellerated in the direction the
|
||||||
|
pad was moving at the moment of impact. Also, an idea of 'slope' is
|
||||||
|
introduced: If the ball hits the pad at some distance from the pad's center,
|
||||||
|
the ball's trajectory is slightly disturbed, as if the pad has a slope.
|
||||||
|
|
||||||
|
After 5 balls were lost, the game is over. If all bricks have been
|
||||||
|
destroyed, a next level is started.
|
||||||
|
|
||||||
|
As stated above, the game will be implemented using double buffering.
|
||||||
|
The ball and pad themselves will be implemented as pixmaps; the bricks
|
||||||
|
will be drawn as simple rectangles.
|
||||||
|
|
||||||
|
These three objects will be implemented using a series of classes:
|
||||||
|
\var{TGraphicalObject}, which introduces a position and size. This class
|
||||||
|
will have 2 descendents: \var{TBlock}, which will draw a block on the
|
||||||
|
screen and \var{TSprite}, which contains all functionality to draw a moving
|
||||||
|
pixmap on the screen. From \var{TSprite}, \var{TBall} and \var{TPad} will be
|
||||||
|
derived. These two objects introduce the behaviour specific to the ball and
|
||||||
|
pad in the game.
|
||||||
|
|
||||||
|
The blocks will be managed by a \var{TBlockList} class, which is a
|
||||||
|
descendent of the standard \var{TList} class.
|
||||||
|
|
||||||
|
All these objects are managed by a \var{TBreakOut} class, which contains the
|
||||||
|
game logic. The class structure could be improved a bit, but the idea is
|
||||||
|
more to separate the logic of the different objects.
|
||||||
|
|
||||||
|
The \var{TGraphicalObject} class is a simple object which introduces some
|
||||||
|
easy access properties to get the position and size of the object:
|
||||||
|
\begin{verbatim}
|
||||||
|
TGraphicalObject = Class(TObject)
|
||||||
|
FRect : TGdkRectangle;
|
||||||
|
Public
|
||||||
|
Function Contains(X,Y : Integer) : Boolean;
|
||||||
|
Property Left : SmallInt Read FRect.x Write Frect.x;
|
||||||
|
Property Top : SmallInt Read FRect.y Write Frect.y;
|
||||||
|
Property Width : Word Read Frect.Width Write Frect.Width;
|
||||||
|
Property Height : Word Read Frect.Height Write Frect.Height;
|
||||||
|
end;
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
The \var{TBlock} object is a simple descendent of the var{TGraphicalObject}
|
||||||
|
class:
|
||||||
|
\begin{verbatim}
|
||||||
|
TBlock = Class(TGraphicalObject)
|
||||||
|
Private
|
||||||
|
FMaxHits : Integer;
|
||||||
|
FBlockList : TBlockList;
|
||||||
|
FGC : PGDKGC;
|
||||||
|
FColor : PGDKColor;
|
||||||
|
FNeedRedraw : Boolean;
|
||||||
|
Procedure CreateGC;
|
||||||
|
Function DrawingArea : PGtkWidget;
|
||||||
|
Function PixMap : PgdkPixMap;
|
||||||
|
Public
|
||||||
|
Procedure Draw;
|
||||||
|
Function Hit : Boolean;
|
||||||
|
Constructor Create (ABlockList : TBlockList);
|
||||||
|
Property Color : PGDKColor Read FColor Write FColor;
|
||||||
|
end;
|
||||||
|
\end{verbatim}
|
||||||
|
The \var{FMaxHits} field determines how many times the ball must hit the
|
||||||
|
brick before it dissappears. With each hit, the field is decremented by 1.
|
||||||
|
|
||||||
|
The \var{FBlockList} refers to the blocklist object that will manage the
|
||||||
|
block. The needed drawing widget and the pixmap on which the block must be
|
||||||
|
drawn are obtained from the blockmanager using the \var{DrawingArea} and
|
||||||
|
\var{Pixmap} functions.
|
||||||
|
The \var{Draw} procedure will draw the block at it's position on the pixmap.
|
||||||
|
The \var{Color} property determines the color in which the block will be
|
||||||
|
drawn.
|
||||||
|
|
||||||
|
The implementation of the \var{TBlock} methods are quite simple. The first
|
||||||
|
methods don't need any explanation.
|
||||||
|
\begin{verbatim}
|
||||||
|
Constructor TBlock.Create (ABlockList : TBlockList);
|
||||||
|
|
||||||
|
begin
|
||||||
|
Inherited Create;
|
||||||
|
FBlockList:=ABlockList;
|
||||||
|
FMaxHits:=1;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Function TBlock.DrawingArea : PGtkWidget;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Result:=FBlockList.FBreakout.FDrawingArea;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Function TBlock.PixMap : PgdkPixMap;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Result:=FBlockList.PixMap;
|
||||||
|
end;
|
||||||
|
\end{verbatim}
|
||||||
|
The first interesting method is the \var{CreateGC} method:
|
||||||
|
\begin{verbatim}
|
||||||
|
Procedure TBlock.CreateGC;
|
||||||
|
|
||||||
|
begin
|
||||||
|
FGC:=gdk_gc_new(DrawingArea^.Window);
|
||||||
|
gdk_gc_set_foreground(FGC,FColor);
|
||||||
|
gdk_gc_set_fill(FGC,GDK_SOLID);
|
||||||
|
FNeedRedraw:=True;
|
||||||
|
end;
|
||||||
|
\end{verbatim}
|
||||||
|
The method is called the first time the block must be drawn. It allocates a
|
||||||
|
new graphics context using the \var{gdk\_gc\_new} function. This function
|
||||||
|
accepts a pointer to a \var{TGTKWidget} as a parameter and returns a new
|
||||||
|
graphics context. After the graphics context is created, the foreground
|
||||||
|
color and fill style are set. (it is assumed that \var{FColor} points
|
||||||
|
to a valid color)
|
||||||
|
|
||||||
|
The \var{Draw} procedure actually draws the block on the pixmap, using
|
||||||
|
the graphics context created in the previous method:
|
||||||
|
\begin{verbatim}
|
||||||
|
Procedure TBlock.Draw;
|
||||||
|
|
||||||
|
begin
|
||||||
|
if FGC=Nil then
|
||||||
|
CreateGC;
|
||||||
|
if FNeedRedraw Then
|
||||||
|
begin
|
||||||
|
gdk_draw_rectangle(PGDKDrawable(Pixmap),FGC,-1,Left,Top,Width,Height);
|
||||||
|
FNeedRedraw:=False;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
\end{verbatim}
|
||||||
|
The \var{FNeedRedraw} procedure is used for optimization.
|
||||||
|
|
||||||
|
Finally, the \var{Hit} method is called when the block is hit by the ball.
|
||||||
|
It will decrease the \var{FMaxHits} field, and if it reaches zero, the
|
||||||
|
place occupied by the block is redrawn in the background color. After that,
|
||||||
|
it removes itself from the blocklist and frees itself.
|
||||||
|
\begin{verbatim}
|
||||||
|
Function TBlock.Hit : Boolean;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Dec(FMaxHits);
|
||||||
|
Result:=FMaxHits=0;
|
||||||
|
If Result then
|
||||||
|
begin
|
||||||
|
FBlockList.FBreakOut.DrawBackground(FRect);
|
||||||
|
FBlockList.Remove(Self);
|
||||||
|
Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
The \var{TSprite} object is a little more involved. The declaration is
|
||||||
|
as follows:
|
||||||
|
\begin{verbatim}
|
||||||
|
TSprite = Class(TGraphicalObject)
|
||||||
|
FPreviousTop,
|
||||||
|
FPreviousLeft : Integer;
|
||||||
|
FDrawingArea : PGtkWidget;
|
||||||
|
FDrawPixMap : PgdkPixmap;
|
||||||
|
FPixMap : PgdkPixMap;
|
||||||
|
FBitMap : PGdkBitMap;
|
||||||
|
Protected
|
||||||
|
Procedure CreateSpriteFromData(SpriteData : PPGchar);
|
||||||
|
Procedure CreatePixMap; Virtual; Abstract;
|
||||||
|
Procedure SavePosition;
|
||||||
|
Public
|
||||||
|
Constructor Create(DrawingArea: PGtkWidget);
|
||||||
|
Procedure Draw;
|
||||||
|
Function GetChangeRect (Var Rect : TGDkRectAngle) : Boolean;
|
||||||
|
Property PixMap : PgdkPixMap Read FPixMap;
|
||||||
|
Property BitMap : PGdkBitMap Read FBitMap;
|
||||||
|
end;
|
||||||
|
\end{verbatim}
|
||||||
|
The important property is the \var{PixMap} property; this contains the
|
||||||
|
pixmap with the sprite's image. The \var{BitMap} property contains the
|
||||||
|
bitmap associated with the pixmap. The second important method is the
|
||||||
|
\var{GetChangeRect} method; it returns the rectangle occupied by the
|
||||||
|
sprite at its previous position. This will be used to 'move' the sprite:
|
||||||
|
When moving the sprite, the current position is saved (using
|
||||||
|
\var{SavePosition}), and the new position is set. After that, the old
|
||||||
|
position is cleared, and the sprite is drawn at the new position.
|
||||||
|
|
||||||
|
All this drawing is done on the background pixmap, to avoid flickering
|
||||||
|
when drawing: The result of the two drawing steps is shown at once.
|
||||||
|
|
||||||
|
The implementation of the \var{Draw} method is quite straightforward:
|
||||||
|
\begin{verbatim}
|
||||||
|
Procedure TSprite.Draw;
|
||||||
|
|
||||||
|
Var
|
||||||
|
gc : PGDKGc;
|
||||||
|
|
||||||
|
begin
|
||||||
|
if FPixMap=Nil then
|
||||||
|
CreatePixMap;
|
||||||
|
gc:=gtk_widget_get_style(FDrawingArea)^.fg_gc[GTK_STATE_NORMAL];
|
||||||
|
gdk_gc_set_clip_origin(gc,Left,Top);
|
||||||
|
gdk_gc_set_clip_mask(gc,FBitmap);
|
||||||
|
gdk_draw_pixmap(FDrawPixMap,gc,FPixMap,0,0,Left,Top,Width,Height)
|
||||||
|
gdk_gc_set_clip_mask(gc,Nil);
|
||||||
|
end;
|
||||||
|
\end{verbatim}
|
||||||
|
After the pixmap has been created (a method which must be implemented by
|
||||||
|
descendent objects), the graphics context of the drawing area is retrieved
|
||||||
|
to do the drawing.
|
||||||
|
|
||||||
|
The bitmap is drawn using the clipping functionality of the GDK toolkit:
|
||||||
|
To this end, the clip origin is set to the position of the sprite, and
|
||||||
|
the clip bitmask is set from the \var{FBitmap}, which is created when the
|
||||||
|
sprite's pixmap is created. When drawing the pixmap, only the bits in the
|
||||||
|
bitmap will be drawn, other bits are left untouched.
|
||||||
|
|
||||||
|
The pixmap is drawn using the \var{gdk\_draw\_pixmap} function. This
|
||||||
|
function copies a region from one \var{TGDKDrawable} to another.
|
||||||
|
It is defined as follows:
|
||||||
|
\begin{verbatim}
|
||||||
|
procedure gdk_draw_pixmap(drawable:PGdkDrawable; gc:PGdkGC;
|
||||||
|
src:PGdkDrawable;
|
||||||
|
xsrc,ysrc,xdest,ydest,width,height:gint);
|
||||||
|
\end{verbatim}
|
||||||
|
The function, as all GDK drawing functions, takes a \var{PGDKDrawable}
|
||||||
|
pointer and a graphics contexts as its first two arguments. The third
|
||||||
|
argument is the \var{TGDKDrawable} which should be copied. The
|
||||||
|
\var{xsrc,ysrc} parameters indicate the position of the region that should
|
||||||
|
be copied in the source \var{TGDKDrawable}; the \var{xdest,ydest} indicate
|
||||||
|
the position in the target \var{TGDKDrawable} where the bitmap should be
|
||||||
|
drawn.
|
||||||
|
|
||||||
|
In the case of \var{TSprite}, the function is used to copy the sprite's
|
||||||
|
bitmap onto the memory pixmap with the game image. After the bitmap was
|
||||||
|
copied, the clip mask is removed again.
|
||||||
|
|
||||||
|
The creation of the pixmap happens when the sprite is drawn for the first
|
||||||
|
time; The \var{CreateSpriteFromData} method accepts a pointer to an XPM
|
||||||
|
pixmap, and uses the \var{gdk\_pixmap\_create\_from\_xpm\_d} function
|
||||||
|
(explained in the previous article) to create the actual pixmap and the
|
||||||
|
corresponding bitmap.
|
||||||
|
\begin{verbatim}
|
||||||
|
Procedure TSprite.CreateSpriteFromData(SpriteData : PPGChar);
|
||||||
|
|
||||||
|
begin
|
||||||
|
FPixMap:=gdk_pixmap_create_from_xpm_d(FDrawingArea^.Window,
|
||||||
|
@FBitmap,
|
||||||
|
Nil,
|
||||||
|
SpriteData);
|
||||||
|
end;
|
||||||
|
\end{verbatim}
|
||||||
|
This method can be used by the descendent object's \var{CreatePixmap}
|
||||||
|
procedure.
|
||||||
|
|
||||||
|
The \var{SavePosition} and \var{GetChangeRect} methods are very
|
||||||
|
straightforward:
|
||||||
|
\begin{verbatim}
|
||||||
|
Function TSprite.GetChangeRect (Var Rect : TGDkRectAngle) : Boolean;
|
||||||
|
|
||||||
|
begin
|
||||||
|
Result:=(FPreviousLeft<>Left) or (FPreviousTop<>Top);
|
||||||
|
If Result then
|
||||||
|
With Rect do
|
||||||
|
begin
|
||||||
|
x:=FPreviousLeft;
|
||||||
|
y:=FPreviousTop;
|
||||||
|
Width:=Abs(Left-FPreviousLeft)+self.Width;
|
||||||
|
height:=Abs(Top-FPreviousTop)+self.Height;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
Procedure TSprite.SavePosition;
|
||||||
|
|
||||||
|
begin
|
||||||
|
FPreviousLeft:=Left;
|
||||||
|
FPreviousTop:=Top;
|
||||||
|
end;
|
||||||
|
\end{verbatim}
|
||||||
|
Note that the \var{GetChangeRect} procedure returns false if the position
|
||||||
|
of the sprite didn't change. This is used for optimization purposes.
|
||||||
|
|
||||||
|
The pad is the simplest of the two \var{TSprite} descendents. It only adds a
|
||||||
|
horizontal movement to the sprite:
|
||||||
|
\begin{verbatim}
|
||||||
|
TPad = Class (TSprite)
|
||||||
|
Private
|
||||||
|
FSlope,
|
||||||
|
FSpeed,FCurrentSpeed : Integer;
|
||||||
|
Protected
|
||||||
|
Procedure CreatePixMap; override;
|
||||||
|
Procedure InitialPosition;
|
||||||
|
Public
|
||||||
|
Constructor Create(DrawingArea: PGtkWidget);
|
||||||
|
Procedure Step;
|
||||||
|
Procedure GoLeft;
|
||||||
|
Procedure GoRight;
|
||||||
|
Procedure Stop;
|
||||||
|
Property CurrentSpeed : Integer Read FCurrentSpeed;
|
||||||
|
Property Speed : Integer Read FSpeed Write FSpeed;
|
||||||
|
Property Slope : Integer Read FSlope Write FSlope;
|
||||||
|
end;
|
||||||
|
\end{verbatim}
|
||||||
|
The procedures \var{GoLeft}, \var{GoRight} and \var{Stop} can be used to
|
||||||
|
control the movement of the pad. The method \var{Step} will be called at
|
||||||
|
regular intervals to actually move the pad. The \var{InitialPosition}
|
||||||
|
sets the pad at its initial position on the screen. The \var{Speed} and
|
||||||
|
\var{Slope} properties can be used to set the speed and slope of the pad.
|
||||||
|
|
||||||
|
The implementation is quite straightforward:
|
||||||
|
\begin{verbatim}
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
\end{document}
|
\end{document}
|
Loading…
Reference in New Issue
Block a user