Le tecniche di animazione grafica mostrate nell'esempio precedente sono semplici ma piuttosto lente e producono sfarfallamenti nelle immagini.
Si propone qui un miglioramento della grafica dello stesso gioco ricorrendo a tecniche di visualizzazione e di animazione basate sull'uso diretto delle funzioni grafiche di Windows. Nell'editor di Delphi è possibile ottenere una documentazione in linea su queste funzioni cliccando con il mouse sul loro identificatore e premendo F1.
L'intestazione delle funzioni è nello stile del linguaggio C, ma esse sono pienamente compatibili con il Pascal di Delphi.
I componenti visibili (windowed) di Delphi sono intrinsecamente dotati di una risorsa, di tipo TCanvas, in grado di gestire varie operazioni grafiche (tracciati di linee e forme geometriche, riproduzione di caratteri e di bitmap, ecc.).
L'idea di fondo usata in questa nuova versione del progetto PingPong è quella di affiancare alla Canvas della finestra principale due canvas ausiliarie, una per gestire lo sfondo e una per gestire le immagini mobili.
Ogni nuovo 'fotogramma' viene prodotto
Nella canvas dello sfondo va copiata la bitmap di sfondo e la canvas rimane immutata per tutta la durata dell'animazione.
Prima di disegnare un'immagine mobile nella canvas di lavoro, si individua il rettangolo che essa occupa sullo sfondo.
Nel sorgente la canvas dello sfondo è denominata bg_canvas, quella delle immagini è denominata work_canvas.
Poiché queste canvas non sono associate a componenti di Delphi, per ognuna di esse
bisogna creare un supporto 'astratto' compatibile con la canvas visibile:
questi sono identificati con bg_dc e work_dc;
ognuno dei supporti va dotato di Bitmap.
Per gestire bg_canvas bisogna crearla e inizializzarla nella procedura OnCreate con i seguenti passi:
L'ultima istruzione produce la copia dell'immagine nella canvas di sfondo: se è necessario, l'immagine viene distorta in modo da adattarsi al rettangolo dello sfondo.
Passi analoghi vanno fatti per creare e inizializzare work_canvas.
Creare la cartella per il nuovo progetto e copiare in essa le icone della palla e della racchetta.
Come immagine di sfondo si usa il logo di Windows,'installazione.bmp'. Rintracciarlo e copiarlo nella cartella del progetto.
Con Delphi creare una nuova applicazione, preparare la finestra principale e il menù principale come nel progetto precedente.
Le procedure OnCreate e CM_Lancio vanno modificate come mostrato nel sorgente seguente.
Nella sezione var dichiarare tutte le variabili ausiliarie.
unit U_PingPong; {******************************************************************************} interface {******************************************************************************} uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Menus,ExtCtrls; {******************************************************************************} type T_Palla= record x,y,v,theta,vx,vy: Double; xg,yg: Integer; //rettangoli che contengono la figura della palla: rett: attuale, rett0: precedente rett,rett0: TRect; img: TImage; end; T_Racchetta= record xg,yg: Integer; //rettangoli che contengono la figura della racchetta , come sopra rett,rett0: TRect; img: TImage; end; TF_PingPong = class(TForm) M_Principale: TMainMenu; M_Lancio: TMenuItem; M_Velocita: TMenuItem; procedure CM_Lancio(Sender: TObject); procedure CM_Velocita(Sender: TObject); procedure CM_OnCreate(Sender: TObject); procedure CM_OnPaint(Sender: TObject); private public palla: T_Palla; racchetta: T_Racchetta; end; {******************************************************************************} var F_PingPong: TF_PingPong; bg_img: TImage;//immagine di sfondo (background) work_canvas, bg_canvas: TCanvas; //canvas in memoria work_mappa_h,bg_mappa_h: HBitmap; //parametri per il controllo delle immagini bg_rett: TRect; //rettangolo dello sfondo bg_dc, work_dc: HDC; //parametri per il controllo delle canvas di sfondo e di lavoro {******************************************************************************} implementation {******************************************************************************} {$R *.DFM} uses Math; {******************************************************************************} procedure TF_PingPong.CM_OnCreate(Sender: TObject); begin //rettangolo di sfondo: tutta la schermata bg_rett := Rect(0,0,ClientWidth,ClientHeight); //creazione dell'oggetto TImage per lo sfondo e caricamento dell'immagine bg_img := TImage.Create(Owner); bg_img.Picture.LoadFromFile('installazione.bmp'); //creazione dell'oggetto TImage per la palla e caricamento dell'immagine palla.img := TImage.Create(Owner); palla.img.Picture.LoadFromFile('palla.ico'); //creazione dell'oggetto TImage per la racchetta e caricamento dell'immagine racchetta.img := TImage.Create(Owner); racchetta.img.Picture.LoadFromFile('racchetta.ico'); //velocità di default per la palla palla.v := 1; //posizione verticale della racchetta racchetta.yg := ClientHeight-16; (* Per gestire l'animazione si preparano due 'canvas' (supporti per la grafica) ausiliarie in memoria: una per lo sfondo (bg_canvas) e una (work_canvas) per gli oggetti mobili (palla e racchetta) Queste canvas non sono associate a componenti visibili: vanno quindi creati dei controlli 'astratti' da attribuire alle due canvas. *) //1. Creazione del controllo dello sfondo basato sulla finestra visibile. bg_dc := CreateCompatibleDC(Canvas.Handle); //2. Creazione dell'oggetto Bitmap per lo sfondo basato sulle dimensioni della finestra visibile. bg_mappa_h := CreateCompatibleBitmap(Canvas.Handle,ClientWidth,ClientHeight); //3. Associazione di sfondo e bitmap relativa SelectObject(bg_dc,bg_mappa_h); SelectPalette(bg_dc,bg_img.Picture.Bitmap.Palette,False); //4. Creazione della canvas per lo sfondo e sua associazione con il controllo dello sfondo. bg_canvas := TCanvas.Create; bg_canvas.Handle := bg_dc; //5. Si disegna sulla canvas di sfondo, adattandola, la bitmap di sfondo bg_canvas.StretchDraw(bg_rett,bg_img.Picture.Bitmap); //Le stesse operazioni fatte per creare lo sfondo, si fanno per il controllo di lavoro work_dc := CreateCompatibleDC(Canvas.Handle); work_mappa_h := CreateCompatibleBitmap(Canvas.Handle,ClientWidth,ClientHeight); SelectObject(work_dc,work_mappa_h); SelectPalette(work_dc,bg_img.Picture.Bitmap.Palette,False); work_canvas := TCanvas.Create; work_canvas.Handle := work_dc; //Si copia la canvas di sfondo nella canvas di lavoro work_canvas.CopyRect(bg_rett,bg_canvas,bg_rett); //si attivano i controlli di sfondo e di lavoro RealizePalette(bg_canvas.Handle); RealizePalette(work_canvas.Handle); //si copia la canvas di lavoro (che contiene lo sfondo) nella finestra visibile Canvas.CopyRect(bg_rett,work_canvas,bg_rett); end; {******************************************************************************} procedure TF_PingPong.CM_Lancio(Sender: TObject); var diff_orizz,diff_vert: Integer; disegnare: Boolean; begin Cursor:= crNone; Randomize; //si copia la canvas di sfondo su quella di lavoro work_canvas.CopyRect(bg_rett,bg_canvas,bg_rett); //si attivano le due canvas ausiliarie RealizePalette(bg_canvas.Handle); RealizePalette(work_canvas.Handle); //si copia nella canvas della finestra visibile la canvas di lavoro Canvas.CopyRect(bg_rett,work_canvas,bg_rett); //parametri iniziali della palla with palla do begin x := 0; y := 0; theta := 0.25*Pi*(1+Random); v := v; vx := v*Cos(theta); vy := v*Sin(theta); xg := 0; yg := 0; rett := Rect(xg,yg,xg+16,yg+16); end; //parametri iniziali della racchetta with racchetta do begin xg := ClientWidth div 2; rett := Rect(xg,yg,xg+32,yg+16); end; //ciclo di animazione repeat with palla do begin disegnare := True; rett0 := rett; //si copia nella canvas di lavoro il quadrato di sfondo sul quale sarà disegnata la palla work_canvas.CopyRect(rett,bg_canvas,rett); //nuova posizione della palla x := x+vx; y := y+vy; //coordinate intere xg := Round(x); yg := Round(y); //se la palla urta le pareti if (xg+16>ClientWidth) then begin disegnare := False; x := ClientWidth-16; vx := -vx end; if (xg<0) then begin disegnare := False; x := 0; vx := -vx end; if (y<0) then begin disegnare := False; y := 0; vy := -vy end; //se la palla urta la racchetta if (yg+17>racchetta.yg) then if (xg+8>racchetta.xg) and (xgClientHeight; //cancellazione della racchetta with racchetta do Canvas.CopyRect(rett,bg_canvas,rett); //visualizzazione del cursore normale Cursor:= crDefault; //messaggio di fine ShowMessage('Hai perso la palla.') end; {******************************************************************************} procedure TF_PingPong.CM_Velocita(Sender: TObject); var aux: String; begin aux := FloatToStr(palla.v); if InputQuery('PingPong','Velocità della palla?',aux) then palla.v := StrToFloat(aux) end; {******************************************************************************} procedure TF_PingPong.CM_OnPaint(Sender: TObject); begin RealizePalette(bg_canvas.Handle); RealizePalette(work_canvas.Handle); Canvas.CopyRect(bg_rett,work_canvas,bg_rett); end; {******************************************************************************} end.