Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Scrivere codice per XNA: loop e non pagine

La differenza tra la "classica" filosofia form-componente di Windows Form o di Web Form e quella basata sul loop di XNA
La differenza tra la "classica" filosofia form-componente di Windows Form o di Web Form e quella basata sul loop di XNA
Link copiato negli appunti

Il classico file Program.cs rappresenta, come in qualunque applicazione .NET, l'entry point dell'applicazione, e, nel caso di applicazioni XNA, istanzia la classe Game1 definita nel file Game1.cs e effettua la chiamata al relativo metodo Run.

Il gioco, nel caso di questo semplice esempio, dove non facciamo ricorso a virtuosismi architetturali, sarà definito interamente nel file Game1.cs.

public class Game1 : Microsoft.Xna.Framework.Game
{
  GraphicsDeviceManager graphics;
  SpriteBatch spriteBatch;
  public Game1()
  {
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";
    // Frame rate is 30 fps by default for Windows Phone.
    TargetElapsedTime = TimeSpan.FromTicks(333333);
  }

Nel costruttore della classe troviamo qualche importantissima indicazione: per prima cosa viene creato l'oggetto GraphicsDeviceManager che si incarica di gestire la parte grafica del device rispetto al nostro gioco. Poi viene impostata la proprietà RootDirectory del contenuto verso la directory "Content" del progetto relativo, dove abbiamo aggiunto l'immagine poco fa.

La terza riga di codice ci apre la mente sul mondo XNA: l'interfaccia in un gioco XNA non si basa su form o pagine in cui piazzare controlli che scatenano eventi come in Silverlight, WPF, Windows Forms o addirittura Visual Basic, ma sul concetto di loop eseguito a intervalli di tempo, regolari per default, in cui il nostro codice dovrà disegnare sullo schermo.

Per fare un paragone un po' forzato, ma che dovrebbe spiegare meglio il concetto a chi arriva da una programmazione più tradizionale, è come creare un form vuoto e un controllo Timer che fa scattare un evento a intervalli regolari di tempo in cui gestire il nostro codice.

Il runtime di XNA si basa su questa filosofia: a intervalli regolari (o volendo irregolari) di tempo, il runtime invoca il metodo Update destinato a contenere la logica del gioco e, successivamente, invoca il metodo Draw, destinato al disegno su schermo.

Entrambi i metodi sono già predisposti dal template del progetto Visual Studio e, fra un attimo, li riempiremo con il codice della nostra semplice animazione. La proprietà TargetElapsedTime indica l'intervallo di tempo trascorso il quale il runtime dovrà invocare i due metodi e, conseguentemente, il framerate della nostra applicazione (ossia il numero di volte che il gioco aggiorna la nostra immagine a schermo). Per default, su Windows Phone 7, il frame rate è di 30 fps (frame per secondo), mentre su Windows, normalmente, è 60 fps.

Se stiamo realizzando un progetto per Windows Phone 7.5, nel costruttore troviamo una nuova proprietà (anche se, a voler essere precisi, era già presente nelle versioni precedenti del framework), denominata InactiveSleepTime, che permette di impostare il tempo che deve trascorrere perché il gioco passi in modalità sleep quando non è attivo (di default, un secondo), risparmiando così la batteria (per ulteriori approfondimenti si veda questo link):

// Extend battery life under lock.
InactiveSleepTime = TimeSpan.FromSeconds(1);

Prima di poter cablare il codice nei metodi Update e Draw dobbiamo informare il runtime del fatto che vogliamo utilizzare l'immagine aggiunta in precedenza al nostro progetto "Content". Siccome la RootDirectory è già stata impostata, è sufficiente definire una variabile che conterrà la Texture e assegnarla nell'apposito metodo LoadContent: tale metodo viene richiamato una sola volta alla partenza del gioco e ci consente di avere un posto centralizzato per caricare le risorse. Si utilizza il metodo generico Load<T> dell'oggetto Content che riceve l'Asset Name per caricare la risorsa nel gioco. Nel nostro caso, T, dovrà essere impostato a Texture2D in quando l'immagine inserita nel progetto Content è una Texture a due dimensioni.

Per eseguire questa operazione occorre definire una variabile nella classe Game1 e valorizzarla nel metodo LoadContent come si nota nel codice seguente, che presenta il metodo di default creato dal template di progetto, a cui abbiamo aggiunto alcune righe:

Texture2D logoTexture;
        /// 

        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// 
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            // TODO: use this.Content to load your game content here
			// [aggiunta]
            logoTexture = this.Content.Load<Texture2D>("logo");
        }

Il primo passo, tralasciando per adesso gli algoritmi logici, è disegnare l'immagine a video; come abbiamo accennato, il disegno dell'interfaccia si effettua dal metodo Draw. Grazie al codice proposto da Visual Studio, che inizializza automaticamente l'oggetto SpriteBatch, ossia la classe di XNA che si preoccupa di disegnare gli sprite sullo schermo, possiamo usare direttamente questa istanza per disegnare l'immagine.

Il metodo Draw della classe SpriteBatch deve essere sempre compreso fra una chiamata al metodo Begin e una chiamata al metodo End: questa tecnica consente di raggruppare diverse operazioni di disegno in un'unica operazione a video.

Il codice per disegnare a video la nostra immagine, caricata come Texture2D nel metodo Load, è il seguente:

spriteBatch.Begin();
            spriteBatch.Draw(logoTexture, new Vector2(0, 0), Color.White);
            spriteBatch.End();

Eseguendo la nostra applicazione con il classico F5, si aprirà l'emulatore che presenterà la nostra immagine nell'angolo in alto a sinistra, come abbiamo indicato nel codice precedente usando la classe Vector2, un vettore a due dimensioni, che riporta i valori per l'asse X e l'asse Y (di default, la posizione di un'mmagine è calcolata prendendo come punto di riferimento l'angolo in alto a sinistra dell'immagine stessa).

Le coordinate XNA sul telefonino, per default, si basano sulla posizione Left Landscape, ovvero con il device orientato in orizzontale verso sinistra partendo dalla sua posizione verticale. Ruotando quindi l'emulatore tramite l'apposito tasto verifichiamo il nostro codice:

Figura 6. L'immagine nella posizione 'Left Landscape'

(clic per ingrandire)

L'immagine nella posizione 'Left Landscape'

Arricchiamo il semplice esempio cercando di far spostare l'immagine in diagonale verso il basso: per farlo occorrerà che il metodo Draw abbia due coordinate variabili a ogni ciclo. Definiamo dunque una variabile all'interno della classe Game1, di tipo Vector2 che, a ogni chiamata nel loop del metodo Update verrà incrementata di 1 posizione.

È necessario definire una variabile esterna al metodo, in quanto, come abbiamo ormai capito, sono due i metodi che lavorano durante il loop: Update per la logica applicativa e Draw per il disegno dell'interfaccia.

Definiamo una variabile position di tipo Vector2 nella classe, incrementiamola nel metodo Update e usiamo nel metodo Draw; ecco il codice modificato:

// [modifica] aggiunto il vettore position
        private Vector2 position = new Vector2(0, 0);
        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();
            // TODO: Add your update logic here
			// [modifica] aggiunto un punto
            position += new Vector2(5, 5);
            base.Update(gameTime);
        }
        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            spriteBatch.Begin();
			// [modifica] impostato position come valore per draw
            spriteBatch.Draw(logoTexture, this.position, Color.White);
            spriteBatch.End();
            base.Draw(gameTime);
        }

La variabile position parte dalle coordinate "0,0" esattamente come nel codice precedente, ma viene incrementata di 5 pixel sia sulla X che sulla Y ad ogni ciclo. La stessa variabile viene usata nel metodo Draw per disegnare. Eseguendo l'applicazione, il nostro logo, scorrerà diagonalmente ad ogni loop.

Ecco uno screenshot dopo circa un secondo di esecuzione:

Figura 7. L'applicazione in esecuzione sull'emulatore

(clic per ingrandire)

L'applicazione in esecuzione sull'emulatore

Come esercizio, prima di passare alle prossime lezioni, vi consigliamo di provare a fermare e invertire la rotta dell'immagine, volendo anche solo su un'asse, cercando di cablare la logica nel metodo Update. In pratica occorrerà inserire una variabile nella classe Game1 per indicare la direzione, utilizzarla per sommare o sottrarre i pixel alla variabile position e controllare di non "sfondare" i bordi dello schermo di 800x480 in posizione Left Landscape.

Per effettuare test direttamente sui sorgenti è possibile scaricare il codice dell'esempio.

Ti consigliamo anche