The Xna-Way: Tutorial 1: FPS Counter

Salve a tutti!
Ho deciso di cominciare il prima possibile a scrivere qualcosa riguardante il magnifico mondo dell'XNA.
Le mie conoscenze a riguardo sono per ora esigue, ed è per questo che ho deciso di scrivere qui quello che produrrò di "utile" di volta in volta, in modo da poter rendere diponibile ad altri quel poco che imparerò e per fare in modo che gli sventurati che capiteranno da queste parti e che ne sanno più di me, vedendo i miei strafaciolni, possano (se vogliono) correggermi!

Detto questo cominciamo! ^^

Dato che ho appena incominciato ad avventurarmi in questo mondo non andrò a buttarmi in spiegazioni troppo complicate o astruse...
Anche perchè il mio "sapere" aumenterà mano a mano che proseguirò con la lettura del manuale! Quindi per ora sono un po' a secco...

Cosa abbiamo appreso intanto?
Bè dopo aver scaricato dal sito della microsoft l'ultimo Microsoft Visual C# 2008 Express Edition e XNA 3.1, ho deciso di mettermi all'opera.
Nota: io non ho una X-Box 360 (eh lo so sono stronzo ?_?), quindi tutto il codice che andrò a scrivere sarà per Windows.
Naturalmente il codice potrà funzionare anche su una 360, solo che alle volte si dovranno fare delle modifiche. Quello che conta è che il concetto che sta dietro è lo stesso.

Dopo la creazione di un nuovo progetto di prova quello che appare è un file Game1.cs contenente il seguente codice:
 public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;

public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}

protected override void Initialize()
{
base.Initialize();
}

protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
}

protected override void UnloadContent()
{
}

protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();

base.Update(gameTime);
}

protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);

base.Draw(gameTime);
}
}

A cosa sono questi metodi a grandi linee?
Il Game1() è il costrutture della nostra classe Game1, nella quale andremo a creare tutti gli oggetti che ci serviranno per la nostra applicazione.
Inizialize() è il metodo che viene chiamato dopo il costruttore, e qui andremo ad inizializzare tutti i valori che ci servono.
Il LoadContent() viene chiamato dopo l'Initialize() e sarà il metodo dove inseriremo il caricamento dei file che ci servono, come i file audio, i modelli 3D, le immagini, etc. Insomma di tutta quella roba che sfrutta il il ContentPipeline di XNA per il caricamento degli elementi del gioco.
Dal contempo l'UnloadContent viene chiamato alla chiusura dell'applicazione serve a liverare la memoria occupata dai file caricati.
Il "cuore" della nostra applicazione sono i metodi Update() e Draw().
Questi metodi vengono chiamati a ripetizione, cioè Upadate()-Draw()-Update()-Draw() e così via fino a quando non viene chiusa l'appliacazione.
In Update() verrà messo il codice che serve ad aggiornare lo stato del nostro gioco, come la posizione della telecamera, la posizione dei nemici,...
Nel metodo Draw() tutto il codice che serve a disegnare, a renderizzare, la scena del gioco a schermo.

Se facciamo partire il programma adesso l'unica cosa che vedremo è una scena vuota a tinta unita.

Cosa vogliamo fare adesso?
Bè io intanto volevo vedere gli FPS della scena, anche per rendermi conto quando va veloce il tutto ^^

Si potrebbe pensare di mettere tutto il codice che ci serve nel file Game1.cs.
Certo funzionerebbe, ma dopo che avevo cominciato a scrivere qualcosa mi sono renso conto di questo: adesso metto il conteggio dei frame nel Draw(), poi dovrò renderizzare altri elementi quando mi servono, poi potrei voler fare qualche altra cosa etc.
Tutto questo dopo un po' renderebbe le cose confusionarie, portanto il codice ad una cattiva lettura e bassa possibilità di fare manutenzione e correzione.
Poi diciamocelo, C# è un linguaggio ad oggetti e ci sarà pure il modo di fare fare un oggetto che mi faccia il conto degli FPS da solo e possibilmente in automatico!
Meglio quindi cercare una soluzione più comoda e al contempo performante, cercando al contempo di suddividere il codice in più unità logiche cooperanti.

Con XNA possiamo utilizzare i GameComponents. Con i GameComponent possiamo organizzare al loro interno parte delle operazioni che vogliamo far fare al nostro gioco, e queste operazioni saranno poi richiamate automaticamente dal Framework di XNA.
Infatti XNA ci da la possibilità di gestire una collezione di GameComponents, alla quale possiamo aggiungere gli elementi che vogliamo siano gestiti come GameComponent.
Quindi in questo caso possiamo creare un GameComponent per il calcolo degli FPS, un GameComponent per la gestione e il movimento della telecamera, etc.
Però non dobbiamo pensare che per ogni oggetto che vogliamo gestire si possa creare un GameComponent non va bene. Ma per ora questa cosa non ci tocca. Si affronterà in seguito.
Andiamo quindi a creare il codice del nostro FPS Counter.

Per avere un'ulteriore suddivisione tra il codice del gioco e dei vari elementi che andremo a creare ho deciso di mettere tutti gli elementi in una GameLibrary (nel mio caso una WindowsGameLibrary, chi volesse provare il codice su una 360 devrà creare una X-BOX 360 Library).

Ho quindi creato il mio progetto libreria, chiamato myLibrary (che fantasia -.-).
Aggiungiamo un nuovo elemento al progetto, aggiungendo un GameComponent e chiamiamolo FPSCounter.
Fatto questo invece di far derivare FPSCounter da GameComponent facciamolo derivare da DrawableGameComponent, in questo modo il nostro FPSCount avrà il suo metodo Draw che verrà chiamato in automatico da XNA! (invece il GameComponent classico non offre questo metodo). E voi come direte... e ce ne importa del metodo Draw()?
Bè per contare gli FPS ci serve il metodo Draw() dato che gli FPS indicano il numero di volte al secondo che una scena viene renderizzata.

Per calcolare il valore FPS ci servono le seguenti variabili:
una variabile per tenere a mente il numero di volte che abbiamo renderizzato la scena (frameCount)
una varibile per tenere a mente quando abbiamo renderizzato l'ultima volta la scena (timeLastDraw)
Quelle che ho usato io, e che ho definito nella classe FPSCounter sono:
private int frameCount = 0;
private float intervall;
private float elapsedTime;
private float timeLastDraw;
private float fpsValue;

Dove intervall è l'intervallo in cui voglio misurare il numero di frame (in questo caso un secondo).
elapsedTime contiene il tempo che è passato dall'ultima invocazione di Draw().
fpsValue non credo che abbia bisogno di spiegazioni ^^.

L'unico metodo che ci interessa è il Draw(), ed è questo che andremo a ridefinire.
Tutti gli altri, costruttore a parte, possono essere cancellati.
Per ora nel costruttore mettiamo solo
intervall = 1.0f;


mentre il metodo Draw() sarà così fatto:
elapsedTime = (float)gameTime.ElapsedRealTime.TotalSeconds;
frameCount++;
timeLastDraw += elapsedTime;
if (timeLastDraw > intervall)
{
fpsValue = frameCount / intervall;
frameCount = 0;
timeLastDraw -= intervall;
Game.Window.Title = fpsValue.ToString();
}
base.Draw(gameTime);

prima ci calcoliamo quanto tempo è passato dall'ultima invocazione di Draw(), poi incrementiamo il frameCount, ed aggiungiamo il valore di elapsedTime a timeLastDraw, e poi controlliamo il valore di timeLastDraw.
Se il suo valore è maggiore di intervall, quindi di un secondo, calcolo il valore di fpsValue, azzero il frameCount, sottraggo intervall a timeLastDraw.
Come ultima operazione mostro il valore di fpsValue nella barra del titolo della finestra in cui è avviata l'applicazione (questo fa solo su Windows).

Ok fatto questo dobbiamo ora avviare il counter assieme al programma.
Come collegarla con la nostra applicazione, e cioè con il Game1.cs?
Prima di tutto aggiungiamo il riferimento alla nostra libreria: nell'esplora soluzioni facciamo tassto destro su riferimenti->aggiungi riferimento->scheda progetti->selezioniamo il nostro progetto myLibrary.
Fatto questo in vetta al file Game1.cs, sotto tutti gli altri using, mettiamo questa riga:
using myLibrary
Nel file Game1.cs mettiamo una varibile di classe come questa:
FPSCounter counter;

e dentro il costruttore di Game1.cs aggiungiamo:
counter = new FPSCounter(this);
Components.Add(counter);

Queste righe creano un nuovo FPSCounter e lo aggiungono alla collezione di GameComponent del nostro gioco.
Ricordo che i GameComponent vengono eseguiti automaticamente da XNA.

Quello che vi dovrebbe apparire è un valore nella barra del titolo al posto del nome della vostra applicazione.
Dovrebbe apparirivi un valore pari a 60, o che oscilla tra 58 e 60.
Ora dico...
Ho rifatto nuovo il portatile, e avere un "gioco" senza nulla scena che mi da 60 FPS mi pare un po' pochino...

Perchè accade questo?
Perchè XNA è impostato per fare il ciclo di chiamata Update-Draw 60 volte al secondo.
Quindi se anche fosse possibile renderizzare ad una velocità più alta lui la limita.

Come possiamo ovviare a questo limite?
Per renderizzare al massimo della velocità dobbiamo mettere queste righe di codice nel costruttore di FPS:
GraphicsDeviceManager manager = (GraphicsDeviceManager)Game.Services.GetService(typeof(IGraphicsDeviceManager));

manager.SynchronizeWithVerticalRetrace = false;
Game.IsFixedTimeStep = false;

Con queste andiamo a prendere il servizio che fa da gestore per la grafica del gioco,
e con le altre righe gli diciamo che deve renderizzare il più velocemente possibile.
Più precisamente: mettendo a false il valore SynchronizeWithVerticalRetrace dico che non deve sincronizzarsi con la frequenza del monitor, mentre con IsFixedTimeStep a false dico che non deve chiamare il metodo Update alla frequenza di default di 60 volte al secondo.

Facendo rieseguire ora ottengo un valore che si aggira sui 1500 FPS...
Già meglio cavolo :)

Per ora basta gente ^^
Spero di essere stato chiaro.
Come sempre se qualcosa non torna, se volete esprimere un commento, proporre una modifica, etc, siete liberissimi di farlo!

Ecco intanto il codice prodotto fino ad ora!
XNA-tut1.rar

Alla possima!

0 commenti:

Donazioni

My Menu'