Gibt es eine einfache Möglichkeit, zwei oder mehr Sprites zu gruppieren, damit alle voneinander abhängig sind?

8

Ich denke , dass diese Frage sehr ähnlich ist dies ein, aber ich bin nicht sicher , ob die Antworten sind universell.

Mein Ziel ist also:

  • Platziere zwei Sprites in einer festen Position, zum Beispiel den Spieler und seine Augen
  • Stellen Sie sicher, dass sich bei jeder Drehung des Spielers auch das Augen-Sprite dreht und vom Körper aus dieselbe relative Position einnimmt (damit die Augen nicht auf dem Rücken des Spielers liegen). Also werden sie als Gruppe arbeiten. - Dieser Schritt sollte automatisiert werden, das ist mein Ziel!

So möchte ich zum Beispiel jetzt eine Waffe in die Hände des Benutzers legen. Jetzt sage ich also, dass der Spieler in Position ist Vector2(0, 0)und die Waffe in Position ist Vector2(26, 16). Jetzt möchte ich sie gruppieren. Wenn sich der Spieler dreht, dreht sich auch die Waffe.

Geben Sie hier die Bildbeschreibung ein

Derzeit ist es in diesem Beispiel ziemlich in Ordnung, aber falls ich meine Waffe (nur) auf der Achse y bewegen müsste, bin ich verloren

Martin.
quelle

Antworten:

10

Konzept

Ich würde dieses Problem mit einer Sprite-Hierarchie unter Verwendung einer Variation des zusammengesetzten Entwurfsmusters lösen . Dies bedeutet, dass jedes Sprite eine Liste der untergeordneten Sprites speichert, die daran angehängt sind, sodass alle Änderungen am übergeordneten Sprite automatisch in ihnen berücksichtigt werden (einschließlich Übersetzung, Drehung und Skalierung).

In meinem Motor habe ich es so implementiert:

  • Jeder Spritespeichert aList<Sprite> Children und bietet eine Methode zum Hinzufügen neuer untergeordneter Elemente.
  • Jeder Spriteweiß, wie man einen relativMatrix LocalTransform definierten berechnet zum Elternteil definiert ist.
  • Aufruf Drawan aSprite auch alle seine Kinder angerufen.
  • Kinder multiplizieren ihre lokale Transformation mit der globalen Transformation ihrer Eltern . Das Ergebnis verwenden Sie beim Rendern.

Auf diese Weise können Sie das tun, wonach Sie gefragt haben, ohne weitere Änderungen an Ihrem Code vornehmen zu müssen. Hier ist ein Beispiel:

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

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

Implementierung

Für den Anfang werde ich einfach ein Beispielprojekt mit dieser implementierten Technik einfügen, falls Sie es vorziehen, nur den Code zu betrachten und es herauszufinden:

Geben Sie hier die Bildbeschreibung ein

Hinweis: Ich habe mich hier für Klarheit statt Leistung entschieden. In einer seriösen Implementierung können viele Optimierungen vorgenommen werden, von denen die meisten das Zwischenspeichern von Transformationen und deren Neuberechnung nach Bedarf umfassen (z. B. lokale und globale Transformationen bei jedem Sprite zwischenspeichern und nur dann neu berechnen, wenn das Sprite oder einer seiner Vorfahren ändert sich). Außerdem sind die Versionen der Matrix- und Vektoroperationen von XNA, die Werte als Referenz verwenden, etwas schneller als die hier verwendeten.

Aber ich werde den Prozess weiter unten genauer beschreiben. Lesen Sie also weiter, um weitere Informationen zu erhalten.


Schritt 1 - Nehmen Sie einige Anpassungen an der Sprite-Klasse vor

Angenommen, Sie haben bereits eine SpriteKlasse eingerichtet (und sollten dies auch tun), müssen Sie einige Änderungen daran vornehmen. Insbesondere müssen Sie die Liste der untergeordneten Sprites, die lokale Transformationsmatrix und eine Möglichkeit zum Weitergeben von Transformationen in der Sprite-Hierarchie hinzufügen. Ich fand es am einfachsten, dies nur zu tun, um sie beim Zeichnen als Parameter zu übergeben. Beispiel:

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

Schritt 2 - Berechnung der LocalTransform-Matrix

Die LocalTransformMatrix ist nur eine reguläre Weltmatrix, die aus den Positions-, Rotations- und Skalierungswerten des Sprites erstellt wird. Für den Ursprung habe ich das Zentrum des Sprites angenommen:

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

Schritt 3 - Wissen, wie eine Matrix an SpriteBatch übergeben wird

Ein Problem mit der SpriteBatchKlasse ist, dass ihre DrawMethode nicht weiß, wie man eine Weltmatrix direkt nimmt. Hier ist eine Hilfsmethode, um dieses Problem zu beheben:

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

Schritt 4 - Rendern des Sprites

Hinweis: Die DrawMethode verwendet die globale Transformation des übergeordneten Elements als Parameter. Es gibt andere Möglichkeiten, diese Informationen zu verbreiten, aber ich fand diese einfach zu verwenden.

  1. Berechnen Sie die globale Transformation, indem Sie die lokale Transformation mit der globalen Transformation des übergeordneten Elements multiplizieren.
  2. Passen Sie die globale Transformation an SpriteBatchdas aktuelle Sprite an und rendern Sie es.
  3. Rendern Sie allen untergeordneten Elementen die aktuelle globale Transformation als Parameter.

Wenn Sie das in Code übersetzen, erhalten Sie Folgendes:

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

Beim Zeichnen des Root-Sprites gibt es keine übergeordnete Transformation. Sie übergeben sie also Matrix.Identity. Sie können eine Überladung erstellen, um in diesem Fall zu helfen:

public void Draw(SpriteBatch spriteBatch) { Draw(spriteBatch, Matrix.Identity); }
David Gouveia
quelle
Ich habe ein Problem, mit dem ich mich gerade beschäftige. In meinem aktuellen Setup erstelle ich beim Aufrufen von spriteBatch eine Matrixtransformation, da ich eine Kamera verwende. Für den Fall, dass meine Kamera auf 500,50 und das Sprite meines Players auf 500,50 eingestellt ist, sollte sich der Spieler in der Mitte befinden. In diesem Fall zeichnet es jedoch nirgendwo
Martin.
@Martin Ob Sie eine Kameramatrix verwenden oder nicht, sie sollte trotzdem funktionieren. In diesem Beispiel habe ich keine Kamera verwendet, aber bei meinem Motor verwende ich eine und sie funktioniert normal. Übergeben Sie die Kameramatrix (und nur die Kameramatrix) an SpriteBatch.Begin? Und wenn Sie möchten, dass die Dinge zentriert werden, berücksichtigen Sie beim Erstellen der Kameramatrix die Hälfte der Bildschirmgröße?
David Gouveia
Ich benutze die gleiche Klasse wie hier für die Kamera und bekomme die Kamera-Metrix, ja. Es funktioniert jetzt, ich werde versuchen, es jetzt zu optimieren und dich wissen zu lassen
Martin.
1
PERFEKT! Erstaunlich perfekt! Und wie in den meisten Fällen, in denen ich Probleme hatte, war es meine Schuld. Schau mal ! i.imgur.com/2CDRP.png
Martin.
Ich weiß, dass ich zu spät zur Party komme, da diese Frage wirklich alt ist. Aber während dies funktioniert, bringt es Positionen durcheinander, wenn die x- und y-Skalen nicht gleich sind: /
Erik Skoglund
2

Sie sollten in der Lage sein, sie in einem SpriteBatch zu gruppieren und sie an Ort und Stelle zu verschieben und mithilfe einer Matrix zu drehen.

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

Codes nicht getestet und meine Matrixmultiplikation ist sehr rostig, aber die Technik gilt.

ClassicThunder
quelle
Was tun, wenn ich Matrix in spriteBatch tatsächlich für die Kamera verwende? spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cam.get_transformation(graphics.GraphicsDevice));
Martin.
Ich denke, Sie sollten in der Lage sein, Begin with matrix * cam.get_transformation (graphics.GraphicsDevice) zu verwenden.
ClassicThunder
PERFEKT. Ich habe nicht so schnell eine so gute Antwort bekommen.
Martin.
Einfache Lösung, aber sie macht den ganzen Zweck der Sprite-Stapelverarbeitung zunichte, nämlich so viele Sprites wie möglich in einem Aufruf zu zeichnen. Ich könnte dies in einer einfachen Demo verwenden, aber definitiv nicht in einem Sprite-intensiven Spiel.
David Gouveia
@Martin In meiner Antwort habe ich ein Beispiel, wie man eine vollständige Weltmatrix für das Sprite erstellt. Grundsätzlich sollte die Reihenfolge lauten -Origin * Scale * Rotation * Translation(wobei alle diese Werte vom Sprite stammen).
David Gouveia
0

Ich würde das etwas anders umsetzen.

Geben Sie Ihrer Sprite-Klasse eine Liste für ihre Kinder. Wenn Sie dann die Position und die Transformationen des Sprites aktualisieren, wenden Sie dieselben Übersetzungen auch auf die untergeordneten Elemente mit einer for-each-Schleife an. Die Koordinaten der Kinder-Sprites sollten eher im Modellraum als im Weltraum definiert sein.

Ich verwende gerne zwei Rotationen - eine um den Ursprung des Kindes (um das Sprite an Ort und Stelle zu drehen) und eine um den Ursprung des Elternteils (um das Kind dazu zu bringen, den Elternteil im Wesentlichen zu umkreisen).

Um zu zeichnen, starten Sie einfach Ihren Spritebatch, rufen Sie Ihren player.draw () auf, der zeichnen würde, durchlaufen Sie dann alle untergeordneten Elemente und zeichnen Sie sie ebenfalls.

Bei dieser Art von Hierarchie werden beim Verschieben des übergeordneten Elements alle untergeordneten Elemente mitgeschoben, sodass Sie die untergeordneten Elemente auch unabhängig voneinander verschieben können.

Schaum
quelle