XNA 2d相机滚动-为什么使用矩阵变换?


18

我正在制作一个测试游戏,希望关卡不断滚动。为了创建这种效果,我建立了一个摄像机类,该摄像机类仅存储vector2位置和枚举方向。它还包含一个公共的“移动”方法,该方法仅以固定的速率更改头寸。然后,当我在绘制时遍历我的瓷砖阵列时使用此位置。这一切都很好。

但是,有人告诉我应该使用Transform矩阵来移动相机,并且在启动spritebatch时应该提供此信息。我有点困惑a。)这是如何工作的?好像我只在spritebatch开始时给它一样,它怎么知道要保持位置变化?b。)为什么在遍历图块时仍然确定需要照相机的位置?

目前,我无法正常工作,但这并不奇怪,因为我还不完全了解它的工作原理。目前,在我的尝试(遵循的代码)中,正在绘制的图块发生了变化,这意味着相机的位置正在更改,但是视口的位置保持不变(即,在相机的原点)。对于要如何使用它,我将不胜感激。

相机:

 class Camera {

    // The position of the camera.
    public Vector2 Position {
        get { return mCameraPosition; }
        set { mCameraPosition = value; }
    }
    Vector2 mCameraPosition;

    public Vector2 Origin { get; set; }

    public float Zoom { get; set; }

    public float Rotation { get; set; }

    public ScrollDirection Direction { get; set; }

    private Vector2 mScrollSpeed = new Vector2(20, 18);

    public Camera() {
        Position = Vector2.Zero;
        Origin = Vector2.Zero;
        Zoom = 1;
        Rotation = 0;
    }


    public Matrix GetTransform() {
        return Matrix.CreateTranslation(new Vector3(mCameraPosition, 0.0f)) *
               Matrix.CreateRotationZ(Rotation) *
               Matrix.CreateScale(Zoom, Zoom, 1.0f) *
               Matrix.CreateTranslation(new Vector3(Origin, 0.0f));
    }




    public void MoveCamera(Level level) {
        if (Direction == ScrollDirection.Up)
        {
            mCameraPosition.Y = MathHelper.Clamp(mCameraPosition.Y - mScrollSpeed.Y, 0, (level.Height * Tile.Height - level.mViewport.Height));
        }
    }

水平:

 public void Update(GameTime gameTime, TouchCollection touchState) {

            Camera.MoveCamera(this);
 }


 public void Draw(SpriteBatch spriteBatch) {
        //spriteBatch.Begin();
        spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise, null, mCamera.GetTransform());


        DrawTiles(spriteBatch);

        spriteBatch.End();
    }

游戏-只需在级别内进行平局:

  protected override void Draw(GameTime gameTime)  {
        mGraphics.GraphicsDevice.Clear(Color.Black);

        //mSpriteBatch.Begin();

        // Draw the level.
        mLevel.Draw(mSpriteBatch);

        //mSpriteBatch.End();

        base.Draw(gameTime);
    }

================================================== ==============================编辑:

首先,感谢craftcraftgames到目前为止的帮助。

我听了这个建议。当我绘制所有瓷砖时,fr从30下降到15左右-可能是因为水平相当大。

因此,我要做的就是应用矩阵并进行更新(如建议的那样),但在绘制过程中,我使用相机位置在图块​​之间进行循环(即,起始计数器在左图块,结束在右图块)。这一切都很好,我对此很满意:-)

我的新问题在于播放器。显然,由于我现在移动的是摄像机而不是水平仪,因此在位置固定的情况下,玩家会被照相机甩在后面。我想到了两个解决方案,第一个是在绘制播放器时简单地考虑摄像机的位置。即在抽奖功能中,只需将摄像机位置添加到播放器位置即可。第二种是为没有转换的播放器启动一个新的精灵批处理。也就是说,在绘制瓷砖后结束spritebatch,然后在绘制播放器时开始一个新的spritebatch。我知道两者都可以,但是我不能在性能/良好编码方面做到最好?我不确定两次启动批处理对性能有何影响?


1
“我的新问题在于播放器。很明显,由于我现在正在移动摄像头而不是移动水平仪,因此当位置固定时,播放器会被照相机甩在后面。” 读完这篇文章,我很困惑,为什么在地球上您不将玩家移动到关卡的coordiante系统内?
ClassicThunder13年

Answers:


15

相机矩阵转换很容易

创建基本相机很容易。下面将帮助您入门。四处移动,旋转,缩放。移动每个2d精灵并不是什么大问题,但是如果考虑缩放或旋转,则很难分别应用于每个精灵。

class Camera2D 
{
    public float Zoom { get; set; }
    public Vector2 Location { get; set; }
    public float Rotation { get; set;}

    private Rectangle Bounds { get; set; }

    private Matrix TransformMatrix
    { 
        get: {
            return 
                Matrix.CreateTranslation(new Vector3(-Location.X, -Location.Y, 0)) *
                Matrix.CreateRotationZ(Rotation) *
                Matrix.CreateScale(Zoom) *
                Matrix.CreateTranslation(new Vector3(Bounds.Width * 0.5f, Bounds.Height * 0.5f, 0));
        }
    };

    public Camera2D(Viewport viewport) 
    {
        Bounds = viewport.Bounds;
    }
}

它使在坐标系定义之间转换变得非常容易

只需从屏幕进入世界空间。这通常用于获取鼠标在世界上的位置以进行对象拾取。

Vector2.Transform(mouseLocation, Matrix.Invert(Camera.TransformMatrix));

要从世界进入屏幕空间,要做相反的事情。

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

除了需要一点学习之外,没有其他使用矩阵的缺点。

容易获得可见区域

public Rectangle VisibleArea {
    get {
        var inverseViewMatrix = Matrix.Invert(View);
        var tl = Vector2.Transform(Vector2.Zero, inverseViewMatrix);
        var tr = Vector2.Transform(new Vector2(_screenSize.X, 0), inverseViewMatrix);
        var bl = Vector2.Transform(new Vector2(0, _screenSize.Y), inverseViewMatrix);
        var br = Vector2.Transform(_screenSize, inverseViewMatrix);
        var min = new Vector2(
            MathHelper.Min(tl.X, MathHelper.Min(tr.X, MathHelper.Min(bl.X, br.X))), 
            MathHelper.Min(tl.Y, MathHelper.Min(tr.Y, MathHelper.Min(bl.Y, br.Y))));
        var max = new Vector2(
            MathHelper.Max(tl.X, MathHelper.Max(tr.X, MathHelper.Max(bl.X, br.X))), 
            MathHelper.Max(tl.Y, MathHelper.Max(tr.Y, MathHelper.Max(bl.Y, br.Y))));
        return new Rectangle((int)min.X, (int)min.Y, (int)(max.X - min.X), (int)(max.Y - min.Y));
    }
}

您可以简单地变换摄像机的角落并获取它们在世界空间中的位置。最小化x,y值的最大值,您可以在可见空间周围得到一个矩形。对于剔除和优化绘图调用非常有用。


1
谢谢。在MonoGame.Extended库中实现相机时,我发现这很有帮助。
craftworkgames

6

将矩阵应用于SpriteBatch可以一次转换整个绘制调用。这意味着您根本不需要在DrawTiles方法中使用相机。

这样可能会变得更加简单:

    // Loop through the number of visible tiles.
    for (int y = 0; y <= tiles.GetUpperBound(1); y++) {
        for (int x = 0; x <= tiles.GetUpperBound(0); x++) {

                // If the tile is not an empty space.
                if (tiles[x, y].Texture != null) {
                     // Get the position of the visible tile within the viewport by multiplying the counters by the tile dimensions
                     // and subtracting the camera offset values incase the position of the camera means only part of a tile is visible.
                     Vector2 tilePosition = new Vector2(x * Tile.Width, y * Tile.Height);
                     // Draw the correct tile
                     spriteBatch.Draw(tiles[x, y].Texture, tilePosition, Color.White);
                }
        }
    }

因此,使用矩阵的要点是,您不必考虑它。只需画图并独立移动相机即可。

另外,您的MoveCamera方法看起来有些奇怪。拥有将Level作为依赖项的相机类是非常不寻常的。一个更典型的实现如下所示:

public void MoveCamera(float deltaX, float deltaY) {
    mCameraPosition.X += deltaX;
    mCameraPosition.Y += deltaY;
}

然后,在您的Update方法中,您可以执行以下操作:

    if (Direction == ScrollDirection.Up)
    {
        mCamera.MoveCamera(mScrollSpeed.Y, 0);
    }

总的来说,我的建议是保持简单。让它以最简单的方式工作并在其上进行构建。在基础知识开始工作之前,请不要编写高度优化的代码。您可能会发现,每帧渲染每个图块都还不错。

编辑:对于问题的第二部分。

虽然确实希望将批次数量保持在较低水平,但是拥有2或3个也不是什么问题。因此,如果您有充分的理由创建第二个Sprite批处理,请执行此操作。

也就是说,在这种情况下可能没有充分的理由使用第二个Sprite批处理。更有可能的是,您希望以与应用了摄影机变换的相同子画面批处理中的图块完全相同的方式来绘制播放器。

很难说出您的玩家为什么不看代码就被甩在了后面,但这是有理由的,如果您将玩家绘制在与瓷砖完全相同的位置,他/她将以相同的位置出现在相同的位置雪碧批处理。

例如,如果您希望玩家出现在图块10、10上,则可以这样做:

var playerPosition = new Vector2(10 * Tile.Width, 10 * Tile.Height);
spriteBatch.Draw(player.Texture, playerPosition, Color.White);

尝试进入思考将事物绘制在什么位置的想法,然后照相机将整个“场景”移动到视野中。那就是您的矩阵变换所要做的。


这真的很有帮助,可以清除负载!谢谢(你的)信息; 非常感激。我将在今天晚些时候尝试实施您的建议,并告诉您我的情况。再次感谢。
果肉研究》

另外,出于兴趣,是否有一种方法可以使用矩阵变换仅在视口中绘制图块?
果肉切除》

有一种方法可以做到,但我不知道。我怀疑这可能与使用视口矩形有关,也许是在对其应用了相机变换之后。我不知道,您必须尝试一下。使用调试器。
craftworkgames

好,谢谢。我玩过它并到达某个地方,但是遇到了一个关于如何处理播放器的问题-请参阅要提问的内容。
Pectus Excavatum

谢谢,我接受了您的建议,并选择了使用相同的精灵批处理,并仅在更新中获取相机位置并将其应用于绘制时的玩家位置。似乎运作良好。感谢您的所有帮助,在相机上停留了一段时间。现在要清楚
得多
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.