* * * *
11 Visitatori, 0 Utenti
Pagine: 1 2 [3] 4 5 ... 28
In questo articolo visioneremo i concetti basilari della programmazione per database con i componenti che permettono la visualizzazione dei contenuti di un campo appartenente al record selezionato di un datasource.

Nell'esempio che andremo a vedere prenderemo in considerazione SQLite come DBMS. La struttura SQL che useremo per il nostro esempio è la seguente.

CREATE TABLE "main"."" (
    "Id" INTEGER PRIMARY KEY AUTOINCREMENT,
    "Cognome" TEXT,
    "Nome" TEXT,
    "Citta" TEXT
);

Lazarus possiede molti package per poter lavorare con i DB, i due sicuramente più usati, mantenuti e testati sono: i componenti standard rilasciati con Lazarus stesso e ZeosLib.
Questo articolo prende in considerazione ZeosLib il quale permette un altissimo grado di astrazione del DBMS, cosa estremamente utile per non vincolare l'applicazione al DBMS. L'esempio che andremo a vedere vi permetterà di acquisire i rudimenti per impadronirvi dell'argomento.

Per prima cosa, creiamo una nuova applicazione tenendo in considerazione quanto imparato nei precedenti articoli sui database, quello che segue è il codice del nostro esempio

Codice: [Seleziona]

unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, db, FileUtil, ZConnection, ZDataset, Forms, Controls,
  Graphics, Dialogs, DBGrids, DbCtrls, StdCtrls;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    DataSource1: TDataSource;
    DBEdit1: TDBEdit;
    DBGrid1: TDBGrid;
    DBMemo1: TDBMemo;
    DBNavigator1: TDBNavigator;
    DBText1: TDBText;
    ZConnection1: TZConnection;
    ZQuery1: TZQuery;
    Q_Inserimento: TZQuery;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    function GetLastId(): integer;
  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
   LastId: integer;
begin
     LastId:=Self.GetLastId()+1;
     Self.Q_Inserimento.SQL.Text:='INSERT INTO utenti(Id, Cognome,Nome,Citta)VALUES(' + IntToStr(LastId) + ',''Cognome' + IntToStr(LastId) + ''',''Nome' + IntToStr(LastId) + ''',''Città' + IntToStr(LastId) + ''');';
     Self.Q_Inserimento.ExecSQL;
     Self.ZQuery1.Refresh;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
     //svuoto la tabella
     Self.Q_Inserimento.SQL.Text:='DELETE FROM utenti;';
     Self.Q_Inserimento.ExecSQL;
     Self.ZQuery1.Refresh;
end;

function TForm1.GetLastId(): integer;
var
   ret: integer;
begin
     ret:=0;
     Self.Q_Inserimento.SQL.Text:='select Id as MaxId from utenti group by Id order by Id desc;';
     Self.Q_Inserimento.Open;
     if not Self.Q_Inserimento.EOF then
     begin
          Self.Q_Inserimento.First;
          ret:=Self.Q_Inserimento.FieldByName('MaxId').AsInteger;
     end;
     Self.Q_Inserimento.Close;
     result:=ret;
end;

end.


ma prendiamo in considerazione le singole parti

Andiamo a vedere la FormCreate, ovvero le azioni che vengono svolte dal programma alla creazione della form, nel nostro caso specifico la prima procedura che viene eseguita dal programma

Codice: [Seleziona]

procedure TForm1.FormCreate(Sender: TObject);
begin
     //svuoto la tabella
     Self.Q_Inserimento.SQL.Text:='DELETE FROM utenti;';
     Self.Q_Inserimento.ExecSQL;
     Self.ZQuery1.Refresh;
end;


come si può vedere andiamo a cancellare il contenuto della tabella usata nell'esempio.

Poi possiamo notare che abbiamo creato la seguente funzione
Codice: [Seleziona]

function TForm1.GetLastId(): integer;
var
   ret: integer;
begin
     ret:=0;
     Self.Q_Inserimento.SQL.Text:='select Id as MaxId from utenti group by Id order by Id desc;';
     Self.Q_Inserimento.Open;
     if not Self.Q_Inserimento.EOF then
     begin
          Self.Q_Inserimento.First;
          ret:=Self.Q_Inserimento.FieldByName('MaxId').AsInteger;
     end;
     Self.Q_Inserimento.Close;
     result:=ret;
end;

che permette di ottenere l'indice massimo contenuto nella griglia.
Nell'evento OnClick del pulsante Button1 andiamo ad inserire il codice che automaticamente aggiunge in maniera incrementale le singole righe della tabella basandosi sul risultato della funzione GetLastId visionata poco sopra.
Codice: [Seleziona]

procedure TForm1.Button1Click(Sender: TObject);
var
   LastId: integer;
begin
     LastId:=Self.GetLastId()+1;
     Self.Q_Inserimento.SQL.Text:='INSERT INTO utenti(Id, Cognome,Nome,Citta)VALUES(' + IntToStr(LastId) + ',''Cognome' + IntToStr(LastId) + ''',''Nome' + IntToStr(LastId) + ''',''Città' + IntToStr(LastId) + ''');';
     Self.Q_Inserimento.ExecSQL;
     Self.ZQuery1.Refresh;
end;


Poi successivamente andiamo ad impostare la proprietà ReadOnly a True nella DbGrid del nostro esempio in modo da non lasciare modificare i dati all'utente dalla DBGrid stessa.
Ora aggiungiamo al seguente esempio 3 componenti

    DBEdit1: TDBEdit;
    DBMemo1: TDBMemo;
    DBText1: TDBText;

Andiamo ad impostare per il componente DBEdit1 le seguenti proprietà:
DataSource: DataSource1
DataField: Nome

Andiamo ad impostare per il componente DBText1 le seguenti proprietà:
DataSource: DataSource1
DataField: Cognome

Andiamo ad impostare per il componente DBMemo1 le seguenti proprietà:
DataSource: DataSource1
DataField: Citta

Se eseguiamo il programma vedremo che in funzione del record su cui ci spostiamo possiamo visionare ma non modificare il campo Nome, mentre possiamo visionare e modificare i campi Cognome e Citta.

Qui trovate il link per scaricare l'esempio dell'articolo: www.lazaruspascal.it/esempi/Quarta_Applicazione.zip
Share on Twitter! Digg this story! Del.icio.us Share on Facebook! Technorati Reddit StumbleUpon

Nel gergo videoludico è chiamato sprite ogni elemento grafico che si muove attraverso lo schermo: i fantasmini di Pac-Man, le navicelle spaziali di Space Invaders, gli omini di Prince of Persia, i boss di fine livello di Metal Slug sono tutti esempi di sprites. Il Nintendo DS è equipaggiato con un sistema (in realtà sono due, lo vedremo tra poco) che li gestisce via hardware, chiamato sprite engine, che risparmia al programmatore il compito di scrivere il codice per disegnarli e muoverli; come rovescio della medaglia, questo sistema non gestisce le collisioni, che vanno perciò controllate via software; è inoltre limitato ad un numero massimo di 128 sprites per motore, dei quali soltanto 32, sempre per motore, possono essere ruotati e scalati.

Gli sprites possono avere una dimensione variabile da 8x8 a 64x64 pixel e forma quadrata o rettangolare, sia con il lato lungo disposto orizzontalmente che verticalmente:

CONSTANT VALUE       SPRITE SIZE
SpriteSize_8x8
 8 x  8
SpriteSize_16x16
16 x 16
SpriteSize_32x32
32 x 32
SpriteSize_64x64
64 x 64
SpriteSize_16x8
16 x  8
SpriteSize_32x8
32 x  8
SpriteSize_32x16
32 x 16
SpriteSize_64x32
64 x 32
SpriteSize_8x16
 8 x 16
SpriteSize_8x32
 8 x 32
SpriteSize_16x32
16 x 32
SpriteSize_32x64
32 x 64


Essi sono composti da una serie di tiles di dimensione 8x8 pixel che vanno immagazzinate in memoria con un procedimento del tutto analogo a quello già visto per i background e, come per i background, possono disporre di 16 palette da 16 colori, oppure di una da 256 colori, oltre alla possibilità di utilizzare una palette estesa.


CONSTANT VALUE       COLOR FORMAT
SpriteColorFormat_16Color
16 colori per sprite
SpriteColorFormat_256Color
256 colori per sprite
SpriteColorFormat_Bmp
16 bit di colore per sprite


La tile di partenza di ogni sprite ha bisogno di essere indicizzata per permettere allo sprite engine di ricomporne l'immagine sullo schermo:


CONSTANT VALUE     
BOUNDARY BETWEEN OFFSETS
SpriteMapping_1D_32
32 bytes
SpriteMapping_1D_64
64 bytes
SpriteMapping_1D_128
128 bytes
SpriteMapping_1D_256
256 bytes (main engine only)
SpriteMapping_2D
32 bytes
SpriteMapping_Bmp_1D_128
128 bytes
SpriteMapping_Bmp_1D_256
256 bytes (main engine only)
SpriteMapping_Bmp_2D_128
128 bytes
SpriteMapping_Bmp_2D_256
256 bytes (main engine only)


Il sistema di indicizzazione adottato nel DS presenta delle limitazioni che vengono aggirate mediante degli artifici. Infatti ogni sprite engine ha a disposizione un registro di 10 bit, ovvero di 1024 "indici" (o offset) da utilizzare per gli sprites. Ad esempio, SpriteMapping_1D_32 stabilisce che i vari indici siano separati da 32 bytes l'uno dall'altro, quindi è in grado di indicizzare un massimo di 32kb (32 bytes * 1024 offset) di memoria video per gli sprites; allo stesso modo SpriteMapping_1D_128 è in grado di indicizzare un massimo di 128kb per gli sprites. Posso anticipare fin da ora che è perfettamente inutile perdere tempo dietro a questo concetto in quanto, salvo complicazioni dovute a un numero elevato di sprites molto piccoli, conviene sempre impostare un valore di offset pari alla memoria assegnata allo sprite engine. Ad esempio, assegnando allo sprite engine 64kb di memoria video, è conveniente impostare un offset di 64 bytes tramite SpriteMapping_1D_64.


Inizializzazione

Prima di potere utilizzare gli sprite engine del DS, essi vanno inizializzati tramite la procedura oamInit:

 procedure oamInit(var oam: OamState; mapping: SpriteMapping; extPalette: cbool); cdecl; external;

Essa richiede tre parametri; il primo indica lo sprite engine che intendiamo utilizzare (oamMain per l'engine principale e oamSub per il secondario); il secondo fa riferimento al tipo di layout che gli sprites assumono nella memoria video e all'offset utilizzato per la loro indicizzazione; il terzo fa riferimento all'utilizzo della palette estesa. Vediamo in dettaglio questi tre parametri.

Dicevamo di una distinzione tra due sprite engine, il principale e il secondario, associati rispettivamente al motore 2D principale e al motore 2D secondario. Essi sono rappresentati internamente da due record, chiamati OAM (Object Attribute Memory), che occupano una specifica posizione di memoria, situata a partire dagli indirizzi $07000000 (oamMain) e $07000400 (oamSub). I bit di queste locazioni di memoria funzionano da "interruttori" per attivare e disattivare le caratteristiche degli sprite; così, ad esempio, impostare un bit a 1 invece che a 0 può significare l'attivazione o la disattivazione dell'effetto mosaico, oppure l'impostazione dello sprite alla dimensione 16x16, o ancora il flipping verticale dello stesso, e così via. Altri gruppi di bit rappresentano invece la posizione dello sprite sullo schermo. Non ci complicheremo la vita manipolando direttamente i bit; ci avvarremo piuttosto delle comode funzioni di alto livello che vengono messe a disposizione dalla libreria libnds. Nei prossimi paragrafi vedremo le più importanti e il loro utilizzo.

Torniamo ai parametri della procedura oamInit, analizzando il secondo. Abbiamo parlato in precedenza di layout degli sprites nella memoria video. Esistono due layout utilizzabili: il layout 1D e il layout 2D. Senza farsi intimorire - e fuorviare - dalla definizione, basti sapere che gli sprites 1D sono organizzati nella memoria in maniera consecutiva, uno di seguito all'altro, suddivisi in blocchi da 8x8 pixel; gli sprites 2D sono invece organizzati come se si trovassero in un array, quindi sono disposti in memoria esattamente come vengono disegnati con un programma di grafica. A differenza dei background, gli sprites non hanno bisogno di una mappa per ricomporre sullo schermo le tiles in cui sono suddivisi. È sufficiente indicare all'engine la tile 8x8 di partenza, la forma dello sprite, la sua dimensione e il layout assunto in memoria dall'immagine (1D o 2D), e il gioco è fatto. Le immagini qui in basso mostrano i layout che gli sprites assumono in memoria, rispettivamente nella modalità 1D e in quella 2D:


Layout 1D
Layout 1D



Layout 2D
Layout 2D


I "quadratini" rappresentano le tiles 8x8 che compongono gli sprites; le tiles, da sinistra a destra, dall'alto verso il basso, occupano posizioni consecutive nella memoria video. È facile capire che nella modalità 1D ogni sprite è contenuto in blocchi di memoria contigui; nell'esempio incontreremo prima tutte le tiles dell'uovo, poi tutte le tiles della stella. Personalmente ritengo la modalità 2D poco attraente perché, anche se a prima vista potrebbe sembrare più semplice da capire, limita l'utilizzo degli sprites ad un massimo di 32 kb (più altri 32 kb per il motore secondario), contro i 256 kb utilizzabili nella modalità 1D (più altri 128 kb per il motore secondario).


Gestione degli sprites

La caratteristica interessante degli sprites nel DS è la possibilità di muoverli attraverso gli schermi senza doversi preoccupare di ridisegnarli di frame in frame. La funzione-mostro che se ne occupa è oamSet, definita come segue:

procedure oamSet(var oam: OamState; id, x, y, priority, palette_alpha: cint; size:
SpriteSize; format: SpriteColorFormat; const gfxOffset: pointer; affineIndex: cint;
sizeDouble, hide, hflip, vflip, mosaic: cbool); cdecl; external;

Prima di vedere come utilizzarla, diamo un'occhiata ai tanti parametri che la compongono:

  • oam: come già visto in oamInit, il parametro oam rappresenta lo sprite engine che vogliamo utilizzare
  • id:indica il numero identificativo che lo sprite occupa nella tabella degli sprites del DS e può assumere valori che vanno da 0 a 127 (ricordate? 128 per ogni schermo)
  • x, y:rappresentano la posizione in pixel che lo sprite deve occupare sullo schermo; quindi per muovere uno sprite basta modificare i valori di x e y e lo sprite engine si occuperà di ridisegnare lo sprite nella posizione richiesta
  • priority:possiamo anche aggiustare la priorità dello sprite tramite questo parametro, che può assumere valori da 0 (priorità alta) a 3 (priorità bassa). Come per i background, la priorità serve a decidere quando uno sprite debba essere disegnato sopra un altro, nel caso in cui si sovrappongano
  • palette_alpha: come già detto, gli sprites possono utilizzare una singola palette (8 bpp), 16 palettes distinte (4 bpp), una palette estesa o ancora, nel caso degli sprites bitmap, possono non richiedere una palette (16 bpp). Il parametro palette_alpha serve proprio a gestire tutti questi casi: serve ad indicare il numero della palette (4 bpp), della palette estesa (8 bpp) o il valore alpha (0-15) nel caso di bitmap sprites.
  • size: si occupa di assegnare la dimensione dello sprite
  • format: indica il formato dei colori utilizzato per lo sprite
  • gfxOffset: indica all'engine il puntatore all'indirizzo di memoria della prima tile di cui è composto lo sprite
  • affineIndex: indica quale matrice di rotazione associare allo sprite (0-31). Se < 0 o  > 31 lo sprite non potrà essere ruotato
  • sizeDouble: se ad affineIndex è associata una matrice di rotazione, impostando questo valore a true raddoppierà la dimensione a disposizione dello sprite, per fare in modo che esso venga sempre mostrato completamente. Per chiarire il concetto è consigliabile dare un'occhiata all'esempio Graphics/Sprites/sprite_rotate incluso in libndsfpc
  • hide: se impostato a true lo sprite non viene disegnato
  • hflip: se impostato a true lo sprite viene "ribaltato" orizzontalmente
  • vflip: se impostato a true lo sprite viene "ribaltato" verticalmente
  • mosaic: se impostato a true viene applicato l'effetto mosaico allo sprite

Un esempio vale più di mille parole, perciò proviamo a caricare uno sprite in memoria, a disegnarlo e ad animarlo.


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
program Sprites_a;

{$L build/sprite.o}
{$mode objfpc}

uses
  ctypes, nds9;

type
  TSprite = record 
    x, y: cint;
    gfx_mem: pcuint16;
    gfx_frame: pcuint8;
    anim_frame: cint;
    anim_delay: cint;
    mirror: boolean
  end;            

const
  FRAMES_PER_ANIMATION = 6;
  SCREEN_TOP    = 0;
  SCREEN_BOTTOM = 192;
  SCREEN_LEFT   = 0;
  SCREEN_RIGHT  = 256; 

var
  counter: cint = 0;
  spriteTiles: array [0..0] of cuint; cvar; external;
  spritePal: array [0..0] of cushort; cvar; external;
  MySprite: TSprite = (x: (256 - 64) div 2; y: (192 - 64) div 2; 
                       gfx_mem: nil; gfx_frame: nil;
                       anim_frame: 0;
                       anim_delay: 10;
                       mirror: true;);
  keys: cint;

procedure animSprite(var sprite: TSprite);
var
  offset: pcuint8;
begin
  if (counter mod sprite.anim_delay) = 0 then 
  begin
    inc(sprite.anim_frame);
    if Sprite.anim_frame >= FRAMES_PER_ANIMATION then
      Sprite.anim_frame := 0;
    offset := sprite.gfx_frame + (sprite.anim_frame * 64*64);
    dmaCopy(offset, sprite.gfx_mem, 64*64); 
  end;
end;

begin
  videoSetMode(MODE_0_2D);
  videoSetModeSub(MODE_0_2D);

  vramSetBankA(VRAM_A_MAIN_SPRITE);

  oamInit(oamMain, SpriteMapping_1D_128, false);

  MySprite.gfx_mem := oamAllocateGfx(oamMain, SpriteSize_64x64, SpriteColorFormat_256Color); 
  MySprite.gfx_frame := @spriteTiles;

  dmaCopy(@spritePal, SPRITE_PALETTE, 512);

  while true do
  begin
    scanKeys();

    keys := keysHeld();

    if keys <> 0 then
    begin
      if (keys and KEY_UP) <> 0 then
      begin
        if MySprite.y >= SCREEN_TOP - 16 then dec(MySprite.y);
      end;
      if (keys and KEY_LEFT) <> 0 then
      begin
        if MySprite.x >= SCREEN_LEFT - 16 then dec(MySprite.x);
        MySprite.mirror := false;
      end;
      if (keys and KEY_RIGHT) <> 0 then
      begin
        if MySprite.x <= SCREEN_RIGHT - 48 then inc(MySprite.x);
        MySprite.mirror := true;
      end;
      if (keys and KEY_DOWN) <> 0 then
      begin
        if MySprite.y <= SCREEN_BOTTOM - 48 then inc(MySprite.y);
      end;
    end; 

    animSprite(MySprite); 

    oamSet(oamMain, 0, MySprite.x, MySprite.y, 0, 0,
           SpriteSize_64x64, SpriteColorFormat_256Color,
           MySprite.gfx_mem, -1, false, false, MySprite.mirror, false, false);

    swiWaitForVBlank();
    oamUpdate(oamMain); 
    inc(counter);   
  end;

end.


10-17: Definiamo un record con le caratteristiche dello sprite: le coordinate della posizione sullo schermo (x, y), l'indirizzo in memoria dove è memorizzato lo sprite (gfx_mem) e quello dove è memorizzato il primo frame (gfx_frame), il frame corrente dell'animazione (anim_frame) e la pausa tra un frame e l'altro (anim_delay), il flag per stabilire se lo sprite vada disegnato ribaltato orizzontalmente (mirror).
20-24: Definiamo una serie di costanti che ci serviranno durante l'animazione e nella gestione del movimento dello sprite.
30-34: Dichiariamo una variabile di tipo TSprite, inizializzandone i valori.
55: Riserviamo agli sprites l'intero banco A di memoria.
57: Inizializziamo lo sprite engine primario (oamMain) in modo tale che contenga sprites 1D con un offset di 128 bytes e che non utilizzi la palette estesa.
59-60: Allochiamo memoria per lo sprite tramite oamAllocateGfx() sull'engine principale (oamMain), in modo tale che lo sprite abbia dimensione 64x64 pixel (SpriteSize_64x64) di 256 colori (SpriteColorFormat_256Color). Facciamo quindi puntare il primo frame dell'animazione all'indirizzo in memoria delle tiles che compongono lo sprite.
62: Copiamo l'intera palette dello sprite.
66-90: In questo blocco di codice gestiamo l'input del giocatore e il movimento dello sprite. Niente di eccessivamente complicato, ma per maggiori informazioni vi rimando a un futuro articolo dedicato interamente alla gestione dell'input.
92: Chiamiamo la funzione animSprite() (41-47) che gestisce l'animazione dello sprite: per prima cosa viene eseguito un controllo per vedere se è passato il tempo necessario affinché si debba disegnare il frame successivo. In caso affermativo viene incrementato l'indice dell'animazione oppure, se si è superato l'ultimo frame, viene azzerato. Viene quindi copiato in memoria (offset) l'indirizzo dei dati del frame da disegnare, che viene infine fisicamente copiato nella memoria video (dmaCopy()).
94-96: Effettuiamo una chiamata alla funzione oamSet(), che si occuperà di posizionare e disegnare lo sprite sullo schermo.
99: Aggiorniamo lo stato dello sprite engine.
100: Incrementiamo il contatore che servirà per l'animazione.



Per commenti e chiarimenti: http://www.lazaruspascal.it/index.php?topic=78.0


Scarica i sorgenti




Share on Twitter! Digg this story! Del.icio.us Share on Facebook! Technorati Reddit StumbleUpon

Come si è potuto constatare dalla puntata precedente, lavorare sui background è davvero semplice. La libreria libnds ci mette a disposizione un numero incredibile di funzioni e procedure che coprono qualsiasi aspetto relativo alla gestione dei layer, dal movimento allo zoom, dalla priorità alle rotazioni. Ed è appunto di rotazioni e zoom che ci occuperemo nel prossimo esempio.

Per prima cosa occorre cambiare la modalità dello schermo, perché il Mode 0 non gestisce rotazioni e zoom sui background. Potremmo usare, per esempio, il Mode 5, che permette di gestire due background di tipo text e due di tipo extended rotation. Vediamo subito il codice di esempio, che non varia poi molto da quanto già visto:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
program Mode5_a;
{$L build/wood.o}

{$mode objfpc}

uses
  ctypes, nds9;

const
  woodTilesLen = 16448;
  woodMapLen = 2048;
  woodPalLen = 512;

var
  woodTiles: array [0..0] of cuint; cvar; external;
  woodMap: array [0..0] of cushort; cvar; external;
  woodPal: array [0..0] of cushort; cvar; external;

var
  bg3: integer;

  x: integer  = 256;
  zoomOut: boolean = true;

const
  angle = 64;

begin
  videoSetMode(MODE_5_2D);
  vramSetBankA(VRAM_A_MAIN_BG);

  bg3 := bgInit(3, BgType_ExRotation, BgSize_ER_256x256, 0, 1);

  bgSetScroll(bg3, 128, 128);
  bgSetCenter(bg3, 128, 128);

  dmaCopy(@woodTiles, bgGetGfxPtr(bg3), woodTilesLen);
  dmaCopy(@woodMap, bgGetMapPtr(bg3),  woodMapLen);
  dmaCopy(@woodPal, BG_PALETTE, woodPalLen);

  while true do 
  begin
    if x = 128 then zoomOut := true; 
    if x = 512 then zoomOut := false;

    if zoomOut then
      inc(x)
    else
      dec(x);

    bgSetScale(bg3, x, x);
    bgRotate(bg3, angle); 

    swiWaitForVBlank();
    bgUpdate();
  end;
end.

Come al solito, scorriamo insieme le righe di codice più significative.

[32]: Dopo le solite impostazioni e dopo avere attivato il Mode 5, impostiamo il background 3 che ruoteremo e scaleremo.

[34-35]: Centriamo il background nello schermo e impostiamo il punto di rotazione in modo tale che sia al centro dell'immagine (provate a giocare con i valori per vedere come varia la rotazione).

[51]: Effettuiamo lo zoom, utilizzando lo stesso valore di x, in modo tale da avere uno zoom proporzionato. Cambiando i due valori è possibile ottenere anche un effetto stretching sul background. La funzione che si occupa dello zoom è

bgSetScale(id: cint; sx, sy: cint32); 

che richiede come parametri il background da zoomare (id) e il livello di zoom orizzontale e verticale (sx, sy). Un valore pari a 256 indica che non c'è nessuno zoom; un valore inferiore ingrandisce il background (128 indica un fattore di zoom di 2X) e un valore superiore rimpicciolisce il background (512 indica un fattore di zoom di 1/2X). Da notare che nei background (extended) rotation la tilemap non viene ripetuta, come accade invece nei background di tipo text.

[52]: La rotazione di un background può essere effettuata in due modi, utilizzando una delle funzioni

bgRotate(id, angle: cint);
bgSetRotate(id, angle: cint); 

Queste due funzioni non sono equivalenti. Infatti, in bgRotate la rotazione è cumulativa, quindi è sufficiente passare un valore costante come parametro e l'angolo di rotazione viene incrementato di quel valore ad ogni frame. bgSetRotate non è cumulativa, quindi ad ogni frame il valore dell'angolo viene riportato a 0.

Esiste poi una funzione che si occupa di ruotare e scalare un background in un sol colpo:

bgSetRotateScale(id, angle: cint; sx, sy: cint32);

Il suo uso è del tutto simile alle due funzioni appena viste. Ricordate però che la rotazione non è cumulativa e si comporta come bgSetRotate.

L'esempio seguente dovrebbe chiarire ogni dubbio in proposito:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
program Mode5_b;
{$L build/wood.o}

{$mode objfpc}

uses
  ctypes, nds9;

const
  woodTilesLen = 16448;
  woodMapLen = 2048;
  woodPalLen = 512;

var
  woodTiles: array [0..0] of cuint; cvar; external;
  woodMap: array [0..0] of cushort; cvar; external;
  woodPal: array [0..0] of cushort; cvar; external;

var
  bg3: integer;

  x: integer  = 256;
  zoomOut: boolean = true;
  angle: integer = 0;

begin
  videoSetMode(MODE_5_2D);
  vramSetBankA(VRAM_A_MAIN_BG);
  
  bg3 := bgInit(3, BgType_ExRotation, BgSize_ER_256x256, 0, 1);

  bgSetScroll(bg3, 128, 128);
  bgSetCenter(bg3, 128, 128);

  dmaCopy(@woodTiles, bgGetGfxPtr(bg3), woodTilesLen);
  dmaCopy(@woodMap, bgGetMapPtr(bg3),  woodMapLen);
  dmaCopy(@woodPal, BG_PALETTE, woodPalLen);

  while true do
  begin
    inc(angle, 64);
    if x = 128 then zoomOut := true;
    if x = 512 then zoomOut := false;

    if zoomOut then
      inc(x)
    else
      dec(x);

    bgSetRotateScale(bg3, angle, x, x);
    swiWaitForVBlank();
    bgUpdate();
  end;
end.

L'unica differenza con quanto visto in precedenza è la chiamata alla funzione bgSetRotateScale.


Per commenti e chiarimenti: http://www.lazaruspascal.it/index.php?topic=78.0


Scarica i sorgenti


Share on Twitter! Digg this story! Del.icio.us Share on Facebook! Technorati Reddit StumbleUpon
In questo articolo visioneremo i concetti basilari per usare le DBGrid in maniera professionale.

Partiamo subito dicendo che le DBGrid sono tra i componenti più usati e più comodi per lavorare con i database, permettono di sveltire di molto il lavoro dell'utente finale se sono usate correttamente. Le DBGrid necessitano di un solo parametro ovvero la voce DataSource che a sua volta punta ad un oggetto Query sia che si tratti dei componenti standard sia che si tratti di un componente ZeosLib.
Così otteniamo un uso standard della DBGrid, ma possiamo personalizzarla di molto usando un po' di codice, partiamo dall'esempio realizzato nell'articolo “DB concetti fondamentali e ZeosLib” e andiamo a modificare il database sqlite con il seguente codice:

-- Descrizione UTENTI
CREATE TABLE utenti (
    "Id" INTEGER,
    "Cognome" VARCHAR(50),
    "Nome" VARCHAR(50),
    "Citta" VARCHAR(50),
    "MyCheck" VARCHAR(50),
    "MyButton" VARCHAR(50),
    "MyPick" VARCHAR(50)
, "IdPickExternalKey" INTEGER)

-- Descrizione CHIAVEESTERNAPICK
CREATE TABLE "ChiaveEsternaPick" (
    "Id" INTEGER PRIMARY KEY AUTOINCREMENT,
    "Descrizione" TEXT
)

Come si può notare abbiamo aggiunto il campo IdPickExternalKey alla tabella principale in modo che faccia da chiave esterna e successivamente abbiamo creato una tabella apposita che contiene la chiave e il valore dei dati che vogliamo compaiano nella PickList (o combobox) che apparirà in una colonna della nostra DBGrid.

Ora aggiungiamo al progetto lazarus un componente TZQuery dal nome ZQuery2 e andiamo ad impostare i seguenti valori ai parametri:

Connection: ZConnection1
SQL: select * from chiaveesternapick;
Active: true

Successivamente aggiungiamo una nuova unit al progetto e inseriremo questo codice al suo interno:

Codice: [Seleziona]

{***********************************************************
*** Unit realizzata da Sammarco Francesco
*** per inserire delle combobox/picklist
*** all'interno della DBGrid. Sembra non funzionare con
*** tutti i tipi di DataSet, nel caso qualcuno riesca a
*** migliorare questa unit mi invii il codice migliorato
*** al seguente indirizzo: francesco.sammarco@gmail.com
***********************************************************}
unit Unit_CampoComboDBGrid;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, db, Grids;

function AddDBComboBoxToGrid(NomeNuovoCampo: string;MyDataSet: TDataSet; CampoDataSet: string ;DataSetList: TDataSet; ChiaveLista: string; CampoLista: string): boolean;

implementation

              function AddDBComboBoxToGrid(NomeNuovoCampo: string;MyDataSet: TDataSet; CampoDataSet: string ;DataSetList: TDataSet; ChiaveLista: string; CampoLista: string): boolean;
              var
                i: integer;
                ret: boolean;
                Field:TField;
              begin
                     ret:=false;
                     try
                        try
                           MyDataSet.FieldDefs.Update;
                           MyDataSet.Close;
                           for i:=0 to MyDataSet.FieldDefs.Count-1 do
                               if MyDataSet.FindField(MyDataSet.FieldDefs[i].Name) = nil then
                                  Field:=MyDataSet.FieldDefs[i].CreateField(MyDataSet);

                           if MyDataSet.FindField(NomeNuovoCampo)<>nil then
                              Exit;

                           Field:=TStringField.Create(MyDataSet);
                           with Field do
                           begin
                             FieldName:=NomeNuovoCampo;
                             Dataset:=MyDataSet;
                             FieldKind := fkLookup; //QUI DICO CHE E' UNA COMBOBOX
                             LookupDataSet:=DataSetList; //QUI ASSEGNO IL DATASET DA CUI EREDITARE I DATI DA FAR VEDERE NELLA COMBOBOX
                             LookupCache:=FALSE;
                             LookupKeyFields:=ChiaveLista;//'CHIAVE'; //CHIAVE CHE VERRA' INSERITA NEL CAMPO Temf.KeyFields
                             LookupResultField:=CampoLista;//'NOME'; //VALORE CHE VEDI NELLA COMBOBOX DA SELEZIONE
                             ReadOnly:=FALSE; //SOLA LETTURA
                             ProviderFlags:=[pfInUpdate, pfInWhere];
                             Required:=FALSE;
                             KeyFields:=CampoDataSet;//'CHIAVEPV';
                           end;
                           MyDataSet.Open;
                           ret:=TRUE;
                        finally
                       end;
                     except
                           on E: Exception do
                           begin

                           end;
                     end;
                     result:=ret;
              end;
end.


Bene con questa utilissima unit abbiamo messo i presupposti per proseguire nel nostro intento, ora manca una cosa sola, la seguente riga:

AddDBComboBoxToGrid('appoggio', Self.ZQuery1, 'IdPickExternalKey',Self.ZQuery2,'Id', 'Descrizione');

Che non fa altro se non dire di aggiungere alla griglia una nuova colonna (al fondo) di metterle come titolo della colonna la stringa 'appoggio' che riporta il valore del campo 'Descrizione' che ha come chiave di accoppiamento la colonna 'Id' e che entrambi i dati sono presenti nell'oggetto TZQuery (ma andrebbe benissimo ogni oggetto TdataSet) di nome ZQuery2, in modo tale da modificare automaticamente il valore presente nella colonna 'IdPickExternalKey' della fonte ovvero all'oggetto TZQuery (ma andrebbe benissimo ogni oggetto TdataSet) di nome ZQuery1.

Ma il nostro esempio non si ferma qui, vediamo inoltre come usare delle checkbox e altri utilissimi oggetti all'interno di una colonna (della Dbgrid).

             Self.DBGrid1.Columns.ButtonStyle:=cbsCheckboxColumn;
             Self.DBGrid1.Columns.ValueChecked:='TRUE';
             Self.DBGrid1.Columns.ValueUnchecked:='FALSE';

Come si può notare dal codice posto sopra andiamo a giocare con tre proprietà della colonna numero i (ovviamente i è un indice per scorrere tutte le colonne della griglia), che sono:

ButtonStyle: in cui andiamo a dire che forma possiederà la colonna, nel nostro caso andremo a dirgli che si tratta di una checkbox
ValueChecked: ovvero il valore che deve avere la colonna per apparire selezionata
ValueUncheked: ovvero il valore che deve avere la colonna per apparire deselezionata

Se invece avessimo voluto una picklist “fissa” che non facesse uso di chiavi esterne avremmo potuto usare il codice che segue:

               Self.DBGrid1.Columns.ButtonStyle:=cbsPickList;
               app:=TStringList.Create;
               app.Add('valore1');
               app.Add('valore2');
               app.Add('valore3');
               Self.DBGrid1.Columns.PickList:=app;
               app.Free;


Dove con
                Self.DBGrid1.Columns.ButtonStyle:=cbsPickList;      
andiamo a dire che vogliamo una picklist, mentre concetti
               app:=TStringList.Create;
               app.Add('valore1');
               app.Add('valore2');
               app.Add('valore3');
andiamo a creare una stringlist, ovvero una stringa di liste e gli aggiungiamo dentro tre stringhe, e con la seguente riga diciamo che la colonna deve presentare i valori della stringlist appena creata
               Self.DBGrid1.Columns.PickList:=app;

Ora creiamo l'evento FormCreate e inseriamo il seguente codice al nostro esempio

Codice: [Seleziona]

procedure TForm1.FormCreate(Sender: TObject);
var
   i: integer;
   app: TStringList;
begin
     for i:=0 to Self.DBGrid1.Columns.Count-1 do
     begin
          if Self.DBGrid1.Columns[i].FieldName='MyCheck' then
          begin
             Self.DBGrid1.Columns[i].ButtonStyle:=cbsCheckboxColumn;
             Self.DBGrid1.Columns[i].ValueChecked:='TRUE';
             Self.DBGrid1.Columns[i].ValueUnchecked:='FALSE';
          end
          else if Self.DBGrid1.Columns[i].FieldName='MyButton' then
          begin
               Self.DBGrid1.Columns[i].ButtonStyle:=cbsButton;
          end
          else if Self.DBGrid1.Columns[i].FieldName='MyPick' then
          begin
               Self.DBGrid1.Columns[i].ButtonStyle:=cbsPickList;
               app:=TStringList.Create;
               app.Add('valore1');
               app.Add('valore2');
               app.Add('valore3');
               Self.DBGrid1.Columns[i].PickList:=app;
               app.Free;
          end;
          //ora vado ad impostare una larghezza fissa per tutte le colonne della griglia
          Self.DBGrid1.Columns[i].Width:=100;
     end;
     //ora vado ad impostare la picklist ereditata da una query utilizzando la Unit chiamata Unit_CampoComboDBGrid
     AddDBComboBoxToGrid('appoggio', Self.ZQuery1, 'IdPickExternalKey',Self.ZQuery2,'Id', 'Descrizione');

     //ora vado ad impostare una larghezza fissa per tutte le colonne della griglia
     for i:=0 to Self.DBGrid1.Columns.Count-1 do
     begin
          Self.DBGrid1.Columns[i].Width:=100;
     end;

end;


otterremo così una griglia con una colonna checkbox, una colonna con la picklist fissa, ed una con una picklist variabile in funzione di quanto contenuto nell'apposita tabella. Il tutto con delle colonne tutte larghe 100.

Qui trovate il link per scaricare l'esempio dell'articolo: www.lazaruspascal.it/esempi/Terza_Applicazione.zip
Share on Twitter! Digg this story! Del.icio.us Share on Facebook! Technorati Reddit StumbleUpon

Procediamo ora con un argomento che risulta utilissimo nello sviluppo di giochi: la gestione di tiles e mappe. Nella tabella relativa alle modalità grafiche, abbiamo visto che ogni modalità video permette di accedere a un numero di background differenti. In passato, nelle vecchie versioni di libnds, l'unico modo che si aveva per impostare ed utilizzare i background era quello di utilizzare direttamente i registri, eseguendo operazioni sui bit.

Oggi libnds offre un layer di astrazione di livello più alto, che rende molto più semplice l'utilizzo dei background. Vediamo due esempi: nel primo imposteremo un background utilizzando il vecchio sistema, attivando bit nei registri; nel secondo eseguiremo lo stesso compito, ma utilizzando il nuovo set di istruzioni.

In questi esempi utilizzeremo il Mode 0, che ci permetterà di avere 4 background di tipo text.

program Mode0_a;
{$mode objfpc}

uses
  ctypes, nds9;

begin
  // Attivazione del Mode 0 e del Background 0
  REG_DISPCNT^ := MODE_0_2D or DISPLAY_BG0_ACTIVE;  
  VRAM_A_CR^ := VRAM_ENABLE or VRAM_A_MAIN_BG; 

  // Impostare il Background 0 attraverso il suo registro BGO 
  REG_BG0CNT^ := BG_32x32 or BG_COLOR_256 or
                 BG_MAP_BASE(0) or BG_TILE_BASE(1);

  while true do;  // loop infinito! 

end.

Vediamo il codice in dettaglio. Il primo registro utilizzato è REG_DISPCNT e serve per impostare la modalità grafica Mode0 (con MODE_0_2D) e per attivare il background 0 (tramite DISPLAY_BG0_ACTIVE).

Il secondo registro è VRAM_A_CR e si riferisce al banco di memoria A. Tramite gli switch VRAM_ENABLE e VRAM_A_MAIN_BG lo attiviamo e lo impostiamo per l'utilizzo come background sul main engine.

Utilizziamo poi il registro REG_BG0CNT del BG0 per attivare un background di tipo testo di 32x32 tiles (BG_32x32), in modo che supporti 256 colori (BG_COLOR_256), dove la mappa sia memorizzata in Map Base 0 (BG_MAP_BASE(0)) e il tileset sia memorizzato a partire da Tile Base 1 (BG_TILE_BASE(1)).

Da ricordare che REG_BG0CNT (più in generale REG_BGnCNT, dove n indica il numero del background, da 0 a 3), così come VRAM_A_CR e REG_DISPCNT, sono puntatori a indirizzi di memoria; per questo motivo c'è bisogno di dereferenziarli per avere accesso al contenuto di quelle zone di memoria.

Vediamo ora lo stesso esempio, ma utilizzando il nuovo set di funzioni.

program Mode0_b;
{$mode objfpc}

uses
  ctypes, nds9;

var
  bg0: integer;

begin
  // Attivazione del Mode 0 e del Background 0
  videoSetMode(MODE_0_2D or DISPLAY_BG0_ACTIVE);  
  vramSetBankA(VRAM_A_MAIN_BG); 

  // Impostare il Background 0 attraverso la funzione BgInit 
  bg0 := BgInit(0, BgType_Text8bpp, BgSize_R_256x256, 0, 1);

  while true do;  // loop infinito! 

end.

La funzione videoSetMode() si occupa di impostare la modalità video Mode 0 (tramite MODE_0_2D) e di attivare il background 0 (con DISPLAY_BG0_ACTIVE). Dovreste già conoscere il significato di or; In caso contrario, consideratelo come una semplice addizione, del tipo "Attivare il Mode 0 2D e il background 0".

La funzione vramSetBankA() serve ad impostare il banco di memoria A in modo tale che la sua memoria possa essere utilizzata come background principale (VRAM_A_MAIN_BG).

Come è facile notare, la funzione BgInit richiede cinque parametri:

  1. il numero del background (0, 1, 2 o 3),
  2. il tipo di background,
  3. la sua dimensione (vedere le tabelle negli articoli precedenti),
  4. la map base,
  5. la tile base

Da tenere presente che ogni map base può immagazzinare una mappa di 32x32 tile, (256x256 pixel) e un singolo background. Per questo motivo, se si ha bisogno di una mappa di 64x64 tile, bisognerà ricordare che la mappa richiederà 4 blocchi nella map base. Allo stesso modo, una mappa di 32x32 tiles su 3 background richiederà 3 blocchi. Il vantaggio più grande nell'usare la funzione BgInit è il controllo sui parametri implementato in essa. Grazie a questo meccanismo, se ad esempio si prova ad usare un background di tipo extended rotation su un background di tipo text, verrà mostrato a schermo un messaggio che indicherà il tipo di errore riscontrato, nonché il file e la riga dove l'errore si è verificato.

Lanciando la ROM generata dal codice qui sopra, noterete che sullo schermo non viene mostrato niente. In effetti fino ad ora abbiamo soltanto impostato la console, senza disegnare niente. Come detto in precedenza, avremo bisogno di un tool per convertire i files contenenti la grafica in un formato utilizzabile con il DS. La scelta migliore è GRIT, una utility a linea di comando (disponibile anche con una comoda interfaccia grafica, WIN GRIT) presente sia nella distribuzione del devkitARM che in fpc4nds.

Nel nostro caso, supponiamo di avere un'immagine bitmap di 256x256 pixel, con una profondità di colore di 8bpp, contenente la nostra immagine/tileset; vogliamo convertirla in tiles per utilizzarla in una delle modalità grafiche a tiles e, per risparmiare memoria, vogliamo rimuovere le tiles ripetute. La linea di comando da usare con GRIT sarà:

grit MyImage.bmp -fts -gt -gB8 -mRtpf

Una piccola descrizione:

  • -fts indica a GRIT che desideriamo l'output in formato asm,
  • -gt per l'output a tiles,
  • -gB8 per una profondità di colore di 8 bit,
  • -mRtpf per la riduzione delle tile, per il flipping delle stesse e per l'ottimizzazione della palette.

GRIT è davvero un programma molto potente: il mio consiglio è quello di prendervi il tempo necessario per leggerne il manuale, che contiene anche molti esempi, in modo tale da padroneggiarlo.

La linea di comando mostrata in precedenza restituisce due files, MyImage.s e MyImage.h. Il primo è il file che contiene i dati relativi all'immagine, alla mappa e alla palette suddivisi 3 array; il secondo file è un header in c, che ovviamente non possiamo usare direttamente con Free Pascal, ma che ci permette di conoscere alcune informazioni che ci saranno utili.

Prima di poter essere utilizzato nel nostro programma in Pascal, il file asm generato da GRIT deve essere assemblato:

arm-none-eabi-as -o MyImage.o MyImage.s

Convertiamo ora l'header. Aprendo il file MyImage.h con un editor di testo ci apparirà qualcosa di simile:

#ifndef GRIT_MYIMAGE_H
#define GRIT_MYIMAGE_H

#define MyImageTilesLen 16448 extern const unsigned int MyImageTiles[4112];

#define MyImageMapLen 2048 extern const unsigned short MyImageMap[1024];

#define MyImagePalLen 512 extern const unsigned short MyImagePal[256];

#endif // GRIT_MYIMAGE_H

La conversione in Pascal di queste linee ci permetterà di accedere ai dati generati da GRIT. Il procedimento è davvero molto semplice:

const
  MyImageTilesLen = 16448;
var
  MyImageTiles: array [0..0] of cuint; cvar; external;

const
  MyImageMapLen = 2048;
var
  MyImageMap: array [0..0] of cushort; cvar; external;

const
  MyImagePalLen = 512;
var
  MyImagePal: array [0..0] of cushort; cvar; external;

Come potete vedere, le chiamate contenenti #define sono state tradotte con delle semplici costanti; le altre variabili sono degli array, che è sufficiente dichiarare di un solo elemento. Il compilatore è abbastanza intelligente da stabilirne da solo la dimensione.

Ora abbiamo davvero tutto quello che ci serve per creare la nostra demo in Mode 0:

program Mode0_c;
{$L build/wood.o}

{$mode objfpc}

uses
  ctypes, nds9;

const
  woodTilesLen = 16448;
var
  woodTiles: array [0..0] of cuint; cvar; external;

const
  woodMapLen = 2048;
var
  woodMap: array [0..0] of cushort; cvar; external;

const
  woodPalLen = 512;
var
  woodPal: array [0..0] of cushort; cvar; external;


var
  bg0: cint;
begin     
  videoSetMode(MODE_0_2D);
  vramSetBankA(VRAM_A_MAIN_BG);
  bg0 := bgInit(0, BgType_Text8bpp, BgSize_T_256x256, 0,1); 

  dmaCopy(@woodTiles, bgGetGfxPtr(bg0), woodTilesLen);
  dmaCopy(@woodMap, bgGetMapPtr(bg0),  woodMapLen);
  dmaCopy(@woodPal, BG_PALETTE, woodPalLen);

  while true do;  // loop infinito

end.

Analizziamo il codice. Tramite la direttiva $L del compilatore linkiamo il file oggetto contenente la grafica (ricordate? Quello generato con arm-none-eabi-as), quindi inseriamo le variabili e le costanti ricavate dal file header .h generato da GRIT.

Inizializziamo quindi la console come visto in precedenza negli altri esempi: il background è di tipo text (anche perché in Mode0 non è possibile utilizzare altri tipi di background!); la mappa risiede nel blocco 0, mentre le tiles sono memorizzate nel blocco 1.

L'ultimo passo è quello di copiare la grafica, la mappa e la palette nelle giuste locazioni di memoria. A tale scopo utilizzeremo la funzione dmaCopy(), che nel 99% dei casi è il metodo più veloce per copiare dati da una regione di memoria all'altra:

  • Tiles: bisogna passare alla funzione dmaCopy:
  1. l'indirizzo della locazione di memoria dell'array dove sono memorizzate le tiles (@woodTiles);
  2. l'indirizzo del background (la funzione bgGetGfxPtr() restituisce proprio l'indirizzo del background passato come parametro);
  3. la quantità dei dati da copiare, data dalla costante woodTilesLen
  • Map: in questo caso i dati da passare a dmaCopy sono:
  1. l'indirizzo della locazione di memoria dell'array dove è memorizzata la mappa (@woodMap);
  2. la locazione di memoria dove copiare la mappa (la funzione bgGetMapPtr() restituisce l'indirizzo della mappa associata al background passato come parametro);
  3. la quantità dei dati da copiare, data dalla costante woodMapLen
  • Palette: in ultimo, i valori da passare a dmaCopy per la palette sono:
  1. l'indirizzo della locazione di memoria dell'array dove è memorizzata la palette (@woodPal);

  2. a locazione di memoria dove copiare la palette (BG_PALETTE è un puntatore all'area dove risiede la palette per il background);
  3. la quantità dei dati da copiare, data dalla costante woodPalLen

Con opportuni accorgimenti si potrebbe utilizzare anche la funzione pascal move():

Move(MyImageTiles, bgGetGfxPtr(bg0)^, MyImageTilesLen);
Move(MyImageMap, bgGetMapPtr(bg0)^, MyImageMapLen);
Move(MyImagePal, BG_PALETTE^, MyImagePalLen);

oppure la funzione c memcpy():

memcpy(bgGetGfxPtr(bg0), @MyImageTiles, MyImageTilesLen);
memcpy(bgGetMapPtr(bg0), @MyImageMap, MyImageMapLen);
memcpy(BG_PALETTE, @MyImagePal, MyImagePalLen);

Solitamente move e memcpy sono da 2 a 5 volte più lente di dmaCopy nella velocità di scrittura. Bisogna però segnalare che, in alcuni casi specifici, dmaCopy potrebbe non essere il modo più veloce per copiare i dati. Non c'è comunque bisogno di preoccuparsi, perché per i nostri scopi è più che sufficiente.


Muovere e ruotare i background

Una delle caratteristiche più interessanti del Nintendo DS è la possibilità di muovere, scalare e ruotare i background direttamente dall'hardware della console. Effettuare lo scrolling di un background richiede una semplice, singola chiamata ad una procedura:

bgScroll(id, dx, dy: integer);

oppure l'equivalente:

bgSetScroll(id, dx, dy: integer);

Queste procedure richiedono come parametri l'ID del background restituito da bgInit (o da bgInitSub) e il valore in pixel dello spostamento desiderato del background lungo gli assi x e y. Potreste nel caso utilizzare anche:

bgScrollf(id, dx, dy: integer);

oppure l'equivalente:

bgSetScrollf(id, dx, dy: integer);

In questo secondo caso dx e dy devono essere dei valori fixed point, in virgola fissa. Modifichiamo l'ultimo esempio visto per provare a muovere il background:

while true do // loop infinito
begin
  bgScroll(bg0, 1, 1); // scrolling del background
  bgUpdate();          // aggiorna il background
  swiWaitForVBlank();  // attende il VBlank per evitare
                       // il flickering
end;

La procedura bgScroll() da sola non è sufficiente per muovere il background. Occorre chiamare anche la procedura bgUpdate(), che aggiorna lo stato dei registri impiegati nello scrolling. Nel codice precedente viene introdotta una nuova, utile procedura: swiWaitForVBlank(). Lo schermo del DS funziona come un qualsiasi schermo: una sorta di "pennello" passa su ogni pixel, colonna per colonna, riga per riga, e copia il framebuffer dalla memoria video allo schermo. Questo compito richiede circa 1/60 di secondo, quindi è buona norma attendere che il framebuffer sia completamente copiato sullo schermo prima di cambiarlo. Il DS ci viene in aiuto per mezzo di un interrupt, che viene lanciato ogni volta che il redraw dello schermo viene completato. La procedura swiWaitForVBlank() si occupa di attendere questo evento, fermando il flusso dell'esecuzione fino a quando l'interrupt non si attiva. Come si può vedere dalla esecuzione dell'esempio, il background viene ripetuto infinitamente. Quando viene raggiunto il limite della mappa, lo scroll ricomincia dal lato opposto.

Negli esempi precedenti abbiamo usato un singolo layer, ma ne abbiamo altri tre da utilizzare. Nel prossimo (e ultimo, per il Mode 0) esempio posizioneremo sullo schermo tre layer, utilizzando trasparenza e scrolling. Da ricordare che i layer utilizzano un sistema di priorità che va da 0 (il livello - per così dire - più vicino al vetro dello schermo, ed è il layer più in alto) a 3 (il layer più in profondità).
Nella nostra "scena" avremo un cielo stellato fisso, delle montagne e alcune nuvole, che scorreranno a velocità differenti, dando l'illusione di un movimento laterale. Il background impiegato per il cielo avrà priorità 3, le montagne priorità 1 e le nuvole priorità 0. Il DS considera come trasparente il primo colore della palette, cioè quello con indice 0, quindi prestate attenzione nell'impostare il colore per la trasparenza del tileset.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
program Mode0_e;
{$L build/mounts.o} 
{$L build/sky.o}
{$L build/clouds.o} 

{$mode objfpc}

uses
  ctypes, nds9;

const
  mountsTilesLen = 5120; 
  mountsMapLen = 2048;
  mountsPalLen = 512;
  
  skyTilesLen = 2112;
  skyMapLen = 2048;
  skyPalLen = 512;

  cloudsTilesLen = 3328;
  cloudsMapLen = 2048;
  cloudsPalLen = 512;

var
  mountsTiles: array [0..0] of cuint; cvar; external;
  mountsMap: array [0..0] of cushort; cvar; external;
  mountsPal: array [0..0] of cushort; cvar; external;

  skyTiles: array [0..0] of cuint; cvar; external;
  skyMap: array [0..0] of cushort; cvar; external;
  skyPal: array [0..0] of cushort; cvar; external;

  cloudsTiles: array [0..0] of cuint; cvar; external;
  cloudsMap: array [0..0] of cushort; cvar; external;
  cloudsPal: array [0..0] of cushort; cvar; external; 

var
  mounts, clouds, sky: integer;
begin
  videoSetMode(MODE_0_2D);
  vramSetBankA(VRAM_A_MAIN_BG);
  
  clouds := bgInit(0, BgType_Text8bpp, BgSize_T_256x256, 0, 1); 
  mounts := bgInit(1, BgType_Text8bpp, BgSize_T_256x256, 1, 2);
  sky    := bgInit(2, BgType_Text8bpp, BgSize_T_256x256, 2, 3); 

  dmaCopy(@mountsTiles, bgGetGfxPtr(mounts), mountsTilesLen);
  dmaCopy(@mountsMap, bgGetMapPtr(mounts),  mountsMapLen);
  dmaCopy(@mountsPal, BG_PALETTE, mountsPalLen);
  
  dmaCopy(@skyTiles, bgGetGfxPtr(sky), skyTilesLen);
  dmaCopy(@skyMap, bgGetMapPtr(sky),  skyMapLen);

  dmaCopy(@cloudsTiles, bgGetGfxPtr(clouds), cloudsTilesLen);
  dmaCopy(@cloudsMap, bgGetMapPtr(clouds),  cloudsMapLen);

  while true do
  begin
    swiWaitForVBlank();
    bgScrollf(mounts, 1 shl 8 {256}, 0);
    bgScrollf(clouds, -(1 shl 6){-64}, 0);
    bgUpdate();
  end;
end.

Vediamo di commentare il codice dell'esempio. Come prima cosa sono stati linkati i file oggetto contenenti la grafica e le mappe, come già visto nell'esempio precedente.

Sono state quindi dichiarate variabili e costanti generate da GRIT.

Alle righe 43-45 sono stati creati i tre background. Da prestare particolare attenzione ai valori assegnati alle tre base map e alle tre tile map.

Le righe 47-55 si occupano di copiare i dati nelle giuste locazioni di memoria. Notare che la palette è unica per tutti e tre i background, quindi viene copiata soltanto una volta. La palette infatti è stata ottimizzata per utilizzare 256 colori, suddivisi in 16 palette di 16 colori ciascuna. In questo caso stiamo utilizzando soltanto le prime 3 minipalette.

Il codice nella linea 60 si occupa di eseguire lo scroll del background "mounts" di (1 shl 8), cioè un pixel per frame; il background "clouds" (riga 61) invece verrà spostato di un valore pari a -(1 shl 6), che equivale a un pixel ogni 4 frames, ma nella direzione opposta rispetto al movimento delle montagne (tramite il segno "-").

A questo punto non ci rimane che compilare l'esempio e ammirare la scena, di cui potete ammirare una cattura in figura.


Un'ultima cosa da dire sui background riguarda la possibilità di leggere la loro priorità e cambiarla al volo, nel caso in cui si volesse spostarne uno in primo o secondo piano:

function bgGetPriority(id: cint): cint;
procedure bgSetPriority(id: cint; priority: cuint);

dove id è il valore restituito dalla funzione bgInit(). Nel nostro codice di esempio, le nuvole sono visualizzate davanti alle montagne; nel caso in cui volessimo spostarle dietro, basterebbe inserire questa riga di codice appena dopo la creazione dei background (dopo le righe 43-45):

bgSetPriority(clouds, 2);


Per commenti e chiarimenti: http://www.lazaruspascal.it/index.php?topic=78.0


Scarica i sorgenti

Share on Twitter! Digg this story! Del.icio.us Share on Facebook! Technorati Reddit StumbleUpon
Pagine: 1 2 [3] 4 5 ... 28

Recenti

How To

Trascinare un file nel programma da xinyiman
DB concetti fondamentali e ZeosLib da xinyiman
Recuperare codice HTML da pagina web da xinyiman
Mandare mail con Lazarus da xinyiman
Stabilire il sistema operativo da xinyiman
Esempio lista in pascal da xinyiman
File INI da xinyiman
Codice di attivazione da xinyiman
Realizzare programmi multilingua da xinyiman
Lavorare con le directory da xinyiman
Utilizzare Unità esterne da Loryea
TTreeView da xinyiman
TTreeview e Menu da xinyiman
Generare controlli RUN-TIME da xinyiman
LazReport, PDF ed immagini da xinyiman
Intercettare tasti premuti da xinyiman
Ampliare Lazarus da xinyiman
Lazarus e la crittografia da xinyiman
System Tray con Lazarus da xinyiman
UIB: Unified Interbase da Microges2000
Il file: questo sconosciuto da Microges2000
Conferma di chiusura di un applicazione da xinyiman
Liste e puntatori da Microges2000
Overload di funzioni da Microges2000
Funzioni a parametri variabili da Microges2000
Proprietà da Microges2000
Conversione numerica da Microges2000
TImage su Form e Panel da Maverich
Indy gestiore server FTP lato Client da Maverich
PopUpMenu sotto Pulsante (TSpeedButton) da Maverich
Direttiva $macro da Microges2000
Toolbar da xinyiman
Evidenziare voci TreeView da Maverich
Visualizzare un file Html esterno da Maverich
StatusBar - aggirare l'errore variabile duplicata da Maverich
Da DataSource a Excel da xinyiman
Le permutazioni da xinyiman
Brute force da xinyiman
Indy 10 - Invio email con allegati da Maverich
La gestione degli errori in Lazarus da xinyiman
Pascal Script da xinyiman
Linux + Zeos + Firebird da xinyiman
Dataset virtuale da xinyiman
Overload di operatori da Microges2000
Lavorare con file in formato JSON con Lazarus da nomorelogic
Zeos ... dietro le quinte (prima parte) da Stilgar
Disporre le finestre in un blocco unico (come Delphi) da Maverich
Aspetto retrò (Cmd Line) da xinyiman
Come interfacciare periferica twain da Narciso
Ubuntu - aggiornare free pascal e lazarus da xinyiman
fpcup: installazioni parallele di lazarus e fpc da nomorelogic
Free Pascal e Lazarus sul Raspberry Pi da nomorelogic
Cifratura: breve guida all'uso dell'algoritmo BlowFish con lazarus e free pascal. da nomorelogic
Creare un server multithread da xinyiman
guida all'installazione di fpc trunk da subversion in linux gentoo da nomorelogic
Indice da nomorelogic
DB concetti fondamentali e connessioni standard da xinyiman
Advanced Record Syntax da nomorelogic
DB concetti fondamentali e DBGrid da xinyiman
DB concetti fondamentali e TDBEdit, TDBMemo e TDBText da xinyiman
Advanced Record Syntax: un esempio pratico da nomorelogic
Superclasse form base per programmi gestionali (e non) da nomorelogic
Superclasse form base per programmi gestionali (e non) #2 - log, exception call stack, application toolbox da nomorelogic
Superclasse form base per programmi gestionali (e non) #3 - traduzione delle form da nomorelogic
Superclasse form base per programmi gestionali (e non) #4 - wait animation da nomorelogic
Un dialog per la connessione al database:TfmSimpleDbConnectionDialog da nomorelogic
Installare lazarus su mac osx sierra da xinyiman
Utenti
Stats
  • Post in totale: 10424
  • Topic in totale: 1326
  • Online Today: 23
  • Online Ever: 74
  • (Luglio 09, 2012, 11:05:53 am)
Utenti Online
Users: 0
Guests: 11
Total: 11

Disclaimer:

Questo blog non rappresenta una testata giornalistica poiché viene aggiornato senza alcuna periodicità. Non può pertanto considerarsi un prodotto editoriale ai sensi della legge n. 62/2001.