Défilement de la caméra XNA 2d - pourquoi utiliser la transformation matricielle?


18

Je fais un jeu de test où je veux que le niveau défile constamment. Pour créer cet effet, j'ai créé une classe de caméra qui stocke simplement une position vector2 et une direction enum. Il contient également une méthode publique de «déplacement» qui modifie simplement la position à un taux fixe. J'utilise ensuite cette position lors de la boucle à travers mon tableau de tuiles lors du dessin. Tout cela fonctionne bien.

Cependant, on m'a dit que je devrais utiliser une matrice de transformation pour déplacer la caméra et que je devrais fournir cela lorsque je démarre le spritebatch. Je suis un peu confus a.) Comment ça marche? comme si je ne le donnais qu'au début du spritebatch, comment sait-il continuer à changer de position? b.) Pourquoi le faire, car j'ai sûrement encore besoin de la position de la caméra pour parcourir les tuiles?

Pour le moment, je ne peux pas le faire fonctionner, mais ce n'est pas une surprise car je ne comprends pas complètement comment il est censé fonctionner. Actuellement, dans ma tentative (code à suivre), les tuiles dessinées changent, ce qui signifie que la position des caméras change, mais la position de la fenêtre reste inchangée (c'est-à-dire à l'origine de la caméra). J'apprécierais vraiment quelques conseils / conseils sur la façon dont il est censé être utilisé?

Caméra:

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

Jeu - appelle simplement le tirage au sein du niveau:

  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:

Tout d'abord, merci craftworkgames pour votre aide jusqu'à présent.

J'ai joué avec la suggestion. Quand j'ai dessiné toutes les tuiles, le fr a atteint environ 15 sur 30 - probablement parce que les niveaux sont assez grands.

Donc, ce que j'ai fait, c'est appliquer la matrice et se déplacer dans la mise à jour (comme suggéré), mais dans le dessin, j'utilise la position des caméras pour parcourir les tuiles (c'est-à-dire commencer le compteur à gauche et terminer à droite). Tout fonctionne bien et j'en suis satisfait :-)

Mon nouveau problème réside dans le joueur. Évidemment, comme je déplace maintenant la caméra plutôt que le niveau, le joueur est laissé par le camer car sa position reste fixe. J'ai pensé à deux solutions à ce problème, la première est de simplement considérer la position des caméras lors du dessin du lecteur. C'est-à-dire dans la fonction de dessin, ajoutez simplement la position de la caméra à la position du joueur. La seconde consiste à démarrer un nouveau lot de sprites pour le joueur qui n'a pas de transformation. c'est-à-dire terminer le spritebatch après avoir dessiné les tuiles puis en commencer une nouvelle lorsque vous dessinez le joueur. Je sais que les deux fonctionneront, mais je ne peux pas faire de têtes de queues qui seraient meilleures en termes de performances / bon codage? Je ne sais pas quelles sont les implications en termes de performances du démarrage du lot deux fois?


1
"Mon nouveau problème réside dans le lecteur. De toute évidence, comme je déplace maintenant la caméra plutôt que le niveau, le joueur est laissé par le camer alors que sa position reste fixe." Lisez juste ceci et je suis tellement confus pourquoi diable ne déplaceriez-vous pas le joueur dans le système coordiante du niveau?
ClassicThunder

Réponses:


15

Les transformations de la matrice de la caméra sont faciles

La création d'une caméra de base est simple. Vous obtiendrez ci-dessous les bases. Déplacement, rotation et mise à l'échelle. Déplacer chaque sprite 2D n'est pas vraiment un problème, mais si vous tenez compte de l'échelle ou de la rotation, il devient très difficile de l'appliquer individuellement à chaque sprite.

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

Il facilite la conversion entre les définitions de système de coordonnées

Pour passer de l'écran à l'espace mondial simplement. Ceci est couramment utilisé pour obtenir l'emplacement de la souris dans le monde pour la cueillette d'objets.

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

Pour passer du monde à l'espace écran, faites simplement le contraire.

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

Il n'y a aucun inconvénient à utiliser une matrice autre que cela demande un peu d'apprentissage.

Il est facile d'obtenir la zone visible

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

Vous pouvez simplement transformer les coins des caméras et obtenir leur emplacement dans l'espace mondial. Min max les valeurs x, y et vous pouvez obtenir un rectangle autour de l'espace visible. Très utile pour éliminer et optimiser les appels de tirage.


1
Merci. J'ai trouvé cela utile lors de l'implémentation d'une caméra dans la bibliothèque MonoGame.Extended .
craftworkgames

6

L'application d'une matrice à votre SpriteBatch transforme tout l'appel de tirage en une seule fois. Cela signifie que vous n'avez pas du tout besoin d'utiliser votre caméra dans votre méthode DrawTiles.

Cela pourrait devenir beaucoup plus simple comme ceci:

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

Le but de l'utilisation d'une matrice est donc de ne pas avoir à y penser. Il suffit de dessiner des choses et de déplacer la caméra de manière indépendante.

De plus, votre méthode MoveCamera semble un peu étrange. Il est très inhabituel d'avoir une classe de caméra qui prend un niveau comme dépendance. Une implémentation plus typique ressemblerait à ceci:

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

Ensuite, dans votre méthode de mise à jour, vous pourriez faire quelque chose comme ceci:

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

Dans l'ensemble, ma suggestion est de rester simple. Faites-le fonctionner de la manière la plus simple et construisez-le. Essayez de ne pas écrire de code hautement optimisé tant que les bases ne fonctionnent pas en premier. Vous pouvez constater que le rendu de chaque tuile à chaque image n'est pas si mauvais.

EDIT: Pour la deuxième partie de la question.

Bien qu'il soit vrai que vous souhaitez maintenir votre nombre de lots bas, avoir 2 ou 3 ne devrait pas être un problème du tout. Donc, si vous avez une bonne raison de créer un deuxième lot de sprites, faites-le.

Cela dit, il n'y a probablement pas de bonne raison d'utiliser un deuxième lot de sprites dans ce cas. Plus probablement, vous voulez dessiner votre joueur de la même manière que vous dessinez des tuiles dans le même lot de sprites avec la transformation de caméra appliquée.

Il est un peu difficile de dire pourquoi votre joueur est laissé sans regarder un code, mais il va de soi que si vous dessinez votre joueur exactement à la même position qu'une tuile, il apparaîtra à la même position avec la même lot de sprites.

Par exemple, si vous voulez que le joueur apparaisse sur la tuile 10, 10 vous pouvez faire ceci:

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

Essayez d'entrer dans la mentalité de penser à dessiner les choses où elles sont et la caméra met littéralement toute la «scène» en vue. C'est ce que fait votre transformation matricielle.


C'est vraiment utile et clarifié les choses! Merci pour l'info; très appréciée. Je vais essayer de mettre en œuvre vos conseils plus tard dans la journée et je vous ferai savoir comment je vais de l'avant. Merci encore.
Pectus Excavatum

De plus, juste par intérêt, existe-t-il un moyen d'utiliser la transformation de matrice pour dessiner uniquement des tuiles dans la fenêtre?
Pectus Excavatum

Il y a un moyen de le faire, mais je ne sais pas du haut de ma tête. Je soupçonne que cela aura quelque chose à voir avec l'utilisation du rectangle de la fenêtre, peut-être après y avoir appliqué la transformation de la caméra. Je sais pas, vous devrez expérimenter. Utilisez votre débogueur.
craftworkgames

OK merci. J'ai joué avec lui et je suis arrivé quelque part, mais je me suis posé une question sur ce qu'il faut faire avec le lecteur - veuillez consulter les modifications apportées.
Pectus Excavatum

Merci, j'ai suivi vos conseils et j'ai choisi d'utiliser le même lot de sprites et d'obtenir simplement la position de la caméra dans la mise à jour et de l'appliquer à la position des joueurs lors du dessin. Semble bien fonctionner. Merci pour toute votre aide, bloqué sur l'appareil photo depuis un moment. Beaucoup plus clair maintenant :-)
Pectus Excavatum
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.