Pengguliran Kamera XNA 2d - mengapa menggunakan transformasi matriks?

18

Saya membuat game uji di mana saya ingin levelnya terus bergulir. Untuk membuat efek ini, saya telah membuat kelas kamera yang hanya menyimpan posisi vektor2 dan arah enum. Ini juga berisi metode publik untuk 'bergerak' yang hanya mengubah posisi pada tingkat yang tetap. Saya kemudian menggunakan posisi ini ketika memutar melalui array ubin saya saat menggambar. Ini semua berfungsi dengan baik.

Namun, saya telah diberitahu bahwa saya harus menggunakan matriks Transform untuk menggerakkan kamera, dan bahwa saya harus menyediakan ini ketika saya memulai spritebatch. Saya agak bingung a.) Bagaimana cara kerjanya? seolah-olah saya hanya memberikannya ketika spritebatch dimulai, bagaimana ia tahu untuk terus mengubah posisi? b.) Mengapa melakukannya karena saya masih memerlukan posisi kamera ketika saya melewati ubin?

Saat ini saya tidak bisa membuatnya bekerja, tetapi itu tidak mengejutkan karena saya tidak sepenuhnya mengerti bagaimana ini dimaksudkan untuk bekerja. Saat ini dalam upaya saya (kode untuk mengikuti) ubin ditarik perubahan yang berarti posisi kamera berubah, tetapi posisi viewport tetap tidak berubah (yaitu pada asal kamera). Saya akan sangat menghargai beberapa saran / bimbingan tentang bagaimana seharusnya digunakan?

Kamera:

 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));
        }
    }

Tingkat:

 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();
    }

Game - cukup panggil undian dalam level:

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

        //mSpriteBatch.Begin();

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

        //mSpriteBatch.End();

        base.Draw(gameTime);
    }

================================================== =============================== Edit:

Pertama, terima kasih craftworkgames atas bantuan Anda sejauh ini.

Saya bermain-main dengan saran itu. Ketika saya benar-benar menggambar semua ubin, fr memangkas sekitar 15 dari 30 - mungkin karena levelnya cukup besar.

Jadi apa yang telah saya lakukan adalah menerapkan matriks dan bergerak dalam pembaruan (seperti yang disarankan) tetapi dalam menggambar saya menggunakan posisi kamera untuk perulangan melalui ubin (yaitu mulai counter di kiri dan berakhir di ubin kanan). Ini semua bekerja dengan baik dan saya senang dengan itu :-)

Masalah baru saya terletak pada pemain. Jelas karena saya sekarang memindahkan kamera daripada level, pemain akan tertinggal oleh kamera saat posisinya tetap. Saya telah memikirkan dua solusi untuk masalah ini, yang pertama adalah hanya mempertimbangkan posisi kamera saat menggambar pemain. Yaitu di fungsi undian cukup menambahkan posisi kamera ke posisi pemain. Yang kedua adalah memulai batch sprite baru untuk pemain yang tidak memiliki transformasi. yaitu mengakhiri spritebatch setelah menggambar ubin lalu mulai yang baru saat menggambar pemain. Saya tahu keduanya akan bekerja, tetapi saya tidak bisa membuat kepala yang lebih baik dalam hal kinerja / pengkodean yang baik? Saya tidak yakin apa implikasi kinerja dari memulai batch dua kali?

Pectus Excavatum
sumber
1
"Masalah baru saya terletak pada pemain. Jelas karena saya sekarang menggerakkan kamera daripada tingkat, pemain akan ditinggalkan oleh kamera saat posisinya tetap." Baca saja ini dan saya sangat bingung mengapa Anda tidak memindahkan pemain dalam sistem koordinasi level?
ClassicThunder

Jawaban:

15

Transformasi matriks kamera mudah

Membuat kamera dasar itu mudah. Di bawah ini akan membantu Anda memulai dengan dasar-dasarnya. Memindahkannya, berputar, dan scaling. Memindahkan setiap sprite 2d tidak banyak masalah tetapi jika Anda memfaktorkan penskalaan atau rotasi, maka akan sangat sulit untuk diterapkan ke setiap sprite secara individual.

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;
    }
}

Itu membuatnya sangat mudah untuk mengkonversi antara definisi sistem koordinat

Untuk berpindah dari layar ke ruang dunia secara sederhana. Ini biasanya digunakan untuk mendapatkan lokasi mouse di dunia untuk pengambilan objek.

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

Untuk pergi dari dunia ke ruang layar cukup lakukan sebaliknya.

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

Tidak ada yang menarik untuk menggunakan matriks selain butuh sedikit pembelajaran.

Sangat mudah untuk mendapatkan area yang terlihat

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));
    }
}

Anda dapat dengan mudah mengubah sudut kamera dan mendapatkan lokasinya di ruang dunia. Min maks nilai x, y dan Anda bisa mendapatkan persegi panjang di sekitar ruang yang dapat dilihat. Sangat berguna untuk menyisihkan dan mengoptimalkan panggilan draw.

ClassicThunder
sumber
1
Terima kasih. Saya menemukan ini membantu saat mengimplementasikan kamera di perpustakaan MonoGame.Extended .
craftworkgames
6

Menerapkan sebuah matriks ke SpriteBatch Anda mengubah seluruh panggilan undian sekaligus. Ini berarti Anda tidak perlu menggunakan kamera dalam metode DrawTiles Anda sama sekali.

Itu bisa menjadi jauh lebih sederhana seperti:

    // 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);
                }
        }
    }

Jadi intinya menggunakan matriks adalah agar Anda tidak perlu memikirkannya. Hanya menggambar dan memindahkan kamera secara mandiri.

Juga, metode MoveCamera Anda terlihat sedikit aneh. Sangat tidak biasa memiliki kelas kamera yang menggunakan Level sebagai ketergantungan. Implementasi yang lebih khas akan terlihat seperti ini:

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

Kemudian dalam metode Pembaruan Anda, Anda mungkin melakukan sesuatu seperti ini:

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

Secara keseluruhan, saran saya adalah membuatnya sederhana. Dapatkan itu bekerja dengan cara paling sederhana dan membangunnya. Cobalah untuk tidak menulis kode yang sangat dioptimalkan sampai dasar-dasar Anda berhasil terlebih dahulu. Anda mungkin menemukan bahwa memberikan setiap ubin setiap frame tidak terlalu buruk.

EDIT: Untuk bagian kedua dari pertanyaan.

Meskipun benar bahwa Anda ingin menjaga jumlah batch Anda tetap rendah, memiliki 2 atau 3 seharusnya tidak menjadi masalah sama sekali. Jadi, jika Anda memiliki alasan yang bagus untuk membuat batch sprite kedua lakukan saja.

Yang mengatakan, mungkin tidak ada alasan yang baik untuk menggunakan batch sprite kedua dalam kasus ini. Kemungkinan besar, Anda ingin menggambar pemain Anda dengan cara yang sama persis seperti Anda menggambar ubin dalam kumpulan sprite yang sama dengan transformasi kamera yang diterapkan.

Agak sulit untuk mengatakan mengapa pemain Anda tertinggal tanpa melihat beberapa kode tetapi masuk akal bahwa jika Anda menarik pemain Anda pada posisi yang sama persis dengan ubin, ia akan muncul di posisi yang sama dengan yang sama. kumpulan sprite.

Misalnya, jika Anda ingin pemain tampil di ubin 10, 10 Anda bisa melakukan ini:

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

Cobalah untuk masuk ke dalam pola pikir berpikir tentang menggambar hal-hal di mana mereka berada dan kamera benar-benar menggerakkan seluruh "pemandangan" ke dalam tampilan. Itulah yang dilakukan oleh transformasi matriks Anda.

craftworkgames
sumber
Itu sangat membantu dan membereskan semuanya! Terimakasih atas infonya; sangat dihargai. Saya akan mencoba menerapkan saran Anda hari ini dan akan memberi tahu Anda bagaimana caranya. Terima kasih lagi.
Pectus Excavatum
Juga, hanya karena ketertarikan, apakah ada cara untuk menggunakan transformasi matriks untuk hanya menggambar ubin di viewport?
Pectus Excavatum
Ada cara untuk melakukan itu, tetapi saya tidak tahu dari atas kepala saya. Saya menduga itu ada hubungannya dengan menggunakan persegi panjang viewport, mungkin setelah menerapkan transformasi kamera untuk itu. Saya tidak tahu, Anda harus bereksperimen. Gunakan debugger Anda.
craftworkgames
Ok terima kasih. Saya telah bermain-main dengannya dan mendapatkan suatu tempat tetapi telah mengalami pertanyaan tentang apa yang harus dilakukan dengan pemain - silakan lihat suntingan untuk pertanyaan.
Pectus Excavatum
Terima kasih, saya menerima saran Anda dan memilih opsi menggunakan kumpulan sprite yang sama dan hanya mendapatkan posisi kamera di pembaruan dan menerapkannya pada posisi pemain saat menggambar. Tampaknya bekerja dengan baik. Terima kasih atas semua bantuan Anda, macet di kamera untuk sementara waktu. Jauh lebih jelas sekarang :-)
Pectus Excavatum