¿Hay una manera simple de agrupar dos o más sprites, de modo que todos dependan unos de otros?

8

Creo que esta pregunta es muy similar a esta , pero no estoy seguro de si las respuestas son universales.

Entonces, mi objetivo es:

  • Coloque dos sprites en posición fija, por ejemplo, el jugador y sus ojos.
  • Asegúrese de que cada vez que el jugador gire, el sprite de los ojos también gire y llegue a la misma posición relativa del cuerpo (para que los ojos no estén en la espalda del jugador). Entonces trabajarán en grupo. - Este paso debe ser automatizado, ¡ese es mi objetivo!

Entonces, por ejemplo, ahora quiero colocar un arma en las manos del usuario. Así que ahora digo que ese jugador está en posición Vector2(0, 0)y el arma está en posición Vector2(26, 16). Ahora quiero agruparlos, así que cada vez que el jugador gira, la pistola también gira.

ingrese la descripción de la imagen aquí

Actualmente en este ejemplo está bastante bien, pero en caso de que necesite mover mi arma en el eje y (solo), estoy perdido

Martín.
fuente

Respuestas:

10

Concepto

Solucionaría este problema con una jerarquía de sprites usando una variación del patrón de diseño compuesto . Esto significa que cada sprite almacene una lista de los sprites secundarios que están adjuntos para que cualquier modificación en el padre se refleje automáticamente en ellos (incluida la traducción, rotación y escalado).

En mi motor lo he implementado así:

  • Cada uno Spritealmacena List<Sprite> Childreny proporciona un método para agregar nuevos hijos.
  • Cada uno Spritesabe cómo calcular un Matrix LocalTransformque se define en relación con el padre.
  • Llamar Drawa un Spritetambién lo llama a todos sus hijos.
  • Los niños multiplican su transformación local por la transformación global de sus padres . El resultado es lo que usa al renderizar.

Con esto, podrá hacer lo que solicitó sin ninguna otra modificación en su código. Aquí hay un ejemplo:

Sprite tank = new Sprite(tankTexture);
tank.Children.Add(new Sprite(turretTexture) {Position = new Vector2(26, 16) });

spriteBatch.Begin();
tank.Draw(spriteBatch);
spriteBatch.End();

Implementación

Para empezar, solo agregaré un proyecto de muestra con esta técnica implementada, en caso de que prefiera mirar el código y descifrarlo:

ingrese la descripción de la imagen aquí

Nota: He optado por la claridad en lugar del rendimiento aquí. En una implementación seria, hay muchas optimizaciones que podrían hacerse, la mayoría de las cuales implican el almacenamiento en caché de las transformaciones y solo recalcularlas según sea necesario (por ejemplo, almacenar en caché las transformaciones locales y globales en cada sprite, y recalcularlas solo cuando el sprite o uno de sus antepasados ​​cambia). Además, las versiones de las operaciones de matriz y vector de XNA que toman valores por referencia son un poco más rápidas que las que utilicé aquí.

Pero describiré el proceso con más detalle a continuación, así que siga leyendo para obtener más información.


Paso 1: haz algunos ajustes en la clase Sprite

Suponiendo que ya tiene una Spriteclase en su lugar (y debería hacerlo), deberá realizar algunas modificaciones. En particular, deberá agregar la lista de sprites secundarios, la matriz de transformación local y una forma de propagar transformaciones en la jerarquía de sprites. Encontré que la forma más fácil de hacerlo es pasarlos como un parámetro al dibujar. Ejemplo:

public class Sprite
{
    public Vector2 Position { get; set; } 
    public float Rotation { get; set; }
    public Vector2 Scale { get; set; }
    public Texture2D Texture { get; set; }

    public List<Sprite> Children { get; }
    public Matrix LocalTransform { get; }
    public void Draw(SpriteBatch spriteBatch, Matrix parentTransform);
}

Paso 2 - Cálculo de la matriz LocalTransform

La LocalTransformmatriz es solo una matriz mundial regular construida a partir de los valores de posición, rotación y escala del sprite. Para el origen, asumí el centro del sprite:

public Matrix LocalTransform
{
    get 
    {
        // Transform = -Origin * Scale * Rotation * Translation
        return Matrix.CreateTranslation(-Texture.Width/2f, -Texture.Height/2f, 0f) *
               Matrix.CreateScale(Scale.X, Scale.Y, 1f) *
               Matrix.CreateRotationZ(Rotation) *
               Matrix.CreateTranslation(Position.X, Position.Y, 0f);
   }
}

Paso 3: saber cómo pasar una matriz a SpriteBatch

Un problema con la SpriteBatchclase es que su Drawmétodo no sabe cómo tomar una matriz mundial directamente. Aquí hay un método auxiliar para salvar este problema:

public static void DecomposeMatrix(ref Matrix matrix, out Vector2 position, out float rotation, out Vector2 scale)
{
    Vector3 position3, scale3;
    Quaternion rotationQ;
    matrix.Decompose(out scale3, out rotationQ, out position3);
    Vector2 direction = Vector2.Transform(Vector2.UnitX, rotationQ);
    rotation = (float) Math.Atan2(direction.Y, direction.X);
    position = new Vector2(position3.X, position3.Y);
    scale = new Vector2(scale3.X, scale3.Y);
}

Paso 4 - Renderizando el Sprite

Nota: El Drawmétodo toma la transformación global del padre como parámetro. Hay otras formas de propagar esta información, pero esta me pareció fácil de usar.

  1. Calcule la transformación global multiplicando la transformación local por la transformación global del padre.
  2. Adapte la transformación global SpriteBatchy renderice el sprite actual.
  3. Haga que todos los hijos pasen la transformación global actual como parámetro.

Traduciendo eso en código obtendrás algo como:

public void Draw(SpriteBatch spriteBatch, Matrix parentTransform)
{
    // Calculate global transform
    Matrix globalTransform = LocalTransform * parentTransform;

    // Get values from GlobalTransform for SpriteBatch and render sprite
    Vector2 position, scale;
    float rotation;
    DecomposeMatrix(ref globalTransform, out position, out rotation, out scale);
    spriteBatch.Draw(Texture, position, null, Color.White, rotation, Vector2.Zero, scale, SpriteEffects.None, 0.0f);

    // Draw Children
    Children.ForEach(c => c.Draw(spriteBatch, globalTransform));
}

Al dibujar el sprite raíz no hay transformación principal, por lo que debe pasarla Matrix.Identity. Puede crear una sobrecarga para ayudar con este caso:

public void Draw(SpriteBatch spriteBatch) { Draw(spriteBatch, Matrix.Identity); }
David Gouveia
fuente
Tengo un problema con el que estoy lidiando actualmente. En mi configuración actual, creo la transformación Matrix al llamar a spriteBatch, porque estoy usando la cámara. Entonces, en caso de que mi cámara esté en 500,50 y el sprite de mi jugador en 500,50, el jugador debería estar en el centro. Sin embargo, en este caso, no está dibujando en ninguna parte
Martin.
@ Martin Si usa una matriz de cámara o no, debería funcionar igual. En este ejemplo, no usé una cámara, pero en mi motor uso una y funciona normalmente. ¿Está pasando la matriz de la cámara (y la matriz de la cámara solamente) a SpriteBatch.Begin? Y si quieres que las cosas estén centradas, ¿estás teniendo en cuenta la mitad del tamaño de la pantalla al crear la matriz de la cámara?
David Gouveia
Estoy usando la misma clase que aquí para la cámara, obteniendo su cámara metrix, sí. Ahora funciona, intentaré modificarlo ahora y hacerle saber
Martin.
1
¡PERFECTO! Increíble PERFECTO! Y como en la mayoría de los casos tuve problemas, fue mi culpa. Echar un vistazo ! i.imgur.com/2CDRP.png
Martin.
Sé que llego tarde a la fiesta aquí ya que esta pregunta es muy antigua. Pero mientras esto funciona, arruina las posiciones cuando las escalas x e y no son las mismas: /
Erik Skoglund
2

Debería poder agruparlos en un SpriteBatch y moverlos a su lugar y rotarlos usando una matriz.

var matrix = 
    Matrix.CreateRotationZ(radians) *
    Matrix.CreateTranslation(new Vector3(x, y, 0));

SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, matrix);
SpriteBatch.Draw(player, Vector2.Zero, null, Color.White);
SpriteBatch.Draw(gun, Vector2(handDistance, 0), null, Color.White);
SpriteBatch.End();

Códigos no probados y mi multiplicación matricial es muy oxidada, pero la técnica es cierta.

Trueno clásico
fuente
¿Qué hacer en caso de que realmente use Matrix en spriteBatch para cámara? spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cam.get_transformation(graphics.GraphicsDevice));
Martin.
Creo que debería poder usar Begin with matrix * cam.get_transformation (graphics.GraphicsDevice).
ClassicThunder
PERFECTO. No respondí tan bien tan rápido.
Martin.
Solución simple, pero frustra un poco todo el propósito del procesamiento por lotes de sprites, que es atraer tantos sprites como sea posible en una sola llamada. Podría usar esto en una demostración simple, pero definitivamente no en un juego intensivo de sprites.
David Gouveia
@ Martin En mi respuesta tengo un ejemplo de cómo crear una matriz mundial completa para el sprite. Básicamente, el orden debería ser -Origin * Scale * Rotation * Translation(donde todos estos valores provienen del sprite).
David Gouveia
0

Implementaría esto un poco diferente.

Dele a su clase de sprites una lista de sus hijos. Luego, cuando actualice la posición y las transformaciones del sprite, también aplique las mismas traducciones a los niños con un ciclo for-each. Los sprites secundarios deben tener sus coordenadas definidas en el espacio modelo en lugar del espacio mundial.

Me gusta usar dos rotaciones: una alrededor del origen del niño (para girar el sprite en su lugar) y otra alrededor del origen del padre (para hacer que el niño orbita esencialmente al padre).

Para dibujar, solo comience su spritebatch, llame a su player.draw (), que dibujaría, luego recorrería a todos sus hijos y los dibujaría también.

Con este tipo de jerarquía, cuando mueve el padre, todos sus hijos se mueven con él, lo que también le permite mover los hijos de forma independiente.

Jabonaduras
fuente