Verfasst von: bletra | 14. Juni 2011

XNA: Einführung und Wandering Steering Behaviour

Autor: David Reinig

Vorwort

Im Rahmen des Praktikums, der Lehrveranstaltung C# und .NET, wurde die Aufgabe gestellt ein Programmierprojekt selbstständig zu erstellen, wobei hier die Konzentration auf einer bestimmten (beliebig wählbaren) Microsoft Technologie lag.
Ich entschied mich für XNA, einer Technologie die von Microsoft speziell für die Entwicklung von Spielen bereit gestellt wird.
Das Projekt sollte ein 2D-Spiel, mit bekanntem Spielprinzip (Bubbles, Bubblet, …) werden und einige der angebotenen XNA Funktionalitäten ausnutzen.
Dieser Artikel umreist im ersten Abschnitt sehr grob die Grundlagen der Entwicklung mit XNA. Im zweiten Abschnitt wird auf ein Teilthema eingegangen das im entwickelten Spiel eine Rolle spielt.

2D Grafik mit XNA

Grundlagen

Da in meinem Projekt 2D-Grafik als Darstellung verwendet wird, werden hier die entsprechenden Grundlagen kurz aufgeführt.
Jedes XNA Projekt besitzt als Einstiegspunkt eine Instanz der Klasse Game. Diese Klasse stellt alle notwendigen Schnittstellen zur Steuerung der Spiellogik, das Zeichnen des Outputs usw. zur Verfügung. Der Einstiegspunkt ist die Methode Run. Diese initialisiert zunächst das Spiel (Initialize), bei diesem Vorgang besteht die Möglichkeit, notwendige Ressourcen zu laden (LoadContent).
Nach dem Initialisierungsvorgang befindet sich das Spiel in der „Gameloop“, wobei von dort frequentiell die Methoden Update und Draw aufgerufen werden. Erstere dient zum Updaten der Spiel-Logik, letztere um den grafischen Output zu realisieren.
Ein grundlegendes Spielgerüst sähe z.B. aus wie folgt:


public class Game1 : Microsoft.Xna.Framework.Game
{
  GraphicsDeviceManager graphics;

  public Game1()
  {
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";
  }

  protected override void Initialize()
  {
    base.Initialize();
  }

  protected override void LoadContent(){}
  protected override void UnloadContent(){}
  protected override void Update(GameTime gameTime){}

  protected override void Draw(GameTime gameTime)
  {
    GraphicsDevice.Clear(Color.Black);
  }
}

Game Content & Sprites zeichnen

XNA bietet eine komfortable Unterstützung um Game Content in das Spiel zu integrieren. Hierzu zählen z.B. eine Hand voll Default-Loader für diverse Ressourcen-Formate (bmp, dds, wav, …) das Management der Ressourcen und Content Prozessoren (z.B. um Bitmap-Fonts voll automatisch zu interpretieren).
Der verwendete Content kann im Solution Explorer unter {Projektname}Content eingesehen und dessen Eigenschaften bearbeitet werden. Um z.B. ein Sprite in das Spiel zu integrieren, kann die Grafikdatei einfach auf den Content Zweig gezogen werden (alternativ: Rechtsklick -> Add -> Existing Item…).
Sind die Ressourcen so bekannt gemacht, können diese im Spiel geladen und verwendet werden. Um Beispielsweise ein Sprite zu rendern, muss dieses zunächst in LoadContent geladen werden.

protected override void LoadContent()
{
  mSpriteBatch = new SpriteBatch(GraphicsDevice);
  mSprite = Content.Load("Bubble");
}

Wie hier zu erkennen ist, wurde zusätzlich ein SpriteBatch erzeugt. Dieses Objekt ermöglicht das Zeichnen von Grafiken.

protected override void Draw(GameTime gameTime)
{
  GraphicsDevice.Clear(Color.Black);
  try
  {
    mSpriteBatch.Begin();
    mSpriteBatch.Draw(mSprite, new Vector2(100, 100), Color.White);
  }
  finally
  {
    mSpriteBatch.End();
  }
}

Die Methode Draw bietet eine Vielzahl von Parametern. Hier können die gezeichneten Grafiken u. A. skaliert, rotiert, eingefärbt oder nur ein bestimmter Teil der Quellgrafik gezeichnet werden.

Steering Behaviour – Wandernde „Bubbles“

Grundlagen

Nachdem die Grundfunktionalität des Spiels entwickelt wurde, mussten im Zuge des grafischen „Feintunings“ Ideen her, um das Spiede „aufzupeppen“ und den Inhalt interessanter zu präsentieren. Der schwarze Hintergrund des Menüs sollte weg und mit einigen Bubbles versehen werden, die ziellos über den Bildschirm wandern.
Um dies zu realisieren, wurde ein Algorithmus aus dem Bereich der AI (Artificial Intelligence) verwendet, spezieller Wandering Steering Behaviour. Die grundlegende Idee ist es ein Objekt in einer virtuellen Umgebung, möglichst lebensnah, ziellos herumlaufen zu lassen.

Abb 1: Bubbles Spielmenü mit Menü-Hintergrund

Der Algorithmus

Hierfür bekommt jedes Bubble, zusätzlich zu seiner aktuellen Position, eine Bewegungsrichtung. Um ein plötzliches ‚Umspringen‘ zu vermeiden kann jeder Bubble seine Richtung nur in einem bestimmten Bereich abändern. In diesem Bereich wird ein zufälliger Wert bestimmt und die Bewegungsrichtung entsprechend auf den neu bestimmten Punkt ausgerichtet.
Anschließend wird der Bubble mit normalen Steering auf den neuen Punkt zubewegt.

private void initializeSteering(ITransformable sprite)
{
  mWanderTheta = (Random.Next(0, 360) * Math.PI / 180.0);
  sprite.Position = new Vector2(Random.Next(mBounds.Left, mBounds.Right), Random.Next(mBounds.Top, mBounds.Bottom));
  mHeading = new Vector2((float)Math.Cos(mWanderTheta), (float)Math.Sin(mWanderTheta));
}

protected override void ApplyInternal(ITransformable sprite, double scaledTimeFactor)
{
  Vector2 pos;

  mWanderTheta += Random.Next(-1, 1) / 100.0;

  if (mWanderTheta > Math.PI * 2)
    mWanderTheta -= Math.PI * 2;

  pos = sprite.Position;
  mHeading.X = (float)Math.Cos(mWanderTheta);
  mHeading.Y = (float)Math.Sin(mWanderTheta);
  pos = Vector2.Add(pos, new Vector2(mHeading.X * mSpeed, mHeading.Y * mSpeed));
  sprite.Position = pos;
}

Übermütige Wanderer

Einige Bubbles könnten so allerdings auf die Idee kommen sich aus dem Bildschirmbereich zu entfernen und nicht wieder zurück kommen. Daher wurden zwei Strategien implementiert um dies zu vermeiden:

  • Wrap Around

Beim Übertreten der gegebenen Grenzen erscheint das Bubble an der gegenüberliegende Grenze wieder.

  • Stay In Bounds

Bubbles prüfen ob sie die Grenzen überschreiten würden und ändern entsprechend ihre Position um dies zu verhindern.
Bevor also die Sprite Position zugewiesen wird muss diese entsprechend vorbereitet werden:

if (mWrapType == WrapType.WrapAround)
{
  if (pos.X > mBounds.Right)
    pos.X = mBounds.Left;
  if (pos.X < mBounds.Left)     pos.X = mBounds.Right;   if (pos.Y > mBounds.Bottom)
    pos.Y = mBounds.Top;
  if (pos.Y < mBounds.Top)     pos.Y = mBounds.Bottom; } else {   if (pos.X > mBounds.Right || pos.X < mBounds.Left || pos.Y > mBounds.Bottom || pos.Y < mBounds.Top)
    mWanderTheta += 0.1 + (Math.PI / 2);
}
Advertisements

Responses

  1. Coole Sache und schön geschrieben… Habe auch schon was zu diesem Thema und noch weiteren Behaviours vorbereitet, bin aber noch nicht zur Veröffentlichung gekommen 😦


Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

Kategorien

%d Bloggern gefällt das: