Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I think that this question is very similar to this one, but I'm not sure if the answers are universal.

So, my goal is:

  • Place two sprites in fixed position, for example player and his eyes
  • Make sure that whenever the player rotates, eyes sprite rotates too and gets to the same relative position from the body (so eyes aren't on player's back). So they'll be working as a group. - This step should be automated, that's my goal!

So, for example, I now want to place a gun in user's hands. So now I say, that player is in position Vector2(0, 0) and gun is in position Vector2(26, 16). Now I want to group them up, so whenever the player rotates, gun rotates too.

enter image description here

Currently in this example it's pretty okay but in case that I'd need to move my gun on axis y (only), I'm lost

share|improve this question

3 Answers 3

up vote 8 down vote accepted

Concept

I would solve this problem with a sprite hierarchy using a varitation of the composite design pattern. This means having each sprite store a list of the children sprites that are attached to it so that any modifications to the parent are automatically reflected in them (including translation, rotation and scaling).

In my engine I have implemented it like this:

  • Each Sprite stores a List<Sprite> Children and provides a method to add new children.
  • Each Sprite knows how to calculate a Matrix LocalTransform which is defined relative to the parent.
  • Calling Draw on a Sprite also calls it on all of its children.
  • Children multiply their local transform by their parent's global transform. The result is what you use when rendering.

With this you'll be able to do what you asked for without any other modifications to your code. Here's an example:

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

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

Implementation

For starters I'll just drop in a sample project with this technique implemented, in case you prefer to just look at the code and figure it out:

enter image description here

Note: I've opted for clarity instead of performance here. In a serious implementation, there are a lot of optimizations that could be done, most of which involve caching transforms and only recalculating them as needed (for instance, cache both local and global transforms at each sprite, and recalculate them only when the sprite or one of its ancestors changes). Also, the versions of XNA's matrix and vector operations that take values by reference are a bit faster than the ones I used here.

But I'll describe the process in more detail below, so read on for more information.


Step 1 - Make a few adjustments to the Sprite class

Assuming you already have a Sprite class in place (and you should) then you'll need to make a few modifications to it. In particular you'll need to add the list of children sprites, the local transform matrix, and a way to propagate transforms down the sprite hierarchy. I found it easiest way to do that just to pass them as a parameter when drawing. Example:

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

Step 2 - Calculating the LocalTransform matrix

The LocalTransform matrix is just a regular world matrix built from the sprite's position, rotation and scale values. For the origin I assumed the center of the 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);
   }
}

Step 3 - Knowing how to pass a Matrix to SpriteBatch

One problem with the SpriteBatch class is that its Draw method doesn't know how to take a world matrix directly. Here's a an helper method to bridge this problem:

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

Step 4 - Rendering the Sprite

Note: The Draw method takes the parent's global transform as a parameter. There are other ways to propagate this information, but I found this one to be easy to use.

  1. Calculate global transform by multiplying the local transform by the parent's global transform.
  2. Adapt the global transform to SpriteBatch and render the current sprite.
  3. Render all children passing them the current global transform as parameter.

Translating that into code you'll get something like:

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

When drawing the root sprite there's no parent transform, so you pass it Matrix.Identity. You can create an overload to help with this case:

public void Draw(SpriteBatch spriteBatch) { Draw(spriteBatch, Matrix.Identity); }
share|improve this answer
    
I have a problem I'm currently dealing with. In my current setup, I create Matrix transformation when calling spriteBatch, because I'm using camera. So in case that my camera is on 500,50 and my player's sprite on 500,50, player should be in the center. However, in this case, it's not drawing anywhere –  Martin. Jan 28 '12 at 12:46
    
@Martin Whether you use a camera matrix or not, it should work just the same. In this example I didn't use a camera, but on my engine I use one and it works normally. Are you passing the camera matrix (and the camera matrix only) to SpriteBatch.Begin? And if you want things to be centered, are you taking into account half of the screen's size when creating the camera matrix? –  David Gouveia Jan 28 '12 at 12:56
    
I'm using the same class as here for camera, getting it's camera metrix, yes. It now works, I'll try to tweak it now and let you know –  Martin. Jan 28 '12 at 13:03
1  
PERFECT! Amazing PERFECT! And as in most cases I had problem in, it was my fault. Take a look ! i.imgur.com/2CDRP.png –  Martin. Jan 28 '12 at 13:15

You should be able to group them into a SpriteBatch and them move it into place and rotate it using a matrix.

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 not tested and my matrix multiplication is very rust but the technique holds true.

share|improve this answer
    
What to do in case I actually USE Matrix in spriteBatch for camera? spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cam.get_transformation(graphics.GraphicsDevice)); –  Martin. Jan 28 '12 at 1:50
    
I think you should be able to use Begin with matrix * cam.get_transformation(graphics.GraphicsDevice). –  ClassicThunder Jan 28 '12 at 1:54
    
PERFECT. I didn't such a great answer so fast. –  Martin. Jan 28 '12 at 2:10
    
Simple solution, but it kinda defeats the whole purpose of sprite batching which is to draw as many sprites as possible in one call. I might use this in a simple demo, but definitively not in a sprite intensive game. –  David Gouveia Jan 28 '12 at 3:46
    
@Martin In my answer I have an example of how to create a complete world matrix for the sprite. Basically the order should be -Origin * Scale * Rotation * Translation (where all of these values come from the sprite). –  David Gouveia Jan 28 '12 at 3:57

I would implement this a little differently.

Give your sprite class a list for its children. Then when you update the position and transformations of the sprite, also apply the same translations to the children with a for-each loop. The child sprites should have their coordinates defined in model space rather than world space.

I like to use two rotations - one around the origin of the child (to spin the sprite in place) and one around the origin of the parent (to make the child essentially orbit the parent).

To draw, just begin your spritebatch, call your player.draw() which would draw then loop through all its children and draw them as well.

With this sort of hierarchy, when you move the parent all its children move with it - also allowing you to move the children independently.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.