XNA 2d Kamera Kaydırma - neden matris dönüşümü kullanılır?


18

Seviyenin sürekli kaydırılmasını istediğim bir test oyunu yapıyorum. Bu efekti oluşturmak için sadece bir vector2 pozisyonu ve bir enum yönü saklayan bir kamera sınıfı oluşturdum. Ayrıca, konumu sabit bir hızda değiştiren 'hareket ettirmek' için genel bir yöntem içerir. Daha sonra çizim yaparken fayans dizimde döngü yaparken bu konumu kullanıyorum. Tüm bunlar iyi çalışıyor.

Ancak, kamerayı hareket ettirmek için bir Transform matrisi kullanmam gerektiği ve spritebatch'ı başlattığımda bunu sağlamam gerektiği söylendi. Biraz kafam karıştı.) Nasıl çalışır? sanki sadece spritebatch başladığında veriyorum pozisyon değiştirmeyi nasıl bilebilir? b.) Fayanslarda dolaşırken neden kamera pozisyonuna hala ihtiyacım olduğu gibi?

Şu anda işe yarayamıyorum, ama bunun nasıl çalıştığını tam olarak anlamadığım için sürpriz değil. Şu anda denememde (takip edilecek kod) çizilen karolar değişiyor, bu da kameraların konumunun değiştiği anlamına geliyor, ancak görünümün konumu değişmeden kalıyor (yani kamera orijininde). Gerçekten nasıl kullanılması gerektiği konusunda bazı tavsiye / rehberlik takdir ediyorum?

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

Seviye:

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

Oyun - sadece beraberlik seviyesi içinde çağırır:

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

        //mSpriteBatch.Begin();

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

        //mSpriteBatch.End();

        base.Draw(gameTime);
    }

================================================== =============================== DÜZENLEME:

Öncelikle, şimdiye kadarki yardımlarınız için el sanatları oyunlarına teşekkür ederiz.

Öneri ile oynadım. Tüm karoları çizdiğimde, fr 30'dan 15'e kadar tanklandı - muhtemelen seviyeler oldukça büyük.

Yaptığım şey matrisi uygulamak ve güncellemede (önerildiği gibi) hareket etmektir, ancak çizimde kameraların döşemeleri arasında döngü yapmak için kullanıyorum (yani soldaki sayacı başlat ve sağ döşemede son). Her şey iyi çalışıyor ve bundan memnunum :-)

Yeni sayım oyuncuda. Görünüşe göre şimdi seviyeden ziyade kamerayı hareket ettirdiğimde, oyuncu pozisyonu sabit kalırken kamera tarafından geride bırakılıyor. Bu soruna iki çözüm düşündüm, birincisi, oynatıcıyı çizerken kameraların konumunu düşünmektir. Yani, çizim işlevinde kamera konumunu oynatıcı konumuna eklemeniz yeterlidir. İkincisi, dönüşümü olmayan oyuncu için yeni bir sprite partisi başlatmak. fayansları çizdikten sonra komut satırını sonlandırın, ardından oynatıcıyı çizerken yeni bir tane başlatın. İkisinin de işe yarayacağını biliyorum, ancak performans / iyi kodlama açısından daha iyi olacak kuyrukların başlarını yapamıyorum? Partiyi iki kez başlatmaktan kaynaklanan performans etkilerinin ne olduğundan emin değilim?


1
"Yeni sorunum oyuncuda yatmaktadır. Belli ki şimdi seviyeden ziyade kamerayı hareket ettirdiğim için, oyuncu pozisyonu sabit kalırken kamera tarafından geride bırakılıyor." Sadece bunu okuyun ve neden dünyadaki oyuncuyu seviyenin koordinat sistemi içinde hareket ettirmeyesiniz diye kafam karıştı?
ClassicThunder

Yanıtlar:


15

Kamera matris dönüşümleri kolaydır

Temel bir kamera oluşturmak kolaydır. Aşağıda temel bilgileri kullanmaya başlayabilirsiniz. Etrafında dolaşma, döndürme ve ölçeklendirme. Her 2 boyutlu hareketli grafiği taşımak çok önemli değildir ancak ölçeklendirme veya döndürmeyi hesaba katarsanız, bağımsız hareketli her bir hareketli grafiğe uygulamak gerçekten zorlaşır.

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

Koordinat sistemi tanımları arasında dönüştürmeyi gerçekten kolaylaştırır

Ekrandan dünya alanına geçmek için. Bu genel olarak nesne toplama için farenin dünyadaki yerini almak için kullanılır.

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

Dünyadan ekran alanına geçmek için tam tersini yapmanız yeterlidir.

Vector2.Transform(mouseLocation, Camera.TransformMatrix);

Bir matris kullanmanın, biraz öğrenmekten başka bir geri dönüşü yoktur.

Görünür alanı elde etmek kolaydır

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

Sadece kamera köşelerini dönüştürebilir ve dünyadaki yerlerini alabilirsiniz. Min max x, y değerleri ile görüntülenebilir alanın çevresinde bir dikdörtgen elde edebilirsiniz. Çizim çağrılarını iptal etmek ve optimize etmek için çok kullanışlıdır.


1
Teşekkürler. MonoGame.Extended kütüphanesinde bir kamera uygularken bu yararlı buldum .
craftworkgames

6

SpriteBatch'ınıza bir matris uygulamak, tüm çizim çağrısını aynı anda dönüştürür. Bu, kameranızı hiç DrawTiles yönteminizde kullanmanız gerekmediği anlamına gelir.

Böyle çok daha basit olabilir:

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

Yani bir matris kullanmanın amacı, onu düşünmek zorunda kalmamanızdır. Sadece bir şeyler çizin ve kamerayı bağımsız olarak hareket ettirin.

Ayrıca, MoveCamera yönteminiz biraz garip görünüyor. Bir Seviyeyi bağımlılık olarak alan bir kamera sınıfına sahip olmak çok sıra dışı. Daha tipik bir uygulama şöyle görünecektir:

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

Daha sonra Güncelleme yönteminizde böyle bir şey yapabilirsiniz:

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

Genel olarak, benim önerim basit tutmaktır. Yumruğun en basit şekilde çalışmasını sağlayın ve üzerine inşa edin. Öncelikle temel özellikleri çalışana kadar yüksek düzeyde optimize edilmiş kod yazmamaya çalışın. Her kareyi her kareyi oluşturmanın o kadar da kötü olmadığını görebilirsiniz.

EDIT: Sorunun ikinci kısmı için.

Toplu iş sayınızı düşük tutmak istediğiniz doğru olsa da, 2 veya 3'e sahip olmak hiç sorun olmamalı. Eğer ikinci bir hareketli grafik grubu oluşturmak için iyi bir nedeniniz varsa, bunu yapın.

Bununla birlikte, muhtemelen bu durumda ikinci bir sprite toplu iş kullanmak için iyi bir neden yoktur. Büyük olasılıkla, oynatıcınızı, kamera dönüşümü uygulandığında aynı hareketli grafik kümesinde fayans çizdiğiniz gibi çizmek istersiniz.

Oynatıcınızın neden bazı kodlara bakmadan geride bırakıldığını söylemek biraz zor, ancak oynatıcınızı bir döşemeyle aynı konumda çizerseniz, aynı konumda aynı şekilde görünmesi için mantıklıdır. sprite toplu iş.

Örneğin, oynatıcının 10, 10 numaralı döşemede görünmesini istiyorsanız bunu yapabilirsiniz:

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

Nerede olduklarını çizmeyi düşünmenin zihniyetine girmeye çalışın ve kamera tam anlamıyla tüm "sahneyi" görünüme taşır. Matris dönüşümünüz bunu yapıyor.


Bu gerçekten yararlı ve yükleri temizledi şeyler! Bilgi için teşekkürler; çok takdir etmek. Tavsiyenizi bugün daha sonra uygulamaya çalışacağım ve nasıl devam ettiğimi size bildireceğim. Tekrar teşekkürler.
Pectus Excavatum

Ayrıca, sadece ilgisiz, sadece görünüm alanında fayans çizmek için matris dönüşümünü kullanmanın bir yolu var mı?
Pectus Excavatum

Bunu yapmanın bir yolu var, ama başımın üstünü bilmiyorum. Belki de kamera dönüşümü uyguladıktan sonra, görünüm dikdörtgeni kullanarak bir şey olacağını sanıyorum. Bilmiyorum, denemen gerekecek. Hata ayıklayıcınızı kullanın.
craftworkgames

Tamam teşekkürler. Onunla oynadım ve bir yere girdim ama oyuncu ile ne yapacağım hakkında bir soruya girdim - lütfen soruya yönelik düzenlemelere bakın.
Pectus Excavatum

Teşekkürler, tavsiyeni aldım ve aynı sprite partisini kullanma ve güncelleme sırasında kamera pozisyonunu alma ve çizim yaparken oyuncuların pozisyonuna uygulama seçeneği ile gittim. İyi çalışıyor gibi görünüyor. Tüm yardımlarınız için teşekkürler, bir süre kameraya takılı kaldı. Çok daha net şimdi :-)
Pectus Excavatum
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.