The Xna-Way: Tutorial 15: Utente collegato ed X-Box Live pt2 L'Avatar

Questa volta vi voglio parlare un poco degli Avatar degli utenti.
Come sapete è possibile creare e personalizzare il proprio Avatar, sia attraverso la console, sia attraverso il sito di X-Box live.
Voglio farvi vedere come si può, con poche righe di codice, renderizzare il proprio Avatar :)

NOTA: il codice per la gestione degli Avatar può essere fatto girare anche sotto Windows 7 (e mi pare che per i Phone, ma non avendo un tale dispositivo non ne sono sicuro), solo che in tal caso i valori ritornati dalle varie funzioni non saranno validi, e quindi non verrà renderizzato nulla.
In parole povere, GLI AVATAR SONO ESCULISA DELLA CONSOLE!

In parte riutilizzerò il codice scritto nel precedente articolo.
Nella classe del gioco aggiungiamo questi 3 campi:
   1:          AvatarDescription avatarDescription;
   2:          AvatarRenderer avatarRenderer;        
   3:          AvatarAnimation avatarAnimation;
   4:          AvatarRenderer avatarRenderer2;
   5:          AvatarDescription avatarDescription2;
che rappresentano la descrizione di come è fatto il nostro Avatar (in parole povere la descrizione fisica), l'oggetto che utilizzeremo per renderizzare il nostro Avatar, l'animazione corrente che stiamo riproducendo, ed infine l'enumeratore che indica quale delle animazioni preimpostate vogliamo usare.
Gli ultimi due oggetti, avatarRender2 ed avatarDescription2, serviranno per renderizzare e contenere un Avatar creato in modo random, in modo che alla fine del nostro esempio avremo a schermo il proprio Avatar ed un altro Avatar in più.
Avremo naturalmente bisogno di mantenere i dati del giocatore attualmente loggato (rivedere articolo precedente).

Nel LoadContent del game aggiungiamo questo:
   1:              avatarDescription2 = AvatarDescription.CreateRandom(AvatarBodyType.Female);
   2:              avatarRenderer2 = new AvatarRenderer(avatarDescription2);
   3:   
   4:              avatarAnimation = new AvatarAnimation(preset);
Le prime due righe creano un avatar casuale (femminile) ed un renderer basato sulla descrizione dell'Avatar appena creata.
La terza riga imposta l'animazione con il preset definito come campo della classe.

Ci servirà poi un metodo per il recupero in modo asincrono dei dati dell'Avatar, che può essere il seguente:
   1:          void LoadMyAvatar(IAsyncResult result)
   2:          {
   3:              avatarDescription = AvatarDescription.EndGetFromGamer(result);
   4:   
   5:              if (avatarDescription.IsValid)
   6:                  avatarRenderer = new AvatarRenderer(avatarDescription);
   7:          }
Questo perchè per caricare la descrizione dell'Avatar dobbiamo chiamare il metodo AvatarDescription.BeginGetFromGamer, il quale lavora in metodo asincrono (quindi la sua esecuzione non si blocca in attesa del suo completamento) e può richiedere diverso tempo per essere completato. Quando il metodo avrò completato la sua esecuzione chiamerà il metodo da noi definito per completare le operazioni.
Per mettere tutto assieme aggiungiamo alla fine del metoto LoadContent questo:
   1:              gamer = SignedInGamer.SignedInGamers[0];
   2:              if (gamer != null)
   3:                  AvatarDescription.BeginGetFromGamer(gamer, LoadMyAvatar, null);
che non fa altro che provare a recuperare i dati del giocatore loggato, ed avvia il recupero dei dati dell'Avatar.

Nel metodo update le uniche cose da mettere sono queste:

Ci manca solo il Draw:
GraphicsDevice.Clear(Color.CornflowerBlue);
 
//creo la matrix per la view
Matrix view = Matrix.CreateLookAt(new Vector3(0, 0.3f, -3), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
//creo la matrix per la projection
Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, .01f, 40.0f);
 
if (avatarRenderer != null && gamer != null)
{
avatarRenderer.View = view;
avatarRenderer.Projection = projection;
avatarRenderer.World = Matrix.CreateTranslation(-1, -1, 0);
avatarRenderer.Draw(avatarAnimation.BoneTransforms, avatarAnimation.Expression);
}
 
if (avatarRenderer2 != null && avatarDescription2.IsValid)
{
avatarRenderer2.View = view;
avatarRenderer2.Projection = projection;
avatarRenderer2.World = Matrix.CreateTranslation(1, -1, 0);
avatarRenderer2.Draw(avatarAnimation.BoneTransforms, avatarAnimation.Expression);
}
 
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
if (gamer != null)
{
spriteBatch.DrawString(myFont, gamer.Gamertag, Vector2.Zero, Color.Black);
spriteBatch.Draw(image, new Vector2(0, 30), Color.White);
spriteBatch.DrawString(myFont, String.Format("Current Animation: {0}", preset.ToString()), new Vector2(0, 100),
Color.Black);
}
spriteBatch.End();
Prima creiamo la view e la projection per il render, controlliamo che il giocatore loggato, il renderer e l'animazione siano validi, e poi renderiziamo il tutto.
Poi ripetiamo anche per l'Avatar random.
Alla fine, come nel precedente articolo, si visualizza il nome della nostra gamer tag, la nostra immagine e poi si stampa il nome dell'animazione che stiamo riproducendo.

Ecco il codice completo sorgente del Game in questione:

Show∇


Sinceramente pensavo che sarebbe stato molto più difficile fare una cosa del genere, invece ho trovato tutto molto ben strutturato e semplificato.
Certo non ho ancora provato ad utilizzare le animazioni personalizzate per gli Avatart, o a fare il blend tra due animazioni... Ma quelle le vedrò in futuro!

Intanto ecco a voi uno screen di quello che vedo utilizzando il mio profilo: l'Avatar sulla destra è il mio, quello sulla sinistra è quello generato casualmente!



Alla prossima :)
Continua a leggere!

The Xna-Way: Tutorial 14: Utente collegato ed X-Box Live pt1 Dati di base

Oggi voglio condividere alcune cose che ho provato riguardo alla gestione degli utenti XBox Live, come riconoscere se un utente ha fatto login, se ha fatto logout, chi è attualmente loggato, e le sue informazioni.

Questa sarà la prima parte di considerazioni, dovuto al fatto che alcune funzionalità sono esclusive per X-Box 360, come al gestione degli Avatar.
Riporto quindi qua solo quanto provato sotto piattaforma Windows.

Il metodo più semplice per recuperare le informazioni sull'untente attualmente collegato è tramite il seguente codice:

   1:  SignedInGamer gamer1 = Gamer.SignedInGamers[PlayerIndex.One];
Scrivendo questo, chiediamo di recuperare l'oggetto SignedInGamer relativo al giocatore che sta utilizzando il gamepad numero 1 (cosa simile si può fare con gli altri gamepad, fino all'indice 4).
Questa classe ci fornisce tutti metodi necessari per recuperare le informazioni del nostro giocatore.
Il problema però sorge quando non c'è nessuno giocatore che ha fatto log-in. In questo caso il codice precedente ci fornirà un null.
Sulla console questo si verifica quando lanciamo un gioco in modalità "anonima" possiamo dire, senza aver selezionato cioè il profilo che vogliamo usare.

Ma come possiamo fare, su pc, ad poter fare una cosa simile?
NOTA: per poter far funzionare correttamente il codice che segue, il LiveID che utilizzerete per fare log-in deve avere a disposizione un account Studente o superiore su App Hub, altrimenti andrete contro a delle spiacevoli eccezioni.

La prima cosa da fare è di aggiungere un GamerServicesComponent al nostro gioco. Possiamo farlo all'interno del costruttore del nostro Game:

   1:  Components.Add(new GamerServicesComponent(this));

Il primo passo è fatto. Ora dobbiamo far si che quando si effettua il login di un account si deve recuperare i dati, tra cui per ora ci occuperemo del nome della GamerTag e dell'immagine associata.
Aggiungiamo i seguenti campi alla nostra classe:

   1:  SignedInGamer gamer;
   2:  Texture2D image;
così facendo abbiamo definito dove andremo a memorizzare i dati recuperati.

Abbiamo bisogno di registrare un EventHandler per recuperare i dati del profilo quando viene completata la fase di login. Ecco un esempio di come può essere fatto:

   1:          void LoadGamer(object sender, SignedInEventArgs e)
   2:          {
   3:              gamer = e.Gamer;
   4:              //recupero lo stream per l'immagine
   5:              System.IO.Stream imgIO = gamer.GetProfile().GetGamerPicture();
   6:              //creo la texture2D a partire dallo stream
   7:              image = Texture2D.FromStream(GraphicsDevice, imgIO);
   8:          }
mentre nel costruttore, dopo la creazione del GamerServicesComponent, andiamo a registrare il nostro handler come segue:
   1:  SignedInGamer.SignedIn += new EventHandler<SignedInEventArgs>(LoadGamer); 

Il nostro metodo Draw sarà semplicissimo!
   1:          protected override void Draw(GameTime gameTime)
   2:          {
   3:              GraphicsDevice.Clear(Color.CornflowerBlue);
   4:   
   5:              spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
   6:              if (gamer != null)
   7:              {
   8:                  spriteBatch.DrawString(myFont, gamer.Gamertag, Vector2.Zero, Color.Black);
   9:                  spriteBatch.Draw(image, new Vector2(0,30), Color.White);
  10:              }
  11:              spriteBatch.End();
  12:   
  13:              base.Draw(gameTime);
  14:          }

Ok vediamo di riassumere: creiamo il nostro EventHandler, il quale serve a recuperare i dati del Gamer che fa login (quindi la GamerTag, la sua immagine, i suoi Ack, la lista dei suoi amici, etc). Questo Handler viene chiamato quando il login viene completato con successo. E dopo di questo verrano visualizzate dal metodo Draw il nome della GamerTag e la sua immagine.

Ecco ora a voi alcuni screenshot! (sono relativi alla versione che ho fatto girare sotto Windows 7, quindi sulla console potrebbero essere "leggermente" differenti)
Una volta fatto partire il gioco premete il tastone centrale del vostro pad (o il tasto Home sulla tastiera). E quello che vedrete sarà più o meno questo:
Dopo che avrete accettato le condizioni d'uso, andate su "Accedi", dove avrete una schermata come la seguente:
Inserite i dati del vostro LiveID (che vi ricordo deve disporre di un account Studente o superiore su Abb Hup per essere provato con successo da chi scrive il codice), e avrete che inizierà la procedura di login (io ho anche spuntato l'opzione per fare in modo che ogni volta effettui l'accesso in automatico).
Quando il login sarà completo vi verrà notificato con un messaggio!
Se fate come me, e avete messo il login automatico dell'account (in modo da non dover rifare la procedura a mano ogni volta), se riavviate l'applicazione ed aspettate qualche secondo, ecco cosa vedrete (salvo GamerTag diversa XD):

Ora le basi ci sono! Potete sbizzarrirvi!

Spero di esservi stato utile!
A presto :)
Continua a leggere!

The Xna-Way: Tutorial 13: Semplice gestore audio

Oggi voglio proporvi una componente per gestire gli effetti sonori e le musiche del nostro gioco.
Perchè diciamocelo, un gioco muto non ha proprio un bell'aspetto ed è anche poco accattivamente.

Quello che mi propongo è di mostrarvi un componente che si occupi di caricare gli effetti sonori e le canzoni che vogliamo utilizzare nel nostro progetto, per poi poterle richiamare ed utilizzare a nostro piacimento da qualsiasi punto del nostro progetto (a patto di poter accedere e riferire l'interfaccia definita per interagire con il nostro componente).

Come prima cosa definiamoci l'interfaccia da utilizzare con il nostro audioManager:

Show∇

Molto intuitivamente diamo così la possibilità di agire sul volume dei suoni e delle musiche, permettiamo di caricare un singolo suono/canzone specificando anche l'ID da assegnargli, e che sarà utilizzato per recuperarlo e riprodurlo. Definiamo poi quei metodi che serviranno per riprodurre/fermare/riprendere una cazone e per riprodurre e fermare i suoni.
Qualcuno mi potrà criticare il fatto che utilizzo degli anonimi e diffcili ID di tipo long per identifcare i suoni e le canzoni e non un nome. Io ragiono sul fatto che molto probabilmente la lista dei file audio da gestire mi arriverà da un qualche editor, che avrò usato per definire la lista di tutti i miei asset. Tali asset saranno riferiti da ogni luogo tramite i loro identificativi numeri, questo perchè il nome può essere cambiato per varie ragioni (preferenze, cambio di idee, diversa classificazione, etc) mentre un ID numerico rimarrà presumibilmente (a meno di persone poco sagge che vanno a mettere le manine nutellose dove non dovrebbero) lo stesso per tutto il tempo.
Oltre al fatto che un numero occupa molto meno spazio di un stringa ^__^

Continuiamo però nella nostra esposizione: dobbiamo ora implementare il tutto.

Show∇


Il codice mi pare che sia abbastanza autoesplicativo, se necessita di commenti non esitate a chiedermeli per email o nei commenti.
Solo una minima spiegazione: un SoundEffect rappresenta una singola risorsa sonora caricata, ed è possibile riprodurla utilizzando il metodo play fornito da tale classe.
Abbiamo invece che la SoundEffectInstance rappresenta una singola istanza di una risorsa sonora in fase di riproduzione (o sospesa o fermata). Questo vuol dire che con le SoundEffectInstance possiamo riprodurre più copie del nostro suono tranquillamente. Da fare attenzione al fatto che possiamo creare molte instance da un singolo SoundEffect, e nel caso liberiamo la memoria di quest'ultimo per poi provare a riprodurre una istanza ottenuta da questo... bè gli effetti saranno non piacevoli! Eccezioni su eccezioni!

Forse c'è un punto che può essere in dubbio: come faccio a sapere che tutti i suoni che riproduco verrano riprodotti?
Oppure possiamo mettere la questione nel senso di "quale è il numero massimo di suoni che posso riprodurre sulla mia console?"
Come potete leggere a questa pagine di MSDN abbiamo che
On Zune, a game can have a maximum of 16 total playing SoundEffectInstance instances at one time, combined across all loaded SoundEffect objects. The only limit to the total number of loaded SoundEffectInstance and SoundEffect objects is available memory. However, the user can play only 16 sound effects at one time. Attempts to play a SoundEffectInstance beyond this limit will fail. On Windows, there is no hard limit. Playing too many instances can lead to performance degradation. On Xbox 360, the limit is 300 sound effect instances loaded or playing. Dispose of old instances if you need more.
quindi credo che 300 suoni contemporaneamente siano sufficienti per i nostri scopi.

Spero di esservi stato un minimo utile.

Se volete vedere un esempio più complesso di gestore audio, con una personalizzazione migliore di quello che potete fare, vi rimando alla XnaWiki. Il loro esempio è quello a cui mi sono ispirato per creare il mio personale AudioManager, solo che come ho detto, non mi piacevano alcune cose riguardo all'identificazione degli asset, oltre al fatto che il numero dei suoni riproducibili tutti assieme era limitato a livello di codice.


A presto!
Continua a leggere!

The Xna-Way: Tutorial 12: Performance & Ottimizzazioni pt1

Vediamod i fare il punto su qualche considerazione che ho fatto relativamente allo sviluppo e deploy su PC vs quello su Xbox 360.

Da quello che ricordo, nonostante si possa far girare i progetti XNA sia su pc che su console, i frameword utilizzati per far girare gli applicativi sono simili ma diversi sotto alcuni aspetti fondamentali. Primo tra tutti la gestione del garbage collector.

Su PC viene fatto girare il .NET framework completo, mentre sulla console viene utilizzato quello che si chiama .NET Compact Framework (che dovrebbe essere tra l'altro utilizzato anche per alcuni dispositivi portatili sempre di proprietà della Microsoft).

Tale Compact Framework è alla fin fine un sottoinsieme di quello che gira su PC, ma questo implica che le funzioni che mette a disposizione sono minori.
Per quanto riguarda il Garbage Collector, il compact framework ha una versione che possiamo vedere come meno potente.
Con il Compact Framework si è costretti a controllare ogni oggetto presente in memoria per vedere se è ancora necessario o meno. Quindi attenzione all'allocazione di tanti oggetti e di oggeti di grosse dimenzioni.


Altro problema è il filtraggio degli oggetti di una collezione secondo una certa condizione.
Per un esempio: in una prova dovevo renderizzare gli oggetti che rientravano nella zona della vista del livello, e per fare questo nella versione WINDOWS del gioco avevo utilizzato il metodo Where di Enumerator, defidendomi delle lambda expression per definire il filtro. Sotto Windows funzionava tutto abbastanza bene.
Ma non appena ho eseguito il tutto sulla console le prestazioni sono precipitate vertiginosamente.
A quanto pare i metodi offerti da Enumerator, che accettano delle lambda expression, creano delle nuove liste/oggetti ogni volta che vengono invocati, e questo porta via tante risorse all'esecuzione.
Come risolvere e quindi ottimizzare il codice XNA?
Per risolvere il calo di prestazioni ho provato con il un classico for/foreach con un semplice if-then per l'esecuzione del codice. Ed il guadagno in frame per secondo è stato notevole (per farvi un esempio numerico: dai 26 fps che ottenevo inizialmente sono arrivato a 170 circa!).
Un'altra prova che ho fatto è stata con le query definite tramite LinQ. Il vantaggio di queste query è che permettono di esprimere in modo facile e naturale espressioni di filtraggio come se si scrivesse una query per un database relazionale.
La lettura di tale query è decisamente più semplice di un if-then-else che occupa diverse righe.
E per le prestazioni direte voi?
Da quello che ho visto (sempre con i dati numerici che ho riportato prima) da 170 fps sulla console sono sceso a circa 165.
5 fps per una maggiore leggibilità e manutenzione del codice... Accettabile secondo voi? Per ora secondo me si. Ma in caso si abbia bisogno di prestazioni estreme, e dove anche un solo fps guadagnato è un successo armatevi di calma e scrivetevi le vostre belle condizioni a manina!

Da considerare perè che fino a quando il vostro progetto gira oltre i 60 fps potete stare tranquilli e occuparvi poco delle ottimizzazioni in generale. Il problema è quando scendete sotto i 60... 60 fps è la vostra line della morte.

Ok...
queste sono solo delle mie considerazioni, dovute a prove che ho fatto e a qualche pagina di libro che ho letto.
In caso abbia sbagliato qualcosa siete pregati di farmelo notare.

A presto.
Odino
Continua a leggere!

The Xna-Way: Special Tutorial: DreamSpark! Develop and deploy on the xBox 360

Finalmente in questi giorni sono riuscito a decidermi a comprare una xBox 360.
Si sono stanto tanto stronzo che per tutti questi anni ho sviluppato con XNA solo su pc.

Ma ora adesso che avevo la mia adorata console (piccolo momento di gloria: la slim è una cosa fantastica ^__^) dovevo trovare il modo di svilupparci sopra in modo appropriato.

Il problema principale stava nel fatto che non avevo ora come ora la possibilithttp://www.blogger.com/img/blank.gifà di pagare l'abbonamento annuale di 75€ per l'XNA creator club. E sul sito MSDNAA della mia università non c'era nemmeno più la possibilità di richiedere l'abbonamento studenti.

Ma per fortuna il WEB ci viene in aiuto. Può darsi che queste info siano ugualmente recuperabili da altre parti sul web, ma ho deciso lo stesso di condividerle tramite questo blog.

La Microsoft ha messo a disposizione di tutti gli studenti DEL MONDO (o perlomeno di moltissimi paesi) la possiblità di usufruire di molti http://www.blogger.com/img/blank.gifsuoi prodotti (certificati) in modo gratuito o scontato.
Il segreto di tutto questo è il progetto DreamSpark, a cui potete accedere tramite www.dreamspark.com.

Non voglio stare a farla tanto lunga: in parole povere questo permette a tutti gli stundenti universitari di qualsiasi facoltà (basta che sia registrata negli elenchi della Microsoft) di accedere ai loro prodotti.

Ma vediamo come utilizzare tutto ciò per ottenere un account studente di 12 mesi per Xna creator Club (oramai di dovrebbe dire App Hub O_O).

Come presequisito è necessario essere studenti iscritti ad una qualche facoltà italiana!
La prima cosa da fare è accedere con il vostro LiveID (MI RACCOMANDO! DOVETE ACCEDERE CON IL LiveID A CUI E' COLLEGATA LA GAMERTAG CHE UTILIZZATE SULLA VOSTRA CONSOLE!) ed andare qua https://users.create.msdn.com/Register/ e selezionare la voce Student quando vi verrà richiesto.
Premendo next, al prossimo passo vi verrrà richiesta la certificazione tramite DreamSpark. Naturalmente si parte dal presesupposto che voi non l'abbiate, e quindi dovrete premere su Visit DreamSpark now to get verified.
Fatto ciò verrete reindirizzati al sito di DreamSpark, il quale vi chiederà di inserire alcuni dati fondamentali.
Step1
Vi chiederà di inserire il paese in cui risiedete, poi selezionate Verify as a Student, e nell'ultima voce Get Verified through my School

Step2
Selezionate la vostra università nell'elenco di quelle disponibili per il vostro paese (se non è presente allora è un bel problema... ma non credo che ci dovrebbero essere atenei italiani mancanti).

Step3
Adesso dovete inserire la vostra mail da studente, quella data dal vostro dipartimento per intenderci. A questa mail sarà inviata una mail con il codice per poter completare la verifica su DreamSpark.
Voi potreste chiedere "Chi mi vieta di inserire una mail diversa?". Io non ho provato... ma non credo che sia molto complicato verificare al volo se la mail che inserite viene gestita dei server dell'università che avete scelto.
Quindi è bene inserire il vostro vero indirizzo e-mail di dipartimento.

Step4
Andate a leggere la mail che vi è arrivata. All'interno troverete il codice ed il link da visitare (dove appunto vi verrà richiesto di inserire il codice) per completare la registrazione.

Step5
Ok ora siete verificati su DreamSpark!
Tornate alla registrazione su App Hub, ripetere la procedura vista prima, selezionando sempre Student. Solo che questa volta mamma Microsoft si http://www.blogger.com/img/blank.gifaccorgerà della registrazione a DreamSpark e vi farà procedere.
Ora non dovete far altro che seguire tutti i passi, riempire tutti i campi (NOTA: le info che inserite du App Hub devono essere coerenti con quelle inserite sulla vostra gamerTag, se no il sistema si altera e vi chiede di modificare i dati), ed alla fine avrete il vostro account!

Nota: per chi vuole anche delle immagini sono riuscito a trovare proprio ora questo topic!
Guida alla verifica su DreamSpark.

Ora non dovete far altro che accendere la vostra console, accedere con la gamerTag relativa all'indirizzo/LiveID con cui vi siete registrati su App Hub.
Nel market place, sotto la lettera X, troverete l'XNA Game Studio Connect (NOTA: il programmino è scaricabile anche da chi non si è registrato ad App Hub, solo che se l'account non soddisfa il tipo di registrazione il programma non parte -.-).

Ora non ci resta che collegare la nostra console al pc.
Per fare questo dobbiamo fare alcuni altri passi.
Connect xBox Step1
Dovete lanciare Xna Game Studio Connect. Quando sarà partito vedrete a schermo un codice. Segnatevelo da qualche parte perchè vi servirà.

Connect xBox Step2
Dal vostro pc dovete andare nel menù Tutti i programmi/Microsoft Xna Game Studio 4.0/ e selezionare la voce XNA Game Studio Device Center.
Qua dovrete selezionare la vostra console, dargli un nome, e quando richiesto inserire il codice che vi siete segnati prima.

Ok tutto fatto!
Pc e console sono collegati.
Per provare qualcosa adesso dovete fare quando segue:
con Xna Game Studio Connect attivo sulla console ed in attesa di connessione dal vostro pc, dovete aprire Visual Studio (o Visual C#), aprire un progetto Xna (di tipo xBox360) e lanciarlo.
Il progetto verrà compilato e distribuito sulla console in automatico. E potrete provarlo direttamente li!

Tanto per gradire: funziona anche il debug! Cioè potete mettere dei break point nel codice, e l'esecuzione del gioco sulla console si bloccherà al raggiungimento del break point. :))
Certo non potrete modificare al volo il codice come si fa in locale, ma una cosa pur sempre utile.


Spero di essere stato il più possibile chiaro.
In seguito pubblicherò altri articoli sulle prove e sulle performance ottenute eseguendo vari progetti sulla console.

Per ogni chiarimento chiedete pure tramite mail o tramite commenti.
Saluti a tutti!
Odino
Continua a leggere!

The Xna-Way: Tutorial 11: Spippolando con il gamePad

Ho recentemenete acquistato tramite Amazon.it il gamepad della xbox 360 con cavo usb, in modo da poterlo utilizzare su pc, e da poter provare direttamente su di esso le funzionalità del pad, che altrimenti sarebbero disponibili solo sulla console.

Ho cominciato a giocarci, e questo è quanto ne ho ricavato fino ad ora.

Il pad in se per se è un ottimo prodotto. Ma questo già si sapeva :)

Xna mette a dispozizione il GamePadState per poter interrogare lo stato del nostro pad. Come per tastiera e mouse, possiamo recuperare lo stato corrente del nostro pad tramite il metodo GamePad.GetState(PlayerIndex), dove PlayerIndex indica il numero del giocatore/pad di cui vogliamo lo stato (il giocatore è identificato sul pad dal let acceso attorno al pulsante centrale, ed il valore di PlayerIndex varia tra 1 e 4).

Tra i valori ed i metodi messi a disposizione da tale oggetto possiamo mettere in evidenza:
ThumbStick: ha due proprietà, Left e Right, che permettono di sapere la posizione dei due stick analogici del pad.
Left e Right sono dei Vector2, quindi a loro volta hanno i valori per X ed Y. I valori che possono assumere variano in [-1,+1] (estremi compresi), dove -1 significa che lo stick è completamente a sinistra/basso, 1 significa che lo stick è completamente a destra/alto. Lo 0 significa che lo stick non è stato mosso. Valori intermedi rappresentano posizione intermedie dello stick nelle varie direzioni.

Triggers: i due grilletti posteriori del pad, oltre che come dei semplici bottoni (LeftTrigger e RightTrigger) che hanno stato rilasciato/premuto, possono essere identificati tramite questa proprietà, che contiene a sua volta due valori Left e Right, di tipo float. Questi due valori, varianti tra [0,1], estremi compresi, mi dicono quanto il grilletto è premuto, con 0 = rilasciato, 1 = completamente premuto, e valori intermedi, come prima, identificano posizioni intermedie.

Dpad: identifica lo stato delle "frecce direzionali" del pad. Le direzioni possibili sono 4, ma possono essere combinate due a due. Tramite questo oggetto possiamo controllare quali delle direzioni sono premute.
Notate che il Dpad è completamente diverso dallo stick sinistro, quindi quando muovete lo stick sinistro non cambiate in nessun modo lo stato del Dpad (come era stato portato a credere inizialmente -.-), e viceversa.

Spero che queste poche e brevi considerazioni personali sull'uso del gamepad possano tornare utili a qualcuno. Se avrò altro da aggiungere aggiornerò il post.


A presto.
Odino
Continua a leggere!

The Xna-Way: Tutorial 10: Sprite Sheet & 2D Animation

Vi siete mai imbattuti, mentre ricercavate risorse grafiche per i vostri giochi, negli sprite sheet? Immagini contenenti frame delle animazioni di personaggi, effetti speciali o altro?
(Nota: molto belli sono quelli rippati dai vecchi giochi per il SNES, che potete trovare su alcuni siti, i cui link metterò alla fine del post).

Vediamo un piccolo esempio su come possono essere utilizzati.



Il codice sarà allegato alla fine del post!
La strutturazione del codice per come l'ho pensata io è la seguente:
- TextureCollection: un catalogo contenente tutte le texture2D caricate nel gioco, accedibili tramite un id unico
- SpriteSheetCollection: un catalogo contenente gli SpriteSheet definiti per il gioco, anche questi accedibili tramite un id unico
- AnimationDescCollection: un catalogo contenente la descrizione delle animazioni definite per il gioco, anche queste accedibili trami un id unico

Le collezioni sopra definite sono definite come statiche, in modo che possano essere accedibili da ogni parte del codice.

VI sono poi altre classi:
- SpriteSheet: rappresenta un'immagine divisa in frame. Contiene l'identificatore della texture all'interno della TextureCollection, il numero di frame sull'asse X e sull'asse Y, oltre che un metodo per calcolare il rettangolo relativo al frammento di immagine che si vuole utilizzare.

-AnimationDesc: contiene una collezione di frame che specificano l'animazione, oltre a dire se l'animazione deve essere ripetuta all'infinito o meno.
- Frame: speficifica quale SpriteSheet utilizzare e quale è l'id del frammento da utilizzare (NOTA: I frammenti contentuni all'interno di uno spriteSheet sono numerati da 0 al numero massimo -1, partendo dall'angolo in alto a sinistra e procedendo da destra verso sinistra, e dall'alto verso il basso).

- Animation: è l'oggetto che implementa l'animazione vera e propria. Contiene l'id della descrizione dell'animazione che si sta utilizzando, il frame corrente (cioè dove siamo arrivati a riprodurre l'animazione), il tempo passato dall'ultimo aggiornamento (in modo da poter avanzare il frame corrente al momento giusto), e la posizione dell'animazione nella vista del gioco.

Infine arriviamo al componente principale:
- AnimationManager: è il gameComponent che si preoccupa di gestire ed animare gli oggetti Animation che gli vengono dati in pasto.

Ecco il codice per le classi principali:

Show∇




   1:      /// <summary>

   2:      /// rappresenta un singolo frame dell'animazione

   3:      /// Si caratterizza per l'id dello spriteSheet e per il frammento di immagine utilizzato dal frame

   4:      /// </summary>

   5:      public class Frame

   6:      {

   7:          /// <summary>

   8:          /// Id dello spriteSheet che deve essere presente nella spriteSheetCollection

   9:          /// </summary>

  10:          public long IdSpriteSheet { get; private set; }

  11:          /// <summary>

  12:          /// Id del frammento dell'immagine che si utilizza per il particolare frame

  13:          /// </summary>

  14:          public int IdFragment { get; private set; }

  15:   

  16:          /// <summary>

  17:          /// Costruisce un frame per l'animazione

  18:          /// </summary>

  19:          /// <param name="idSpriteSheet">Id di riferimento per lo spriteSheet che deve essere presente nella spriteSheetCollection</param>

  20:          /// <param name="idFragment">Id del frammento dell'immagine che si utilizza per il particolare frame</param>

  21:          public Frame(long idSpriteSheet, int idFragment)

  22:          {

  23:              this.IdSpriteSheet = idSpriteSheet;

  24:              this.IdFragment = idFragment;

  25:          }

  26:      }

  27:   

  28:      /// <summary>

  29:      /// Descrive come è fatta un'animazione

  30:      /// </summary>

  31:      public class AnimationDesc

  32:      {

  33:          /// <summary>

  34:          /// Lista dei frame che compongono l'animazione

  35:          /// </summary>

  36:          public List<Frame> FrameList { get; set; }

  37:   

  38:          /// <summary>

  39:          /// Vale true se l'animazione deve ripetersi

  40:          /// False altrimenti

  41:          /// </summary>

  42:          public bool Loop { get; set; }

  43:   

  44:          /// <summary>

  45:          /// Crea la descrizione di un'animazione che non si ripete dopo la fine

  46:          /// </summary>

  47:          public AnimationDesc()

  48:              : this(false)

  49:          {

  50:          }

  51:   

  52:          /// <summary>

  53:          /// Crea la descrizione di un'animazione permettendo di configurare la ripetizione

  54:          /// </summary>

  55:          /// <param name="loop">True se si vuol far ripetere l'animazione, false altrimenti</param>

  56:          public AnimationDesc(bool loop)

  57:          {

  58:              FrameList = new List<Frame>();

  59:              Loop = loop;

  60:          }

  61:      }

  62:   

  63:      /// <summary>

  64:      /// Definisce un'animazione da visualizzare a schermo dall'AnimationManager

  65:      /// </summary>

  66:      public class Animation

  67:      {

  68:          /// <summary>

  69:          /// Posizione (nel range dello schermo) in cui viene visualizzata l'animazione

  70:          /// </summary>

  71:          public Vector2 Position { get; set; }

  72:   

  73:          /// <summary>

  74:          /// Frame corrente dell'animazione

  75:          /// </summary>

  76:          public int CurrentFrame { get; set; }

  77:   

  78:          /// <summary>

  79:          /// Id dell'AnimationDesc utilizzata

  80:          /// </summary>

  81:          public long IdAnimationDesc { get; private set; }

  82:   

  83:          /// <summary>

  84:          /// Tempo passato dall'ultimo cambio di frame

  85:          /// </summary>

  86:          public int ElapsedTime { get; set; }

  87:   

  88:          /// <summary>

  89:          /// Crea una nuova Animazione con riferimento all'AnimationDesc con id passato. Creata in posizione (0,0)

  90:          /// </summary>

  91:          /// <param name="idAnimation">Id dell'AnimationDesc presente nella AnimationsDescCollection</param>

  92:          public Animation(long idAnimationDesc)

  93:              : this(idAnimationDesc, Vector2.Zero)

  94:          {

  95:          }

  96:   

  97:          /// <summary>

  98:          /// Crea una nuova Animazione con riferimento all'AnimationDesc con id passato.

  99:          /// </summary>

 100:          /// <param name="idAnimation">Id dell'AnimationDesc presente nella AnimationsDescCollection</param>

 101:          /// <param name="position">Posizione dell'Animazione</param>

 102:          public Animation(long idAnimationDesc, Vector2 position)

 103:          {

 104:              this.IdAnimationDesc = idAnimationDesc;

 105:              this.Position = position;

 106:              ElapsedTime = 0;

 107:          }

 108:      }

 109:   

 110:      /// <summary>

 111:      /// Component per il rendering della Animation a schermo

 112:      /// </summary>

 113:      public class AnimationManager : Microsoft.Xna.Framework.DrawableGameComponent

 114:      {

 115:          //spritebatch su cui disegnare

 116:          SpriteBatch spriteBatch = null;

 117:   

 118:          /// <summary>

 119:          /// lista delle animazioni da eseguire. Un'animazione viene automaticamente rimossa al termine della sua animazione

 120:          /// </summary>

 121:          public List<Animation> AnimationList { get; set; }

 122:   

 123:          public AnimationManager(Game game)

 124:              : base(game)

 125:          {

 126:              AnimationList = new List<Animation>();

 127:          }

 128:   

 129:   

 130:          protected override void LoadContent()

 131:          {

 132:              spriteBatch = new SpriteBatch(Game.GraphicsDevice);

 133:              base.LoadContent();

 134:          }

 135:   

 136:          public override void Initialize()

 137:          {

 138:              base.Initialize();

 139:          }

 140:   

 141:          public override void Update(GameTime gameTime)

 142:          {

 143:              int delta = gameTime.ElapsedGameTime.Milliseconds;

 144:   

 145:              AnimationDesc desc = null;

 146:              for (int i = AnimationList.Count - 1; i >= 0; i--)

 147:              {

 148:                  desc = AnimationsDescCollection.AnimationCollection[AnimationList[i].IdAnimationDesc];

 149:                  if (!desc.Loop && AnimationList[i].CurrentFrame == desc.FrameList.Count - 1)

 150:                      AnimationList.RemoveAt(i);

 151:                  else if (desc.Loop && AnimationList[i].CurrentFrame == desc.FrameList.Count - 1)

 152:                      AnimationList[i].CurrentFrame = 0;

 153:              }

 154:   

 155:              for (int i = 0; i < AnimationList.Count; i++)

 156:              {

 157:                  desc = AnimationsDescCollection.AnimationCollection[AnimationList[i].IdAnimationDesc];

 158:                  AnimationList[i].ElapsedTime += delta;

 159:                  if (AnimationList[i].ElapsedTime >= 40)

 160:                  {

 161:                      AnimationList[i].ElapsedTime -= 40;

 162:                      AnimationList[i].CurrentFrame++;

 163:                  }

 164:              }

 165:   

 166:              base.Update(gameTime);

 167:          }

 168:   

 169:          public override void  Draw(GameTime gameTime)

 170:          {

 171:              spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);

 172:              AnimationDesc desc = null;

 173:              for (int i = 0; i < AnimationList.Count; i++)

 174:              {

 175:                  desc = AnimationsDescCollection.AnimationCollection[AnimationList[i].IdAnimationDesc];

 176:                  Frame current = desc.FrameList[AnimationList[i].CurrentFrame];

 177:                  SpriteSheet spriteSheet = SpriteSheetCollection.SpritesSheetCollection[current.IdSpriteSheet];

 178:   

 179:                  Texture2D img = TextColl.TextureCollection[spriteSheet.IdTexture];

 180:                  Rectangle sRec = spriteSheet.GetRectangle(current.IdFragment);

 181:                  spriteBatch.Draw(img, AnimationList[i].Position - new Vector2(sRec.Width / 2, sRec.Height / 2),

 182:                      sRec, Color.White);

 183:              }

 184:              spriteBatch.End();

 185:   

 186:              base.Draw(gameTime);

 187:          }

 188:      }


HideΔ




E questo è il codice per il Game:

Show∇




   1:      public class Game1 : Microsoft.Xna.Framework.Game

   2:      {

   3:          GraphicsDeviceManager graphics;

   4:          SpriteBatch spriteBatch;

   5:   

   6:          SpriteFont font;

   7:   

   8:          AnimationManager animMng;

   9:   

  10:          //stato del mouse al ciclo precedente

  11:          MouseState lastMS;

  12:   

  13:          public Game1()

  14:          {

  15:              graphics = new GraphicsDeviceManager(this);

  16:              Content.RootDirectory = "Content";

  17:   

  18:              graphics.PreferredBackBufferWidth = 800;

  19:              graphics.PreferredBackBufferHeight = 600;

  20:   

  21:              TextColl.Initialize();

  22:              SpriteSheetCollection.Initialize();

  23:              AnimationsDescCollection.Initialize();

  24:   

  25:              Components.Add((animMng = new AnimationManager(this)));

  26:   

  27:              IsMouseVisible = true;

  28:          }

  29:   

  30:          protected override void Initialize()

  31:          {

  32:              base.Initialize();

  33:          }

  34:   

  35:          protected override void LoadContent()

  36:          {

  37:              spriteBatch = new SpriteBatch(GraphicsDevice);

  38:   

  39:              font = Content.Load<SpriteFont>("font");

  40:   

  41:              TextColl.TextureCollection.Add(0, Content.Load<Texture2D>("prova"));

  42:   

  43:              SpriteSheet ss = new SpriteSheet(0, 5, 2);

  44:              SpriteSheetCollection.SpritesSheetCollection.Add(0, ss);

  45:   

  46:              AnimationDesc ad = new AnimationDesc();

  47:              ad.FrameList.Add(new Frame(0, 0));

  48:              ad.FrameList.Add(new Frame(0, 1));

  49:              ad.FrameList.Add(new Frame(0, 2));

  50:              ad.FrameList.Add(new Frame(0, 3));

  51:              ad.FrameList.Add(new Frame(0, 4));

  52:              AnimationsDescCollection.AnimationCollection.Add(0, ad);

  53:   

  54:              lastMS = Mouse.GetState();

  55:          }

  56:   

  57:          protected override void UnloadContent()

  58:          {

  59:          }

  60:   

  61:          protected override void Update(GameTime gameTime)

  62:          {

  63:              if (Keyboard.GetState().IsKeyDown(Keys.Escape))

  64:                  this.Exit();

  65:   

  66:              if (lastMS.LeftButton == ButtonState.Released && Mouse.GetState().LeftButton == ButtonState.Pressed)

  67:              {

  68:                  MouseState ms = Mouse.GetState();

  69:                  Animation anim = new Animation(0, new Vector2(ms.X, ms.Y));

  70:                  animMng.AnimationList.Add(anim);

  71:              }

  72:              lastMS = Mouse.GetState();

  73:              base.Update(gameTime);

  74:          }

  75:   

  76:          protected override void Draw(GameTime gameTime)

  77:          {

  78:              GraphicsDevice.Clear(Color.CornflowerBlue);

  79:   

  80:              base.Draw(gameTime);

  81:          }

  82:      }


HideΔ




Come potete vedere non è nulla di trascendentale o di così complicato come poteva sembrare.
Vi allego anche la soluzione per Xna4 (se il link non funziona contattatemi):
Soluzione Visual Sutdio

Ed ecco un buonissimo sito con tantissimi spriteSheet!
Sprite Database

Alla prossima!
Odino
Continua a leggere!

Donazioni

My Menu'