4
\$\begingroup\$

I'm working on a simple Sinistar-style shooter in XNA, and I'm constantly running into trouble with transformations. I feel like I'll get it eventually, but this problem is really bending my mind.

My little ship has two states: idle and engines-firing. Idle is a single frame, and engines is a few frames that animate. Got all that. Part 1 of the issue: Instead of using rotated images, I just use XNA's rotation to orient the ship. Seems to work ok, but I think my origin point is off because it seems to rotate at a point just below the vertical axis of the ship (default orientation is ship's nose pointing up), instead of in the dead center of the ship. Likewise, when I switch to engines, the sprites are different heights (due to the flames) so the origin of rotation doesn't seem like it would be constant. Do I need to specify all my frames the same size so as to preserve the origin point?

Second bit: The gun turret sits on top of the ship, as a separate sprite. With a bit of trial-and-error and calculation, I can get the gun to align on top where I want it. But then when the ship rotates, it rotates out from under the gun! Would fixing the first problem adequately cover this one, or is there some trick to getting the gun's position to translate properly?

So:

  1. Do I need to have all my sprite's frame-rectangles be the same dimensions?
  2. Is there a standard technique in XNA for stacking multiple sprites so they act as one unit, including for rotations?
\$\endgroup\$
2
  • \$\begingroup\$ I would say that all sprite frames (for a given entity) should be the same size, always. \$\endgroup\$
    – Nate
    Oct 12, 2010 at 16:08
  • \$\begingroup\$ And in specific cases, use second sprite (as you have with the gun turret) when it would be wasteful to make them all the same. \$\endgroup\$
    – Nate
    Oct 12, 2010 at 16:09

2 Answers 2

3
\$\begingroup\$

What you want to do is get the center coordinate for you ship sprite and turn it on that point. I don't have the code with my here at work, but when I get home I'll share a little snippet that I made that does just want you want.

For this to work however your sprite has to be well centered in the first place. Your image needs to be very aligned and centered using Photoshop or whatever program you use. If your image is not aligned correctly then using the established techniques to rotate won't work because your picture center won't be the same as your pictureContent center.

Edit

Ok, I got back from work and here's a little example. I'm going to rotate a cannon barrel on a given point using the following code:

//Angle is just a float variable that saves the angle the cannon is facing.
players[currentPlayer].Angle -= 0.1f;

The code above is only run when the left key is pressed. I'm moving the angle to the left or counter-clockwise.

Now in the Draw method which in XNA is called 60 times every second I redraw the cannon. Using the .Angle property I modified earlier I know exactly where to draw it and at what angle.

spriteBatch.Draw(CannonBody, player.Position, null, player.Color, 0, new Vector2(0, CannonBody.Height), playerScaling, SpriteEffects.None, 0);
spriteBatch.Draw(CannonBarrel, new Vector2(xPosition + 20, yPosition - 10), null, player.Color, player.Angle, cannonOrigin, playerScaling, SpriteEffects.None, 1);

Basically, you want to draw the cannon using modified positions every given second; then each second modify the position so you get that fluid movement effect.

\$\endgroup\$
6
\$\begingroup\$

Old topic but I'd like to talk a bit about the subject. About grouping sprites together there are two nice ways to handle that, depending on whether you want all the sprites to be siblings (i.e. grouped but on the same level) or if you want to have an hierarchy (i.e. sprites inside other sprites, where transforming the parent also transforms the children). Here's the general idea for each of them.

Scenario 1) All the sprites in the group are siblings:

Let's say you have a Sprite class and a Group class with this interface (pseudocode)...

Sprite: Position, Rotation, Scale, Origin, Draw()
Group: Sprites, Move(displacement), Rotate(angle, origin), Scale(amount, origin)

Each Sprite object will hold its Position/Rotation/Scale/Origin in world space and when calling Draw those values are fed to a SpriteBatch instance and drawn.

As for the Group class, it keeps a list of all the Sprites in the group and provides methods to manipulate the group as a whole. Those methods take an origin parameter which is the "center" of the group (e.g. around which point do you want it to rotate). The implementation goes something like:

Move(displacement): For every sprite, Add displacement to Position
Rotate(angle, origin): For every Sprite, Add angle to Rotation & apply Matrix to Position
Scale(amount, origin): For every sprite, Add amount to Scale & apply Matrix to Position

Where that Matrix is calculated with the origin of the transformation taken into account, i.e.:

Matrix = CreateTranslation(-origin) *
         CreateRotation(angle) OR CreateScale(amount) * 
         CreateTranslation(origin)

What this does is basically move the Sprite temporarily into a new space where the origin is at (0,0), then it applies the transformation, and reverts the original displacement back to normal. So as long as you manipulate the Sprites through the Group interface, they'll be bound together.

Note: To apply the matrix to Position you use the Vector2.Transform() method of XNA.

Scenario 2) Sprite hierarchy

On the other hand if you can have sprites inside each other, the strategy is different. Basically you need to create a hierarchy of Parent and Children sprites, and each sprite will keep a transformation matrix that is not defined in World Space but rather in relation to its parent.

So you can create something like:

class Sprite {
    List<Sprite> children;
    Matrix localTransform;
    void Draw(Matrix parentTransform);
}

And when drawing the sprite you need to get the global transform by multiplying the current sprite's transform by its parents' (which I'm passing as a parameter), then decompose that transform into values that SpriteBatch can understand, and finally draw the children. Something like (not 100% accurate, just to give an idea):

void Draw(Matrix parentTransform) 
{
    // Get actual matrix
    Matrix globalTransform = localTransform * parentTransform;

    // Translate into values
    Vector2 position; float rotation; float scale;
    DecomposeMatrix(globalTransform, out position, out rotation, out scale);

    // Draw sprite
    SpriteBatch.Draw(texture, position, rotation, scale);

    // Draw children
    foreach(Sprite child in children) children.draw(globalTransform);
}

And here's my implementation of DecomposeMatrix (the one I use on my project):

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

Using this strategy you could have the turret be a child of the tank, and whenever you rotated and moved the tank, the turret would follow automatically.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .