* * * *
5 Visitatori, 0 Utenti

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

Articles in « Free Pascal per DS »

Comments *

Commenting option has been turned off for this article.

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
  • Utenti in totale: 542
  • Latest: LIzarib
Stats
  • Post in totale: 10437
  • Topic in totale: 1329
  • Online Today: 15
  • Online Ever: 74
  • (Luglio 09, 2012, 11:05:53 am)
Utenti Online
Users: 0
Guests: 5
Total: 5

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.