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

Gestire le collisioni

Link copiato negli appunti

Per gestire le collisioni con la MMGameLibrary2D esistono sostanzialmente 2 modi:

  • Lo sviluppatore si crea la propria logica per verificare le collisioni nel game loop, utilizzando il metodo CheckSpritesCollision della classe ScrollableBackground. Questo metodo ritorna true se i 2 oggetti passati come parametri sono in collisione
  • Impostare la gestione delle collisioni automatica e mettersi semplicemente in attesa dell'evento CollisionDetect della classe ScrollableBackground.

Il primo metodo è più performante e lascia molta libertà di gestione allo sviluppatore, sollevandolo solo dall'onere di implementare l'algoritmo che determina se 2 oggetti si stanno toccando.

Il secondo metodo è un po' meno prestazionale ma facilita enormemente la gestione delle collisioni. In questo tutorial infatti, vedremo come utilizzare proprio quest'ultimo.

Come appena spiegato, per gestire le collisioni non dobbiamo fare altro che aggiungere un delegato per l'evento CollisionDetect dell'istanza del nostro Background, dove rimuoveremo semplicemente dalla lista degli oggetti a video l'oggetto che è in collisione con il nostro personaggio.

Iniziamo dunque con il creare la funzione Background_CollisionDetect nel nostro GameModel:

void Background_CollisionDetect(Sprite sprite1, Sprite sprite2)
{
	// se il personaggio è sprite1 allora rimuovo sprite2
	// altrimenti viceversa
	if(sprite1.SpriteType == "player")
	{
		this.GameBackground.RemoveSprite(sprite2);
	}
	else
	{
		this.GameBackground.RemoveSprite(sprite1);
	}
}

e la registriamo per l'evento CollisionDetect nel costruttore della classe (appena dopo aver preparato i layer dello sfondo)

public GameModel
{
	...
	// Crea il layer per le colline e lo aggiunge all'istanza del background
	...
	// registra il delegato per l'evento delle collisioni
	_gameBackground.CollisionDetect += Background_CollisionDetect;
	// Aggancia gli eventi della tastiera
	...
}

Finalmente (se tutto va bene..) il nostro amichetto è in grado di papparsi le sue amate fragole! Quindi proviamo a compilare ed eseguire, e che la caccia alla fragola abbia inizio!

Figura 18. (click per ingrandire)


Adesso che il nostro personaggio può riempinzarsi di fragole, cerchiamo di rendergli la vita più difficile inserendo qua e là qualche temibile bomba.

Per inserire le bombe modifichiamo metodo AddObject, aggiungendo un parametro che ci consenta di specificare il tipo di oggetto che desideriamo aggiungere (fragola o bomba) in modo da utilizzare lo stesso metodo per entrambe:

public async void AddObject(string objectType)
{
	// genera un numero casuale per determinare l'altezza di uscita
	// dell'oggetto che va da 0 (bordo superiore dello schermo)
	// a 668 (= 768 - l'altezza degli oggetti che è 100)
	int top = HeightRandom.Next(0, 668);
	// crea un nuovo oggetto Sprite (fragola o bomba) appena fuori dallo schermo a destra
	Sprite newSprite;
	if(objectType == "strawberry")
	{
		newSprite = new Sprite(81, 100, new Point(1366, top));
		// aggiunge l'immagine
		await newSprite.SetSpriteSheet(new Uri("ms-appx:///Assets/Images/strawberry.tif"), 1, 1);
	}
	else
	{
		newSprite = new Sprite(79, 100, new Point(1366, top));
		// aggiunge l'immagine
		await newSprite.SetSpriteSheet(new Uri("ms-appx:///Assets/Images/bomb.tif"), 1, 1);
	}
	// imposta il tipo
	newSprite.SpriteType = objectType;
	// abilita le collisioni
	newSprite.isCollisionEnabled = true;
	// inserisce l'oggetto nella lista degli oggetti a video
	this.GameBackground.AddSprite(newSprite);
}

Poi andiamo nella MainPage e ripetiamo la stessa procedura delle fragole anche per le bombe, dichiarando un'altra coppia di variabili per la gestione della casualità

public sealed partial class MainPage : Page
{
	...
	int bombInterval = 0;
	Random bombRandom = new Random();

E gestendole infine nell'evento del timer, dove dobbiamo anche modificare la chiamata al metodo AddObject creato in precedenza, in quanto è stato aggiunto il parametro del tipo e di conseguenza lo dobbiamo passare alla funzione:

void TimeTick(object timer, object e)
{
	...
	// verifica se è il momento di far apparire una fragola
	if(strawberryInterval == 0)
	{
		// se è il momento la aggiunge
		this.game.AddObject("strawberry");
		// e imposta il successivo intervallo casualmente
		// tra 0 e 4 secondi
		strawberryRandom = strawberryRandom.Next(0, 4);
	}
	else
	{
		// se non è il momento decrementa semplicemente l'intervallo
		strawberryInterval -= 1;
	}
	// verifica se è il momento di far apparire una bomba
	if(bombInterval == 0)
	{
		// se è il momento la aggiunge
		this.game.AddObject("bomb");
		// e imposta il successivo intervallo casualmente
		// tra 1 e 5 secondi
		bombRandom = bombRandom.Next(1, 5);
	}
	else
	{
		// se non è il momento decrementa semplicemente l'intervallo
		bombInterval -= 1;
	}
}

L'ultima cosa che ci resta da fare è gestire la collisione con le bombe nel delegato dell'evento CollisionDetect. Infatti se il personaggio entra in collisione con una bomba, dobbiamo fermare il gioco in quanto lo sfortunato mostriciattolo è saltato per aria...

Aggiungiamo per comodità un metodo GameOver al nostro GameModel, dove fermeremo tutto il gioco e visualizzeremo un messaggio per l'utente:

public async void GameOver()
{
	// ferma il gioco
	this.GameloopStoryboard.Stop();
	// ferma lo scroll dello sfondo
	this.GameBackgound.StopScroll();
	// ferma l'animazione del personaggio
	this.PlayerSprite.StopAnimation();
	// imposta lo stato
	this.GameState = enGameState.STOP;
	// visualizza il messaggio
	Windows.UI.Popups.MessageDialog message = new Windows.UI.Popups.MessageDialog("Mi dispiace, hai perso", "Game Over");
	await message.ShowAsync();
}

Poi modifichiamo la gestione delle collisioni aggiungendo la chiamata al metodo GameOver in caso di collisione con una bomba:

void Background_CollisionDetect(Sprite sprite1, Sprite sprite2)
{
	// se il personaggio è sprite1 allora rimuovo sprite2
	// altrimenti viceversa
	if(sprite1.SpriteType == "player")
	{
		if(sprite2.SpriteType == "strawberry")
		{
			this.GameBackground.RemoveSprite(sprite2);
		}
		else
		{
			this.GameOver();
		}
	}
	else if(sprite2.SpriteType == "player")
	{
		if(sprite1.SpriteType == "strawberry")
		{
			this.GameBackground.RemoveSprite(sprite1);
		}
		else
		{
			this.GameOver();
		}
	}
}

Infine dobbiamo richiamare il metodo GameOver anche nel caso in cui una fragola finisca fuori dallo schermo senza che il personaggio sia riuscito a mangiarla. Per fare questo è sufficiente verificare la posizione di ogni singola fragola nel GameLoop e, se la sua X è minore di 0, vuol dire che sta uscendo dall'area di gioco, pertanto il gioco finisce.

Già che ci siamo facciamo anche un controllo sulle bombe in quanto, una volta uscite dall'area di gioco, vanno rimosse dalla lista degli oggetti a video (altrimenti continuano ad occupare risorse inutilmente, andando a spasso nel nulla...). Non è possibile però rimuovere un oggetto dalla collection che si sta ciclando, pertanto per rimuovere le bombe dobbiamo creare una lista di appoggio dove inserire gli oggetti "eliminabili", che utilizzeremo alla fine del ciclo for per rimuoverli dalla lista principale.

private void GameLoop(Object sender, Object e)
{
	...
	// lista di appoggio per le bombe da eliminare
	List<Sprite> bombsToRemove = new List<Sprite>();  
	// fa avanzare tutti gli oggetti presenti a video di 5 pixel alla volta
	foreach(Sprite sprite in this.GameBackground.Sprites)
	{
		if(sprite.SpriteType != "player")
		{
			sprite.Move(new Point(sprite.Position.X - 5, sprite.Position.Y)); 
			// se una fragola finisce fuori dallo schermo il gioco finisce
			if(sprite.SpriteType == "strawberry" && sprite.Position.X < -sprite.Size.Width)
			{
				this.GameOver();
				return;
			}
			// se una bomba finisce fuori dallo schermo va aggiunta alla lista
			// degli oggetti da eliminare
			if(sprite.SpriteType == "bomb" && sprite.Position.X < -sprite.Size.Width)
			{
				bombsToRemove.Add(sprite);
			}
		}
	}
	// fa ripartire il timer dello StoryBoard
	this.GameloopStoryboard.Begin)();
}

Se provate ora a compilare ed eseguire, dovreste essere in grado di giocare effettivamente alla vostra prima creazione videoludica, complimenti!

Prima di passare al capitolo finale del tutorial, ovvero l'aggiunta dei suoni, sarebbe bello fare in modo che quando il gioco finisce, si possa ricominciare da capo senza dover riavviare il progetto ogni volta, non credete? Aggiungiamo allora qualche riga di codice dopo la visualizzazione del messaggio nel metodo GameOver:

public async void GameOver()
{
	...
	// elimina tutti gli oggetti a video
	List<Sprite> objectsToRemove = new List<Sprite>();
	objectsToRemove.AddRange(this.GameBackground.Sprites);
	foreach (Sprite obj in objectsToRemove)
	{
		if(obj.SpriteType == "player")
		{
			// se è il personaggio lo riposiziona
			obj.Position = new Point(100, 200);
		}
		else
		{
			// tutti gli altri oggetti li rimuove
			this.GameBackground.RemoveSprite(obj);
			obj.Dispose();
		}
	}
	// resetta le variabili dei comandi
	this.LeftButtonPressed = false;
	this.RightButtonPressed = false;
	this.UpButtonPressed = false;
	this.DownButtonPressed = false;
	// fa ripartire il gioco
	this.StartGame();
}

Benissimo, in questo modo quando l'utente chiude il messaggio di game over, il gioco riparte automaticamente dall'inizio. Adesso noi siamo davvero pronti a dare l'ultimo tocco al nostro gioco.

Ti consigliamo anche