Academic Club Website

Categorie Articoli

Testo e primitive 2D in XNA

Disegnare oggetti 2D sullo schermo

by Giuseppe Maggiore

Introduzione

XNA supporta in modo molto ordinato il disegno di oggetti bidimensionali su schermo. Tipicamente queste operazioni di disegno verranno eseguite durante la chiamata a Game.Draw, tramite un oggetto helper, lo sprite batch:

SpriteBatch spriteBatch;

Lo sprite batch, come suggerisce il nome, e' un sistema di raggruppamento ottimizzato di chiamate di disegno di sprites. Gli sprites sono piccoli rettangoli che vogliamo prendere da una texture e mostrare su schermo. Tale sistema richiede che le chiamate di disegno avvengano in mezzo tra apertura (Begin()) e chiusura (End()), in modo che lo sprite batch le possa ottimizzare o ordinare come preferisce:

spriteBatch.Begin();

/*...*/

spriteBatch.End();

La funzione Begin prende opzionalmente tre parametri:

  • SpriteBlendMode che specifica se vogliamo usare alpha blending, alpha blending additivo o nessun blending
  • SpriteSortMode che indica in che ordine lo sprite batch deve disegnare: per z crescente, per z decrescente, immediato, deferito alla chiamata di End()
  • SaveStateMode per far si che la chiamata a End() ripristini tutte le flags della scheda video ai valori che avevano prima della chiamata a Begin(): utile per rendere l'uso dello SpriteBatch del tutto trasparente agli altri moduli di disegno

SpriteBatch.Draw

Quando arriva il momento di disegnare ecco che entra in gioco la prima delle due primitive di disegno: SpriteBatch.Draw. Tale funzione prende come parametri di base una texture, un rettangolo in pixel su schermo in cui la texture va messa, ed un colore per cui moltiplicare quello della texture. Quindi per inserire un'immmagine come sfondo dovremo scrivere:

spriteBatch.Draw(Content.Load<Texture2D>("background"),

new Rectangle(0, 0, 800, 600), Color.White);

Questa chiamata a draw sta indicando di disegnare la texture "background" con il suo colore originale (moltiplicare per Color.White lascia inalterato il colore) nell'area di schermo che inizia al pixel (0,0) ed ha dimensioni (800,600) pixel:

Ipotizziamo ora di voler disegnare un oggetto di qualche tipo che si muova ruotando sullo schermo. Diciamo anche che tale oggetto ha posizione "Position" ed e' ruotato di "Rotation" radianti rispetto al suo centro. Allora dovremo invocare nuovamente draw, ma prima sara' necessario introdurre un nuovo di draw parametro che definisce il centro di rotazione. Di default infatti uno sprite viene fatto ruotare intorno al suo angolo in alto a sinistra, dove effettivamente l'oggetto viene posizionato:

Questo pero' non e' affatto cio' che desideriamo: vorremmo che lo sprite ruotasse attorno al suo centro, non attorno ad un suo angolo. Esiste una versione della funzione Draw che accetta anche il parametro origin. Tale parametro serve per indicare quale e' l'origine della texture, rispetto a cui Position e' definita. Nel disegno qui accanto vediamo che l'origine e' stata messa al centro della texture (25,25) posto che la texture fosse grande (50,50). Qualsiasi rotazione adesso sara' relativa al centro e non agli angoli, proprio come desideriamo. La chiamata risultante per ottenere questo effetto sara':

spriteBatch.Draw(Content.Load<Texture2D>("rotating_particle"),

new Rectangle((int)Position.X, (int)Position.Y, (int)Size.X, (int)Size.Y),

null, Color.White, Rotation, Size / 2, SpriteEffects.None, 0.0f);

in cui possiamo notare come la texture sia posizionata in Position, abbia dimensione Size, e sia centrata sul suo punto medio Size/2, affinche' le rotazioni usino quello come riferimento e non l'angolo (0,0).

SpriteBatch.DrawString

Per disegnare testo la situazione non e' molto piu' complicata. Prima pero' ci serve un file di contenuto di tipo spritebatch, per descrivere il tipo di font da usare, i suoi parametri (dimensione, bold, etc.) e il set di caratteri da caricare (di default si usa Basic Latin, che va dallo spazio ASCII sino alla tilde ~). Creiamo il file di contenuto "Arial.SpriteFont":

In questo semplice file xml sono specificati il tipo del font di sistema da usare per il disegno (arial), la sua dimensione in punti (14), spaziatura, uso di kerning, stile (bold, regular, italic) e i range di caratteri da usare (qui vediamo che e' stato richiesto di caricare i caratteri dal 32 al 126, ossia il set Basic Latin menzionato prima).

Ipotizziamo di voler disegnare la doverosa stringa "Hello World!" su schermo. La chiamata opportuna risulta essere:

spriteBatch.DrawString(Content.Load<SpriteFont>("arial"), "Hello World!",

new Vector2(10, 70), Color.LightBlue);

Vediamo che la funzione DrawString richiede lo SpriteFont da usare per costruire la scritta, il testo da scrivere, la posizione su schermo (dell'angolo in alto a sinistra della scritta) ed infine il colore del testo. Effettivamente questa invocazione produce la scritta desiderata in azzurro a partire dal pixel (10,70):

Se pero' volessimo far ruotare la scritta intorno al suo centro? Avremmo bisogno di specificare l'Origin della nostra DrawString a meta' della dimensione della stringa disegnata, ma per fare cio' e' necessario sapere quanto grande risultera' la stringa su schermo. SpriteFont fortunatamente e' in grado di fornirci questa misura:

SpriteFont Arial = Content.Load<SpriteFont>("arial");

Vector2 stringSize = Arial.MeasureString("Hello World!");

Tramite cui possiamo disegnare la nostra stringa di testo roteante intorno al proprio centro:

float Rotation = (float)gameTime.TotalGameTime.TotalSeconds;

SpriteFont arialBig = Content.Load<SpriteFont>("arialBig");

Vector2 stringSize = arialBig.MeasureString("Hello World!");

spriteBatch.DrawString(arialBig, "Hello World!", new Vector2(400, 300),

    Color.Yellow, Rotation, stringSize / 2, 1.0f, SpriteEffects.None, 0.0f);

Font disegnati a mano

Tutto cio' che abbiamo visto finora e' relativo a fonts true type che siano installati nel sistema. Immaginiamo pero' di avere un font disegnato a mano da un artista, come quello riportato in figura:

Sara' possibile usare questo font non come texture (ossia importandolo direttamente), ma elaborarlo con un content processor appropriato e caricarlo in quanto sprite font. Il primo passo e' quello di importare il file tra i contenuti del nostro gioco:

A questo punto sappiamo che il file dovra' essere importato con un caricatore di texture, ma vogliamo che sia processato con un processor in grado di leggere i singoli caratteri e metterceli a disposizione. Tale processor e' chiamato Sprite Font Texture - XNA Framework, e puo' essere attivato dalle proprieta' del file "customfont.png":

Da tenere a mente che il font, per funzionare correttamente, dovra' rispettare due punti fondamentali:

  • Il canale alfa dovra' rispecchiare quali parti del testo sono caratteri e quali sono lo sfondo dei caratteri
  • In mezzo tra i vari caratteri dovra' esserci un colore magenta con alfa impostato ad opaco

Ed e' fatta, a questo punto e' possibile caricare ed usare il nostro font come vedete qui sotto:

spriteBatch.DrawString(Content.Load<SpriteFont>("customfont"), "test-text",

new Vector2(10, 10), Color.LightBlue);

Caratteri Unicode speciali

Ipotizziamo adesso di tornare al caso iniziale, ossia di caricare un font true type installato nel sistema. Vogliamo pero' poter inserire dei simboli speciali, nel caso specifico vogliamo vedere come stampare tramite sprite font i caratteri elencati di seguito: "€™∞". Se proviamo direttamente a stampare una stringa del genere pero' otterremo un'errore runtime che causera' il crash del nostro gioco! Ricordiamoci che alla creazione dello sprite font abbiamo visto che alla fine del file generato automaticamente ci sono una serie di "character regions", che denotano gli insiemi di caratteri che intendiamo usare. Di default il solo insieme caricato e' il cosiddetto Basic Latin, ma possiamo estenderlo in due modi. Il primo e' naturalmente quello di trovare una lista di caratteri unicode ed aggiungere a mano i codici dei caratteri desiderati nella definizione dello sprite font. La seconda strada e' apparentemente piu' complessa, ma in realta' ci da' molto piu' controllo. Per prima cosa creiamo una custom pipeline nella soluzione che contiene il nostro gioco:

La sola classe che deve essere contenuta nel nostro processor sara' una che aggiunge i caratteri desiderati al set di caratteri da caricare, e quindi lancera' il processor originale con il set di caratteri desiderato:

namespace FontProcessor

{

[ContentProcessor(DisplayName = "ExtendedCharacterSetFontProcessor")]

public class ContentProcessor1 : FontDescriptionProcessor

{

public override SpriteFontContent Process(FontDescription input,

ContentProcessorContext context)

{

input.Characters.Add('€');

input.Characters.Add('™');

input.Characters.Add('∞');

// should load this list of characters from a file!

return base.Process(input, context);

}

}

}

A questo punto aggiungiamo questo nuovo progetto tra le references del progetto content:

Ed infine definiamo come font processor dello sprite font da estendere il processor appena creato:

Il font arial adesso sara' identico a prima, ma in aggiunta supportera' i caratteri che il nuovo processor aggiunge, rendendo una chiamata come

spriteBatch.DrawString(Content.Load<SpriteFont>("arial"), "€™∞",

new Vector2(10, 70), Color.LightBlue);

perfettamente valida!

Conclusione

Trovate il sorgente online qui, mentre il webcast in cui viene scritto e spiegato nel dettaglio tutto il sorgente puo' essere scaricato da questo link, o guardato direttamente nella sua versione low-res embedded:

Comments

No Comments
Syndication
Archive