Edit: Just wanted to make the question I have more clear. I pretty much am having trouble seeing how something like Matrix.CreateTransformationZ works in the context of not only matrix multiplication but more importantly what this does to the screen space/world space so I can get a clearer picture. So maybe someone could alter the code or give me a short snippet to test out where I can use this to either rotate around an axis and/or orbit around the axis. I have also changed the example.
So I’m still kind of having trouble visualizing how matrices work with the xna screen space.
I’ll give you an example:
public class Game1 : Microsoft.Xna.Framework.Game
{
Texture2D shipTexture, rockTexture;
Vector2 shipPosition = new Vector2(100.0f, 100.0f);
Vector2 rockPosition = new Vector2(100.0f, 29.0f);
int count;
float shipRotation, rockRotation;
float rockSpeed, rockRotationSpeed;
bool move = true;
const int rock = 0;
const int ship = 1;
Color[] rockColor;
Color[] shipColor;
float testRot = 0.0f;
Vector2 shipCenter; int shipWidth, shipHeight;
Vector2 rockCenter; int rockWidth, rockHeight;
GraphicsDeviceManager graphics;
SpriteBatch spriteBatch;
#region maincontent
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}
/// <summary>
/// Allows the game to perform any initialization it needs to before starting to run.
/// This is where it can query for any required services and load any non-graphic
/// related content. Calling base.Initialize will enumerate through any components
/// and initialize them as well.
/// </summary>
protected override void Initialize()
{
// TODO: Add your initialization logic here
rockSpeed = 0.16f;
rockRotationSpeed = 0.3f;
base.Initialize();
}
/// <summary>
/// LoadContent will be called once per game and is the place to load
/// all of your content.
/// </summary>
protected override void LoadContent()
{
shipTexture = Content.Load<Texture2D>("Images\\ship");
rockTexture = Content.Load<Texture2D>("Images\\asteroid");
rockWidth = rockTexture.Width; rockHeight = rockTexture.Height;
shipWidth = shipTexture.Width; shipHeight = shipTexture.Height;
rockCenter = new Vector2(rockWidth / 2, rockHeight / 2);
shipCenter = new Vector2(shipWidth / 2, shipHeight / 2);
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
// TODO: use this.Content to load your game content here
rockColor = new Color[rockTexture.Width * rockTexture.Height];
rockTexture.GetData(rockColor);
shipColor = new Color[shipTexture.Width * shipTexture.Height];
shipTexture.GetData(shipColor);
}
/// <summary>
/// UnloadContent will be called once per game and is the place to unload
/// all content.
/// </summary>
protected override void UnloadContent()
{
// TODO: Unload any non ContentManager content here
}
/// <summary>
/// This is called when the game should draw itself.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
spriteBatch.Draw(rockTexture, rockPosition,
null, Color.White, testRot, rockCenter, 1.0f,
SpriteEffects.None, 0.0f);
spriteBatch.Draw(shipTexture, shipPosition,
null, Color.White, shipRotation, shipCenter,
1.0f, SpriteEffects.None, 0.0f);
spriteBatch.End();
// TODO: Add your drawing code here
base.Draw(gameTime);
}
#endregion
/// <summary>
/// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio.
/// </summary>
/// <param name="gameTime">Provides a snapshot of timing values.</param>
protected override void Update(GameTime gameTime)
{
testRot += 0.034906585f;
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
UpdateAsteroid(gameTime);
RotateShip(gameTime);
MoveShip(gameTime);
// TODO: Add your update logic here
CheckCollisions();
base.Update(gameTime);
}
#region Collisions
public Color PixelColor(int objectNum, int pixelNum)
{
switch (objectNum)
{
case rock:
return rockColor[pixelNum];
case ship:
return shipColor[pixelNum];
}
return Color.White;
}
public bool PixelCollision(Matrix transformA, int pixelWidthA, int pixelHeightA, int A,
Matrix transformB, int pixelWidthB, int pixelHeightB, int B)
{
Matrix temp = Matrix.Invert(transformB);
Matrix AtoB = transformA * Matrix.Invert(transformB);
Vector2 columnStep, rowStep, rowStartPosition;
columnStep = Vector2.TransformNormal(Vector2.UnitX, AtoB);
rowStep = Vector2.TransformNormal(Vector2.UnitY, AtoB);
rowStartPosition = Vector2.Transform(Vector2.Zero, AtoB);
for (int rowA = 0; rowA < pixelHeightA; rowA++)
{
// begin at the left
Vector2 pixelPositionA = rowStartPosition;
// for each column in the row (move left to right)
for (int colA = 0; colA < pixelWidthA; colA++)
{
// get the pixel position
int X = (int)Math.Round(pixelPositionA.X);
int Y = (int)Math.Round(pixelPositionA.Y);
// if the pixel is within the bounds of B
if (X >= 0 && X < pixelWidthB && Y >= 0 && Y < pixelHeightB)
{
// get colors of overlapping pixels
Color colorA = PixelColor(A, colA + rowA * pixelWidthA);
Color colorB = PixelColor(B, X + Y * pixelWidthB);
// if both pixels are not completely transparent,
if (colorA.A != 0 && colorB.A != 0)
return true; // collision
}
// move to the next pixel in the row of A
pixelPositionA += columnStep;
}
// move to the next row of A
rowStartPosition += rowStep;
}
return false; // no collision
}
public Matrix Transform(Vector2 center, float rotation, Vector2 position)
{
return Matrix.CreateTranslation(new Vector3(-center, 0.0f)) *
Matrix.CreateRotationZ(rotation) *
Matrix.CreateTranslation(new Vector3(position, 0.0f));
}
public static Rectangle TransformRectangle(Matrix transform, int width, int height)
{
Vector2 leftTop = new Vector2(0.0f, 0.0f);
Vector2 rightTop = new Vector2(width, 0.0f);
Vector2 leftBottom = new Vector2(0.0f, height);
Vector2 rightBottom = new Vector2(width, height);
Vector2.Transform(ref leftTop, ref transform, out leftTop);
Vector2.Transform(ref rightTop, ref transform, out rightTop);
Vector2.Transform(ref leftBottom, ref transform, out leftBottom);
Vector2.Transform(ref rightBottom, ref transform, out rightBottom);
Vector2 min = Vector2.Min(Vector2.Min(leftTop, rightTop), Vector2.Min(leftBottom, rightBottom));
Vector2 max = Vector2.Max(Vector2.Max(leftTop, rightTop), Vector2.Max(leftBottom, rightBottom));
return new Rectangle((int)min.X, (int)min.Y,
(int)(max.X - min.X), (int)(max.Y - min.Y));
}
private void CheckCollisions()
{
Matrix shipTransform, rockTransform;
Rectangle shipRectangle, rockRectangle;
rockTransform = Transform(rockCenter, rockRotation, rockPosition);
rockRectangle = TransformRectangle(rockTransform, rockWidth, rockHeight);
shipTransform = Transform(shipCenter, shipRotation, shipPosition);
shipRectangle = TransformRectangle(shipTransform, shipWidth, shipHeight);
if (rockRectangle.Intersects(shipRectangle)) // rough collision check
if (PixelCollision( // exact collision check
rockTransform, rockWidth, rockHeight, rock,
shipTransform, shipWidth, shipHeight, ship))
move = false;
}
#endregion
#region Moves_and_Rotations
private void UpdateAsteroid(GameTime gameTime)
{
float timeLapse = (float)gameTime.ElapsedGameTime.Milliseconds;
if (move == true)
{
if ((rockWidth + rockPosition.X >= Window.ClientBounds.Width))
{
rockSpeed *= -1.0f;
rockPosition.X += rockSpeed * timeLapse;
}
else if ((rockPosition.X <= 0))
{
rockSpeed *= -1.0f;
rockPosition.X += rockSpeed * timeLapse;
}
else
rockPosition.X += rockSpeed * timeLapse;
const float SCALE = 50.0f;
rockRotation += rockRotationSpeed * timeLapse / SCALE;
rockRotation = rockRotation % (MathHelper.Pi * 2.0f);
}
}
private float RotateShip(GameTime gameTime)
{
float rotation = 0.0f;
float speed = gameTime.ElapsedGameTime.Milliseconds / 300.0f;
if (!move)
return rotation;
KeyboardState keyboard = Keyboard.GetState();
if (keyboard.IsKeyDown(Keys.Right))
rotation = speed;
else if (keyboard.IsKeyDown(Keys.Left))
rotation = -speed;
shipRotation += rotation;
shipRotation = shipRotation % (MathHelper.Pi * 2.0f);
return shipRotation;
}
private void MoveShip(GameTime gameTime)
{
const float SCALE = 20.0f;
float speed = gameTime.ElapsedGameTime.Milliseconds / 100.0f;
KeyboardState keyboard = Keyboard.GetState();
if (keyboard.IsKeyDown(Keys.Up))
{
shipPosition.X += (float)Math.Sin(shipRotation) * speed * SCALE;
shipPosition.Y -= (float)Math.Cos(shipRotation) * speed * SCALE;
}
else if (keyboard.IsKeyDown(Keys.Down))
{
shipPosition.X -= (float)Math.Sin(shipRotation) * speed * SCALE;
shipPosition.Y += (float)Math.Cos(shipRotation) * speed * SCALE;
}
}
#endregion
}
I took this from XNA Game Creators, it’s simply a method of doing Pixel Detection.
-
In the Transform method above, matrix multiplication occurs against I guess a rectangle. What exactly is happening in terms of the screen space/world space?
-
Why is author multiplying the matrix by the inverse of another matrix? (He mentions that somehow this makes it relative to the other asset)
Screen space is presumably the same thing as Client Space. Client space goes from (0,0) in the top left corner to (width, height) in the bottom right. “Up” is Y-.
Projection space goes from (-1,-1) in the bottom-left corner to (1,1) in the top-right. This is what the GPU uses for its final rendering. SpriteBatch handles this for you (by contrast: BasicEffect requires you to provide a projection matrix).
World space is whatever you want it to be. This is the coordinate system that your gameplay takes place in. In your example it seems that this is the same as Client space.
Traditionally, when doing this sort of thing, you have an object defined in its own space. In your example the rock and ship rectangles are hard coded into the function
TransformRectangleas the initial values of the variablestopLeftthroughbottomRight.You then have a World matrix for each object. This moves that object from its own space into its position in World space. In your example this is
shipTransformandrockTransform. A World transform is also done insideSpriteBatch.Draw, based on the arguments you pass in (using the texture itself as the initial object).Then you have a View matrix – which you can think of as your camera. You example doesn’t have one of these. But if you wanted, for example, to pan the view around to follow the player, you would use a translation matrix here, created from the player’s position (and pass it to
SpriteBatch.Begin).And finally you have a Projection matrix that converts your World space into Projection space so that the GPU can render your scene.
Now a possible problem here is that SpriteBatch internally defines a projection matrix that converts a Client space to Projection space (so it basically “assumes” World space is Client space). Not a problem in your example because the two spaces are the same.
If your World space is not the same thing as Client space, and you want to use SpriteBatch, you must create an additional matrix to convert from World space to Client space and insert it between the View and Project matrices (ie: multiply it with View and pass it into
SpriteBatch.Begin).If your World space defines which way is “up” (or “right”, for that matter) differently to SpriteBatch, then you must keep in mind that the original object used by
SpriteBatch.Drawdefines “up” to be Y-.