The XNA-Way #3: Screen shotter

Altro articolo.

Dopo il precedente articolo sul component per la gestione dell’input vediamo adesso un semplice modo per usarlo.

Vediamo di progettare un component che ci permetta di fare degli screenshot del nostro gioco e di salvarli su file.


Quello che voglio fare è un DrawableGameComponet che permetta di salvare quello che è renderizzato a schermo su di un file immagine.

Voglio anche fare in modo che chi usa il component possa decidere se salvare lo screenshot in formato jpg o png (i due formati resi disponibili da XNA).
Per controllare il formato e discriminare il salvataggio userò il seguente enumeratore. Per prima cosa creiamo nella nostra GameLibrary una nuova cartella chiamata Enumerators nel quale andremo ad inserire tutti gli eventuali enumeratori che andremo a creare in seguito.
Il codice per il nostro enumeratore sarà il seguente

namespace GameLibrary.Enumerators
{
    public enum ScreenShotFileFormat
    {
        jpg,
        png
    }
}

Ora vediamo di esporre la logica del gameComponet che voglio andare ad implementare:

  • Recupererà il riferimento al servizio di gestione dell’input per sapere quando è stato premuto il tasto per la creazione dello screenshot (io ho scelto F12)
  • Il metodo update non ci servirà dato che la logica sarà inserita nel metodo Draw
  • Alla pressione del tasto recupereremo i dati disegnati nel backBuffer e li salveremo su file, utilizzando il valore impostato alla creazione del component

Nulla di troppo complicato giusto?
Detto questo vediamo il codice:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.IO;
 
using GameLibrary.Interfaces;
using GameLibrary.Enumerators;
 
namespace GameLibrary.Component
{
    public class ScreenShotter : Microsoft.Xna.Framework.DrawableGameComponent
    {
        SimpleInputDeviceI input;
        ScreenShotFileFormat fileFormat;
 
        public ScreenShotter(Game game)
            : this(game, ScreenShotFileFormat.png)
        { }
 
        public ScreenShotter(Game game, ScreenShotFileFormat fileFormat)
            : base(game)
        {
                        //cambiando l'ordine relativo del draw ed impostandolo al massimo valore per gli interi dovrei 
            //essere sicuro che questo component venga eseguito per ultimo e che quindi si ritrovi con la scena completamente renderizzata
            this.DrawOrder = int.MaxValue;
            //imposto il formato del file al valore passato
            this.fileFormat = fileFormat;
        }
 
        public override void Initialize()
        {
            //recuper il servizio dedicato alla gestione dell'input
            input = (SimpleInputDeviceI)Game.Services.GetService(typeof(SimpleInputDeviceI));
            base.Initialize();
        }
 
        public override void Draw(GameTime gameTime)
        {
            base.Draw(gameTime);
 
            if (input.IsPressed(Keys.F12))
            {
                //recupero i dati sulla dimensione dell'area video
                int weight = Game.GraphicsDevice.PresentationParameters.BackBufferWidth;
                int height = Game.GraphicsDevice.PresentationParameters.BackBufferHeight;
                //creo il vettore per contenere i color dei pixel dell'immagine
                Color[] backBuffer = new Color[height * weight];
                //recupero i dati dal backbuffer
                Game.GraphicsDevice.GetBackBufferData(backBuffer);
                //creo la texture
                Texture2D texture = new Texture2D(Game.GraphicsDevice, weight, height, false, Game.GraphicsDevice.PresentationParameters.BackBufferFormat);
                //setto i dati dei pixel della texture
                texture.SetData(backBuffer);
                //creo il percorso del file
                string path = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
                //apro lo stream e salvo su disco
                path = String.Format("{0}\\screen{1}.{2}", path, DateTime.Now.Ticks.ToString(), fileFormat.ToString());
                FileStream file = new FileStream(path, FileMode.OpenOrCreate);
                switch (fileFormat)
                {
                    case ScreenShotFileFormat.jpg: texture.SaveAsJpeg(file, weight, height); break;
                    case ScreenShotFileFormat.png: texture.SaveAsPng(file, weight, height); break;
                }                
                file.Close();
            }
        }
    }
}

[important]Voglio solo far notare una cosa: in teoria non si dovrebbe inserire il controllo dell’input all’interno del metodo Draw, questo perchè esula dai compiti ad esso assegnati[/important]

[notice]Un’altra cosa che voglio far notare è il trucco che ho usato per recuperare l’immagine disegnata e salvarla su file. Se ci pensate bene possiamo avere diversi GameComponent che disegnano ognuno un pezzo diverso della scena. Come possiamo essere sicuri che il nostro ScreenShotter abbia a disposizione tutta la scena completa?

Durante la creazione del component ho settato l’ordinamento relativo dell’esecuzione dei vari metodi Draw dei vari component, in questo modo dovrei essere ragionevolmente sicuro che questo componente venga renderizzato per ultimo e quindi abbia tutta la scena a disposizione.

Avevo provato a far eseguire il tutto nel metodo Update, così in teoria avrei dovuto avere la scena renderizzata al passo precedente a completa disposizione cosa che però è risultata inesatta, perchè facendo varie prove ho notato che viene salvata una scena vuota.
Forse giocando con qualche impostazione o con altri metodi potrei riuscire a spostare il tutto nel posto giusto… chissà…
è da provare :P[/notice]

Per il resto non ho altro da aggiungere.

Vi allego qua sotto l’archivio con i sorgenti.
IndieGearLab_03.rar

Se ci sono domande o dubbi, come al solito, vi invito a contattarmi o a lasciar scritto nei commenti.
Alla prossima.

Lascia un commento

Your email address will not be published.

We use cookies to ensure that we give you the best experience on our website.
Ok