C'è un modo semplice per raggruppare due o più sprite, quindi tutti dipenderanno l'uno dall'altro?


8

Penso che questa domanda sia molto simile a questa , ma non sono sicuro che le risposte siano universali.

Quindi, il mio obiettivo è:

  • Metti due folletti in posizione fissa, ad esempio il giocatore e i suoi occhi
  • Assicurati che ogni volta che il giocatore ruota, anche lo sprite degli occhi ruota e raggiunge la stessa posizione relativa dal corpo (quindi gli occhi non sono sulla schiena del giocatore). Quindi lavoreranno in gruppo. - Questo passaggio dovrebbe essere automatizzato, questo è il mio obiettivo!

Quindi, ad esempio, ora voglio mettere una pistola nelle mani dell'utente. Quindi ora dico che quel giocatore è in posizione Vector2(0, 0)e la pistola è in posizione Vector2(26, 16). Ora voglio raggrupparli, quindi ogni volta che il giocatore ruota, anche la pistola ruota.

inserisci qui la descrizione dell'immagine

Attualmente in questo esempio va bene, ma nel caso in cui dovessi spostare la pistola sull'asse y (solo), mi sono perso

Risposte:


10

Concetto

Vorrei risolvere questo problema con una gerarchia di sprite usando una variante del modello di disegno composito . Ciò significa che ogni sprite memorizza un elenco degli sprite secondari ad esso collegati in modo che qualsiasi modifica al genitore si rifletta automaticamente in essi (inclusi traduzione, rotazione e ridimensionamento).

Nel mio motore l'ho implementato in questo modo:

  • Ciascuno Spritememorizza un List<Sprite> Childrene fornisce un metodo per aggiungere nuovi figli.
  • Ognuno Spritesa come calcolare un Matrix LocalTransformche è definito rispetto al genitore.
  • Chiamare Drawa Spritelo chiama anche su tutti i suoi figli.
  • I bambini moltiplicano la loro trasformazione locale per la trasformazione globale dei genitori . Il risultato è quello che usi durante il rendering.

Con questo sarai in grado di fare ciò che hai richiesto senza altre modifiche al tuo codice. Ecco un esempio:

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

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

Implementazione

Per cominciare, inserirò semplicemente un progetto di esempio con questa tecnica implementata, nel caso in cui preferiate semplicemente guardare il codice e capirlo:

inserisci qui la descrizione dell'immagine

Nota: ho optato per la chiarezza anziché per le prestazioni qui. In un'implementazione seria, ci sono molte ottimizzazioni che potrebbero essere fatte, la maggior parte delle quali comporta la memorizzazione nella cache delle trasformazioni e il loro ricalcolo solo se necessario (ad esempio, memorizza nella cache sia le trasformazioni locali che globali per ogni sprite e ricalcola solo quando lo sprite o uno dei suoi antenati cambia). Inoltre, le versioni della matrice XNA e le operazioni vettoriali che prendono valori per riferimento sono un po 'più veloci di quelle che ho usato qui.

Ma descriverò il processo in modo più dettagliato di seguito, quindi continua a leggere per ulteriori informazioni.


Passaggio 1: apportare alcune modifiche alla classe Sprite

Supponendo che tu abbia già una Spriteclasse (e dovresti), allora dovrai apportare alcune modifiche. In particolare dovrai aggiungere l'elenco degli sprite figlio, la matrice di trasformazione locale e un modo per propagare trasforma la gerarchia degli sprite. Ho trovato il modo più semplice per farlo solo per passarli come parametro durante il disegno. Esempio:

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

Passaggio 2: calcolo della matrice LocalTransform

La LocalTransformmatrice è solo una normale matrice mondiale costruita dalla posizione dello sprite, dalla rotazione e dai valori di scala. Per l'origine ho assunto il centro dello 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);
   }
}

Passaggio 3: sapere come passare una matrice a SpriteBatch

Un problema con la SpriteBatchclasse è che il suo Drawmetodo non sa come prendere direttamente una matrice mondiale. Ecco un metodo di supporto per colmare questo 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);
}

Passaggio 4: rendering dello Sprite

Nota: il Drawmetodo accetta la trasformazione globale del genitore come parametro. Esistono altri modi per propagare queste informazioni, ma ho trovato questo facile da usare.

  1. Calcola la trasformazione globale moltiplicando la trasformazione locale per la trasformazione globale del genitore.
  2. Adatta la trasformazione globale SpriteBatche rendi lo sprite corrente.
  3. Renderizza tutti i bambini passando loro l'attuale trasformazione globale come parametro.

Traducendolo in codice otterrai qualcosa del tipo:

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

Quando si disegna lo sprite di root non c'è trasformazione genitore, quindi la si passa Matrix.Identity. È possibile creare un sovraccarico per aiutare in questo caso:

public void Draw(SpriteBatch spriteBatch) { Draw(spriteBatch, Matrix.Identity); }

Ho un problema con cui sto attualmente affrontando. Nella mia configurazione attuale, creo la trasformazione Matrix quando chiamo spriteBatch, perché sto usando la fotocamera. Quindi, nel caso in cui la mia macchina fotografica sia su 500,50 e lo sprite del mio giocatore su 500,50, il giocatore dovrebbe essere al centro. Tuttavia, in questo caso, non sta disegnando da nessuna parte
Martin.

@Martin Che tu usi o meno una matrice per videocamera, dovrebbe funzionare allo stesso modo. In questo esempio non ho usato una macchina fotografica, ma sul mio motore ne uso una e funziona normalmente. Stai passando la matrice videocamera (e solo la matrice videocamera) a SpriteBatch.Begin? E se vuoi che le cose siano centrate, stai prendendo in considerazione metà delle dimensioni dello schermo quando crei la matrice della fotocamera?
David Gouveia,

Sto usando la stessa classe di qui per la fotocamera, ricevendo la fotocamera metrix, sì. Ora funziona, proverò a modificarlo ora e ti faccio sapere
Martin.

1
PERFETTO! Incredibile PERFETTO! E come nella maggior parte dei casi ho avuto problemi, è stata colpa mia. Guarda ! i.imgur.com/2CDRP.png
Martin.

So di essere in ritardo alla festa qui poiché questa domanda è davvero vecchia. Ma mentre funziona, incasina le posizioni quando le scale xey non sono le stesse: /
Erik Skoglund

2

Dovresti essere in grado di raggrupparli in uno SpriteBatch e spostarli in posizione e ruotarlo usando una matrice.

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

Codici non testati e la mia moltiplicazione della matrice è molto ruggine ma la tecnica è valida.


Cosa fare se utilizzo effettivamente Matrix in spriteBatch per la fotocamera? spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cam.get_transformation(graphics.GraphicsDevice));
Martin.

Penso che dovresti essere in grado di usare Begin con matrix * cam.get_transformation (graphics.GraphicsDevice).
ClassicThunder,

PERFETTO. Non ho avuto un'ottima risposta così in fretta.
Martin.

Soluzione semplice, ma in qualche modo sconfigge l'intero scopo del batch di sprite che è quello di disegnare il maggior numero possibile di sprite in una chiamata. Potrei usarlo in una semplice demo, ma sicuramente non in un gioco intensivo di sprite.
David Gouveia,

@Martin Nella mia risposta ho un esempio di come creare una matrice mondiale completa per lo sprite. Fondamentalmente l'ordine dovrebbe essere -Origin * Scale * Rotation * Translation(dove tutti questi valori provengono dallo sprite).
David Gouveia,

0

Lo implementerei in modo leggermente diverso.

Dai alla tua classe sprite un elenco per i suoi figli. Quindi, quando aggiorni la posizione e le trasformazioni dello sprite, applica anche le stesse traduzioni ai bambini con un ciclo for-each. Gli sprite figlio dovrebbero avere le loro coordinate definite nello spazio modello anziché nello spazio mondiale.

Mi piace usare due rotazioni: una attorno all'origine del bambino (per ruotare lo sprite in posizione) e una intorno all'origine del genitore (per rendere il bambino essenzialmente in orbita attorno al genitore).

Per disegnare, basta iniziare il tuo spritebatch, chiamare il tuo player.draw () che disegnerebbe, quindi fare un ciclo tra tutti i suoi figli e disegnarli pure.

Con questo tipo di gerarchia, quando sposti il ​​genitore tutti i suoi figli si muovono con esso, permettendoti anche di spostare i figli in modo indipendente.

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.