Rotating a sprite around an axis other than Z

I’m developing a small gallery-shooter type game. I’d like to have an effect where when the player strikes a target, the target falls over backwards (rotates around the X axis). I can get the effect I want using a textured quad, but is there any way to do this using SpriteBatch? I’m much more comfortable there than in 3D land lol.

I tried this test code:

if(Keyboard.GetState().IsKeyDown(Keys.F1))
transform *= Matrix.CreateFromYawPitchRoll(0, .001f, 0);
if (Keyboard.GetState().IsKeyDown(Keys.F2))
transform *= Matrix.CreateFromYawPitchRoll(0, -.001f, 0);

and then in Draw:

spriteBatch.Begin(0, null, null, null, null, null, transform);
spriteBatch.Draw(ducktex, new Vector2(50, 50), Color.White);
spriteBatch.End();

Instead of falling over, the sprite just disappears slowly as I press the corresponding key. I’ve tried googling about and searching here, and found a couple of old threads, but no definitive answers. Is there any way to achieve the effect I want using SpriteBatch?

  1. did you start with a transform = Matrix.Identity?
  2. do you see any difference if you draw at layer 0.5f ?
    SpriteBatch draws everything in the Z space from 0 to 1. So maybe the sprite falls out of the view when rolled.
    Try to squeeze it after the roll with something like transform * CreateScale(0,0,1f/16f) and also draw it at the ‘Z-center’ (depth =0.5f)

A full spritebatch overload takes a origin as well.

or

Just shrink the draw rectangles height as you re-position it downwards.

Whats wrong with using a quad though you can make the bottom of the quad have 0 y coordinates then use CreateRotationX.

An alternative is to give SpriteBatch a View and a Projection through a BasicEffect and you can then draw Sprites in 3D space.
look here: https://blogs.msdn.microsoft.com/shawnhar/2011/01/12/spritebatch-billboards-in-a-3d-world/

You can just scale the height of your component by cos(rotationX). That will give the same result. If you want lighting to go with it you can’t go with SpriteBatch since it doesn’t support actual 3D.
Edit: unless you use a custom effect of course! :slight_smile:

1 Like

Wow, thank you all for the quick replies.

nkast: Yes I did initialize my transform to Matrix.Identity. :slight_smile: The layer depth does have a little effect, but not any 3D - it just determines how fast the sprite disappears from which rotation.

willmotil: I figured I’d have to do something like shrink the bounding rect as you suggest - or actually make a spritesheet animation in something like blender. I absolutely suck at content creation tho, so I was looking for a progmatic solution. :slight_smile: I could use a textured quad, but all the matrices scare me lol.

Thank you Jjagg for the exact formula - that probably saved an hour of hunt-and-peck fiddling.

Your edit made me start thinking, which led to me crash-coursing youtube vids, trying to remember trig classes from 20 years ago. I assume if I did want to light the sprite, I’d just have to calculate the normal of the plane created by the rotation, pass that to whatever shader I implemented, and then light the sprite?

Monogames matrix store the forward normal in the 3rd row of the matrix.

i.e.
m11 m12 m13 = the x axis normal
m21 m22 m23 = the y axis normal
m31 m32 m33 = the z axis normal // <

@willmotil we’re not working with a matrix.

@archnem What kind of lighting do you want? If you just have a directional light source you can use the color parameter in SpriteBatch.Draw. For more advanced lighting you’ll need a custom shader. That will complicate things a little.

To get the normal, use n = (0, sin(rotateX), -cos(rotateX)). Make sure you rotate in the positive direction, so let rotateX go from 0 to PI/2 (direction does not matter if you’re just scaling, but it matters if you want the normal). Then for your color if you want to use a directional light source, you can use the negated dot product of the light direction with the normal multiplied with your light color (which all your sprite’s colors should be multiplied with if you’re gonna do this). In the easy case where your light direction is along the positive z-axis (so pointing directly at your sprites. The dot product will just be cos(rotateX) again.

Ask away if any of that was not clear :slight_smile:

Well if he is passing a matrix to spritebatch as he stated previously.

if (Keyboard.GetState().IsKeyDown(Keys.F2))
transform *= Matrix.CreateFromYawPitchRoll(0, -.001f, 0);

were transform is a matrix.

spriteBatch.Begin(0, null, null, null, null, null, transform);
// were this is his sprite
spriteBatch.Draw(ducktex, new Vector2(50, 50), Color.White);
spriteBatch.End();

Then transforms.Forward or -Forward is actually the normal for his 2d spritebatch quad i.e.

n = (0, sin(rotateX), -cos(rotateX)) == transform.Forward == m31,m32,m33

The quick dirty way in the spritebatch call with a couple extra steps and no effect, is…
// Realign the drawing for this specifically
Set the origin to pass to spritebatch.Draw by new Vector2(0,ducktex.Height)
Add ducktex.Height to the draw positions Y;
Rotate his transform as he showed already in his first post.
// Pass a Color to spritebatch to simulate lighting of the sprite.
// Pre-calculate light intensity on the surface.
var L = Math.Abs( Vector3.Dot(transform.Forward, Vector3.Forward) );
Color lightBlendingColor = new Color(L,L,L,255);
// were Vector3.Forward is the light direction above.
// Create a additional perspective projection matrix p. instead of passing transform to the the begin call pass transform * p

Wrap the whole thing up in a method.

The problem with this way and not using a quad directly without a spritebatch call is that each draw of this type requires a different transform be passed to begin which is terribly inefficient.