Desplazamiento de cámara XNA 2d: ¿por qué usar la transformación de matriz?


18

Estoy haciendo un juego de prueba donde quiero que el nivel se desplace constantemente. Para crear este efecto, he establecido una clase de cámara que simplemente almacena una posición vector2 y una dirección de enumeración. También contiene un método público para 'moverse' que simplemente cambia la posición a una tasa fija. Luego uso esta posición cuando recorro mi conjunto de mosaicos cuando dibujo. Todo esto funciona bien.

Sin embargo, me han dicho que debería usar una matriz de transformación para mover la cámara, y que debería proporcionar esto cuando empiece el spritebatch. Estoy un poco confundido a.) ¿Cómo funciona esto? como si solo lo estuviera dando cuando comience el spritebatch, ¿cómo sabe seguir cambiando de posición? b.) ¿Por qué lo hago con tanta seguridad que todavía necesito la posición de la cámara cuando paso por los mosaicos?

Por el momento no puedo hacer que funcione, pero eso no es una sorpresa, ya que no entiendo completamente cómo debe funcionar. Actualmente, en mi intento (código para seguir), los mosaicos que se dibujan cambian, lo que significa que la posición de las cámaras está cambiando, pero la posición de la ventana gráfica permanece sin cambios (es decir, en el origen de la cámara). Realmente agradecería algún consejo / guía sobre cómo se supone que debe usarse?

Cámara:

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

Nivel:

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

Juego: solo llama al sorteo dentro del nivel:

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

        //mSpriteBatch.Begin();

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

        //mSpriteBatch.End();

        base.Draw(gameTime);
    }

================================================== =============================== EDITAR:

En primer lugar, gracias craftworkgames por tu ayuda hasta ahora.

Jugué con la sugerencia. Cuando dibujé todas las fichas, el franco se redujo a alrededor de 15 desde 30, probablemente porque los niveles son bastante grandes.

Entonces, lo que he hecho es aplicar la matriz y moverme en la actualización (como se sugiere), pero en el dibujo uso la posición de la cámara para recorrer los mosaicos (es decir, iniciar el contador a la izquierda y terminar en el mosaico derecho). Todo esto funciona bien y estoy feliz con eso :-)

Mi nuevo problema radica en el jugador. Obviamente, como ahora estoy moviendo la cámara en lugar del nivel, el jugador se queda atrás por la cámara mientras su posición permanece fija. He pensado en dos soluciones a este problema, la primera es simplemente considerar la posición de las cámaras al dibujar el reproductor. Es decir, en la función de sorteo simplemente agregue la posición de la cámara a la posición del jugador. El segundo es comenzar un nuevo lote de sprites para el jugador que no tiene transformación. es decir, finalizar el spritebatch después de dibujar las fichas y luego comenzar una nueva al dibujar el jugador. Sé que ambos funcionarán, pero no puedo determinar cuál sería mejor en términos de rendimiento / buena codificación. No estoy seguro de cuáles son las implicaciones de rendimiento al comenzar el lote dos veces.


1
"Mi nuevo problema radica en el jugador. Obviamente, como ahora estoy moviendo la cámara en lugar del nivel, el jugador se queda atrás por la cámara mientras su posición permanece fija". Acabo de leer esto y estoy tan confundido ¿por qué no moverías al jugador dentro del sistema de coordinación del nivel?
ClassicThunder

Respuestas:


15

Las transformaciones de la matriz de la cámara son fáciles.

Crear una cámara básica es fácil. A continuación, comenzará con los conceptos básicos. Moviéndolo, girando y escalando. Mover cada sprite 2D no es un gran problema, pero si tiene en cuenta la escala o la rotación, se vuelve realmente difícil aplicarlo a cada 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;
    }
}

Hace que sea muy fácil convertir entre definiciones de sistemas de coordenadas

Para ir de la pantalla al espacio mundial simplemente. Esto se usa comúnmente para obtener la ubicación del mouse en el mundo para la selección de objetos.

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

Para ir del mundo al espacio de la pantalla, simplemente haga lo contrario.

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

No hay inconveniente en usar una matriz que no sea un poco de aprendizaje.

Es fácil obtener el área 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));
    }
}

Simplemente puede transformar las esquinas de las cámaras y obtener su ubicación en el espacio mundial. Min max los valores x, y y puede obtener un rectángulo alrededor del espacio visible. Muy útil para seleccionar y optimizar llamadas de sorteo.


1
Gracias. Esto me resultó útil al implementar una cámara en la biblioteca MonoGame.Extended .
craftworkgames

6

La aplicación de una matriz a su SpriteBatch transforma toda la llamada de sorteo a la vez. Esto significa que no necesita usar su cámara en su método DrawTiles en absoluto.

Podría ser mucho más simple así:

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

Entonces, el punto de usar una matriz es para que no tenga que pensar en ello. Simplemente dibuja cosas y mueve la cámara de forma independiente.

Además, su método MoveCamera se ve un poco extraño. Es muy inusual tener una clase de cámara que tome un Nivel como dependencia. Una implementación más típica se vería así:

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

Luego, en su método de actualización, puede hacer algo como esto:

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

En general, mi sugerencia es que sea simple. Haz que funcione de la manera más simple y construye sobre él. Intente no escribir código altamente optimizado hasta que tenga lo básico funcionando primero. Puede encontrar que renderizar cada mosaico cada cuadro no es tan malo.

EDITAR: Para la segunda parte de la pregunta.

Si bien es cierto que desea mantener un recuento bajo de lotes, tener 2 o 3 no debería ser un problema en absoluto. Entonces, si tiene una buena razón para crear un segundo lote de sprites, simplemente hágalo.

Dicho esto, probablemente no haya una buena razón para usar un segundo lote de sprites en este caso. Lo más probable es que quieras dibujar a tu jugador exactamente de la misma manera que dibujas fichas en el mismo lote de sprites con la transformación de la cámara aplicada.

Es un poco difícil saber por qué su jugador se queda atrás sin mirar algún código, pero es lógico que si dibuja a su jugador exactamente en la misma posición que un mosaico, aparecerá en la misma posición con el mismo lote de sprites

Por ejemplo, si desea que el jugador aparezca en el mosaico 10, 10 puede hacer esto:

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

Trate de entrar en la mentalidad de pensar en dibujar cosas donde están y la cámara literalmente mueve toda la "escena" a la vista. Eso es lo que está haciendo su transformación de matriz.


¡Eso es realmente útil y aclara las cosas! Gracias por la info; muy apreciado. Trataré de implementar su consejo más tarde hoy y le haré saber cómo me llevo. Gracias de nuevo.
Pectus Excavatum

Además, solo por interés, ¿hay alguna manera de usar la transformación de matriz para dibujar solo mosaicos en la ventana gráfica?
Pectus Excavatum

Hay una manera de hacerlo, pero no lo sé de la cabeza. Sospecho que tendrá algo que ver con el uso del rectángulo de la ventana gráfica, tal vez después de aplicar la transformación de la cámara. No sé, tendrás que experimentar. Usa tu depurador.
craftworkgames

OK gracias. Jugué con él y llegué a algún lado, pero me he encontrado con una pregunta sobre qué hacer con el reproductor; consulte las ediciones para preguntar.
Pectus Excavatum

Gracias, tomé tu consejo y decidí usar el mismo lote de sprites y obtener la posición de la cámara en la actualización y aplicarla a la posición de los jugadores al dibujar. Parece funcionar bien. Gracias por toda su ayuda, he estado atrapado en la cámara por un tiempo. Mucho más claro ahora :-)
Pectus Excavatum
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.