Existe-t-il un moyen simple de regrouper deux ou plusieurs sprites, de sorte que tous dépendront les uns des autres?

8

Je pense que cette question est très similaire à celle- ci, mais je ne sais pas si les réponses sont universelles.

Mon objectif est donc:

  • Placez deux sprites en position fixe, par exemple le joueur et ses yeux
  • Assurez-vous que chaque fois que le joueur tourne, le sprite des yeux tourne aussi et arrive à la même position relative du corps (donc les yeux ne sont pas sur le dos du joueur). Ils travailleront donc en groupe. - Cette étape doit être automatisée, c'est mon objectif!

Ainsi, par exemple, je veux maintenant placer une arme à feu dans les mains de l'utilisateur. Alors maintenant, je dis, ce joueur est en position Vector2(0, 0)et le pistolet est en position Vector2(26, 16). Maintenant, je veux les regrouper, donc chaque fois que le joueur tourne, le pistolet tourne aussi.

entrez la description de l'image ici

Actuellement, dans cet exemple, ça va, mais au cas où j'aurais besoin de déplacer mon arme sur l'axe y (uniquement), je suis perdu

Martin.
la source

Réponses:

10

Concept

Je résoudrais ce problème avec une hiérarchie de sprites en utilisant une varitation du modèle de conception composite . Cela signifie que chaque image-objet stocke une liste des images-objets enfants qui lui sont attachées afin que toutes les modifications apportées au parent y soient automatiquement reflétées (y compris la traduction, la rotation et la mise à l'échelle).

Dans mon moteur, je l'ai implémenté comme ceci:

  • Chacun Spritestocke un List<Sprite> Childrenet fournit une méthode pour ajouter de nouveaux enfants.
  • Chacun Spritesait calculer un Matrix LocalTransformqui est défini par rapport au parent.
  • Faire appel Drawà un l' Spriteappelle également à tous ses enfants.
  • Les enfants multiplient leur transformation locale par la transformation globale de leurs parents . Le résultat est ce que vous utilisez lors du rendu.

Avec cela, vous pourrez faire ce que vous avez demandé sans aucune autre modification à votre code. Voici un exemple:

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

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

la mise en oeuvre

Pour commencer, je vais simplement déposer un exemple de projet avec cette technique mise en œuvre, au cas où vous préféreriez simplement regarder le code et le comprendre:

entrez la description de l'image ici

Remarque: J'ai opté pour la clarté au lieu des performances ici. Dans une implémentation sérieuse, de nombreuses optimisations peuvent être effectuées, dont la plupart impliquent la mise en cache des transformations et leur recalcul uniquement si nécessaire (par exemple, mettre en cache les transformations locales et globales à chaque sprite, et les recalculer uniquement lorsque le sprite ou l'un de ses ancêtres change). De plus, les versions des opérations matricielles et vectorielles de XNA qui prennent des valeurs par référence sont un peu plus rapides que celles que j'ai utilisées ici.

Mais je vais décrire le processus plus en détail ci-dessous, alors lisez la suite pour plus d'informations.


Étape 1 - Faites quelques ajustements à la classe Sprite

En supposant que vous avez déjà une Spriteclasse en place (et vous devriez), vous devrez y apporter quelques modifications. En particulier, vous devrez ajouter la liste des sprites enfants, la matrice de transformation locale et un moyen de propager les transformations dans la hiérarchie des sprites. J'ai trouvé le moyen le plus simple de le faire simplement pour les passer en paramètre lors du dessin. Exemple:

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

Étape 2 - Calcul de la matrice LocalTransform

La LocalTransformmatrice est juste une matrice mondiale régulière construite à partir des valeurs de position, de rotation et d'échelle du sprite. Pour l'origine, j'ai supposé le centre du 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);
   }
}

Étape 3 - Savoir comment passer une matrice à SpriteBatch

Un problème avec la SpriteBatchclasse est que sa Drawméthode ne sait pas comment prendre directement une matrice mondiale. Voici une méthode d'aide pour résoudre ce problème:

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

Étape 4 - Rendre le Sprite

Remarque: La Drawméthode prend la transformation globale du parent comme paramètre. Il existe d'autres façons de propager ces informations, mais j'ai trouvé que celle-ci était facile à utiliser.

  1. Calculez la transformation globale en multipliant la transformation locale par la transformation globale du parent.
  2. Adaptez la transformation globale SpriteBatchet rendez le sprite actuel.
  3. Rendre tous les enfants en leur passant la transformation globale actuelle en tant que paramètre.

En traduisant cela en code, vous obtiendrez quelque chose comme:

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

Lors du dessin du sprite racine, il n'y a pas de transformation parent, vous devez donc le passer Matrix.Identity. Vous pouvez créer une surcharge pour vous aider dans ce cas:

public void Draw(SpriteBatch spriteBatch) { Draw(spriteBatch, Matrix.Identity); }
David Gouveia
la source
J'ai un problème que je traite actuellement. Dans ma configuration actuelle, je crée une transformation Matrix lors de l'appel de spriteBatch, car j'utilise la caméra. Donc, dans le cas où ma caméra est sur 500,50 et le sprite de mon joueur sur 500,50, le joueur devrait être au centre. Cependant, dans ce cas, il ne tire nulle part
Martin.
@Martin Que vous utilisiez une matrice de caméra ou non, cela devrait fonctionner de la même manière. Dans cet exemple, je n'ai pas utilisé d'appareil photo, mais sur mon moteur j'en utilise un et cela fonctionne normalement. Passez-vous la matrice de la caméra (et la matrice de la caméra uniquement) à SpriteBatch.Begin? Et si vous voulez que les choses soient centrées, tenez-vous compte de la moitié de la taille de l'écran lors de la création de la matrice de la caméra?
David Gouveia
J'utilise la même classe qu'ici pour la caméra, j'obtiens sa métrique de caméra, oui. Cela fonctionne maintenant, je vais essayer de le modifier maintenant et je vous le ferai savoir
Martin.
1
PARFAIT! PARFAIT incroyable! Et comme dans la plupart des cas, j'avais des problèmes, c'était de ma faute. Regarde ! i.imgur.com/2CDRP.png
Martin.
Je sais que je suis en retard à la fête ici car cette question est vraiment ancienne. Mais pendant que cela fonctionne, cela gâche les positions lorsque les échelles x et y ne sont pas les mêmes: /
Erik Skoglund
2

Vous devriez pouvoir les regrouper dans un SpriteBatch et les déplacer en place et le faire pivoter à l'aide d'une 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();

Codes non testés et ma multiplication matricielle est très rouillée mais la technique reste vraie.

ClassicThunder
la source
Que faire si j'UTILISE réellement Matrix dans spriteBatch pour appareil photo? spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cam.get_transformation(graphics.GraphicsDevice));
Martin.
Je pense que vous devriez pouvoir utiliser Begin avec matrix * cam.get_transformation (graphics.GraphicsDevice).
ClassicThunder
PARFAIT. Je n'ai pas eu une si bonne réponse si vite.
Martin.
Solution simple, mais elle défait en quelque sorte tout le but du traitement par lots de sprites qui est de dessiner autant de sprites que possible en un seul appel. Je pourrais l'utiliser dans une démo simple, mais certainement pas dans un jeu intensif de sprites.
David Gouveia
@Martin Dans ma réponse, j'ai un exemple de la façon de créer une matrice mondiale complète pour le sprite. Fondamentalement, l'ordre devrait être -Origin * Scale * Rotation * Translation(où toutes ces valeurs proviennent du sprite).
David Gouveia
0

Je mettrais cela en œuvre un peu différemment.

Donnez à votre classe de sprites une liste pour ses enfants. Ensuite, lorsque vous mettez à jour la position et les transformations du sprite, appliquez également les mêmes traductions aux enfants avec une boucle pour chaque. Les sprites enfants doivent avoir leurs coordonnées définies dans l'espace objet plutôt que dans l'espace monde.

J'aime utiliser deux rotations - une autour de l'origine de l'enfant (pour faire tourner le sprite en place) et l'autre autour de l'origine du parent (pour que l'enfant tourne essentiellement autour du parent).

Pour dessiner, commencez simplement votre spritebatch, appelez votre player.draw () qui dessinerait puis bouclerait tous ses enfants et les dessinerait également.

Avec ce type de hiérarchie, lorsque vous déplacez le parent, tous ses enfants se déplacent avec lui, ce qui vous permet également de déplacer les enfants indépendamment.

Suds
la source