2010-08-21 78 views
2

编辑:只是想让问题更清楚。我几乎无法看到Matrix.CreateTransformationZ如何在不仅是矩阵乘法的情况下工作,更重要的是这对屏幕空间/世界空间有什么影响,因此我可以得到更清晰的图像。所以也许有人可以改变代码或给我一小段代码来测试我可以在哪里使用它来围绕轴旋转和/或围绕轴旋转。我也改变了这个例子。XNA - 关于世界空间与屏幕空间的关系

所以我仍然很难想象如何矩阵与xna屏幕空间一起工作。

我给你举个例子:

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 
} 

我把这个从XNA Game Creators处,它只是在做像素检测的方法。

  1. 在上面的变换方法中,矩阵乘法发生在我猜矩形上。屏幕空间/世界空间究竟发生了什么?

  2. 为什么作者将矩阵乘以另一个矩阵的逆? (他提到,不知怎的,这使得它相对于其他资产)

回答

5

屏幕空间大概与客户端空间相同。客户端空间从左上角的(0,0)到右下角的(宽度,高度)。 “上”是Y-。

投影空间从左下角的(-1,-1)到右上角的(1,1)。这是GPU用于最终渲染的内容。 SpriteBatch为你处理这个问题(相反:BasicEffect要求你提供一个投影矩阵)。

世界空间就是你想要的东西。这是您的游戏进行的坐标系。在您的示例中,它似乎是与客户端空间相同的

传统上,当做这样的事情时,你有一个对象在自己的空间中定义。在您的示例中,岩石和船的矩形被硬编码到函数TransformRectangle中,作为变量topLeftbottomRight的初始值。

然后,您对每个对象都有一个世界矩阵。这将该物体从其自己的空间移动到其在世界空间中的位置。在你的例子中,这是shipTransformrockTransform。根据您传入的参数(使用纹理本身作为初始对象),在SpriteBatch.Draw内完成的世界转换为

然后你有一个查看矩阵 - 你可以认为你的相机。你的例子没有其中之一。但是,例如,如果您想要平移视角以跟随玩家,您可以在此处使用一个由玩家位置创建的翻译矩阵(并将其传递给SpriteBatch.Begin)。

最后你有一个投影矩阵将你的世界空间转换为投影空间,以便GPU可以渲染你的场景。

现在可能存在的一个问题是,SpriteBatch在内部定义了一个将客户端空间转换为投影空间的投影矩阵(因此它基本上“假设”世界空间客户端空间)。你的例子中没有问题,因为两个空格都是一样。

如果你的世界空间同样的事情,客户空间,并要使用SpriteBatch,你必须创建一个额外的矩阵,从世界空间转换到客户端的空间,将其插入视图和项目矩阵之间(即:将它与View相乘并传递给SpriteBatch.Begin)。

如果您的世界空间定义了与SpriteBatch不同的“向上”(或“正确”)方式,那么您必须记住SpriteBatch.Draw使用的原始对象将“向上”定义为Y- 。

+0

你和Steve的回答对我很有帮助,现在认为工作很好。 – Ilya 2010-08-22 02:54:17

0

在TestMatrix():

shipPosition = Vector2.Transform(shipPosition, rotZ);

应该

shipPosition = Vector2.Transform(shipPosition, comboMatrix);

0

的概念: - 翻译,旋转,+翻译。是一种引起某物旋转或旋转的机制。 但是你正在将它应用到一个点(vector2)。有一个点旋转的地方没有什么用处。我相信你真的想要的是船舶精灵在原地旋转。这通常是通过更改shipRotation变量完成的,该变量是一个浮点数,用于描述您希望精灵旋转的角度差(从0开始)的数量。

出于某种原因,你糊涂船舶对象的旋转点(shipPosition)的旋转...

在2D,虽然在矩阵数学工程,以及它在3D中,spritebatch .Draw()方法使用单个浮点来描述旋转,并且不直接与Matrix生成的旋转数据相关。

有趣的是,在2D中使用矩阵来转换点是很好的,许多人不理解这一点。在这里,你正试图理解它,但真的希望它对一个点以外的对象起作用。所以,不要放弃矩阵。但只需更改Draw调用中的旋转值即可将Sprite旋转到位。

现在,如果我错过了解释你的目标,请让我知道,我会尽力帮助进一步。

+0

好吧,但或多或​​少,我想知道当我做这些矩阵乘法时,我所看到的屏幕空间和世界空间之间的关系。 – Ilya 2010-08-21 22:10:29

1

我不相信这是造成你所看到的空间关系(在你的第一版问题中)。矩阵在它所处的空间方面是灵巧的。如果给它提供屏幕空间值,它将返回屏幕空间值。所以这种关系(屏幕/世界)并不相关,也不存在。

举例来说,如果你想你的轨道绕船使用矩阵的2D画面的中心点:

Vector2 screenCenter = new Vec2(width/2, h/2);// abbreviated 
Vector2 shipPosition = screenCenter; 
shipPosition.X += 25;//offset ship from center slightly 

shipPosition = Vector2.Transform(shipPosition, Matrix.CreateTranslation(-screenCenter)); 
shipPosition = Vector2.Transform(shipPosition , Matrix.CreateRotationZ(someRadians)); 
shipPosition = Vector2.Transform(shipPosition , Matrix.CreateTranslation(screenCenter)); 


//although the above would typically be written as: 
shipPosition = Vector2.Transform(shipPosition - screenCenter, Matrix.CreateRotationZ(someAngle)) + screenCenter; 

通知,所有的值仅屏幕空间值。没有世界/屏幕空间的关系。所以这就是如何使用矩阵在二维屏幕空间中围绕另一点旋转一个点。对于3D,它将是确切的代码,但带有Z分量(vector3)并使用3d世界空间。

你的comboMatrix(来自更早的代码)和你的新代码片段中的transform()可能会让你失望。将矩阵相乘时,就像向另一个旋转一样。所以你的comboMatrix就像3 + 5 +( - 3)......你所做的全部相当于5.你所有的comboMatrix所做的都是rotZ的平等......它没有翻译。和你的Transform()类似。当我将这三个矩阵应用于上面的shipPosition时,我确信每个Matrix都被应用到shipPosition,以便移动到下一个操作。有时你可以在应用之前连接矩阵,但在你的情况下,不是。

这是否帮助或我仍然错过了你的问题?

+0

2.)哦,关于倒置的矩阵。 如果您认为矩阵表示方向,假设MatrixA表示90度左方向,MatrixB表示130度方向。 A和B之间的差异是45度左方向。 现在Matrix.Invert()反转矩阵,所以MatrixA的逆矩阵是90度** Right **旋转。 所以MatrixB(剩下135个)* InvA(90右)导致45左边。 因此,乘以一倍的差异其他结果的倒数。差别可以认为是相对于第一个。 – 2010-08-22 00:24:34

+0

这主要帮助我。那么,如果我想旋转那个2d火箭飞船,而不是围绕空间中的任意点旋转,但是让我们假设围绕世界空间中的z轴和/或精灵的局部空间旋转?使用CreateRotation矩阵仍然很简单吗? – Ilya 2010-08-22 00:45:55

+0

另一件事。你说:“comboMatrix就像3 + 5 +( - 3)......你真的做的只有5个。你所有的comboMatrix都是rotZ的等价物......它没有翻译,你的Transform()是类似...“ 另外,你的意思是当我做旋转-Z之前,因为我正在喂它的屏幕坐标,我基本上围绕屏幕坐标原点旋转? 好的,但在我的程序中,无论是旋转发生(意味着图像位置从未改变)或旋转一些奇怪的程度,我猜想屏幕坐标原点发生。 – Ilya 2010-08-22 00:48:35