Parcourir la source

started initial animation explanation

michael il y a 24 ans
Parent
commit
ad353b52cd
1 fichiers modifiés avec 352 ajouts et 12 suppressions
  1. 352 12
      docs/gtk5.tex

+ 352 - 12
docs/gtk5.tex

@@ -171,29 +171,36 @@ functions for points, lines, segments, rectangles, polygons, circles, text
 and bitmaps.
 
 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
-to a graphics context. These parameters are omitted from the following
-declarations:
+can be a pointer to a \var{TGDKWindow} or a \var{TGDkPixmap}, and a 
+\var{PGdkGC}, a pointer to a graphics context. 
+
+These parameters are omitted from the following declarations:
 \begin{verbatim}
 procedure gdk_draw_point(x,y:gint);
 procedure gdk_draw_line(x1,y1,x2,y2:gint);
 procedure gdk_draw_rectangle(filled,x,y,width,height:gint);
 \end{verbatim}
+The above functions draw respectively a dot, a line and a rectangle.
 The meaning of the parameters for these functions is obvious.
-For the rectangle, care must be taken. If \var{Filled} is false (-1) then
-the drawn rectangle is actually \var{Width+1}, \var{Height+1}. If it is
-filled, then the width are height are as specified.
+For the rectangle, care must be taken. If the parameter \var{Filled} is 
+False (-1) then the drawn rectangle has actually a width and height of
+\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:
 \begin{verbatim}
 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_segments(segs:PGdkSegment; nsegs:gint);
 \end{verbatim}
 The \var{gdk\_draw\_polygon} polygon takes a series of dots and connects 
-them using lines, optionally filling them. 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.
+them using lines, optionally filling them. The points are specified by a
+pointer to an array of \var{TGDKPoint} records (there should be \var{npoint}
+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 
 will not connect the first and last points.
 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}
 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:
 \begin{lstlisting}{}
 program graphics;
@@ -352,17 +366,343 @@ Begin
   gtk_main();
 end.
 \end{lstlisting}
-The main program startsby creating a main window,
+The main program starts by creating a main window,
 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}),  
 the other to draw the \var{TGTKDrawingArea} when it is exposed 
 (\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
 contain a memory leak.
 
 \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}