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
Sprite
stocke un List<Sprite> Children
et fournit une méthode pour ajouter de nouveaux enfants.
- Chacun
Sprite
sait calculer un Matrix LocalTransform
qui est défini par rapport au parent.
- Faire appel
Draw
à un l' Sprite
appelle é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:
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 Sprite
classe 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 LocalTransform
matrice 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 SpriteBatch
classe est que sa Draw
mé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 Draw
mé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.
- Calculez la transformation globale en multipliant la transformation locale par la transformation globale du parent.
- Adaptez la transformation globale
SpriteBatch
et rendez le sprite actuel.
- 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); }