XNA - 关于世界空间和屏幕空间之间的关系

发布于 2024-09-15 01:38:40 字数 10756 浏览 2 评论 0原文

编辑:只是想让我的问题更清楚。我几乎很难看到像 Matrix.CreateTransformationZ 这样的东西不仅在矩阵乘法的背景下如何工作,而且更重要的是它对屏幕空间/世界空间的作用,这样我才能得到更清晰的图片。因此,也许有人可以更改代码或给我一个简短的片段来测试我可以在哪里使用它来绕轴旋转和/或绕轴轨道运行。我也改变了这个例子。

所以我仍然很难想象矩阵如何与 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. 在上面的 Transform 方法中,我猜是针对矩形进行矩阵乘法。就屏幕空间/世界空间而言到底发生了什么?

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

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.

  1. 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?

  2. Why is author multiplying the matrix by the inverse of another matrix? (He mentions that somehow this makes it relative to the other asset)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

一笑百媚生 2024-09-22 01:38:40

屏幕空间大概与客户端空间相同。客户端空间从左上角的 (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-。

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 TransformRectangle as the initial values of the variables topLeft through bottomRight.

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 shipTransform and rockTransform. A World transform is also done inside SpriteBatch.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.Draw defines "up" to be Y-.

佞臣 2024-09-22 01:38:40

我不认为是空间关系导致了您所看到的(在问题的第一版中)。该矩阵在其所在空间方面具有灵巧性。如果您向其提供屏幕空间值,它就会返回屏幕空间值。所以关系(屏幕/世界)不相关也不存在。

例如,如果您想使用矩阵使飞船围绕 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;

请注意,所有值都只是屏幕空间值。世界/屏幕空间关系并不重要。这就是如何使用矩阵在 2d 屏幕空间中围绕另一个点旋转一个点。对于 3d 来说,这将是精确的代码,但带有 Z 分量(向量 3)并使用 3d 世界空间。

您的comboMatrix(来自早期代码)和新代码片段中的transform()可能会让您感到困惑。将矩阵相乘时,就像将一个旋转与另一个旋转相加。所以你的comboMatrix就像3 + 5 +(-3)...你真正做的只是相当于5。你的comboMatrix所做的只是相当于rotZ...它没有翻译。和你的 Transform() 类似。当我将三个矩阵应用于上面的 ShipPosition 时,我确保在进行下一个操作之前将每个矩阵应用于 ShipPosition。有时您可以在应用之前连接矩阵,但就您而言,不能。

这有帮助还是我仍然错过了你的问题?

I don't believe it was space relationships that caused what you saw (in your first edition of your question). The matrix is ambidextrous in that regard to what space it is in. If you feed it screen space values, it returns screen space values. So the relationship (screen/world) is not relevant and not existent.

For instance, if you wanted to orbit your ship around the centerpoint of the 2d screen using a matrix:

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;

Notice, all the values are screenspace values only. No world/screen space relationship mattered. So that is how to rotate a point around another point in 2d screenspace using matrices. For 3d it would be the exact code but with a Z component (vector3) and use 3d world space.

Your comboMatrix (from earlier code) and in your new code snippet the transform() may be tripping you up some. When multiplying matrices together, its like adding one rotation to another. So your comboMatrix was like 3 + 5 +(-3) ... all you really did was the equivalent of 5. All your comboMatrix did was the equivelent of rotZ... it did no translations. and your Transform() is similar. When I applied the three matrices to shipPosition above, I made sure each Matrix was applied to shipPosition befor moving onto the next operation. Sometimes you can concatenate matrices before applying, but in your case, not.

Does this help or have I still missed your question?

没企图 2024-09-22 01:38:40

在 TestMatrix() 中:

shipPosition = Vector2.Transform(shipPosition, rotZ);

应该是

ShipPosition = Vector2.Transform(shipPosition,comboMatrix);

In TestMatrix():

shipPosition = Vector2.Transform(shipPosition, rotZ);

should be

shipPosition = Vector2.Transform(shipPosition, comboMatrix);

帥小哥 2024-09-22 01:38:40

概念:-平移、旋转、+平移。是一种使物体旋转或旋转的机制。
但你将它应用到一个点(向量2)。点旋转几乎没有什么用处。我相信你真正想要的是让船精灵原地旋转。这通常是通过更改 ShipRotation 变量来完成的,该变量是一个浮点数,描述您希望精灵旋转的角度差值(从 0 开始)。

由于某种原因,您将点(shipPosition)的旋转与船舶对象的旋转混淆了......

在 2d 中,尽管矩阵中的数学工作与 3d 中一样好,但 spritebatch.Draw() 方法使用单个float 来描述旋转,并且与 Matrix 生成的旋转数据没有直接关系。

有趣的是,在二维中使用矩阵来变换点是很好的,但很多人不明白这一点。在这里,您试图理解它,但真正希望它作用于一个对象而不是一个点。所以,不要放弃矩阵。但只需更改 Draw 调用内的旋转值即可将精灵旋转到位。

现在,如果我未能解释您的目标,请告诉我,我会尽力提供进一步帮助。

The concept of: -translation, rotation, +translation. is a mechanism to cause something to rotate or spin in place.
But you are applying it to a point (a vector2). There is little use having a point spin in place. What I believe you really want is for the ship sprite to rotate in place. This is typically done by altering the shipRotation variable which is a float describing the amount of angular difference (from 0) that you want the sprite to be rotated.

For some reason you confused the rotation of a point (shipPosition) for the rotation of the ship object...

In 2d, although the math in matrices works as well as it does in 3d, the spritebatch.Draw() method uses a single float to describe rotation and that does not directly relate to the rotation data generated by a Matrix.

The funny thing is, it is good to use Matrices in 2d to transform points and many people don't understand that. Here, you are trying to understand it but really want it to act on an object other than a point. So, don't give up on Matrices. But simply change the rotation value inside the Draw call to rotate the sprite in place.

Now if I've miss interpreted your goal, pleas let me know and I'll try to help further.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文