Xbox-Programmierung mit Sound und neuen Features

Xbox gibt Laut
Kommentare

Unser im letzten Teil der Serie realisiertes Programmierbeispiel, in dem wir einen Affen mit Bällen beworfen haben, machte erst einmal nur begrenzt Freude, denn vor allem der Sound fehlte noch. Das holen wir in diesem Teil nach.

Unser Affe, den wir im letzten Artikel in aller Ruhe mit Bällen beworfen haben, ist ja immer noch nicht in der Lage, seinen Unmut durch „Dschungellaute“ zu artikulieren. Auch hören wir noch keine Hintergrundmusik. Bis zum fertigen Affensimulator ist es also noch ein weiter Weg. Die folgenden Schritte erledigen wir anhand des Beispiels Xbox3603D1. Seine Rendering Pipeline wurde im vorigen Teil der Serie im Detail erklärt. Unser Simulator kennt derzeit drei mögliche Kollisionen:

  • die Kollision zwischen Affe und Ball
  • die Kollision zwischen Ball und Ball
  • die Kollision zwischen einem beliebigen Objekt und einer Spielfeldwand

Die Webseite FlashKit.com bietet eine Vielzahl von Audioeffekten an, die man zum Großteil lizenzfrei verwenden kann. Für unser Programmierbeispiel verwenden wir die Sounds [1], [2] und [3]. Sie werden jeweils heruntergeladen und zum Content Project des Projekts hinzugefügt. Die Content Pipeline von XNA findet sie danach automatisch. Als Dateiformat ist hier nur .wav zulässig. Alles andere wird nach der Konversion zu einem Song-Objekt. Klassische Soundeffekte (im Programmiererjargon als SFX bezeichnet) werden in XNA durch die Klasse SoundEffect dargestellt. Pro SoundEffect-Instanz wird eine Datei verarbeitet. Wer Lautstärke und Pitch effizient festlegen will, muss die Wrapper-Klasse SoundEffectInstance benutzen. Doch damit genug der Theorie. Die globalen Variablen wollen erstellt werden, wie in Listing 1 veranschaulicht.

Listing 1

public class Game1 : Microsoft.Xna.Framework.Game
{
  . . .
  public SoundEffect myMonkeySound;
  public SoundEffectInstance myMonkeyInstance;
  public SoundEffect myBallSound;
  public SoundEffectInstance myBallInstance;
  public static SoundEffect myWallSound;
  public static SoundEffectInstance myWallInstance;  

Wie schon erwähnt, besteht ein SFX-Effekt aus einem SoundEffect und mindestens einer SoundEffectInstance, die auf die Tondateien zurückgreift und sie mit einer bestimmten Lautstärke abspielt. Übrigens kann man auf allen Plattformen außer der Xbox so viele SoundEffectInstance-Instanzen laden, wie in den Speicher passen. Auf dem Windows Phone 7 kann man maximal 16 parallel abspielen. Unter Windows ist die Grenze nicht definiert. Auf der Xbox 360 hingegen darf man nur 300 Instanzen von SoundEffectInstance erstellen. Benötigt man mehr, muss man die Instanzen recyceln. In LoadContent beleben wir die Variablen wie in Listing 2.

Listing 2

myMonkeySound = Content.Load("Monkey_S-SlySnipe-1385");
myMonkeyInstance = myMonkeySound.CreateInstance();
myWallSound = Content.Load("snare-nick_f-1492");
myWallInstance = myWallSound.CreateInstance();
myBallSound = Content.Load("Xylophon-Digital_-9050");
myBallInstance = myBallSound.CreateInstance();  

Im ersten Schritt wird die .wav-Datei aus der Content Pipeline gezogen und in die SoundEffect-Variable geschrieben. Im zweiten Schritt wird von diesem Effect eine Instance abgeleitet. Dabei handelt es sich um eine dünne Wrapper-Klasse, die die Tondaten nicht kopiert. Sie enthält nur einige Flags und einen Positionszeiger. Würde man also den SoundEffect abtragen, würden automatisch auch alle SoundEffectInstances davon verfallen. In der Kollisions-Detektionsroutine spielen wir die Sounds ab (Listing 3).

Listing 3

if(myModels[i].getBoundingBox().Contains(myModels[j].getBoundingBox())!=ContainmentType.Disjoint)
{ // boom!
  if (i == 0)
  {
    myMonkeyInstance.Play();
  }
  else
  {
    myBallInstance.Play();
  }  

Diese Routine ist völlig unkompliziert: Tritt eine Kollision auf, wird eine Tondatei abgespielt. Die Auswahl anhand des Index des ersten Objekts dient dazu, den Affen ausfindig zu machen.

Da die in Teil 3 implementierte Kollisionsroutine nicht perfekt ist, kann es sein, dass hin und wieder zwei Bälle zusammenkleben. Wenn das passiert, hört man in etwa „Boink! Boink! Boink!“, da die bereits aktiv abspielende SoundEffectInstance den zweiten Aufruf von Play ignoriert. In diesem Fall hilft es, entweder mehrere SoundEffectInstances vorzuhalten oder die komplexe Play()-Routine des SoundEffects zu nutzen.

Wer will, kann SmartModel anpassen und dort den dritten Soundeffekt abspielen, wenn ein Objekt gegen die imaginäre Grenze des Spielfelds prallt. Der Code dazu sieht wie in Listing 4 aus.

Listing 4

public void Update()
{
  myPos.X += myMoveX;
  if (myPos.X < -1000 || myPos.X > 1000)
  {
    Game1.myWallInstance.Play();
    myMoveX *= -1; 
  }  
Immer wieder, ohne Ende

Im nächsten Schritt werden wir unserem Affensimulator einen „dschungeligen“ Groove verpassen. Während man bei einem kommerziellen Projekt in der Regel auf hunderte von Anbietern von „Royalty Free Music“ zurückgreift, kann man hier auch mit dem Amerikaner Kevin MacLeod vorliebnehmen. Er bietet nämlich auf seiner Webseite [4] eine Vielzahl von Musikstücken an, die man bei entsprechender Namensnennung kostenlos in kommerziellen Projekten verwenden darf. Dass man seiner Bitte um eine kleine Spende per PayPal und eine E-Mail mit einer Demoversion des Produkts nachkommen sollte, ist nach Meinung des Autors unter Systemtechnikern wohl selbstverständlich. Für unser Beispiel wählen wir „Brightly Fancy“. Wer diesen Track hört, fühlt sich sofort in den Dschungel versetzt. Nach dem Download der .mp3-Datei wird sie zum Contentprojekt unseres Beispiels hinzugefügt. Ein Hintergrundmusikstück (Programmierer bezeichnen es auch als BGM) wird durch eine Instanz der Klasse Song abgebildet. In unserem Beispiel sieht das so aus:

public class Game1 : Microsoft.Xna.Framework.Game
{
  . . .
  public Song myBGMSound;  

An sich gibt es hier nicht viel zu tun: Wir deklarieren die Member-Variable myBGMSound, die die Sounddaten enthält. Im nächsten Schritt wird LoadContent() angepasst, um das Abspielen der Musik zu starten:

myBGMSound = Content.Load("Brightly Fancy");
MediaPlayer.IsRepeating = true;
MediaPlayer.Play(myBGMSound);  

Im ersten Schritt wird hier mittels XNA Content Pipeline die .mp3-Datei geladen. Alternativ sind auch .wav, .mp3 und .wma-Dateien zulässig. Intern erfolgt übrigens eine Konversion ins .wma-Format, was den durchschnittlichen Entwickler vermutlich aber wenig bis gar nicht interessiert.

Im nächsten Schritt wird die statische Klasse MediaPlayer angewiesen, den ihr übergebenen Song immer wieder abzuspielen. Dafür gibt es besonders gut geeignete Tondateien, die als Loops bezeichnet werden: Der Benutzer hört bei ihnen den Übergang zwischen Anfang und Ende des Durchlaufs nicht. Im letzten Schritt wird Play aufgerufen. Fortan dudelt „Brightly Fancy“ aus den Speakern.

Anpassen der Lautstärke

Der Grund für die Spielerei mit der SoundEffectInstance ist, dass wir auf den laufenden Soundeffekt zugreifen und seine Lautstärke anpassen wollen. Das wird in der Methode setInstanceVolumes realisiert, wie in Listing 5 zu sehen.

Listing 5

private void setInstanceVolumes()
{
  myMonkeyInstance.Volume = myVolume;
  myBallInstance.Volume = myVolume;
  myWallInstance.Volume = myVolume;
  MediaPlayer.Volume = myVolume;
}  

Die Variable myVolume ist dabei eine Member-Variable vom Typ float. Steht sie auf 0, ist Ruhe. Steht sie hingegen auf 1, plärrt die Xbox 360 mit voller Lautstärke. Wann immer setInstanceVolumes aufgerufen wird, aktualisiert die Funktion die Lautstärken der einzelnen EffektInstances und des MediaPlayers. setInstanceVolumes wird aus Update() heraus aufgerufen, wenn die Links- oder Rechtstaste des Controllers gedrückt wird (Listing 6).

Listing 6

protected override void Update(GameTime gameTime)
{
  // Allows the game to exit
  if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
    this.Exit();

  GamePadState gs = GamePad.GetState(PlayerIndex.One);

  if (gs.DPad.Left == ButtonState.Pressed)
  {
    myVolume = MathHelper.Clamp(myVolume - 0.01f, 0f, 1f);
    setInstanceVolumes();
  }
  if (gs.DPad.Right == ButtonState.Pressed)
  {
    myVolume = MathHelper.Clamp(myVolume + 0.01f, 0f, 1f);
    setInstanceVolumes();
  }  

Obwohl dieses Idiom im vorigen Teil der Serie im Detail behandelt wurde, nochmals zur Auffrischung: Der Zustand eines Controllers der Xbox wird durch ein GamePadState-Objekt dargestellt. Die Member-Variablen des GamePadState-Objekts enthalten Informationen über die einzelnen Steuerelemente.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -