Windows Phone 7, Silverlight e la gestione dei suoni
Se preferite utilizzare Silverlight per creare le vostre applicazioni, perchè magari è più intuitivo, o semplicemente perchè Silverlight è la tecnologia che si presta meglio per fare quello che avete in testa, prima o poi dovrete scontrarvi con la gestione dei suoni. Mica è necessario, se però magari fate un piccolo videogioco, oppure volete condire l’interazione con l’utente con qualche effettino, la cosa può risultare gradevole. Io personalmente non ho riscontrato grandi difficoltà quando ho creato Keyzard, però qualche gabola c’è.
Spero in questo post di poter fornire a chi verrà dopo di me qualche consiglio utile.
Inserite <MediaElement/> nelle vostre pagine ed il gioco è fatto
E’ semplice: nella pagina XAML in cui volete fare qualche suono, inserite un elemento MediaElement. Mettetelo pure nascosto (Visibility = Collapsed) e dategli un bel nome (x:Name=”MediaPlayer”) – in questo modo visivamente non apparirà nulla, e potete comandarlo & gestirlo nel code-behind.
Attenzione: una delle grosse limitazioni è che potete inserire un solo MediaElement per pagina. Se ne inserite più di uno, non avete alcun errore di compilazione o a runtime, semplicemente quelli in più vengono ignorati.
Occhio che Zune blocca tutto!
Per sviluppare, deployare e debuggare le vostre applicazioni, utilizzate ovviamente la coppia Visual Studio 2010 + Zune. Attenzione, però: quando Zune è aperto, qualsiasi play generato attraverso MediaElement viene bloccato. Quindi, supponiamo:
<Grid Background="Transparent"> <MediaElement Source="Avvio.mp3" x:Name="MediaPlayer1" /> </Grid>
Compilate, avviate e non sentirete un bel nulla. Tenete pure collegato il WP7, chiudete Zune e lanciate dal cellulare l’applicazione. Questa volta sentirete il suono che avete impostato.
I files audio permessi quali sono? Files .mp3 o wav, ed impostateli come Resource
Più semplice di così si muore. Potete inserire nella vostra solution files .mp3 o files .wav. Assicuratevi di aver impostato come Build Action “Resource”, che dovrebbe comunque essere il valore di default.
Gestione del suono da code-behind
Allora, tenete presente che la proprietà AutoPlay dell’oggetto MediaElement è True. Quindi la porzione di XAML nel paragrafo precedente fa partire immediatamente il suono, appena la pagina viene renderizzata sul display del WP7. Ovviamente è possibile impostare AutoPlay=”false” in questo modo:
<Grid Background="Transparent"> <MediaElement AutoPlay="False" Source="Avvio.mp3" x:Name="MediaPlayer1" /> </Grid>
A questo punto da code-behind è sufficiente chiamare il metodo Play() dell’oggetto MediaPlayer1 per sentire il suono. Attenzione: non mettere questa chiamata nel costruttore, oppure di seguito all’InitializeComponent(), perchè il MediaElement non sembra reagire. Piuttosto usate l’evento Loaded della phone:PhoneApplicationPage e quindi:
private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e) { this.MediaPlayer1.Play(); }
Danza dello stesso MediaElement con più suoni diversi
La cosa più fastidiosa con cui ho avuto a che fare è questa: dal momento che possiamo inserire un solo MediaElement sulla pagina, va da sé che dobbiamo continuare reimpostare la proprietà Source in base a quello che ci serve in quel momento. Quindi:
- All’inizio Source è “Avvio.mp3”
- L’utente clicca un bottone e vorremmo sentire “Sparo.wav”
- L’utente tocca qualcosa e vorremmo sentire “Urlo.mp3”
- Finisce il tempo, vorremmo sentire “GameLose.mp3”
La prima cosa che vorreste fare è probabilmente la seguente:
this.MediaPlayer1.Source = new Uri("Loser.wav", UriKind.RelativeOrAbsolute); this.MediaPlayer1.Play();
Qui possono nascere dei problemi. E’ un po’ tutto casuale, però a volte il Play(); non fa sentire nulla. Perchè? Credo perchè la reimpostazione del Source avviene in modo asincrono, quindi a volte viene eseguito il Play() prima che il nuovo file sia stato effettivamente caricato (dipende dalla dimensione, I suppose). Come risolvere? Io ho risolto in questa maniera:
public MainPage() { InitializeComponent(); this.MediaPlayer1.MediaOpened += (o, s) => { this.MediaPlayer1.Position = TimeSpan.Zero; this.MediaPlayer1.Play(); }; }
In pratica, nel costruttore della pagina dico: ogni volta che c’è un evento MediaOpened, fai il Play() del file stesso. Notare che reimposto anche Position, in modo tale da posizionarmi all’inizio dello stream audio. Quindi, nel momento in cui voglio sentire un certo suono è sufficiente reimpostare il Source ed il gioco è fatto. Una riga simile…
this.MediaPlayer1.Source = new Uri("Loser.wav", UriKind.RelativeOrAbsolute);
…farebbe partire in modo automatico il suono senza sforzi.
Aspettare la fine di un suono prima di fare qualcos’altro
Ecco lo scenario: in Keyzard, quando il vostro cowboy viene colpito da un proiettile non dice niente. Semplicemente si passa al round successivo. Quando perde tutte le vite, invece, fa un urlo un po’ prolungato – poverino, sta morendo , e dopo si naviga alla pagina di Game Over. Se adottate solo la tecnica del paragrafo precedente…supponiamo…
if (numberLives == 0) { this.MediaPlayer1.Source = new Uri("UrloDiChiMuore.wav", UriKind.RelativeOrAbsolute); this.NavigationService.Navigate(new Uri("GameOverPage.xaml", UriKind.RelativeOrAbsolute); }
…non va troppo bene: il suono comincia, dura una frazione di secondo, poi si naviga sulla pagina indicata. Dovete aspettare la fine dell’urlo, e solo dopo spostarvi di pagina.
Come si risolvre questa cosa? E’ sufficiente gestire l’evento MediaEnded in questo modo:
private void gameOver() { if (numberLives == 0) { this.MediaPlayer1.MediaEnded += (o, s) => { this.NavigationService.Navigate(new Uri("GameOverPage.xaml", UriKind.RelativeOrAbsolute); }; this.MediaPlayer1.Source = new Uri("UrloDiChiMuore.wav", UriKind.RelativeOrAbsolute); } }
In pratica, si fa la .Navigate solo al termine del nostro suono. Piuttosto elementare.
Attenzione al MediaEnded e al suono disattivato
In un’applicazione seria dovreste avere un bool da qualche parte (nei Settings?) per ricordarvi se l’utente vuole o non vuole il suono attivo. Se questo bool è false evito di reimpostare il Source e quindi non sento nulla. Attenzione! Se vi è servito gestire il MediaEnded descritto prima per navigare in modo sincrono, questo evento non si scatena, ovviamente perchè fisicamente non è stato caricato e playato alcun file, e quindi il .Navigate() non viene mai eseguito. Quindi attenzione! Ecco un codice brutto, forse, ma che funziona:
private void gameOver() { if (numberLives == 0) { this.MediaPlayer1.MediaEnded += (o, s) => { this.NavigationService.Navigate(new Uri("GameOverPage.xaml", UriKind.RelativeOrAbsolute); }; if(utente_vuole_i_suoni) this.MediaPlayer1.Source = new Uri("UrloDiChiMuore.wav", UriKind.RelativeOrAbsolute); else this.NavigationService.Navigate(new Uri("GameOverPage.xaml", UriKind.RelativeOrAbsolute); } }
Conclusioni
La cosa è semplice: se volete un suono di avvio, oppure non c’è molta interazione, allora ve la cavate con poco. Invece potreste trovarvi nelle condizioni di sincronizzare un po’ gli eventi, e quindi dovete organizzarvi meglio. Spero di aver aiutato qualcuno! Ho parzialmente scritto codice direttamente in WLW, per cui potrebbero esserci bachi: perdonatemi!