Get rich and famous, be a game designer

Game Services

Aus einer Komponente heraus existiert per
Default keine direkte Zugriffsmöglichkeit
auf den Content Manager. Stattdessen ist
ein Game-Objekt mit einem Game Service
Container
ausgestattet (Eigenschaft: Services),
über den diverse Dienste geboten
werden. Die Integration eines Dienstes
zum Zugriff auf die Content Pipeline obliegt
jedem Programmierer selbst:

public interface IContentService
{
ContentManager Content { get; }
}

Obige Schnittstelle stellt den Vertrag dar,
dem der neue Service genügen muss. Implementiert
wird die Schnittstelle in der
Ableitung von der Game-Klasse. Im Rahmen
der Initialisierung registriert die Ableitung
den Service:

protected override void Initialize()
{
this.Services.AddService(typeof(IContentService), this);
//...
}

Nun können Assets direkt innerhalb einer
Komponente geladen werden. Die Sprite
Animation benötigt ein Texture2D-Objekt,
das alle Frames beinhaltet. Im Anschluss
berechnet die Methode die Anzahl
der Spalten und Zeilen und zentriert den
Ursprung des Sprites (Listing 1). Rein didaktischen
Zwecken dient die Initialisierung
der SpriteBatch-Klasse innerhalb
der Komponente. Standardmäßig ist das
Game-Objekt mit dem IGraphicsDevice-
Service-
Dienst augestattet, der die Referenz
des Graphcis Device liefert, um die Sprite-Batch-Klasse zu instanziieren (Listing 2):

protected override void LoadGraphicsContent(
bool loadAllContent)
{
oDevice = ((IGraphicsDeviceService)this.Game
.Services.GetService(
typeof(IGraphicsDeviceService))).GraphicsDevice;
m_oSpriteBatch = new SpriteBatch(m_oDevice);
if (loadAllContent)
{
if (string.IsNullOrEmpty(m_sAsset))
throw new ArgumentNullException("No asset");
m_oTexture = ((IContentService)this.Game.Services
.GetService(
typeof(IContentService))).Content.Load
(m_sAsset);
if (m_iColumns == 0 && m_oSourceRect.Width !=
0 && m_oTexture != null)
m_iColumns = m_oTexture.Width / m_oSourceRect
.Width;
if (m_iRows == 0 && m_oSourceRect.Height !=
0 && m_oTexture != null)
m_iRows = m_oTexture.Height / m_oSourceRect.Height;
m_oOrigin.X = m_oSourceRect.Width / 2;
m_oOrigin.Y = m_oSourceRect.Height / 2;
}
base.LoadGraphicsContent(loadAllContent);
}

Selektion und Rendern des Frames
Dank des Game Loop liefert jeder Aufruf
der Update- bzw. der Draw-Methode eine
Angabe zur verstrichenen Zeit seit dem
letzten Frame. Anhand jener Zeitmessung
prüft die Komponente, ob der nächste
Animationsframe angezeigt werden muss.
Pro Aufruf summiert die Methode die
vergangen Sekunden. Ist die Zeitperiode,
die zwischen zwei Animationsframes
liegt, verstrichen, verschiebt die Methode
den Zeiger, der in Form einer Rectangle-Struktur vorliegt. Jene Struktur selektiert
den Quellbereich der Textur, der in den
Render-Vorgang einbezogen werden soll
(Listing 3):

public override void Update(GameTime gameTime)
{
if (!this.Enabled)
return;
m_fTimeElapsed += (float)gameTime
.ElapsedGameTime.TotalSeconds;
if (m_fTimeElapsed > m_fTimePerFrame)
{ //Spalten/ Zeilennr. Aktualisieren }
m_oSourceRect.X = m_iActualColumn * m_oSourceRect
.Width;
m_oSourceRect.Y = m_iActualRow * m_oSourceRect
.Height;
m_fTimeElapsed -= m_fTimePerFrame;
}

Damit Direct3D nur den ausgewählten
Bereich der Grafik auf den Bildschirm
bringt, kommt folgender Methodenaufruf
zum Einsatz:

m_oSpriteBatch.Draw(m_oTexture, oPosition,
m_oSourceRect, Color.White, 0.0f,
m_oOrigin, fScale, SpriteEffects.None, 1.0f);

Die Methode erwartet neben der Textur
und der Zielposition auf dem Back Buffer
eine Rectangle-Struktur, die in der
Update-Methode mit Daten versehen
wurde. Dem folgt eine Gewichtung der
Farbkanäle, ein Rotationswinkel im
Bogenmaß, der Ursprung des Sprites als
Vector2-Struktur, ein Fließkommawert
zur Skalierung der Grafik (Original: 1.0f)
sowie eine Angabe ob die Grafik gespiegelt
werden soll und auf welcher Ebene
sich das Sprite befindet. Beim letzten Argument
sind Zahlen im Bereich von 0.0
und 1.0 erlaubt, wobei 0.0 der höchsten
Ebene entspricht. Alle Sprites, die darunter
liegen können vom obersten Element
überlappt werden.

Einsatz der Komponente

Da die Komponente nun komplett ist, muss
jene irgendwie in der Hauptanwendung
integriert werden. Dazu genügen die folgenden
drei Anweisungen:

protected override void Initialize()
{
m_oSpriteAnimation = new SpriteAnimation(
this, "content\textures\explosion",
256, 256, 50);
m_oSpriteAnimation.Position = new Vector2(100, 100);
this.Components.Add(m_oSpriteAnimation);
base.Initialize();
}

Dadurch, dass die Komponente der Components-Auflistung hinzugefügt wird,
kümmert sich das Application Model um
die Initialisierung der Komponente, um
die Aktualisierung und um den Render-Prozess.

Ausblick aus Episode 2

Der nächste Teil dieser Serie beschäftigt
sich mit dem Background Scrolling, kümmert
sich um die Kommunikation mit den
Peripheriegeräten und behandelt die Kollisionskontrolle.
Der Entwurf eines Menüs
für das erste Spiel rundet den Teil ab.

Jens Konerow studiert Informatik im Norden Deutschlands, schreibt Programme für KEEP IT SIMPLE und ist inzwischen mehrfacher Buchautor
bei entwickler.press.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.