2
0

gtk5.tex 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555
  1. \documentclass[10pt]{article}
  2. \usepackage{a4}
  3. \usepackage{epsfig}
  4. \usepackage{listings}
  5. \usepackage{tabularx}
  6. \lstset{language=Delphi}%
  7. \lstset{basicstyle=\sffamily\small}%
  8. \lstset{commentstyle=\itshape}%
  9. \lstset{keywordstyle=\bfseries}%
  10. %\lstset{blankstring=true}%
  11. \newcommand{\file}[1]{\textsf{#1}}
  12. \newcommand{\var}[1]{\texttt{#1}}
  13. \usepackage[pdftex]{hyperref}
  14. \newif\ifpdf
  15. \ifx\pdfoutput\undefined
  16. \pdffalse
  17. \else
  18. \pdfoutput=1
  19. \pdftrue
  20. \fi
  21. \begin{document}
  22. \title{Programming GTK in Free Pascal: Using GDK}
  23. \author{Florian Kl\"ampfl\\and\\Micha\"el Van Canneyt}
  24. \date{July 2001}
  25. \maketitle
  26. \section{Introduction}
  27. In this article, some of the graphics primitives from the gdk toolkit will
  28. be demonstrated in a small game - breakout.
  29. The GTK toolkit widgets are built upon the GDK: Graphics Drawing Kit.
  30. The GDK does not know anything about buttons, menus checkboxes and so on.
  31. Instead, it knows how to create windows, draw on them, handle mouse clicks
  32. and keypresses. This functionality is used by the GTK widget set to create
  33. usable widgets.
  34. Sometimes, the widgets offered by GTK are not enough, and one has to fall
  35. back on the graphics functionality of the GDK to be able to do what is
  36. needed for a program.
  37. Fortunately, it is not necessary to create a GTK window and handle all
  38. GDK events to be able to use the GDK functions. The GTK widget set has a
  39. special widget, which can be used to draw upon. This widget is the
  40. \var{TGtkDrawingArea} widget. The use of the \var{TGtkDrawingArea} is what
  41. will be explained below.
  42. The GDK graphics functions will be explained using a simple arcade game,
  43. to demonstrate that the speed of the GDK is sufficient for the creation of
  44. simple games. The breakout game is chosen because it is conceptually simple,
  45. requires moving graphics and can be extended in many ways.
  46. \section{The drawing area widget}
  47. The drawing area widget (\var{TGTKDrawingArea}) is a simple widget which
  48. just provides a drawing window. It responds to all widget events, and adds
  49. additionally the 'configure\_event', which is called when the widget is
  50. realized (i.e. when the window handle is created.)
  51. The widget has only 1 method: \var{gtk\_drawing\_area\_size}, which sets
  52. the size of the drawing area. It is defined as follows:
  53. \begin{lstlisting}{}
  54. procedure gtk_drawing_area_size(Area:PGtkDrawingArea;
  55. width,height:gint)
  56. \end{lstlisting}{}
  57. The arguments to this function are self-explaining.
  58. To use the drawing area widget, one should respond to the 'expose\_event'.
  59. This event is triggered whenever a part of the window that was invisible,
  60. becomes visible. The event handler gets an \var{PGDKEventExpose} parameter,
  61. which describes which area was exposed. This can be used for optimization
  62. purposes.
  63. To draw in the drawing area widget, the \var{Window} field of the
  64. \var{TGTKWidget} parent can be used. This is of type \var{TGDKWindow}.
  65. All drawing functions require a parameter of type \var{TGdkDrawable}
  66. which can be one of the \var{TGdkWindow} or \var{TGdkPixMap} types.
  67. \section{Graphics contexts}
  68. Most drawing functions do not only require a drawable to draw on, they also
  69. require a {\em Graphics Context}. A graphics context is a series of
  70. parameters that determine how lines are drawn, what colors and font are
  71. used etc.
  72. The Graphics Context is an opaque record, and its members cannot be
  73. accessed. The relevant parameters are set in a \var{TGdkGCValues} record,
  74. which is defined as follows:
  75. \begin{lstlisting}{}
  76. foreground : TGdkColor;
  77. background : TGdkColor;
  78. font : PGdkFont;
  79. thefunction : TGdkfunction;
  80. fill : TGdkFill;
  81. tile : PGdkPixmap;
  82. stipple : PGdkPixmap;
  83. clip_mask : PGdkPixmap;
  84. subwindow_mode : TGdkSubwindowMode;
  85. ts_x_origin : gint;
  86. ts_y_origin : gint;
  87. clip_x_origin : gint;
  88. clip_y_origin : gint;
  89. graphics_exposures : gint;
  90. line_width : gint;
  91. line_style : TGdkLineStyle;
  92. cap_style : TGdkCapStyle;
  93. join_style : TGdkJoinStyle;
  94. \end{lstlisting}{}
  95. The \var{ForeGround} and \var{Background} parameters determine the foreground
  96. and background colors. \var{Font} is the default font. The \var{Fill} field
  97. describes how areas are filled. It can be one of the following:
  98. \begin{description}
  99. \item[GDK\_SOLID] fill with the foreground color.
  100. \item[GDK\_TILED] Use the pixmap specified in \var{Tile} to fill the area.
  101. \item[GDK\_STIPPLED] Use the pixmap specified in \var{Stipple} to draw
  102. pixels that are in the bitmap in the foreground color. Other bits are not
  103. drawn.
  104. \item[GDK\_OPAQUE\_STIPPLED] Same as \var{GDK\_STIPPLED} except that bits
  105. not in the pixmap will be drawn in the background color.
  106. \end{description}
  107. The \var{clip\_bitmap} is used to define a clip area. The
  108. \var{ts\_x\_origin} and \var{ts\_y\_origin} define the stipple or tile
  109. origin. The \var{clip\_x\_origin} and \var{clip\_y\_origin} fields define
  110. the origin of the clipping region.
  111. \var{LineWidth} is the linewidth used when drawing lines. \var{Line\_Style}
  112. determines how dashed lines are drawn. It can have one of the following
  113. values:
  114. \begin{description}
  115. \item[GDK\_LINE\_SOLID] Lines are drawn solid.
  116. \item[GDK\_LINE\_ON\_OFF\_DASH] Even segments are drawn, odd segments are
  117. not.
  118. \item[GDK\_LINE\_DOUBLE\_DASH] Even segments are drawn, Odd segments are
  119. drawn in the background color if the fill style is \var{GDK\_SOLID}.
  120. \end{description}
  121. \var{cap\_style} determines how line ends are drawn. The following values are
  122. defined:
  123. \begin{description}
  124. \item[GDK\_CAP\_BUTT] The lines are drawn with square ends.
  125. \item[GDK\_CAP\_NOT\_LAST] Idem as \var{GDK\_CAP\_BUTT}, only for zero-width
  126. lines, the last dot is not drawn.
  127. \item[GDK\_CAP\_ROUND] The end of the line is a semicircle. The circle has
  128. diameter equal to the linewidth, and the center is the endpoint of the line.
  129. \item[GDK\_CAP\_PROJECTING] Idem as [GDK\_CAP\_BUTT], only the line extends
  130. half the linewidth outside the endpoint.
  131. \end{description}
  132. The effect of these elements will be shown in the next section.
  133. To set a color, a \var{TGDkColor} record must be allocated. Colors are
  134. specified using a RGB value. Unfortunately, not all graphics cards can
  135. show all colors. In order to find out which screen color corresponds
  136. to the RGB-specified color, the GDK uses a colormap, and allocates a
  137. color that matches the closest to the specified color values.
  138. When allocating a new color, the colormap should be specified.
  139. A colormap can be obtained from a \var{TGTKWidget} descdendant using the GTK function
  140. \var{gtk\_widget\_get\_colormap}; A color can then be allocated
  141. using the following \var{gdk\_colormap\_alloc\_color} function:
  142. \begin{lstlisting}{}
  143. function gdk_colormap_alloc_color(colormap:PGdkColormap;
  144. color:PGdkColor;
  145. writeable:gboolean;
  146. best_match:gboolean):gboolean;
  147. \end{lstlisting}{}
  148. The \var{writeable} parameter specifies whether changes to
  149. \var{color} using \var{gdk\_color\_change} are allowed.
  150. \var{best\_match} specifies whether a best match should be attempted
  151. on existing colors or an exact value is required.
  152. The function returns \var{True} if the allocation succeeded,
  153. \var{False} otherwise.
  154. \section{Drawing primitives}
  155. Using the properties introduced in the previous section, drawing can be
  156. attempted using the drawing primitives offered by GDK. GDK offers drawing
  157. functions for points, lines, segments, rectangles, polygons, circles, text
  158. and bitmaps.
  159. All functions accept as the first two parameters a \var{PGDKdrawable}, which
  160. can be a pointer to a \var{TGDKWindow} or a \var{TGDkPixmap}, and a
  161. \var{PGdkGC}, a pointer to a graphics context.
  162. These parameters are omitted from the following declarations:
  163. \begin{lstlisting}{}
  164. procedure gdk_draw_point(x,y:gint);
  165. procedure gdk_draw_line(x1,y1,x2,y2:gint);
  166. procedure gdk_draw_rectangle(filled,x,y,width,height:gint);
  167. \end{lstlisting}{}
  168. The above functions draw respectively a dot, a line and a rectangle.
  169. The meaning of the parameters for these functions is obvious.
  170. For the rectangle, care must be taken. If the parameter \var{Filled} is
  171. False (-1) then the drawn rectangle has actually a width and height of
  172. \var{Width+1}, \var{Height+1}. If it is filled, then the width and
  173. height are as specified in the call to \var{gdk\_draw\_rectangle}.
  174. The following procedures can be used to draw a series of lines:
  175. \begin{lstlisting}{}
  176. procedure gdk_draw_polygon(filled:gint;points:PGdkPoint; npoints:gint);
  177. procedure gdk_draw_lines(points:PGdkPoint; npoints:gint);
  178. procedure gdk_draw_segments(segs:PGdkSegment; nsegs:gint);
  179. \end{lstlisting}{}
  180. The \var{gdk\_draw\_polygon} polygon takes a series of dots and connects
  181. them using lines, optionally filling them. The points are specified by a
  182. pointer to an array of \var{TGDKPoint} records (there should be \var{npoint}
  183. such records in the array).
  184. A \var{TGDKPoint} record contains 2 fields: \var{X,Y} which specify the
  185. location of a point.
  186. If needed, the first and last points are also connected using a line.
  187. The \var{gdk\_draw\_lines} does the same, only it cannot be filled, and it
  188. will not connect the first and last points.
  189. The \var{gdk\_draw\_segments} requires a series of \var{TGDKSegment}
  190. records. These consist of 4 fields: \var{x1,y1,x2,y2}, each describing
  191. the start and end point of a line segment. The segments will not be
  192. connected.
  193. The \var{gdk\_draw\_arc} can be used to draw a circle or a segment of
  194. the circle, or an ellipse.
  195. \begin{lstlisting}{}
  196. procedure gdk_draw_arc(filled,x,y,width,height,
  197. angle1,angle2 : gint);
  198. \end{lstlisting}{}
  199. The \var{x,y, width} and \var{height} parameters describe a bounding
  200. rectangle for the circle. The angles describe the start and extending
  201. angle of the segment to be drawn: The circle segment starts at angle
  202. \var{angle1} and ends at \var{angle1+angle2}. These angles are specified
  203. in 1/64ths of a degree and are measured counterclockwise, starting at
  204. the 3 o'clock direction. A circle segment drawn from 90 to 270 degrees
  205. should therefore have as angles 90*64=5760 and 270*64=17280.
  206. If filled is \var{True} (-1), then the segment will be connected to
  207. the circle centre, and filled, in effect drawing a pie-slice.
  208. Finally, for the \var{gdk\_draw\_string} function, the graphics context comes
  209. before the graphics context:
  210. \begin{lstlisting}{}
  211. procedure gdk_draw_string(drawable:PGdkDrawable; font:PGdkFont;
  212. gc:PGdkGC; x,y:gint; thestring:Pgchar);
  213. \end{lstlisting}{}
  214. The meaning of the parameters for this functions should be obvious.
  215. The font for the \var{gdk\_draw\_string} can be obtained using the
  216. \var{gdk\_font\_load} function:
  217. \begin{lstlisting}{}
  218. function gdk_font_load(font_name:Pgchar):PGdkFont;
  219. \end{lstlisting}{}
  220. The font name should be specified as an X font path.
  221. All this is demonstrated in the following program:
  222. \begin{lstlisting}{}
  223. program graphics;
  224. {$mode objfpc}
  225. {$h+}
  226. uses glib,gdk,gtk,sysutils;
  227. var
  228. window,
  229. area : PGtkWidget;
  230. Function CloseApp(widget : PGtkWidget ;
  231. event : PGdkEvent;
  232. data : gpointer) : boolean; cdecl;
  233. Begin
  234. gtk_main_quit();
  235. close_application := false;
  236. End;
  237. Function AllocateColor(R,G,B : Integer;
  238. Widget : PGtkWidget) : PGdkColor;
  239. begin
  240. Result:=New(PgdkColor);
  241. With Result^ do
  242. begin
  243. Pixel:=0;
  244. Red:=R;
  245. Blue:=B;
  246. Green:=G;
  247. end;
  248. gdk_colormap_alloc_color(gtk_widget_get_colormap(Widget),
  249. Result,true,False);
  250. end;
  251. function Exposed(Widget: PGtkWidget;
  252. event : PGdkEventExpose;
  253. Data : gpointer) : Integer; cdecl;
  254. Const
  255. Triangle : Array[1..4] of TgdkPoint =
  256. ((X:10;Y:195),
  257. (X:110;Y:195),
  258. (X:55;Y:145),
  259. (X:10;Y:195));
  260. LineStyles : Array[1..5] of TgdkLineStyle =
  261. (GDK_LINE_SOLID, GDK_LINE_ON_OFF_DASH,
  262. GDK_LINE_DOUBLE_DASH, GDK_LINE_ON_OFF_DASH,
  263. GDK_LINE_SOLID);
  264. capstyles : Array[1..5] of TgdkCapStyle =
  265. (GDK_CAP_ROUND,GDK_CAP_NOT_LAST, GDK_CAP_BUTT,
  266. GDK_CAP_PROJECTING, GDK_CAP_NOT_LAST);
  267. FontName : Pchar =
  268. '-*-helvetica-bold-r-normal--*-120-*-*-*-*-iso8859-1';
  269. Var
  270. SegTriangle : Array[1..3] of TgdkSegment;
  271. Win : pgdkWindow;
  272. gc : PgdkGC;
  273. i,seg : Integer;
  274. font : PgdkFont;
  275. Angle1,Angle2 : Longint;
  276. begin
  277. gc:=gdk_gc_new(widget^.Window);
  278. Win:=widget^.window;
  279. With Event^.area do
  280. gdk_window_clear_area (win,x,y,width,height);
  281. gdk_gc_set_foreground(gc,allocatecolor(0,0,0,Widget));
  282. gdk_draw_rectangle(win,gc,0,5,5,590,390);
  283. gdk_gc_set_foreground(gc,allocatecolor(0,0,$ffff,Widget));
  284. for I:=10 to 50 do
  285. gdk_draw_point(win,gc,I*10,100);
  286. gdk_gc_set_foreground(gc,allocatecolor($ffff,0,0,Widget));
  287. for I:=10 to 50 do
  288. begin
  289. gdk_gc_set_line_attributes(gc,6,LineStyles[i div 10],
  290. CapStyles[i div 10],GDK_JOIN_MITER);
  291. gdk_draw_line(win,gc,I*10,20,I*10,90)
  292. end;
  293. gdk_gc_set_line_attributes(gc,1,GDK_LINE_SOLID,
  294. GDK_CAP_BUTT,GDK_JOIN_MITER);
  295. gdk_gc_set_foreground(gc,allocatecolor($ffff,0,$ffff,Widget));
  296. seg:=(360 div 20) * 64;
  297. For I:=1 to 20 do
  298. gdk_draw_arc(win,gc,0,220-I*4,200-i*4,8*i,8*i,i*seg,seg*19);
  299. For I:=1 to 20 do
  300. gdk_draw_arc(win,gc,-1,380-I*4,200-i*4,8*i,8*i,(i-1)*seg,seg);
  301. gdk_gc_set_foreground(gc,allocatecolor(0,$ffff,$ffff,Widget));
  302. gdk_draw_polygon(win,gc,0,@triangle[1],4);
  303. For I:=1 to 4 do
  304. Triangle[i].Y:=400-Triangle[i].y;
  305. gdk_draw_polygon(win,gc,-1,@triangle[1],4);
  306. gdk_gc_set_foreground(gc,allocatecolor(0,$ffff,0,Widget));
  307. For I:=1 to 4 do
  308. Triangle[i].X:=600-Triangle[i].x;
  309. gdk_draw_lines(win,gc,@triangle[1],4);
  310. For I:=1 to 3 do
  311. begin
  312. SegTriangle[i].X1:=Triangle[i].X;
  313. SegTriangle[i].Y1:=400-Triangle[i].Y;
  314. SegTriangle[i].X2:=Triangle[i+1].X;
  315. SegTriangle[i].Y2:=400-Triangle[i+1].Y;
  316. end;
  317. gdk_draw_segments(win,gc,@segtriangle[1],3);
  318. font:=gdk_font_load(FontName);
  319. gdk_gc_set_foreground(gc,allocatecolor($ffff,$ffff,0,Widget));
  320. For I:=1 to 4 do
  321. gdk_draw_string(win,font,gc,I*100,300,
  322. Pchar(format('String %d',[i])));
  323. result:=0;
  324. end;
  325. Begin
  326. // Initialize GTK and create the main window
  327. gtk_init( @argc, @argv );
  328. window := gtk_window_new( GTK_WINDOW_TOPLEVEL );
  329. gtk_window_set_policy(PgtkWindow(Window),0,0,1);
  330. gtk_signal_connect (GTK_OBJECT (window), 'delete_event',
  331. GTK_SIGNAL_FUNC( @CloseApp ), NIL);
  332. gtk_container_set_border_width (GTK_CONTAINER (window), 10);
  333. area := gtk_drawing_area_new();
  334. gtk_container_add( GTK_CONTAINER(window), Area);
  335. gtk_signal_connect (GTK_OBJECT (area),'expose_event',
  336. GTK_SIGNAL_FUNC(@Exposed),Nil);
  337. gtk_drawing_area_size (PGTKDRAWINGAREA(area),600,400);
  338. gtk_widget_show_all( window );
  339. gtk_main();
  340. end.
  341. \end{lstlisting}
  342. The main program starts by creating a main window,
  343. and adding a \var{TGTKDrawingArea} to it. It then connects 2 event handlers,
  344. one to stop the application if the window is closed (\var{CloseApp}),
  345. the other to draw the \var{TGTKDrawingArea} when it is exposed
  346. (\var{Exposed}). This latter contains the actual drawing routines, and is
  347. pretty self-explaining. It simply demonstrates the use of the drawing
  348. primitives explained above.
  349. Note that the allocated colors are not freed again, so this program does
  350. contain a memory leak.
  351. The result of the program is shown in figure \ref{fig:screenshot1}.
  352. \begin{figure}[ht]
  353. \caption{The graphics program in action.}\label{fig:screenshot1}
  354. \epsfig{file=gtk5ex/graphics.png,width=\textwidth}
  355. \end{figure}
  356. \section{Animation}
  357. The GDK drawing functions can be used to draw directly on a window visible
  358. on the screen. This is OK for normal applications, but applications that
  359. have a lot of (changing) graphics will soon see a flickering screen.
  360. Luckily, GDK provides a means to cope with this: Instead of drawing directly
  361. on the screen, one can draw on a bitmap which exists in memory, and copy
  362. parts of the bitmap to the screen on an as-need basis.
  363. This is the reason why the GDK drawing functions generally accept a
  364. \var{PGDKdrawable} parameter: This can be of the type \var{PgdkWindow} or
  365. \var{PGDKPixmap}: The \var{TGDKPixmap} can be used to do the drawing in the
  366. background, and then copy the pixmap to the actual window.
  367. This technique, known as double buffering, will be demonstrated in a small
  368. arcade game: BreakOut. The game is quite simple: at the top of the screen,
  369. there are a series of bricks. At the bottom of the screen is a small pad,
  370. which can be move left or right using the cursor keys. A ball bounces on the
  371. screen. When the ball hits a brick, the brick dissappears. When the ball
  372. hits the bottom of the window, the ball is lost. The pad can be used to
  373. prevent the ball from hitting the bottom window.
  374. When the pad hits the ball, the ball is accellerated in the direction the
  375. pad was moving at the moment of impact. Also, an idea of 'slope' is
  376. introduced: If the ball hits the pad at some distance from the pad's center,
  377. the ball's trajectory is slightly disturbed, as if the pad has a slope.
  378. After 5 balls were lost, the game is over. If all bricks have been
  379. destroyed, a next level is started.
  380. As stated above, the game will be implemented using double buffering.
  381. The ball and pad themselves will be implemented as pixmaps; the bricks
  382. will be drawn as simple rectangles.
  383. These three objects will be implemented using a series of classes:
  384. \var{TGraphicalObject}, which introduces a position and size. This class
  385. will have 2 descendents: \var{TBlock}, which will draw a block on the
  386. screen and \var{TSprite}, which contains all functionality to draw a moving
  387. pixmap on the screen. From \var{TSprite}, \var{TBall} and \var{TPad} will be
  388. derived. These two objects introduce the behaviour specific to the ball and
  389. pad in the game.
  390. The blocks will be managed by a \var{TBlockList} class, which is a
  391. descendent of the standard \var{TList} class.
  392. All these objects are managed by a \var{TBreakOut} class, which contains the
  393. game logic. The class structure could be improved a bit, but the idea is
  394. more to separate the logic of the different objects.
  395. The \var{TGraphicalObject} class is a simple object which introduces some
  396. easy access properties to get the position and size of the object:
  397. \begin{lstlisting}{}
  398. TGraphicalObject = Class(TObject)
  399. FRect : TGdkRectangle;
  400. Public
  401. Function Contains(X,Y : Integer) : Boolean;
  402. Property Left : SmallInt Read FRect.x Write Frect.x;
  403. Property Top : SmallInt Read FRect.y Write Frect.y;
  404. Property Width : Word Read Frect.Width Write Frect.Width;
  405. Property Height : Word Read Frect.Height Write Frect.Height;
  406. end;
  407. \end{lstlisting}{}
  408. The \var{TBlock} object is a simple descendent of the var{TGraphicalObject}
  409. class:
  410. \begin{lstlisting}{}
  411. TBlock = Class(TGraphicalObject)
  412. Private
  413. FMaxHits : Integer;
  414. FBlockList : TBlockList;
  415. FGC : PGDKGC;
  416. FColor : PGDKColor;
  417. FNeedRedraw : Boolean;
  418. Procedure CreateGC;
  419. Function DrawingArea : PGtkWidget;
  420. Function PixMap : PgdkPixMap;
  421. Public
  422. Procedure Draw;
  423. Function Hit : Boolean;
  424. Constructor Create (ABlockList : TBlockList);
  425. Property Color : PGDKColor Read FColor Write FColor;
  426. end;
  427. \end{lstlisting}{}
  428. The \var{FMaxHits} field determines how many times the ball must hit the
  429. brick before it dissappears. With each hit, the field is decremented by 1.
  430. The \var{FBlockList} refers to the blocklist object that will manage the
  431. block. The needed drawing widget and the pixmap on which the block must be
  432. drawn are obtained from the blockmanager using the \var{DrawingArea} and
  433. \var{Pixmap} functions.
  434. The \var{Draw} procedure will draw the block at it's position on the pixmap.
  435. The \var{Color} property determines the color in which the block will be
  436. drawn.
  437. The implementation of the \var{TBlock} methods are quite simple. The first
  438. methods don't need any explanation.
  439. \begin{lstlisting}{}
  440. Constructor TBlock.Create (ABlockList : TBlockList);
  441. begin
  442. Inherited Create;
  443. FBlockList:=ABlockList;
  444. FMaxHits:=1;
  445. end;
  446. Function TBlock.DrawingArea : PGtkWidget;
  447. begin
  448. Result:=FBlockList.FBreakout.FDrawingArea;
  449. end;
  450. Function TBlock.PixMap : PgdkPixMap;
  451. begin
  452. Result:=FBlockList.PixMap;
  453. end;
  454. \end{lstlisting}{}
  455. The first interesting method is the \var{CreateGC} method:
  456. \begin{lstlisting}{}
  457. Procedure TBlock.CreateGC;
  458. begin
  459. FGC:=gdk_gc_new(DrawingArea^.Window);
  460. gdk_gc_set_foreground(FGC,FColor);
  461. gdk_gc_set_fill(FGC,GDK_SOLID);
  462. FNeedRedraw:=True;
  463. end;
  464. \end{lstlisting}{}
  465. The method is called the first time the block must be drawn. It allocates a
  466. new graphics context using the \var{gdk\_gc\_new} function. This function
  467. accepts a pointer to a \var{TGTKWidget} as a parameter and returns a new
  468. graphics context. After the graphics context is created, the foreground
  469. color and fill style are set. (it is assumed that \var{FColor} points
  470. to a valid color)
  471. The \var{Draw} procedure actually draws the block on the pixmap, using
  472. the graphics context created in the previous method:
  473. \begin{lstlisting}{}
  474. Procedure TBlock.Draw;
  475. begin
  476. if FGC=Nil then
  477. CreateGC;
  478. if FNeedRedraw Then
  479. begin
  480. gdk_draw_rectangle(PGDKDrawable(Pixmap),FGC,-1,Left,Top,Width,Height);
  481. FNeedRedraw:=False;
  482. end;
  483. end;
  484. \end{lstlisting}{}
  485. The \var{FNeedRedraw} procedure is used for optimization.
  486. Finally, the \var{Hit} method is called when the block is hit by the ball.
  487. It will decrease the \var{FMaxHits} field, and if it reaches zero, the
  488. place occupied by the block is redrawn in the background color. After that,
  489. it removes itself from the blocklist and frees itself.
  490. \begin{lstlisting}{}
  491. Function TBlock.Hit : Boolean;
  492. begin
  493. Dec(FMaxHits);
  494. Result:=FMaxHits=0;
  495. If Result then
  496. begin
  497. FBlockList.FBreakOut.DrawBackground(FRect);
  498. FBlockList.Remove(Self);
  499. Free;
  500. end;
  501. end;
  502. \end{lstlisting}{}
  503. The \var{TSprite} object is a little more involved. The declaration is
  504. as follows:
  505. \begin{lstlisting}{}
  506. TSprite = Class(TGraphicalObject)
  507. FPreviousTop,
  508. FPreviousLeft : Integer;
  509. FDrawingArea : PGtkWidget;
  510. FDrawPixMap : PgdkPixmap;
  511. FPixMap : PgdkPixMap;
  512. FBitMap : PGdkBitMap;
  513. Protected
  514. Procedure CreateSpriteFromData(SpriteData : PPGchar);
  515. Procedure CreatePixMap; Virtual; Abstract;
  516. Procedure SavePosition;
  517. Public
  518. Constructor Create(DrawingArea: PGtkWidget);
  519. Procedure Draw;
  520. Function GetChangeRect (Var Rect : TGDkRectAngle) : Boolean;
  521. Property PixMap : PgdkPixMap Read FPixMap;
  522. Property BitMap : PGdkBitMap Read FBitMap;
  523. end;
  524. \end{lstlisting}{}
  525. The important property is the \var{PixMap} property; this contains the
  526. pixmap with the sprite's image. The \var{BitMap} property contains the
  527. bitmap associated with the pixmap. The second important method is the
  528. \var{GetChangeRect} method; it returns the rectangle occupied by the
  529. sprite at its previous position. This will be used to 'move' the sprite:
  530. When moving the sprite, the current position is saved (using
  531. \var{SavePosition}), and the new position is set. After that, the old
  532. position is cleared, and the sprite is drawn at the new position.
  533. All this drawing is done on the background pixmap, to avoid flickering
  534. when drawing: The result of the two drawing steps is shown at once.
  535. The implementation of the \var{Draw} method is quite straightforward:
  536. \begin{lstlisting}{}
  537. Procedure TSprite.Draw;
  538. Var
  539. gc : PGDKGc;
  540. begin
  541. if FPixMap=Nil then
  542. CreatePixMap;
  543. gc:=gtk_widget_get_style(FDrawingArea)^.fg_gc[GTK_STATE_NORMAL];
  544. gdk_gc_set_clip_origin(gc,Left,Top);
  545. gdk_gc_set_clip_mask(gc,FBitmap);
  546. gdk_draw_pixmap(FDrawPixMap,gc,FPixMap,0,0,Left,Top,Width,Height)
  547. gdk_gc_set_clip_mask(gc,Nil);
  548. end;
  549. \end{lstlisting}{}
  550. After the pixmap has been created (a method which must be implemented by
  551. descendent objects), the graphics context of the drawing area is retrieved
  552. to do the drawing.
  553. The bitmap is drawn using the clipping functionality of the GDK toolkit:
  554. To this end, the clip origin is set to the position of the sprite, and
  555. the clip bitmask is set from the \var{FBitmap}, which is created when the
  556. sprite's pixmap is created. When drawing the pixmap, only the bits in the
  557. bitmap will be drawn, other bits are left untouched.
  558. The pixmap is drawn using the \var{gdk\_draw\_pixmap} function. This
  559. function copies a region from one \var{TGDKDrawable} to another.
  560. It is defined as follows:
  561. \begin{lstlisting}{}
  562. procedure gdk_draw_pixmap(drawable:PGdkDrawable; gc:PGdkGC;
  563. src:PGdkDrawable;
  564. xsrc,ysrc,xdest,ydest,width,height:gint);
  565. \end{lstlisting}{}
  566. The function, as all GDK drawing functions, takes a \var{PGDKDrawable}
  567. pointer and a graphics contexts as its first two arguments. The third
  568. argument is the \var{TGDKDrawable} which should be copied. The
  569. \var{xsrc,ysrc} parameters indicate the position of the region that should
  570. be copied in the source \var{TGDKDrawable}; the \var{xdest,ydest} indicate
  571. the position in the target \var{TGDKDrawable} where the bitmap should be
  572. drawn.
  573. In the case of \var{TSprite}, the function is used to copy the sprite's
  574. bitmap onto the memory pixmap with the game image. After the bitmap was
  575. copied, the clip mask is removed again.
  576. The creation of the pixmap happens when the sprite is drawn for the first
  577. time; The \var{CreateSpriteFromData} method accepts a pointer to an XPM
  578. pixmap, and uses the \var{gdk\_pixmap\_create\_from\_xpm\_d} function
  579. (explained in the previous article) to create the actual pixmap and the
  580. corresponding bitmap.
  581. \begin{lstlisting}{}
  582. Procedure TSprite.CreateSpriteFromData(SpriteData : PPGChar);
  583. begin
  584. FPixMap:=gdk_pixmap_create_from_xpm_d(FDrawingArea^.Window,
  585. @FBitmap,
  586. Nil,
  587. SpriteData);
  588. end;
  589. \end{lstlisting}{}
  590. This method can be used by the descendent object's \var{CreatePixmap}
  591. procedure.
  592. The \var{SavePosition} and \var{GetChangeRect} methods are very
  593. straightforward:
  594. \begin{lstlisting}{}
  595. Function TSprite.GetChangeRect (Var Rect : TGDkRectAngle) : Boolean;
  596. begin
  597. Result:=(FPreviousLeft<>Left) or (FPreviousTop<>Top);
  598. If Result then
  599. With Rect do
  600. begin
  601. x:=FPreviousLeft;
  602. y:=FPreviousTop;
  603. Width:=Abs(Left-FPreviousLeft)+self.Width;
  604. height:=Abs(Top-FPreviousTop)+self.Height;
  605. end;
  606. end;
  607. Procedure TSprite.SavePosition;
  608. begin
  609. FPreviousLeft:=Left;
  610. FPreviousTop:=Top;
  611. end;
  612. \end{lstlisting}{}
  613. Note that the \var{GetChangeRect} procedure returns false if the position
  614. of the sprite didn't change. This is used for optimization purposes.
  615. The pad is the simplest of the two \var{TSprite} descendents. It only adds a
  616. horizontal movement to the sprite:
  617. \begin{lstlisting}{}
  618. TPad = Class (TSprite)
  619. Private
  620. FSlope,
  621. FSpeed,FCurrentSpeed : Integer;
  622. Protected
  623. Procedure CreatePixMap; override;
  624. Procedure InitialPosition;
  625. Public
  626. Constructor Create(DrawingArea: PGtkWidget);
  627. Procedure Step;
  628. Procedure GoLeft;
  629. Procedure GoRight;
  630. Procedure Stop;
  631. Property CurrentSpeed : Integer Read FCurrentSpeed;
  632. Property Speed : Integer Read FSpeed Write FSpeed;
  633. Property Slope : Integer Read FSlope Write FSlope;
  634. end;
  635. \end{lstlisting}{}
  636. The procedures \var{GoLeft}, \var{GoRight} and \var{Stop} can be used to
  637. control the movement of the pad. The method \var{Step} will be called at
  638. regular intervals to actually move the pad. The \var{InitialPosition}
  639. sets the pad at its initial position on the screen. The \var{Speed} and
  640. \var{Slope} properties can be used to set the speed and slope of the pad.
  641. The \var{Speed} is a number of pixels the pad will move per time unit.
  642. The 'Slope' is a positive number.
  643. The implementation is quite straightforward:
  644. \begin{lstlisting}{}
  645. Constructor TPad.Create(DrawingArea: PGtkWidget);
  646. begin
  647. Inherited Create(DrawingArea);
  648. FSpeed:=6;
  649. FSlope:=50;
  650. end;
  651. Procedure TPad.InitialPosition;
  652. begin
  653. Left:=(FDrawingArea^.Allocation.Width-Width) div 2;
  654. Top:=FDrawingArea^.Allocation.Height-(2*Height);
  655. FCurrentSpeed:=0;
  656. end;
  657. \end{lstlisting}{}
  658. The \var{InitialPosition} is used to reset the pad to its initial position
  659. when the game starts, after a ball is lost or when a new level starts.
  660. The various moving procedures do nothing except manipulate the current speed.
  661. The handling here is quite simple, more complex handling (accelleration and
  662. so on) coul be handled.
  663. \begin{lstlisting}{}
  664. Procedure TPad.GoLeft;
  665. begin
  666. FCurrentSpeed:=-FSpeed;
  667. end;
  668. Procedure TPad.GoRight;
  669. begin
  670. FCurrentSpeed:=FSpeed;
  671. end;
  672. Procedure TPad.Stop;
  673. begin
  674. FCurrentSpeed:=0;
  675. end;
  676. \end{lstlisting}{}
  677. The pixmap for the pad is defined in a global constant \var{PadBitmap}. It is
  678. an array of \var{PCHar} which make up a XPM pixmap. The height and width of
  679. the bitmap are defined in global constants \var{PadHeight} and \var{PadWidth}
  680. \begin{lstlisting}{}
  681. Procedure TPad.CreatePixMap;
  682. begin
  683. CreateSpriteFromData(@PadBitmap[1]);
  684. Width:=PadWidth;
  685. Height:=PadHeight;
  686. InitialPosition;
  687. end;
  688. \end{lstlisting}{}
  689. The \var{Step} method does the actual moving of the pad. It is called at regular intervals
  690. by a timer. It saves the current position, and calculates the new position. A check is
  691. done for the boundaries of the game.
  692. \begin{lstlisting}{}
  693. Procedure TPad.Step;
  694. begin
  695. SavePosition;
  696. Left:=Left+FCurrentSpeed;
  697. if Left<=0 then
  698. begin
  699. FCurrentSpeed:=-FCurrentSpeed;
  700. Left:=0;
  701. end
  702. else if Left+Width>=FDrawingArea^.allocation.width then
  703. begin
  704. FCurrentSpeed:=-FCurrentSpeed;
  705. Left:=FDrawingArea^.allocation.width-Width;
  706. end;
  707. end;
  708. \end{lstlisting}{}
  709. The implementation of the \var{Tball} class is similar to the one of the \var{TPad},
  710. only it introduces also a vertical speed. The speed of the ball is determined by 3
  711. numbers:
  712. \begin{enumerate}
  713. \item A horizontal speed.
  714. \item A vertical speed.
  715. \item A speed factor. (a number between 0 and 100)
  716. \end{enumerate}
  717. The sum of the absolute values of the vertical and horizontal speeds is always 100.
  718. To change the speed direction, the horizontal speed can be set to a value between 0
  719. and 90. This means that the ball can never fly horizontally. The actual speed is
  720. determined by multiplying the horizontal speed and vertical speed with a speed
  721. factor. The 2 values that are obtained like that are the actual horizontal and
  722. vertical speed of the ball.
  723. All this is implemented in the following class:
  724. \begin{lstlisting}{}
  725. TBall = Class (TSprite)
  726. Private
  727. FBreakOut : TBreakOut;
  728. FCurrentSpeedX,
  729. FCurrentSpeedY : Integer;
  730. FSpeedfactor : Integer;
  731. Protected
  732. Procedure CreatePixMap; override;
  733. Procedure SetSpeed(Value : Integer);
  734. Public
  735. Constructor Create(BreakOut : TBreakOut);
  736. Procedure Step;
  737. Procedure IncSpeed (Value: Integer);
  738. Procedure FlipSpeed (FlipX,FlipY : Boolean);
  739. Property CurrentSpeedX : Integer Read FCurrentSpeedX Write SetSpeed;
  740. Property CurrentSpeedY : Integer Read FCurrentSpeedY;
  741. Property SpeedFactor : Integer Read FSpeedFactor Write FSpeedFactor;
  742. end;
  743. \end{lstlisting}{}
  744. The \var{FlipSpeed} method is used to change the ball's direction when it hits a brick
  745. or one of the borders of the game. The \var{IncSpeed} method increases the speed of the
  746. ball.
  747. As usual, the implementation of these methods is quite straightforward;
  748. \begin{lstlisting}{}
  749. Constructor TBall.Create(BreakOut : TBreakOut);
  750. begin
  751. Inherited Create(BreakOut.FDrawingArea);
  752. FBreakOut:=breakout;
  753. FCurrentSpeedY:=-100;
  754. FCurrentSpeedX:=0;
  755. FSpeedFactor:=10;
  756. end;
  757. \end{lstlisting}
  758. The CreatePixmap uses the global constant \var{BallPixmap} to
  759. create the pixmap. The with and height are stored in the \var{BallWidth}
  760. and \var{BallHeight} constants.
  761. \begin{lstlisting}{}
  762. Procedure TBall.CreatePixMap;
  763. begin
  764. CreateSpriteFromData(@BallBitmap[1]);
  765. Width:=BallWidth;
  766. Height:=BallHeight;
  767. end;
  768. \end{lstlisting}
  769. The SetSpeed value is the write handler for the \var{CurrentSpeedX} property.
  770. It makes sure that the value stays within certain bounds, and that the sum
  771. of the horizontal and vertical speeds remains 100.
  772. \begin{lstlisting}{}
  773. Procedure TBall.SetSpeed(Value : Integer);
  774. begin
  775. If Value<-FMaxXspeed then
  776. Value:=-FMaxXSpeed
  777. else if Value>FMaxXspeed then
  778. Value:=FMaxXspeed;
  779. FCurrentSpeedX:=Value;
  780. If FCurrentSpeedY>0 then
  781. FCurrentSpeedY:=100-Abs(FCurrentSpeedX)
  782. else
  783. FCurrentSpeedY:=-100+Abs(FCurrentSpeedX);
  784. end;
  785. \end{lstlisting}
  786. The \var{IncSpeed} procedure increases or decreases the speed of the ball,
  787. making sure it doesn't get smaller as 10.
  788. \begin{lstlisting}{}
  789. Procedure TBall.IncSpeed (Value: Integer);
  790. begin
  791. FSpeedFactor:=FSpeedFactor+Value;
  792. If FSpeedFactor<10 then
  793. FSpeedFactor:=10;
  794. end;
  795. Procedure TBall.FlipSpeed (FlipX,FlipY : Boolean);
  796. begin
  797. If FlipX then
  798. FCurrentSpeedX:=-FCurrentSpeedX;
  799. If FlipY then
  800. FCurrentSpeedY:=-FCurrentSpeedY;
  801. end;
  802. \end{lstlisting}
  803. The last method of \var{TBall} is the \var{Step} method,
  804. which moves the ball on the screen. It adjusts the speed when the ball hits the
  805. border of the game area, and calls the \var{TBreakOut.LostBall} method
  806. when the ball hits the bottom of the game area.
  807. \begin{lstlisting}{}
  808. Procedure TBall.Step;
  809. begin
  810. SavePosition;
  811. Left :=Left + Round((FCurrentSpeedX*FSpeedFactor/100));
  812. Top :=Top + Round((FCurrentSpeedY*FSpeedFactor/100));
  813. if Left<=1 then
  814. begin
  815. FlipSpeed(True,False);
  816. Left:=1;
  817. end
  818. else if Left+Width>=FDrawingArea^.allocation.width then
  819. begin
  820. FlipSpeed(True,False);
  821. Left:=FDrawingArea^.allocation.width-Width-1;
  822. end;
  823. if Top<=1 then
  824. begin
  825. FlipSpeed(False,True);
  826. Top:=1;
  827. end
  828. else if Top+Height>=FDrawingArea^.allocation.Height then
  829. FBreakOut.LostBall
  830. end;
  831. \end{lstlisting}
  832. \section{Game logic}
  833. The previous objects were concerned with the grapical representation of the
  834. game. The logic of the game is concentrated in 2 other objects: \var{TBlockList},
  835. which manages the blocks in the game, and \var{TBreakOut}, which implements the
  836. game logic.
  837. The \var{TBlockList} class is a simple descendent of \var{TList}:
  838. \begin{lstlisting}{}
  839. TBlockList = Class (TList)
  840. FTotalRows,FTotalColums,FStartRow,FBlockRows,FSpacing : Byte;
  841. FBreakOut : TBreakOut;
  842. FColor : PGDKColor;
  843. Function DRawingArea : PGTKWidget;
  844. FPixMap : PGDKPixmap;
  845. Public
  846. Constructor Create(BreakOut : TBreakOut);
  847. Destructor Destroy; override;
  848. Procedure CheckCollision (Ball: TBall);
  849. Procedure DrawBlocks;
  850. Procedure DrawBlocks(Const Area : TGdkRectangle);
  851. Procedure CreateBlocks;
  852. Procedure FreeBlocks;
  853. Property TotalRows : Byte Read FTotalRows Write FTotalRows;
  854. Property TotalColumns : Byte Read FTotalColums Write FTotalColums;
  855. Property StartRow : Byte Read FStartRow Write FStartRow;
  856. Property BlockRows : Byte Read FBlockRows Write FBlockRows;
  857. Property BlockSpacing : Byte Read FSpacing Write FSpacing;
  858. Property PixMap : PGDKPixMap Read FPixMap Write FPixMap;
  859. end;
  860. \end{lstlisting}
  861. It introduces some properties which control the look of the game:
  862. \var{TotalRows}, \var{TotalColumns} is the total number of columns
  863. and rows in which blocks can be placed. \var{StartRow} and \var{BlockRows}
  864. determines how many blocks are actually placed. \var{BlockSpacing} determines
  865. the amount of space between the blocks. The \var{CheckCollision} determines
  866. whether a ball has hit one of the blocks. The \var{DrawBlocks} draws only the blocks
  867. that intersect with the rectangle defined in the \var{Area} parameter.
  868. The other methods are self explaining.
  869. The implementation of the \var{TBlockList} class is -as usual- quite simple:
  870. \begin{lstlisting}{}
  871. Constructor TBlockList.Create(BreakOut : TBreakOut);
  872. begin
  873. FBreakOut:=BreakOut;
  874. end;
  875. Function TBlockList.DrawingArea : PGtkWidget;
  876. begin
  877. Result:=FBreakOut.FDrawingArea;
  878. end;
  879. Destructor TBlockList.Destroy;
  880. begin
  881. If FColor<>Nil then
  882. FreeMem(FColor);
  883. FreeBlocks;
  884. end;
  885. Procedure TBlockList.DrawBlocks;
  886. Var
  887. I : Longint;
  888. begin
  889. If Count=0 then
  890. CreateBlocks;
  891. For I:=0 to Count-1 do
  892. TBlock(Items[i]).draw;
  893. end;
  894. Procedure TBlockList.DrawBlocks (Const Area : TGdkRectangle);
  895. Var
  896. i : longint;
  897. inters : TgdkRectangle;
  898. begin
  899. For I:=0 to Count-1 do
  900. With TBlock(Items[i]) do
  901. FNeedRedraw:=gdk_rectangle_intersect(@area,@Frect,@inters)<>0;
  902. DrawBlocks;
  903. end;
  904. \end{lstlisting}
  905. The \var{gdk\_rectangle\_interset} returns 0 if 2 rectangles do not intersect,
  906. and returns a nonzero constant if they do. If they do, the last parameter
  907. is filled with the position and size of the intersecting rectangle.
  908. \begin{lstlisting}{}
  909. Procedure TBlockList.FreeBlocks;
  910. Var
  911. I : longint;
  912. begin
  913. For I:=Count-1 downto 0 do
  914. begin
  915. TBlock(Items[i]).Free;
  916. Delete(i);
  917. end;
  918. end;
  919. \end{lstlisting}
  920. The \var{CreateBlocks} method creates the blocks and draws them on the screen.
  921. It is called when the blocklist is drawn and there are no blocks.
  922. The algoritthm to color and place the blocks is quite simple, but a more
  923. complex algorithm that implements patterns of blocks depending on the
  924. level, and different colors for blocks could be implemented.
  925. \begin{lstlisting}{}
  926. Procedure TBlockList.CreateBlocks;
  927. Var
  928. TotalHeight,TotalWidth,
  929. Cellheight,CellWidth,
  930. I,J : Integer;
  931. Block : TBlock;
  932. Min : Byte;
  933. begin
  934. FColor:=AllocateColor(0,0,$ffff,DrawingArea);
  935. Min:=FSpacing div 2;
  936. If Min<1 then
  937. Min:=1;
  938. TotalWidth:=Drawingarea^.Allocation.Width;
  939. TotalHeight:=DrawingArea^.Allocation.Height;
  940. Cellheight:=TotalHeight Div TotalRows;
  941. CellWidth:=TotalWidth div TotalColumns;
  942. For I:=StartRow to StartRow+BlockRows-1 do
  943. For J:=0 to TotalColumns-1 do
  944. begin
  945. Block:=TBlock.Create(Self);
  946. With Block do
  947. begin
  948. Top:=TotalHeight-(CellHeight*I)+Min;
  949. Left:=(CellWidth*J)+min;
  950. Width:=CellWidth-2*min;
  951. Height:=CellHeight-2*min;
  952. Color:=Self.FColor;
  953. FNeedRedraw:=True;
  954. end;
  955. add(Block);
  956. end;
  957. end;
  958. \end{lstlisting}
  959. The checkcollision function checks all blocks to see whether it collides with the ball.
  960. If so, it flips the speed of the ball and calls the balls \var{Hit} function. This will
  961. remove the ball from the list if it is destroyed.
  962. Note that the flipping of the speed of the ball checks where the ball came from, i.e.
  963. looks at the previous position of the ball.
  964. \begin{lstlisting}{}
  965. Procedure TBlockList.CheckCollision (Ball: TBall);
  966. var
  967. brect,ints : tgdkrectangle;
  968. B : TBlock;
  969. i : integer;
  970. flipx,flipy : Boolean;
  971. begin
  972. For I:=Count-1 downto 0 do
  973. begin
  974. B:=TBlock(Items[i]);
  975. BRect:=B.FRect;
  976. if gdk_rectangle_intersect(@Ball.Frect,@BRect,@ints)<>0 then
  977. begin
  978. FlipY:=((Ball.FpreviousTop>=(B.Top+B.Height)) and
  979. (Ball.CurrentSpeedY<0)) or
  980. ((Ball.FpreviousTop+Ball.Height<=B.Top) and
  981. (Ball.CurrentSpeedY>0));
  982. FlipX:=Not FlipY;
  983. If FlipX then
  984. FlipX:=((Ball.FPreviousLeft>=(B.Left+B.Width)) and
  985. (Ball.CurrentSpeedX<0)) or
  986. (((Ball.FPreviousLeft+Ball.Width)<=B.Left) and
  987. (Ball.CurrentSpeedX>0));
  988. Ball.FlipSpeed(FlipX,Flipy);
  989. if B.Hit and not (Count=0) then
  990. gtk_widget_draw(DrawingArea,@BRect);
  991. Break;
  992. end;
  993. end;
  994. end;
  995. \end{lstlisting}
  996. Finally, the \var{TBreakOut} class encapsulates the rest of the game logic. Its declaration
  997. is as follows:
  998. \begin{lstlisting}{}
  999. TBreakOut = Class(TObject)
  1000. Private
  1001. FLevel : Integer;
  1002. FBalls : Integer;
  1003. FBGGC : PGDKGC;
  1004. FBackGroundColor : PGDKColor;
  1005. FPad : TPad;
  1006. FBall : TBall;
  1007. FBlockList : TBlockList;
  1008. FDrawingArea : PGTKWidget;
  1009. FPixMap : PGDKPixMap;
  1010. Procedure DrawBackGround (Area : TGdkrectAngle);
  1011. Procedure DrawBoard(Exposed : PGdkEventExpose);
  1012. Procedure CreateGC;
  1013. Procedure CreatePixMap;
  1014. Procedure CopyPixMap(Area : TGdkRectangle);
  1015. Procedure CheckCollision;
  1016. Procedure FreeBall;
  1017. Procedure NextLevel;
  1018. Procedure NextBall;
  1019. Procedure GameOver;
  1020. Procedure LostBall;
  1021. Procedure Redrawgame;
  1022. Public
  1023. Constructor Create (DrawingArea : PGtkWidget);
  1024. Procedure Draw(Exposed : PGDKEventExpose);
  1025. Procedure Step;
  1026. Property BlockList : TBlockList Read FBlockList;
  1027. Property Pad : TPad Read FPad;
  1028. Property Level : Integer Read Flevel;
  1029. Property Balls : Integer Read FBalls Write FBalls;
  1030. end;
  1031. \end{lstlisting}
  1032. The purpose of most of the methods of \var{TBreakOut} is self-evident. The \var{Draw}
  1033. method will be called when the drawing area on which the game is drawn is exposed.
  1034. The \var{Step} method will be called by a timer routine, and this will move all pieces
  1035. in the game, creating the illusion of movement. These are the only 2 public routines
  1036. of the component.
  1037. The constructor simply initializes the Ball and blocklist components. It does not
  1038. create a ball, this will be created when the ball enters the game.
  1039. \begin{lstlisting}{}
  1040. Constructor TBreakOut.Create (DrawingArea : PGtkWidget);
  1041. begin
  1042. FDrawingArea:=DrawingArea;
  1043. FBlockList:=TBlockList.Create (Self);
  1044. FPad:=TPad.Create(FDrawingArea);
  1045. FBalls:=5;
  1046. end;
  1047. \end{lstlisting}
  1048. The following routines are mainly concerned with the drawing of the various parts of the game.
  1049. \begin{lstlisting}{}
  1050. Procedure TBreakOut.DrawBoard(Exposed : PGdkEventExpose);
  1051. begin
  1052. If FBGGC=Nil then
  1053. CreateGC;
  1054. DrawBackGround(Exposed^.Area);
  1055. end;
  1056. Procedure TBreakOut.CreateGC;
  1057. begin
  1058. FBGGC:=gdk_gc_new(FDrawingArea^.Window);
  1059. FBackGroundColor:=AllocateColor(0,0,0,FDrawingArea);
  1060. gdk_gc_set_foreground(FBGGC,FBackGroundColor);
  1061. gdk_gc_set_fill(FBGGC,GDK_SOLID);
  1062. end;
  1063. \end{lstlisting}
  1064. The graphics context is needed for the drawing of the background of the game;
  1065. it sets the drawing color to black and the fill style to solid. The graphics
  1066. context is then used in the \var{DrawBackground} method to draw the background
  1067. on the pixmap with the game image:
  1068. \begin{lstlisting}{}
  1069. Procedure TBreakOut.DrawBackGround (Area : TGdkrectAngle);
  1070. begin
  1071. With Area do
  1072. gdk_draw_rectangle(PGDKDrawable(FPixMap),FBGGC,-1,x,y,Width+1,Height+1);
  1073. end;
  1074. \end{lstlisting}
  1075. The pixmap that contains the game image is created the first time the breakout
  1076. game is drawn. It is created using the \var{gdk\_pixmap\_new} function, which
  1077. needs a \var{PGDKwindow} as the first parameter; from this window certain
  1078. device properties are copied.
  1079. After the pixmap is created, it is assigned to the pad and blocklist objects.
  1080. \begin{lstlisting}{}
  1081. Procedure TBreakOut.CreatePixMap;
  1082. begin
  1083. If FPixMap<>Nil then
  1084. GDK_pixmap_unref(FPixMap);
  1085. With FDrawingArea^ do
  1086. FPixMap:=gdk_pixmap_new(Window,Allocation.Width,Allocation.Height,-1);
  1087. FBlockList.PixMap:=FPixMap;
  1088. FPad.FDrawPixMap:=FPixMap;
  1089. If Assigned(FBall) then
  1090. FBall.FDrawPixMap:=FPixMap;
  1091. end;
  1092. \end{lstlisting}
  1093. The following routine does the actual drawing of the screen:
  1094. It copies the pixmap with the game image to the actual window.
  1095. Not the whole pixmap is drawn (this would be very inefficient),
  1096. but just the part indicated by the \var\var{Area} parameter.
  1097. \begin{lstlisting}{}
  1098. Procedure TBreakOut.CopyPixMap(Area : TGdkRectangle);
  1099. begin
  1100. gdk_draw_pixmap(FDrawingArea^.Window,
  1101. gtk_widget_get_style(FDrawingArea)^.fg_gc[GTK_WIDGET_STATE(FDrawingArea)],
  1102. FPixMap,
  1103. area.x,area.y,
  1104. area.x,area.y,
  1105. area.width,area.height);
  1106. end;
  1107. \end{lstlisting}
  1108. The \var{CopyPixmap} method is called as much as needed
  1109. by the \var{Draw} method. This method tries to determine
  1110. the minimum amount of drawing needed to restore the game image on the screen.
  1111. It will draw the board, the exposed blocks, the previous position of
  1112. the ball and pad on the pixmap. After that the changed portions of
  1113. the pixmap are copied to the screen.
  1114. \begin{lstlisting}{}
  1115. Procedure TBreakOut.Draw(Exposed : PGDKEventExpose);
  1116. Var
  1117. Rect : TGdkRectangle;
  1118. begin
  1119. if FPixMap=Nil then
  1120. CreatePixMap;
  1121. if Exposed<>Nil then
  1122. begin
  1123. DrawBoard(Exposed);
  1124. FBlockList.DrawBlocks(exposed^.area)
  1125. end
  1126. else
  1127. begin
  1128. If Assigned(FBall) then
  1129. if FBall.GetChangeRect(Rect) then
  1130. begin
  1131. DrawBackground(Rect);
  1132. FBLockList.drawBlocks(Rect);
  1133. end;
  1134. if FPad.GetChangeRect(Rect) then
  1135. DrawBackground(Rect)
  1136. end;
  1137. FPad.Draw;
  1138. if Assigned(FBall) Then
  1139. FBall.draw;
  1140. If Exposed<>Nil then
  1141. CopyPixMap(Exposed^.Area);
  1142. If assigned(FBall) then
  1143. if FBall.GetChangeRect(Rect) then
  1144. CopyPixMap(Rect);
  1145. if FPad.GetChangeRect(Rect) then
  1146. CopyPixMap(Rect);
  1147. IF Assigned(FBall) then
  1148. CopyPixMap(FBall.FRect);
  1149. CopyPixMap(FPad.FRect);
  1150. end;
  1151. \end{lstlisting}
  1152. The \var{RedrawGame} forces a redraw of the whole game, by forcing an expose event on the
  1153. drawing area:
  1154. \begin{lstlisting}{}
  1155. Procedure TBreakout.Redrawgame;
  1156. Var
  1157. Rect : TgdkRectangle;
  1158. begin
  1159. Rect.X:=FDrawingArea^.allocation.x;
  1160. Rect.Y:=FDrawingArea^.allocation.y;
  1161. Rect.Width:=FDrawingArea^.allocation.Width;
  1162. Rect.Height:=FDrawingArea^.allocation.Height;
  1163. gtk_Widget_draw(FDrawingArea,@rect)
  1164. end;
  1165. \end{lstlisting}
  1166. The \var{Step} procedure is the central part of the game logic: it moves
  1167. the various components on the screen, and checks for collisions between
  1168. the ball and the pad or the blocks. If a 'game over' or 'end of level'
  1169. condition is detected, the appropriate methods are called to handle
  1170. these situations.
  1171. \begin{lstlisting}{}
  1172. Procedure TBreakOut.Step;
  1173. begin
  1174. FPad.Step;
  1175. If Assigned(FBall) then
  1176. FBall.Step;
  1177. CheckCollision;
  1178. If FBlockList.Count=0 then
  1179. NextLevel;
  1180. if Not Assigned(FBall) and (FBalls=0) then
  1181. GameOver;
  1182. end;
  1183. \end{lstlisting}
  1184. The \var{CheckCollision} method checks for collisions of the ball with the pad
  1185. or with a block. The blocklist handles the collisions with a block, the collision
  1186. between the ball and the pad is handled here, in much the same was as it was handled
  1187. by the blocklist for the blocks. The only difference is that the speed of the ball
  1188. is altered, depending on the speed of the pad:
  1189. \begin{enumerate}
  1190. \item If the pad was moving at the moment of impact, then the speedfactor of
  1191. the ball is increased or decreased, depending on whether the ball and pad
  1192. were moving in the same direction, or in opposite directions.
  1193. \item The angle of the ball is altered using the \var{Slope} of the pad. The horizontal
  1194. component of the speed is increased (or decreased) with a factor that depends on
  1195. the place where the ball hits the pad. If the pad is hit in the middle, no change takes
  1196. place. If it is not hit in the middle, the alteration is proportional to the distance
  1197. between the middle of the pad and the point of impact.
  1198. \end{enumerate}
  1199. \begin{lstlisting}{}
  1200. Procedure TBreakOut.CheckCollision;
  1201. Var
  1202. Inters :TGdkrectangle;
  1203. begin
  1204. If Assigned(FBall) then
  1205. begin
  1206. if gdk_rectangle_intersect(@FBall.FRect,@FPad.Frect,@inters)<>0 then
  1207. If (FBall.FPreviousTop<FPad.Top) and (FBall.FCurrentSpeedY>0) then
  1208. begin
  1209. FBall.FlipSpeed(False,True);
  1210. If (FPad.CurrentSpeed<>0) then
  1211. if (FBall.FCurrentSpeedX*FPad.CurrentSpeed)>0 then
  1212. FBall.IncSpeed(HitAccelleration)
  1213. else
  1214. FBall.IncSpeed(-HitAccelleration);
  1215. FBall.CurrentSpeedX:=FBall.CurrentSpeedX+
  1216. (Round(((FBall.Left+(FBall.Width div 2)) -
  1217. (FPad.left+Fpad.Width div 2))
  1218. * (FPad.Slope / 100)));
  1219. end;
  1220. FBlockList.CheckCollision(FBall);
  1221. end;
  1222. end;
  1223. \end{lstlisting}
  1224. The following methods control the logic of the game. They are kept as simple
  1225. as possible, but they can be altered to make the game more interesting or
  1226. visually attractive.
  1227. \begin{lstlisting}{}
  1228. Procedure TBreakOut.FreeBall;
  1229. begin
  1230. FBall.Free;
  1231. FBall:=Nil;
  1232. end;
  1233. Procedure TbreakOut.NextBall;
  1234. begin
  1235. If FBall=Nil then
  1236. begin
  1237. FBall:=TBall.Create(Self);
  1238. FBall.Top:=FPad.Top-1;
  1239. FBall.Left:=FPad.Left + (FPad.Width div 2);
  1240. FBall.CurrentSpeedX:=FPad.CurrentSpeed*5;
  1241. FBall.FPreviousTop:=FBall.Top;
  1242. FBall.FPreviousLeft:=FBall.Left;
  1243. FBall.FDrawPixMap:=Self.FPixMap;
  1244. FBall.Draw;
  1245. end;
  1246. end;
  1247. Procedure TBreakOut.NextLevel;
  1248. Var
  1249. Area : TGdkRectangle;
  1250. begin
  1251. If Assigned(FBall) then
  1252. FreeBall;
  1253. FPad.FSpeed:=FPad.Speed+LevelAccelleration;
  1254. FPad.InitialPosition;
  1255. RedrawGame;
  1256. end;
  1257. Procedure TBreakout.LostBall;
  1258. begin
  1259. Dec(FBalls);
  1260. If FBalls=0 then
  1261. GameOver;
  1262. FreeBall;
  1263. Fpad.InitialPosition;
  1264. RedrawGame;
  1265. end;
  1266. Procedure TBreakout.GameOver;
  1267. begin
  1268. end;
  1269. \end{lstlisting}
  1270. All the code for these three objects is collected in the unit \file{blocks}.
  1271. The main program uses the \var{TBreakOut} object to draw the game on a screen:
  1272. A simple, non-sizable window is created, and a \var{TGTKDrawingArea} widget is
  1273. dropped on it. A signal handler for the expose event of the widget is installed
  1274. (the \var{Exposed} function), as well as a timeout which will step the game
  1275. every 50 milliseconds (the \var{Step} function). After that, event handlers
  1276. are installed for the keyboard, to the user can move the pad
  1277. (the \var{KeyPress} function). The 'delete' event is also handled, to destroy the
  1278. window (the \var{Close} function).
  1279. The only logic in these functions consists of communicating the events to the
  1280. \var{TBreakout} object, and to set the movement of the Pad based on the key
  1281. that was hit. The program listing is presented without further comment.
  1282. \begin{lstlisting}{}
  1283. program breakout;
  1284. {$mode objfpc}
  1285. uses glib,gdk,gtk,blocks;
  1286. Type
  1287. TBreakOutWindow = Class(TObject)
  1288. Public
  1289. window,
  1290. area : PGtkWidget;
  1291. BreakOut : TBreakOut;
  1292. end;
  1293. Var
  1294. GameWindow : TBreakOutWindow;
  1295. Function Close( widget : PGtkWidget ;
  1296. event : PGdkEvent;
  1297. data : gpointer) : boolean; cdecl;
  1298. Begin
  1299. gtk_main_quit();
  1300. Close := false;
  1301. End;
  1302. function Exposed(Widget: PGtkWidget;
  1303. event : PGdkEventExpose;
  1304. Data : gpointer) : Integer; cdecl;
  1305. begin
  1306. TBreakOutWindow(Data).BreakOut.Draw(Event);
  1307. result:=0;
  1308. end;
  1309. function KeyPress (Widget: PGtkWidget;
  1310. event : PGdkEventKey;
  1311. Data : gpointer) : Integer; cdecl;
  1312. begin
  1313. with TBreakOutWindow(Data).BreakOut do
  1314. Case event^.keyval of
  1315. gdk_left : Pad.Goleft;
  1316. gdk_right : Pad.GoRight;
  1317. gdk_down : Pad.Stop;
  1318. Ord(' ') : NextBall;
  1319. end;
  1320. Result:=0;
  1321. end;
  1322. function Step (data : Gpointer): integer;cdecl;
  1323. Var
  1324. Rect : TGdkRectangle;
  1325. begin
  1326. With TBreakOutWindow(Data) do
  1327. begin
  1328. With Breakout do
  1329. begin
  1330. Step;
  1331. Draw(Nil);
  1332. end;
  1333. end;
  1334. Result:=integer(True);
  1335. end;
  1336. Begin
  1337. gtk_init( @argc, @argv );
  1338. GameWindow:=TBreakOutWindow.Create;
  1339. With GameWindow do
  1340. begin
  1341. window := gtk_window_new( GTK_WINDOW_TOPLEVEL );
  1342. gtk_window_set_policy(PgtkWindow(Window),0,0,1);
  1343. gtk_signal_connect(PGTK_OBJECT (window),'delete_event',
  1344. GTK_SIGNAL_FUNC(@Close),Nil);
  1345. gtk_container_set_border_width (GTK_CONTAINER (window), 10);
  1346. area := gtk_drawing_area_new();
  1347. gtk_container_add( GTK_CONTAINER(window), Area);
  1348. BreakOut:=TBreakOut.Create(area);
  1349. With BreakOut.BlockList do
  1350. begin
  1351. TotalRows:=20;
  1352. TotalColumns:=10;
  1353. StartRow:=15;
  1354. BlockRows:=5;
  1355. BlockSpacing:=2;
  1356. end;
  1357. gtk_signal_connect (GTK_OBJECT (area),'expose_event',
  1358. GTK_SIGNAL_FUNC(@Exposed),GameWindow);
  1359. gtk_drawing_area_size (PGTKDRAWINGAREA(area),600,400);
  1360. gtk_widget_set_events(window,GDK_KEY_RELEASE_MASK);
  1361. gtk_signal_connect(PGTKObject(Window),'key_press_event',
  1362. GTK_SIGNAL_FUNC(@KeyPress),GameWindow);
  1363. gtk_timeout_add(50,@Step,GameWindow);
  1364. gtk_widget_show_all(window);
  1365. gtk_main();
  1366. end;
  1367. End.
  1368. end.
  1369. \end{lstlisting}
  1370. The result of the program can be seen in figure \ref{fig:breakout}.
  1371. \begin{figure}[ht]
  1372. \caption{The breakout program in action.}\label{fig:breakout}
  1373. \epsfig{file=gtk5ex/breakout.png,width=\textwidth}
  1374. \end{figure}
  1375. The program can be enhanced in many ways:
  1376. \begin{enumerate}
  1377. \item More different colors for the blocks.
  1378. \item Different patterns of blocks when going to new levels.
  1379. \item Add some messages at the end of a level, or at game over.
  1380. \item Add a pause mode.
  1381. \item Add a menu to start/stop the game, and with some preferences
  1382. (game size, player level)
  1383. \item add a score based on the time it takes to finish a level.
  1384. \end{enumerate}
  1385. And many more things can probably be done. The program as it is now is playable, and
  1386. fulfills it purpose: to demonstrate that simple game programming using the drawing
  1387. facilities offered by GTK/GDK toolkit is possible and can be quite easy.
  1388. \end{document}