SplitScreen: Mehrere Spieler auf einer Glotze

In letzter Zeit ist er etwas aus der Mode gekommen und wirkt für den ein oder anderen vielleicht wie ein Relikt aus längst vergangenen Gaming-Tagen. Geselliges Zusammensitzen vor der Glotze und ein schnelles Spielchen wurden mittlerweile fast vollkommen virtualisiert und durch Netzwerke und Voice-Chat ersetzt. Trotzdem ist es nach wie vor eine immer noch interessante Technik und dank Konsolen durchaus auch heute noch das ein oder andere mal sinnvoll und vor allem spaßig. Ich schreibe hier natürlich über Split-Screens. Mehrere Spieler spielen an einem Bildschirm, in unterteilten Bereichen mit jeweils einer eigenen Ansicht.

Zu Anfang möchte ich direkt einen wichtigen Hinweis geben, der sicherlich dem ein oder anderen mit Vorkenntnissen etwas Arbeit ersparen wird, da auch ich bereits in diese Falle getappt bin. Bis einschließlich XNA 3.1 bezog sich der Viewport auf alle Operationen des GraphicsDevice, also auch auf den Clear-Befehl. Da aber das sogenannte <em>SubImage Clearing</em> nicht konsistent von den Grafikkarten umgesetzt wurde und in DirectX 9 (auf das alle XNA-Versionen basieren) nicht anständig standardisiert wurde musste dieses Verhalten geändert werden. XNA 4.0 basiert zwar nicht auf DirectX 10, jedoch setzt es bereits einige Konzepte um, um einen späteren Umstieg zu erleichtern. Einer dieser Punkte ist, daß GraphicsDevice.Clear den Viewport ignoriert und immer den gesamten Bildschirm bzw. das gesamte RenderTarget löscht.

Mit diesem Wissen ausgestattet haben wir auch bereits die halbe Lösung erarbeitet 🙂 Wir unterteilen den Bildschirm also einfach in mehrere Viewports und rendern fortan nur noch in diese.

Dazu erzeugen wir uns ersteinmal zwei Viewports, was sehr einfach ist:

this.topViewport = new Viewport(0, 0, GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight / 2);
this.bottomViewport = new Viewport(0, this.topViewport.Height, GraphicsDevice.PresentationParameters.BackBufferWidth, this.topViewport.Height);

Beide Viewports haben jeweils die volle Bildschirmbreite, aber nur die halbe Höhe. Im Grunde genommen ist dies schon die Lösung. Wenn wir nun beide Viewports mit einer Farbe füllen (und damit löschen), dann sehen wir bereits das Ergebnis.

GraphicsDevice.Viewport = topViewport;
spriteBatch.Begin();
spriteBatch.Draw(this.texture, new Rectangle(0, 0, 800, 240), Color.Red);
spriteBatch.End();

GraphicsDevice.Viewport = bottomViewport;
spriteBatch.Begin();
spriteBatch.Draw(this.texture, new Rectangle(0, 0, 800, 240), Color.Green);
spriteBatch.End();

Was ist nun der Vorteil aus den Viewports?

Ganz einfach: Jeder dieser Viewports hat sein eigenes, lokales Koordinaten-System. Die linke obere Ecke des oberen Viewports liegt bei {0.0, 0.0}, genau so wie die linke obere Ecke des unteren Viewports. Wir müssen uns also schlicht und einfach nicht darum kümmern, ob wir nun in einen oder zwei, drei oder vier Viewports rendern. Wir haben immer die gleiche Position an der linken oberen Ecke. Dies macht es sehr einfach.

Im folgenden, kompletten Beispiel habe ich, um dies zu verdeutlichen zwei gelbe Rechtecke animiert. Diese teilen sich die gleichen Koordinaten, werden aber einmal in den oberen und einmal in den unteren Viewport gerendert. Wie dies erledigt wurde, darauf gehe ich nicht näher ein, ich denke, daß dies weitestgehend selbsterklärend sein sollte.

#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
#endregion

namespace SplitScreen
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Viewport topViewport;
        Viewport bottomViewport;

        Texture2D texture;

        float posY;
        float dirY = 50.0f;

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

        protected override void Initialize()
        {
            this.topViewport = new Viewport(0, 0, GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight / 2);
            this.bottomViewport = new Viewport(0, this.topViewport.Height, GraphicsDevice.PresentationParameters.BackBufferWidth, this.topViewport.Height);

            base.Initialize();
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            texture = new Texture2D(GraphicsDevice, 1, 1);
            texture.SetData(new Color[] { Color.White });
        }

        protected override void Update(GameTime gameTime)
        {
            posY += dirY * (float)gameTime.ElapsedGameTime.TotalSeconds;
            if (posY > 240)
            {
                posY = 240.0f;
                dirY *= -1;
            }
            else if (posY < 0)
            {
                posY = 0;
                dirY *= -1;
            }

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            Viewport originalViewport = GraphicsDevice.Viewport;

            GraphicsDevice.Viewport = topViewport;
            spriteBatch.Begin();
            spriteBatch.Draw(this.texture, new Rectangle(0, 0, 800, 240), Color.Red);

            spriteBatch.Draw(this.texture, new Rectangle(300, (int)posY, 16, 16), Color.Yellow);
            spriteBatch.End();

            GraphicsDevice.Viewport = bottomViewport;
            spriteBatch.Begin();
            spriteBatch.Draw(this.texture, new Rectangle(0, 0, 800, 240), Color.Green);

            spriteBatch.Draw(this.texture, new Rectangle(300, (int)posY, 16, 16), Color.Yellow);
            spriteBatch.End();

            GraphicsDevice.Viewport = originalViewport;
            base.Draw(gameTime);
        }
    }
}
Advertisements

Veröffentlicht am 11.07.2011 in Grundlagen, Mini-Tutorial, XNA, XNA 3.1, XNA 4.0 und mit , , , getaggt. Setze ein Lesezeichen auf den Permalink. Hinterlasse einen Kommentar.

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

%d Bloggern gefällt das: