Miglioramento della grafica


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.

 


Canvas ausiliarie

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:

  1. bg_dc := CreateCompatibleDC(Canvas.Handle);
  2. bg_mappa_h := CreateCompatibleBitmap(Canvas.Handle,ClientWidth,ClientHeight);
  3. SelectObject(bg_dc,bg_mappa_h);
  4. SelectPalette(bg_dc,bg_img.Picture.Bitmap.Palette,False);
  5. bg_canvas := TCanvas.Create;
  6. bg_canvas.Handle := bg_dc;
  7. bg_canvas.StretchDraw(bg_rett,bg_img.Picture.Bitmap);

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.

 


Preparazione.

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.


Il sorgente.

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.