XNA 2dカメラスクロール-マトリックス変換を使用する理由


18

レベルを常にスクロールさせるテストゲームを作成しています。この効果を作成するために、vector2の位置と列挙方向を保存するだけのカメラクラスを確立しました。また、単に移動する位置を固定レートで変更する「移動」するパブリックメソッドも含まれています。次に、描画時にタイルの配列をループするときにこの位置を使用します。これはすべて正常に動作します。

ただし、カメラを移動するには変換行列を使用する必要があり、スプライトバッチを開始するときにこれを指定する必要があると言われました。私は少し混乱していますa。)これがどのように機能するのですか?スプライトバッチが開始されたときにのみそれを与えているかのように、どのように位置を変更し続けることがわかりますか?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);
    }

================================================== ===============================編集:

まず、これまでのご協力に感謝します。

私は提案をいじりました。私がすべてのタイルを描いたとき、frは30から15前後に落ちました-おそらくレベルがかなり大きいからです。

だから私がやったことは、マトリックスを適用し、更新で移動することです(提案されています)が、描画ではタイルをループするためにカメラ位置を使用します(つまり、左のカウンターを開始し、右のタイルで終了します)。これはすべてうまく機能し、私はそれで満足です:-)

私の新しい問題はプレーヤーにあります。明らかに、レベルではなくカメラを動かしているので、その位置が固定されたままであるため、プレーヤーはカメラに取り残されます。この問題に対する2つの解決策を考えました。1つ目は、プレーヤーを描くときにカメラの位置を単純に考慮することです。つまり、描画機能では、カメラの位置をプレーヤーの位置に追加するだけです。2つ目は、変換のないプレーヤーの新しいスプライトバッチを開始することです。つまり、タイルを描画した後にスプライトバッチを終了し、プレーヤーを描画するときに新しいタイルを開始します。私は両方が機能することを知っていますが、パフォーマンス/優れたコーディングの点でどちらが優れているのかは気にしませんか?バッチを2回起動した場合のパフォーマンスへの影響はわかりませんか?


1
「私の新しい問題はプレーヤーにあります。明らかに、レベルではなくカメラを動かしているので、プレーヤーは位置が固定されたままカメラに取り残されます。」これを読んで、なぜ私はあなたがレベルのcoordianteシステム内でプレイヤーを動かさないのか、本当に混乱していますか?
ClassicThunder

回答:


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番目の部分。

バッチ数を少なくしたいのは事実ですが、2つまたは3つ持っていても問題はありません。したがって、2番目のスプライトバッチを作成する正当な理由がある場合は、それを実行します。

そうは言っても、この場合、2番目のスプライトバッチを使用する正当な理由はおそらくないでしょう。おそらく、カメラ変換を適用した同じスプライトバッチでタイルを描画するのとまったく同じ方法でプレーヤーを描画したいでしょう。

プレイヤーがコードを見ずに置き去りにされている理由を理解するのは少し難しいですが、プレイヤーがタイルとまったく同じ位置にプレイヤーを描くと、同じ位置に同じ位置に表示されるのは理にかなっていますスプライトバッチ。

たとえば、プレーヤーをタイル10、10に表示するには、次のようにします。

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

そこにあるものを描くことについて考える考え方に入ろうとすると、カメラは文字通り「シーン」全体を視界に移動します。それがあなたの行列変換がしていることです。


それは本当に役立ち、負荷を軽減しました!情報をありがとう; 大変感謝いたします。本日、あなたのアドバイスを実装しようと思います。再度、感謝します。
ペクトス発掘

また、興味がありませんが、マトリックス変換を使用してビューポートでタイルのみを描画する方法はありますか?
ペクトス発掘

それを行う方法はありますが、頭の外ではわかりません。多分カメラ変換を適用した後に、ビューポートの長方形を使用することに関係があるのではないかと思います。私は知らない、あなたは実験する必要があります。デバッガーを使用します。
craftworkgames

はい、ありがとう。私はそれをいじってみましたが、どこかで手に入れましたが、プレーヤーをどうするかという質問に出くわしました。質問の編集をご覧ください。
ペクタス発掘

おかげで、私はあなたのアドバイスを受け、同じスプライトバッチを使用して、更新時にカメラの位置を取得し、描画時にプレーヤーの位置に適用するというオプションを選択しました。うまくいくようです。すべてのあなたの助けをありがとう、しばらくカメラに引っかかっています。ずっと明確になりました:-)
ペクトス発掘
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.