| VS85 Team's profileXNALearnersBlogListsGuestbook | Help |
|
|
3/20/2008 Testo e primitive 2D in XNA
by Giuseppe Maggiore
IntroduzioneXNA 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.DrawQuando 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’immagine 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 parametro di draw 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.DrawStringPer 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 manoTutto 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 specialiIpotizziamo 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! ConclusioneTrovate 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: 3/14/2008 XNA e Windows Forms (controlli di Windows)di Alessandro Piva Ciao a tutti! Nello sviluppo di un videogioco, si ha sempre bisogno di uno o più editor, cioè programmi che servono per creare mappe, missioni, livelli, eccetera. Spesso è possibile trovare, sulla rete, editor già scritti e messi liberamente a disposizione da altri sviluppatori - ma, se non si riesce a trovarne nessuno che soddisfi le nostre esigenze, l'unico modo per risolvere il problema è scriversi un proprio editor. Per creare l'interfaccia utente dei nostri editor, è possibile usare i Windows Forms - i controlli di Windows, cioè quei pulsanti, caselle di testo, barre, menù che possiamo vedere in quasi tutte le applicazioni. Questo webcast è dedicato proprio a questo argomento: come far apparire, nella finestra della nostra applicazione XNA, dei Windows Forms! Inoltre, vengono mostrati alcuni esempi concreti di utilizzo. Ecco una schermata dimostrativa: Purtroppo tali controlli non possono essere usati se eseguiamo il nostro gioco a pieno schermo (fullscreen) né tantomeno sull'XBox 360, ma come già detto sono molto utili per creare interfacce per editor o applicazioni simili. Buon divertimento! P.S.: dimenticavo: ovviamente avete a disposizione il codice sorgente, il webcast ad alta risoluzione (scaricabile in alternativa anche qui), e proprio qui sotto il webcast a bassa risoluzione. 3/13/2008 Gestione dell’input in XNACome far interagire l’utente con il nostro gioco! by Giuseppe Maggiore IntroduzioneIn questo documento spiegheremo brevemente come si gestisce l’input in XNA, in modo da rendere finalmente interattive le nostre opere J. La gestione dell’input pero’ non e’ cosi’ semplice come ci potremmo aspettare: l’input ha luogo (diciamo che si manifestano le interazioni dell’utente) con una frequenza molto differente e molto piu’ bassa rispetto a quella con cui l’applicazione si aggiorna. In un semplice gioco casual le interazioni tra utente e macchina sono dell’ordine di qualcuna al secondo (pochi Hz), mentre il gioco si aggiornera’ a svariate decine di frame al secondo (decine di Hz). Quindi servira’ filtrare la velocita’ di aggiornamento dell’applicazione in relazione all’input. Periferiche supportate da XNAXNA offre pieno e nativo supporto a tre periferiche: - Mouse - Tastiera - Controller XBox 360 Tali periferiche sono state scelte non a caso, ma perche’ sono quelle tipicamente trovate connesse ad una delle piattaforme su cui XNA gira, ossia Windows o l’Xbox 360. Altre periferiche possono essere impiegate, ma bisognera’ usare una libreria diversa da XNA per gestirle e ovviamente perderemo il supporto per l’XBox 360... Lettura dell’inputLeggere l’input consiste nel farsi fornire una copia dello stato istantaneo di un dato controller in un dato momento: GamePadState gps = GamePad.GetState(PlayerIndex.One); Ovviamente tale stato non sara’ valido per sempre, ne’ tantomeno si aggiornera’ da solo: per questo motivo dovremo considerare queste variabili come delle “fotografie istantanee” dell’input di un qualche controller in un dato istante di tempo. Se volessimo fare uscire il nostro gioco alla pressione di un qualsiasi tasto (magari il tasto back del controller XBox 360) allora dovremmo aggiungere questo controllo alla nostra funzione Update: protected override void Update(GameTime gameTime) { gps = GamePad.GetState(PlayerIndex.One); if (gps.Buttons.Back == ButtonState.Pressed) this.Exit(); } In cui si puo’ notare come aggiorniamo come prima cosa ad ogni Update lo stato della periferica di cui usiamo l’input. Problematiche rispetto al tempoIpotizziamo di avere costruito una semplice applicazione che mostra al centro dello schermo un’astronave, e vogliamo che ogni volta che l’utente preme il tasto ‘A’ del controller XBox l’astronave spari un missile. L’approccio naive a questo problema sarebbe una cosa del tipo: if (gps.Buttons.A == ButtonState.Pressed) shoot(); purtroppo per noi le cose non sono cosi’ semplici. Immaginiamo che l’utente tenga premuto il pulsante per un tempo piuttosto breve, diciamo una frazione di due decimi di secondo e che l’applicazione vada a 60 fps:
allora durante la pressione del tasto ‘A’, verranno sparati missili, uno per fotogramma. Il risultato che otteniamo e’ questo, anche per pressioni apparentemente istantanee: Dobbiamo tenere traccia di pressioni prolungate, ma non vorremmo farlo esplicitamente (magari con un array di booleani che ci dicono se un tasto era gia’ premuto precedentemente o no). Beh, il modo piu’ immediato per ottenere questo scopo e’ quello di memorizzare non solo uno stato dell’input (GamePadState) ma anche quello del fotogramma precedente: protected override void Update(GameTime gameTime) { prevGamePadState = gamePadState; gamePadState = GamePad.GetState(PlayerIndex.One); /*…*/ } Grazie a questa seconda struttura di supporto, possiamo trasformare il codice con cui decidiamo se sparare o meno un missile in: if (gamePadState.Buttons.A == ButtonState.Pressed && prevGamePadState.Buttons.A == ButtonState.Released) { Missile m = new Missile(); m.p = Vector3.Zero; m.v = Vector3.Forward; missiles.Add(m); } Con cui una singola (anche lunga) pressione del tasto ‘A’ produce l’effetto desiderato, ossia un solo missile viene lanciato: Con una variazione su questo stesso tema possiamo ottenere anche altri risultati interessanti oltre al timing dei singoli tasti. Immaginiamo di voler far sparare la nostra astronave in tutte le direzioni. In particolare, vogliamo che l’astronave spari verso dove e’ rivolto lo stick analogico destro del controller XBox (evidenziato nella figura): Il primo tentativo e’ analogo a quello che abbiamo fatto prima con il tasto ‘A’: se lo stick e’ premuto piu’ di un certo valore, spariamo il missile: if (gamePadState.ThumbSticks.Left.Length() > 0.25f) { Missile m = new Missile(); m.p = Vector3.Zero; m.v = new Vector3(gamePadState.ThumbSticks.Left.X, 0.0f, gamePadState.ThumbSticks.Left.Y); missiles.Add(m); } Chiaramente sappiamo gia’ come va a finire, ovvero un sacco di missili verranno sparati per pressioni apparentemente anche molto brevi dello stick: Ora potremmo anche usare lo stato del gamepad al frame precedente, ma la soluzione migliore non e’ necessariamente questa. Ricordiamoci che stiamo parlando di uno stick analogico, e vorremmo che il giocatore rilasci la levetta prima di sparare un secondo missile. Quindi dobbiamo tenere traccia piu’ esplicitamente del fatto che in un dato frame stiamo aspettando che l’utente rilasci lo stick analogico prima di poter sparare di nuovo: if (waitThumbstick == true) { if (gamePadState.ThumbSticks.Left.Length() < 0.15f) waitThumbstick = false; } else { if (gamePadState.ThumbSticks.Left.Length() > 0.25f) { Missile m = new Missile(); m.p = Vector3.Zero; m.v = new Vector3(gamePadState.ThumbSticks.Left.X, 0.0f, gamePadState.ThumbSticks.Left.Y); missiles.Add(m); } } Grazie alla variabile waitThumbstick il nostro gestore dell’input aspettera’ che l’utente abbia rilasciato il thumbstick ad almeno lo 0.15 della sua pressione totale, prima di poter sparare di nuovo. Il comportamento di questa soluzione e’ quello desiderato! Una semplice classe CameraImmaginiamo di voler gestire tramite l’input del giocatore lo sguardo dell’utente. In particolare desideriamo che la matrice View venga costruita e aggiornata sulla base di due angoli di rotazione, orizzontale e verticale, che poi vengono aggiornati tramite il mouse o un thumbstick del controller XBox. Spostare il mouse (o il thumbstick) a destra o sinistra fara’ girare la telecamera a destra o sinistra, e similarmente gestiremo la rotazione nella direzione verticale. Come implementare questa utilissima funzione? Beh, intanto dichiariamo gli angoli di rotazione verticale e orizzontale come un vettore a due dimensioni: Vector2 cameraRotationVector = Vector2.Zero; e poi nella funzione Update lo aggiorneremo con la rotazione richiesta dall’utente (moltiplicata per la durata del frame precedente, cosi’ che lo sguardo si sposti alla velocita’ dell’applicazione, e non in modo veloce proporzionalmente al numero di frame per secondo): cameraRotationVector += gamePadState.ThumbSticks.Right *
(float)gameTime.ElapsedGameTime.TotalSeconds;
A questo punto dobbiamo costruire la matrice View, e naturalmente per questo ci sono decine di modi per farlo, differenti a seconda del tipo di telecamera che si vuole implementare e di una enorme quantita’ di altri fattori. Un esempio e’ quello di creare la camera inizialmente sempre nello stesso modo, tramite una chiamata a Matrix.CreateLookAt, e quindi applicare la rotazione desiderata: Matrix View { get { Matrix cameraRotation = Matrix.CreateRotationY(cameraRotationVector.X) * Matrix.CreateRotationX(cameraRotationVector.Y); return Matrix.CreateLookAt(Vector3.Up * 5000.0f, Vector3.Zero, Vector3.Forward) * cameraRotation; } } Se usiamo questa proprieta’ al posto della matrice View in tutte le chiamate di rendering, avremo effettivamente implementato una telecamera capace di ruotare su se stessa. Per spostare avanti e indietro la posizione dell’osservatore, potremo usare cameraRotation.Forward, cameraRotation.Backward, etc. ConclusioneIl sorgente completo di questo sample si puo’ trovare online, assieme al webcast in cui viene mostrata e spiegata la stesura di tale codice. E’ anche possibile guardare il webcast direttamente qui sotto: |
|
|