XNA 2d Camera Scrolling - Warum Matrixtransformation verwenden?

18

Ich mache ein Testspiel, bei dem das Level ständig gescrollt werden soll. Um diesen Effekt zu erzielen, habe ich eine Kameraklasse eingerichtet, in der einfach eine Vektor2-Position und eine Aufzählungsrichtung gespeichert werden. Es enthält auch eine öffentliche Methode zum 'Bewegen', die einfach die Position mit einer festen Rate ändert. Diese Position benutze ich dann, wenn ich beim Zeichnen durch meine Kacheln gehe. Das alles funktioniert gut.

Mir wurde jedoch gesagt, dass ich eine Transformationsmatrix verwenden sollte, um die Kamera zu bewegen, und dass ich dies bereitstellen sollte, wenn ich den Spritebatch starte. Ich bin ein wenig verwirrt a.) Wie funktioniert das? als ob ich es nur gebe, wenn Spritebatch beginnt, woher weiß es, dass es seine Position ändert? b.) Warum brauche ich denn sicher noch die Kameraposition, wenn ich Kacheln durchschleife?

Im Moment kann ich es nicht zum Laufen bringen, aber das ist keine Überraschung, da ich nicht ganz verstehe, wie es funktionieren soll. Momentan ändert sich bei meinem Versuch (Code zum Folgen), dass die Kacheln gezeichnet werden, was bedeutet, dass sich die Kameraposition ändert, aber die Position des Ansichtsfensters unverändert bleibt (dh am Kameraursprung). Ich würde mich wirklich über einen Rat / eine Anleitung darüber freuen, wie es verwendet werden soll.

Kamera:

 class Camera {

    // The position of the camera.
    public Vector2 Position {
        get { return mCameraPosition; }
        set { mCameraPosition = value; }
    }
    Vector2 mCameraPosition;

    public Vector2 Origin { get; set; }

    public float Zoom { get; set; }

    public float Rotation { get; set; }

    public ScrollDirection Direction { get; set; }

    private Vector2 mScrollSpeed = new Vector2(20, 18);

    public Camera() {
        Position = Vector2.Zero;
        Origin = Vector2.Zero;
        Zoom = 1;
        Rotation = 0;
    }


    public Matrix GetTransform() {
        return Matrix.CreateTranslation(new Vector3(mCameraPosition, 0.0f)) *
               Matrix.CreateRotationZ(Rotation) *
               Matrix.CreateScale(Zoom, Zoom, 1.0f) *
               Matrix.CreateTranslation(new Vector3(Origin, 0.0f));
    }




    public void MoveCamera(Level level) {
        if (Direction == ScrollDirection.Up)
        {
            mCameraPosition.Y = MathHelper.Clamp(mCameraPosition.Y - mScrollSpeed.Y, 0, (level.Height * Tile.Height - level.mViewport.Height));
        }
    }

Niveau:

 public void Update(GameTime gameTime, TouchCollection touchState) {

            Camera.MoveCamera(this);
 }


 public void Draw(SpriteBatch spriteBatch) {
        //spriteBatch.Begin();
        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise, null, mCamera.GetTransform());


        DrawTiles(spriteBatch);

        spriteBatch.End();
    }

Spiel - nennt die Auslosung nur innerhalb des Levels:

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

        //mSpriteBatch.Begin();

        // Draw the level.
        mLevel.Draw(mSpriteBatch);

        //mSpriteBatch.End();

        base.Draw(gameTime);
    }

================================================ ============================== EDIT:

Zunächst einmal danke craftworkgames für Ihre bisherige Hilfe.

Ich habe mit dem Vorschlag rumgespielt. Als ich alle Kacheln gezogen habe, hat sich der Fr von 30 auf 15 erhöht - wahrscheinlich, weil die Levels ziemlich groß sind.

Also, was ich getan habe, ist die Matrix anzuwenden und im Update zu bewegen (wie vorgeschlagen), aber beim Zeichnen verwende ich die Kameraposition zum Durchlaufen von Kacheln (dh Startzähler am linken und Ende am rechten Kacheln). Das funktioniert alles gut und ich bin zufrieden damit :-)

Mein neues Problem liegt im Player. Da ich jetzt die Kamera und nicht das Level bewege, wird der Spieler offensichtlich von der Kamera zurückgelassen, da seine Position unverändert bleibt. Ich habe mir zwei Lösungen für dieses Problem ausgedacht. Die erste besteht darin, beim Zeichnen des Players einfach die Kameraposition zu berücksichtigen. Dh in der Ziehfunktion wird einfach die Kameraposition zur Spielerposition hinzugefügt. Die zweite ist, einen neuen Sprite-Stapel für den Spieler zu starten, der keine Transformation hat. dh beenden Sie den Spritebatch nach dem Zeichnen der Kacheln und starten Sie dann einen neuen, wenn Sie den Player zeichnen. Ich weiß, dass beide funktionieren werden, aber ich kann keine Schwänze machen, von denen in Bezug auf Leistung / gute Codierung besser wäre? Ich bin nicht sicher, welche Auswirkungen die Leistung hat, wenn ich den Stapel zweimal starte.

Pectus Excavatum
quelle
1
"Mein neues Problem liegt im Player. Da ich jetzt die Kamera und nicht das Level bewege, wird der Player von der Kamera zurückgelassen, da seine Position unverändert bleibt." Lies einfach das und ich bin so verwirrt, warum um alles in der Welt würdest du den Spieler nicht innerhalb des Koordinatensystems des Levels bewegen?
ClassicThunder

Antworten:

15

Kameramatrixtransformationen sind einfach

Das Erstellen einer einfachen Kamera ist einfach. Im Folgenden werden Sie mit den Grundlagen beginnen. Bewegen, Drehen und Skalieren. Das Verschieben jedes 2D-Sprites ist kein großes Problem, aber wenn Sie entweder die Skalierung oder die Drehung berücksichtigen, wird es sehr schwierig, jedes Sprite einzeln anzuwenden.

class Camera2D 
{
    public float Zoom { get; set; }
    public Vector2 Location { get; set; }
    public float Rotation { get; set;}

    private Rectangle Bounds { get; set; }

    private Matrix TransformMatrix
    { 
        get: {
            return 
                Matrix.CreateTranslation(new Vector3(-Location.X, -Location.Y, 0)) *
                Matrix.CreateRotationZ(Rotation) *
                Matrix.CreateScale(Zoom) *
                Matrix.CreateTranslation(new Vector3(Bounds.Width * 0.5f, Bounds.Height * 0.5f, 0));
        }
    };

    public Camera2D(Viewport viewport) 
    {
        Bounds = viewport.Bounds;
    }
}

Das Konvertieren zwischen Koordinatensystemdefinitionen ist sehr einfach

Einfach vom Bildschirm in den Weltraum. Dies wird häufig verwendet, um die Position der Maus in der Welt für die Objektauswahl zu ermitteln.

Vector2.Transform(mouseLocation, Matrix.Invert(Camera.TransformMatrix));

Um von der Welt in den Bildschirmraum zu gelangen, machen Sie einfach das Gegenteil.

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

Es gibt keinen Nachteil, eine Matrix zu verwenden, außer dass ein wenig Lernen erforderlich ist.

Es ist einfach, den sichtbaren Bereich zu erhalten

public Rectangle VisibleArea {
    get {
        var inverseViewMatrix = Matrix.Invert(View);
        var tl = Vector2.Transform(Vector2.Zero, inverseViewMatrix);
        var tr = Vector2.Transform(new Vector2(_screenSize.X, 0), inverseViewMatrix);
        var bl = Vector2.Transform(new Vector2(0, _screenSize.Y), inverseViewMatrix);
        var br = Vector2.Transform(_screenSize, inverseViewMatrix);
        var min = new Vector2(
            MathHelper.Min(tl.X, MathHelper.Min(tr.X, MathHelper.Min(bl.X, br.X))), 
            MathHelper.Min(tl.Y, MathHelper.Min(tr.Y, MathHelper.Min(bl.Y, br.Y))));
        var max = new Vector2(
            MathHelper.Max(tl.X, MathHelper.Max(tr.X, MathHelper.Max(bl.X, br.X))), 
            MathHelper.Max(tl.Y, MathHelper.Max(tr.Y, MathHelper.Max(bl.Y, br.Y))));
        return new Rectangle((int)min.X, (int)min.Y, (int)(max.X - min.X), (int)(max.Y - min.Y));
    }
}

Sie können die Kameraecken einfach transformieren und ihre Position im Weltraum ermitteln. Min max die x, y-Werte und Sie können ein Rechteck um den sichtbaren Raum erhalten. Sehr nützlich für das Keulen und Optimieren von Draw Calls.

ClassicThunder
quelle
1
Vielen Dank. Ich fand dies hilfreich, als ich eine Kamera in der Bibliothek MonoGame.Extended implementierte .
Bastelspiele
6

Durch Anwenden einer Matrix auf Ihren SpriteBatch wird der gesamte Zeichenaufruf auf einmal transformiert. Dies bedeutet, dass Sie Ihre Kamera in Ihrer DrawTiles-Methode überhaupt nicht verwenden müssen.

Es könnte viel einfacher werden:

    // Loop through the number of visible tiles.
    for (int y = 0; y <= tiles.GetUpperBound(1); y++) {
        for (int x = 0; x <= tiles.GetUpperBound(0); x++) {

                // If the tile is not an empty space.
                if (tiles[x, y].Texture != null) {
                     // Get the position of the visible tile within the viewport by multiplying the counters by the tile dimensions
                     // and subtracting the camera offset values incase the position of the camera means only part of a tile is visible.
                     Vector2 tilePosition = new Vector2(x * Tile.Width, y * Tile.Height);
                     // Draw the correct tile
                     spriteBatch.Draw(tiles[x, y].Texture, tilePosition, Color.White);
                }
        }
    }

Der Sinn einer Matrix ist also, dass Sie nicht darüber nachdenken müssen. Zeichnen Sie einfach Dinge und bewegen Sie die Kamera unabhängig voneinander.

Außerdem sieht Ihre MoveCamera-Methode etwas seltsam aus. Es ist sehr ungewöhnlich, eine Kameraklasse zu haben, die eine Stufe als Abhängigkeit nimmt. Eine typischere Implementierung würde folgendermaßen aussehen:

public void MoveCamera(float deltaX, float deltaY) {
    mCameraPosition.X += deltaX;
    mCameraPosition.Y += deltaY;
}

Dann könnten Sie in Ihrer Update-Methode so etwas tun:

    if (Direction == ScrollDirection.Up)
    {
        mCamera.MoveCamera(mScrollSpeed.Y, 0);
    }

Insgesamt ist mein Vorschlag, es einfach zu halten. Bring es auf einfachste Weise zum Laufen und baue darauf auf. Versuchen Sie, keinen hochoptimierten Code zu schreiben, bevor Sie nicht die Grundlagen erarbeitet haben. Sie können feststellen, dass das Rendern jeder Kachel in jedem Frame nicht so schlecht ist.

EDIT: Für den zweiten Teil der Frage.

Während es stimmt, dass Sie Ihre Batch-Anzahl niedrig halten möchten, sollten 2 oder 3 überhaupt kein Problem sein. Wenn Sie also einen guten Grund haben, einen zweiten Sprite-Stapel zu erstellen, tun Sie es einfach.

Allerdings gibt es in diesem Fall wahrscheinlich keinen guten Grund, einen zweiten Sprite-Stapel zu verwenden. Mit größerer Wahrscheinlichkeit möchten Sie Ihren Player genauso zeichnen, wie Sie Kacheln im selben Sprite-Stapel mit der angewendeten Kameratransformation zeichnen.

Es ist ein wenig schwer zu sagen, warum Ihr Spieler zurückbleibt, ohne sich einen Code anzusehen, aber es liegt auf der Hand, dass er / sie an der gleichen Position wie ein Plättchen erscheint, wenn Sie Ihren Spieler genau an dieselbe Position ziehen Sprite Batch.

Wenn Sie zum Beispiel möchten, dass der Spieler auf Kachel 10, 10 erscheint, können Sie dies tun:

var playerPosition = new Vector2(10 * Tile.Width, 10 * Tile.Height);
spriteBatch.Draw(player.Texture, playerPosition, Color.White);

Versuchen Sie, in die Denkweise zu kommen, Dinge dort zu zeichnen, wo sie sich befinden, und die Kamera verschiebt buchstäblich die gesamte "Szene" in den Blick. Genau das macht Ihre Matrixtransformation.

Bastelspiele
quelle
Das ist wirklich hilfreich und räumt viel auf! Danke für die Information; sehr geschätzt. Ich werde versuchen, Ihren Rat zu einem späteren Zeitpunkt umzusetzen, und Sie darüber informieren, wie es mir geht. Danke noch einmal.
Pectus Excavatum
Gibt es auch eine Möglichkeit, die Matrixtransformation zu verwenden, um nur Kacheln im Ansichtsfenster zu zeichnen?
Pectus Excavatum
Es gibt einen Weg, das zu tun, aber ich weiß es nicht genau. Ich vermute, dass es etwas mit der Verwendung des Ansichtsfensters zu tun haben wird, möglicherweise nachdem die Kameratransformation darauf angewendet wurde. Ich weiß nicht, du musst experimentieren. Verwenden Sie Ihren Debugger.
Bastelspiele
OK danke. Ich habe damit herumgespielt und bin auf eine Frage gestoßen, was ich mit dem Player machen soll - bitte sehen Sie sich die Änderungen an, um sie zu hinterfragen.
Pectus Excavatum
Vielen Dank, ich habe Ihren Rat befolgt und die Option gewählt, denselben Sprite-Stapel zu verwenden und beim Zeichnen nur die Kameraposition im Update abzurufen und auf die Position des Spielers anzuwenden. Scheint gut zu funktionieren. Vielen Dank für all Ihre Hilfe, seit einiger Zeit in der Kamera stecken. Viel klarer jetzt :-)
Pectus Excavatum