Mittwoch, 23. Mai 2012


Artikel

April 2008 | Artikel

Get rich and famous, be a game designer Fortsetzung, Teil 4

Teil 1   Teil 2   Teil 3   Teil 4   

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:

  1. public interface IContentService
  2. {
  3. ContentManager Content { get; }
  4. }

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:

  1. protected override void Initialize()
  2. {
  3. this.Services.AddService(typeof(IContentService), this);
  4. //...
  5. }

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):

  1. protected override void LoadGraphicsContent(
  2. bool loadAllContent)
  3. {
  4. oDevice = ((IGraphicsDeviceService)this.Game
  5. .Services.GetService(
  6. typeof(IGraphicsDeviceService))).GraphicsDevice;
  7. m_oSpriteBatch = new SpriteBatch(m_oDevice);
  8. if (loadAllContent)
  9. {
  10. if (string.IsNullOrEmpty(m_sAsset))
  11. throw new ArgumentNullException(“No asset“);
  12. m_oTexture = ((IContentService)this.Game.Services
  13. .GetService(
  14. typeof(IContentService))).Content.Load
  15. <Texture2D>(m_sAsset);
  16. if (m_iColumns == 0 && m_oSourceRect.Width !=
  17. 0 && m_oTexture != null)
  18. m_iColumns = m_oTexture.Width / m_oSourceRect
  19. .Width;
  20. if (m_iRows == 0 && m_oSourceRect.Height !=
  21. 0 && m_oTexture != null)
  22. m_iRows = m_oTexture.Height / m_oSourceRect.Height;
  23. m_oOrigin.X = m_oSourceRect.Width / 2;
  24. m_oOrigin.Y = m_oSourceRect.Height / 2;
  25. }
  26. base.LoadGraphicsContent(loadAllContent);
  27. }

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):

  1. public override void Update(GameTime gameTime)
  2. {
  3. if (!this.Enabled)
  4. return;
  5. m_fTimeElapsed += (float)gameTime
  6. .ElapsedGameTime.TotalSeconds;
  7. if (m_fTimeElapsed > m_fTimePerFrame)
  8. { //Spalten/ Zeilennr. Aktualisieren }
  9. m_oSourceRect.X = m_iActualColumn * m_oSourceRect
  10. .Width;
  11. m_oSourceRect.Y = m_iActualRow * m_oSourceRect
  12. .Height;
  13. m_fTimeElapsed -= m_fTimePerFrame;
  14. }

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

  1. m_oSpriteBatch.Draw(m_oTexture, oPosition,
  2. m_oSourceRect, Color.White, 0.0f,
  3. 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:

  1. protected override void Initialize()
  2. {
  3. m_oSpriteAnimation = new SpriteAnimation(
  4. this, “content\\textures\\explosion“,
  5. 256, 256, 50);
  6. m_oSpriteAnimation.Position = new Vector2(100, 100);
  7. this.Components.Add(m_oSpriteAnimation);
  8. base.Initialize();
  9. }

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.

  1. Jens Konerow: XNA Framework – schnell & kompakt, entwickler.press 2007

Teil 1   Teil 2   Teil 3   Teil 4   

Kommentare

Gravatar reifrund 16.02.2011
um 19:05 Uhr
Ich finde solche Billig-Baukästen generell nicht besonders. Nachdem das Web mit Baukästen für Websites vollgestopft wurde, werden sich die Entwickler von Billig-Modulen vielleicht wirklich auf Baukästen für Games konzentrieren. Ich finde, wer sein Handwerk ordentlich lernt, kann meistens etwas besseres zaubern ... ;-) #zitieren