Dal momento che in questo esempio utilizziamo un solo gesto, possiamo definire una comoda proprietà per registrare tocchi singoli sul touch device:
bool Tapping { get { return TouchPanel.IsGestureAvailable; } }
Nel caso in cui la partita sia terminata, aspettiamo un tap del giocatore per resettare lo stato di gioco a quello iniziale, riportando la posizione del giocatore ai valori originari, resettando il numero di asteroidi presenti e azzerando il punteggio:
if (game_over)
{
if (Tapping)
{
game_over = false;
playerPosition.X = GraphicsDevice.Viewport.Width / 2;
playerPosition.Y = GraphicsDevice.Viewport.Height - player.Height;
asteroidPositions.Clear();
score = 0;
}
}
Quanto all'uscita dal gioco, il template di XNA associa di default l'uscita dall'applicazione alla pressione del bottone di Back
sul telefonino, in modo piuttosto intuitivo:
GamePadState gamePad = GamePad.GetState(PlayerIndex.One);
if (gamePad.Buttons.Back == ButtonState.Pressed)
{
Exit();
}
Nota: Per pubblicare un gioco sul Windows MarketPlace, leggere con attenzione le specifiche relative all'implementazione del bottone Back
, pena l'esclusione dell'applicazione.
Ciò detto, concentriamoci adesso sull'input proveniente dall'accelerometro del nostro device, cui abbiamo ricollegato tutta la nostra logica di gioco. Quello che vogliamo fare è spostare il giocatore in base al movimento registrato dall'accelerometro, ma facendo attenzione a non uscire mai dallo schermo.
Per utilizzare l'accelerometro riutilizzaremo la classe custom AccelerometerHelper, dalla lezione precedente, il cui metodo statico GetValues
restituisce (tramite un Vector3
denominato Acceleration
) i valori di accelerazione del device lungo i tre assi X
, Y
e Z
(anche se in questo gioco, come vedremo fra un attimo, ci interessano solo i movimenti orizzontali lungo l'asse X
).
var accelerometer = AccelerometerHelper.GetValues();
playerPosition.X -= accelerometer.Acceleration.Y * PlayerMoveSpeed;
playerPosition.X = MathHelper.Clamp(playerPosition.X,
player.Width / 2, GraphicsDevice.Viewport.Width - player.Width / 2);
Da notare l'uso del metodo statico MathHelper.Clamp
per evitare che il giocatore si trovi improvvisamente fuori dallo schermo :
public static float Clamp(float value, float min, float max)
Questo metodo restituisce un valore di tipo float incluso tra il minimo e il massimo. In questo modo, siamo sicuri che la nostra navicella non uscirà mai per più di metà dallo schermo...
Ora possiamo generare gli asteroidi in modo casuale, ma continuo (infatti, una volta che un asteroide esce dallo schermo verrà rimosso e non tornerà più "in gioco", di qui la necessità di creare sempre nuovi asterodi). Ecco come fare.
Utilizzando il metodo NextDouble
esposto dalla classe Random
, generiamo un numero frazionario casuale compreso tra 0
e 1
. Se il numero generato è inferiore al valore di AsteroidSpawnProbability (= 0.1)
, allora aggiungiamo la posizione del nuovo asteroide (un Vector2
) alla nostra lista di asteroidPositions
.
if (random.NextDouble() < AsteroidSpawnProbability)
{
float x = (float)random.NextDouble() * GraphicsDevice.Viewport.Width;
asteroidPositions.Add(new Vector2(x, -asteroid.Height));
}
Inoltre, occorre ricordarsi di aggiornare la posizione di tutti gli asteroidi presenti sullo schermo. Per semplicità, abbiamo previsto che gli asteroidi si muovano esclusivamente in verticale e ad un'identica velocità (AsteroidFallSpeed
):
for (int i = 0; i < asteroidPositions.Count; i++)
{
asteroidPositions[i] = new Vector2(asteroidPositions[i].X,
asteroidPositions[i].Y + AsteroidFallSpeed);
Nel caso in cui l'asteroide entri in collisione con il giocatore, il gioco finisce e il punteggio viene salvato sul disco mediante il metodo SaveHighScore (vedremo fra breve come):
if (player.Collides(playerPosition, asteroid, asteroidPositions[i]) && game_over == false)
{
game_over = true;
SaveHighScore();
}
Se invece l'asteroide supera il bordo inferiore dello schermo, lo rimuoviamo dalla relativa lista e incrementiamo di 1 il punteggio:
if (asteroidPositions[i].Y - 25 > GraphicsDevice.Viewport.Height)
{
asteroidPositions.RemoveAt(i);
i--;
if (game_over == false)
score++;
}
Infine, provvediamo a svuotare il buffer dei gesti correnti, perchè altrimenti ce li troveremmo nuovamente in memoria al frame successivo:
while (TouchPanel.IsGestureAvailable)
TouchPanel.ReadGesture();