In questo capitolo vedremo come disegnare oggetti 2D a schermo. Inizieremo dal modo di farlo manuale, per capire quali sono gli attori coinvolti (anche se il processo manuale è decisamente più complicato) e poi vedremo come grazie a XNA e all'oggetto SpriteBatch possiamo realizzare le stesse operazioni con pochissime righe di codice e (quasi) la stessa flessibilitá.
Ora che siamo in grado di creare una applicazione che non fa nulla, chiaramente ci interessa iniziare a disegnare qualcosa sullo schermo. Per fare questo dobbiamo creare una serie di quadrati colorati, componendo tra loro coppie di triangoli.
La geometria in XNA, come in DirectX, è basata sulla creazione di due buffer che contengono i vertici della geometria (Vertex Buffer) e gli indici dei vertici che presi "a tre a tre" ci permettono di ricostruire i triangoli (Index Buffer); un quadrato potrebbe essere costituito come:
Usando solo il Vertex Buffer avremmo una ripetizione dei vertici lungo la diagonale. I vertici sono pesanti da memorizzare e, se duplicati, devono essere processati due volte in quanto hanno entries diverse nella cache dei vertici trasformati, anche se sono identici come valori interni, per questo usiamo gli indici. Gli indici possono essere duplicati (sono solo interi) e avremo meno vertici. Man mano che la geometria usata diventa più complessa, il numero di vertici da duplicare aumenta e usare gli indici diventa cruciale.
Proviamo a realizzare una prima operazione di disegno in 2D. Per prima cosa dobbiamo dichiarare un Effect, che si occuperá di disegnare per noi la geometria applicandovi una serie di effetti grafici:
BasicEffect fx;
Impostare le matrici World, View e Projection
Nella LoadContent
dobbiamo inizializzare il nostro BasicEffect
e impostare le sue matrici World
, View
e Projection
in modo che le coordinate dei vertici siano interpretate come coordinate di pixel (torneremo su World
, View
e Projection
nel capitolo sul 3D):
fx = new BasicEffect(GraphicsDevice);
var viewport = GraphicsDevice.Viewport;
Matrix projection = Matrix.CreateOrthographicOffCenter(0, viewport.Width, viewport.Height, 0, 0, 1);
Matrix halfPixelOffset = Matrix.CreateTranslation(-0.5f, -0.5f, 0);
fx.World = Matrix.Identity;
fx.View = Matrix.Identity;
fx.Projection = halfPixelOffset * projection;
A questo punto attiviamo i colori dei vertici, ossia istruiamo il nostro BasicEffect affinché applichi ai pixel dei nostri triangoli i colori dei vertici:
fx.VertexColorEnabled = true;
Nel metodo Draw
, iniziamo attivando il nostro BasicEffect
:
fx.CurrentTechnique.Passes[0].Apply();
quindi creiamo vertici e indici:
var vertices = new [] {
new VertexPositionColor(new Vector3(0, 0, 0.0f), Color.White),
new VertexPositionColor(new Vector3(200, 0, 0.0f), Color.White),
new VertexPositionColor(new Vector3(0, 200, 0.0f), Color.White),
new VertexPositionColor(new Vector3(200, 200, 0.0f), Color.White),
};
var indices = new short[] {
0, 1, 2,
2, 1, 3
};
ed infine li disegniamo con il metodo GraphicsDevice.DrawUserIndexedPrimitives che prende i due array, i range da disegnare e li disegna a schermo:
GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, 4, indices, 0, 2);
Il risultato è molto semplice: un quadrato nell'angolo in alto a destra dello schermo:
Possiamo modificare i nostri vertici in modo da avere dei colori dei vertici differenti:
var vertices = new [] {
new VertexPositionColor(new Vector3(0, 0, 0.0f), Color.OrangeRed),
new VertexPositionColor(new Vector3(200, 0, 0.0f), Color.White),
new VertexPositionColor(new Vector3(0, 200, 0.0f), Color.White),
new VertexPositionColor(new Vector3(200, 200, 0.0f), Color.Green),
};
e i nostri triangoli assumeranno un colore diverso:
Applicare una texture
Vogliamo peró ottenere qualcosa di un po' più elaborato dei triangoli colorati, possiamo ad esempio applicare un'immagine (Texture) sopra i nostri triangoli, per dare l'apparenza di qualcosa di più ricco di dettagli: una icona, una particella di fumo o un'astronave.
Per applicare una texture su dei vertici, dobbiamo stabilire una corrispondenza tra i vertici e i punti dell'immagine, perciò dovremo indicare, per ciascun vertice, quale pixel dell'immagine andrá disegnato sopra quel vertice, in un range normalizzato [0..1].
Nei punti dei triangoli che non corrispondono ad un vertice (la maggior parte) useremo i pixel intermedi della texture. Specifichiamo il pixel della texture da disegnare su un vertice nel parametro Texture Coordinate
del vertice. Per avere a disposizione questo parametro non basta più il semplicissimo tipo di vertice VertexPositionColor
che abbiamo usato prima, ma dobbiamo usare VertexPositionColorTexture, un tipo di vertice che contiene più informazioni, comprese le coordinate di texture.
Vediamo come cambia il codice dell'esempio precedente: per prima cosa carichiamo anche una texture nel metodo LoadContent:
asteroid = Content.Load("asteroid1");
poi attiviamo l'uso delle textures nel nostro BasicEffect
e impostiamo la texture attiva alla texture dell'asteroide:
fx.TextureEnabled = true;
fx.Texture = asteroid;
infine usiamo il nuovo tipo di vertice e colleghiamo gli angoli della texture ai nostri vertici:
var vertices = new [] {
new VertexPositionColorTexture(new Vector3(0, 0, 0.0f), Color.White, new Vector2(0,0)),
new VertexPositionColorTexture(new Vector3(200, 0, 0.0f), Color.White, new Vector2(1,0)),
new VertexPositionColorTexture(new Vector3(0, 200, 0.0f), Color.White, new Vector2(0,1)),
new VertexPositionColorTexture(new Vector3(200, 200, 0.0f), Color.White, new Vector2(1,1)),
};
il risultato è:
Proviamo ora ad aggiungere dei colori ai nostri vertici:
var vertices = new [] {
new VertexPositionColorTexture(new Vector3(0, 0, 0.0f), Color.Red, new Vector2(0,0)),
new VertexPositionColorTexture(new Vector3(200, 0, 0.0f), Color.Green, new Vector2(1,0)),
new VertexPositionColorTexture(new Vector3(0, 200, 0.0f), Color.Blue, new Vector2(0,1)),
new VertexPositionColorTexture(new Vector3(200, 200, 0.0f), Color.White, new Vector2(1,1)),
};
e il risultato (orribile ?) diventa:
Naturalmente possiamo anche modificare le posizioni dei vertici; non siamo limitati a quadrati, anche se ai fini del disegno 2D questo è raramente usato:
Per concludere vediamo cosa succede se usiamo delle coordinate di texture differenti: proviamo con delle coordinate più piccole, in modo da applicare al nostro quadrato solo una porzione della texture:
var vertices = new [] {
new VertexPositionColorTexture(new Vector3(0, 0, 0.0f), Color.White, new Vector2(0,0) * 0.5f),
new VertexPositionColorTexture(new Vector3(200, 0, 0.0f), Color.White, new Vector2(1,0) * 0.5f),
new VertexPositionColorTexture(new Vector3(0, 200, 0.0f), Color.White, new Vector2(0,1) * 0.5f),
new VertexPositionColorTexture(new Vector3(200, 200, 0.0f), Color.White, new Vector2(1,1) * 0.5f),
};
e delle coordinate più grandi in modo da ripetere la texture:
var vertices = new [] {
new VertexPositionColorTexture(new Vector3(0, 0, 0.0f), Color.White, new Vector2(0,0) * 4),
new VertexPositionColorTexture(new Vector3(200, 0, 0.0f), Color.White, new Vector2(1,0) * 4),
new VertexPositionColorTexture(new Vector3(0, 200, 0.0f), Color.White, new Vector2(0,1) * 4),
new VertexPositionColorTexture(new Vector3(200, 200, 0.0f), Color.White, new Vector2(1,1) * 4),
};