XNA 2d Camera Scrolling: perché usare la trasformazione a matrice?


18

Sto realizzando un gioco di prova in cui voglio che il livello scorra costantemente. Per creare questo effetto ho stabilito una classe di telecamere che memorizza semplicemente una posizione vector2 e una direzione enum. Contiene anche un metodo pubblico per "spostare" che cambia semplicemente la posizione a un tasso fisso. Quindi uso questa posizione quando eseguo il ciclo attraverso la mia serie di tessere durante il disegno. Funziona tutto bene.

Tuttavia, mi è stato detto che avrei dovuto usare una matrice Transform per spostare la telecamera e che avrei dovuto fornire questo quando avvierò lo spritebatch. Sono un po 'confuso a.) Come funziona? come se lo stessi dando solo quando inizia lo spritebatch come fa a sapere di cambiare posizione? b.) Perché sicuramente ho ancora bisogno della posizione della telecamera per quando cerco tra le piastrelle?

Al momento non riesco a farlo funzionare, ma non è una sorpresa dato che non capisco completamente come dovrebbe funzionare. Attualmente nel mio tentativo (codice da seguire) le tessere disegnate cambiano, il che significa che la posizione della telecamera sta cambiando, ma la posizione della finestra rimane invariata (cioè all'origine della telecamera). Gradirei davvero alcuni consigli / indicazioni su come dovrebbe essere usato?

Telecamera:

 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));
        }
    }

Livello:

 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();
    }

Gioco - chiama solo il sorteggio nel livello:

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

        //mSpriteBatch.Begin();

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

        //mSpriteBatch.End();

        base.Draw(gameTime);
    }

================================================== =============================== MODIFICA:

Innanzitutto, grazie a craftworkgames per il tuo aiuto finora.

Ho giocato con il suggerimento. Quando ho disegnato tutte le tessere, il franco è aumentato a circa 15 da 30 - probabilmente perché i livelli sono piuttosto grandi.

Quindi quello che ho fatto è applicare la matrice e muovermi in aggiornamento (come suggerito) ma in disegno uso la posizione delle telecamere per scorrere le tessere (ovvero iniziare il contatore a sinistra e terminare a destra). Funziona tutto bene e ne sono contento :-)

Il mio nuovo problema risiede nel lettore. Ovviamente, poiché ora sto spostando la telecamera anziché il livello, il giocatore viene lasciato indietro dal camer mentre la sua posizione rimane fissa. Ho pensato a due soluzioni a questo problema, la prima è semplicemente considerare la posizione delle telecamere quando si disegna il lettore. Vale a dire nella funzione di disegno è sufficiente aggiungere la posizione della telecamera alla posizione del giocatore. Il secondo è avviare un nuovo batch di sprite per il giocatore che non ha trasformazioni. vale a dire terminare lo spritebatch dopo aver disegnato le tessere, quindi avviarne uno nuovo quando si disegna il giocatore. So che entrambi funzioneranno, ma non riesco a capire che sarebbe meglio in termini di prestazioni / buona codifica? Non sono sicuro di quali siano le implicazioni sulle prestazioni dall'avvio del batch due volte?


1
"Il mio nuovo problema risiede nel giocatore. Ovviamente mentre sto spostando la telecamera anziché il livello, il giocatore viene lasciato indietro dal camer mentre la sua posizione rimane fissa." Basta leggere questo e sono così confuso perché mai non dovresti spostare il giocatore all'interno del sistema coordiante del livello?
ClassicThunder

Risposte:


15

Le trasformazioni della matrice della fotocamera sono facili

La creazione di una fotocamera di base è semplice. Di seguito inizierai con le basi. Muovendolo, ruotandolo e ridimensionandolo. Spostare ogni sprite 2D non è un grosso problema, ma se si tiene conto del ridimensionamento o della rotazione, diventa davvero difficile applicare a ogni sprite individualmente.

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;
    }
}

Rende davvero facile la conversione tra le definizioni del sistema di coordinate

Per passare dallo schermo allo spazio mondiale semplicemente. Questo è comunemente usato per ottenere la posizione del mouse nel mondo per la raccolta degli oggetti.

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

Per passare dal mondo allo spazio dello schermo, fai semplicemente il contrario.

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

Non c'è alcun inconveniente nell'usare una matrice diversa da quella che richiede un po 'di apprendimento.

È facile ottenere l'area visibile

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));
    }
}

Puoi semplicemente trasformare gli angoli delle telecamere e ottenere la loro posizione nello spazio mondiale. Min max i valori x, y e puoi ottenere un rettangolo attorno allo spazio visualizzabile. Molto utile per l'abbattimento e l'ottimizzazione delle chiamate di disegno.


1
Grazie. L'ho trovato utile durante l'implementazione di una videocamera nella libreria MonoGame.Extended .
craftworkgames

6

L'applicazione di una matrice a SpriteBatch trasforma l'intera chiamata di disegno contemporaneamente. Ciò significa che non è necessario utilizzare la fotocamera nel metodo DrawTiles.

Potrebbe diventare molto più semplice in questo modo:

    // 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);
                }
        }
    }

Quindi il punto di usare una matrice è che non devi pensarci. Basta disegnare le cose e spostare la fotocamera in modo indipendente.

Inoltre, il tuo metodo MoveCamera sembra un po 'strano. È molto insolito avere una classe di telecamere che prende un livello come dipendenza. Un'implementazione più tipica sarebbe simile a questa:

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

Quindi nel tuo metodo di aggiornamento potresti fare qualcosa del genere:

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

Nel complesso, il mio suggerimento è di mantenerlo semplice. Fallo funzionare nel modo più semplice pugno e costruiscilo sopra. Cerca di non scrivere un codice altamente ottimizzato fino a quando non avrai prima funzionato le basi. Potresti scoprire che il rendering di ogni riquadro in ogni fotogramma non è poi così male.

EDIT: per la seconda parte della domanda.

Mentre è vero che vuoi mantenere basso il numero di lotti, avere 2 o 3 non dovrebbe essere affatto un problema. Quindi, se hai una buona ragione per creare un secondo batch di sprite, fallo e basta.

Detto questo, probabilmente non c'è una buona ragione per usare un secondo batch di sprite in questo caso. Più probabilmente, vuoi disegnare il tuo giocatore nello stesso modo in cui disegni le tessere nello stesso batch di sprite con la trasformazione della telecamera applicata.

È un po 'difficile dire perché il tuo giocatore viene lasciato indietro senza guardare un po' di codice, ma è ovvio che se disegni il tuo giocatore esattamente nella stessa posizione di una tessera, apparirà nella stessa posizione con la stessa batch di sprite.

Ad esempio, se vuoi che il giocatore appaia sulla tessera 10, 10 puoi farlo:

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

Cerca di entrare nella mentalità di pensare di disegnare le cose dove sono e la telecamera sposta letteralmente in vista l'intera "scena". Questo è ciò che sta facendo la tua trasformazione di matrice.


Questo è davvero utile e chiarito le cose carichi! Grazie per le informazioni; molto apprezzato. Cercherò di attuare i tuoi consigli più tardi oggi e ti farò sapere come vado avanti. Grazie ancora.
Pectus Excavatum,

Inoltre, solo per interesse, c'è un modo per usare la trasformazione matrice per disegnare solo tessere nella finestra?
Pectus Excavatum,

C'è un modo per farlo, ma non lo so dalla cima della mia testa. Ho il sospetto che avrà qualcosa a che fare con l'uso del rettangolo viewport, forse dopo aver applicato la trasformazione della fotocamera ad esso. Non so, dovrai sperimentare. Usa il tuo debugger.
craftworkgames

Ok grazie. Ci ho giocato e ottenuto da qualche parte, ma ho avuto una domanda su cosa fare con il giocatore - vedi le modifiche alla domanda.
Pectus Excavatum,

Grazie, ho seguito i tuoi consigli e ho scelto l'opzione di utilizzare lo stesso batch di sprite e ottenere semplicemente la posizione della telecamera nell'aggiornamento e applicarla alla posizione dei giocatori quando si disegna. Sembra funzionare bene. Grazie per tutto il vostro aiuto, sono rimasto bloccato sulla fotocamera per un po '. Molto più chiaro ora :-)
Pectus Excavatum,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.