Apakah ada cara sederhana untuk mengelompokkan dua atau lebih sprite, sehingga semuanya akan saling bergantung?


8

Saya pikir pertanyaan ini sangat mirip dengan yang ini , tetapi saya tidak yakin apakah jawabannya universal.

Jadi, tujuan saya adalah:

  • Tempatkan dua sprite di posisi tetap, misalnya pemain dan matanya
  • Pastikan bahwa setiap kali pemain berputar, sprite mata juga berputar dan mendapatkan posisi relatif yang sama dari tubuh (jadi mata tidak berada di belakang pemain). Jadi mereka akan bekerja sebagai kelompok. - Langkah ini harus otomatis, itu tujuan saya!

Jadi, misalnya, saya sekarang ingin menempatkan pistol di tangan pengguna. Jadi sekarang saya katakan, pemain itu ada di posisi Vector2(0, 0)dan pistol ada di posisi Vector2(26, 16). Sekarang saya ingin mengelompokkan mereka, jadi kapan pun pemain berputar, pistol juga berputar.

masukkan deskripsi gambar di sini

Saat ini dalam contoh ini tidak apa-apa tetapi jika saya harus memindahkan pistol saya pada sumbu y (hanya), saya tersesat

Jawaban:


10

Konsep

Saya akan memecahkan masalah ini dengan hierarki sprite menggunakan varitasi dari pola desain komposit . Ini berarti setiap sprite menyimpan daftar sprite anak-anak yang dilampirkan sehingga setiap modifikasi pada orang tua secara otomatis tercermin di dalamnya (termasuk terjemahan, rotasi dan penskalaan).

Di mesin saya, saya telah menerapkannya seperti ini:

  • Setiap Spritetoko aList<Sprite> Children dan menyediakan metode untuk menambah anak baru.
  • Masing-masing Spritetahu cara menghitung Matrix LocalTransformyang didefinisikan relatif terhadap induknya.
  • Memanggil DrawaSprite juga menyerukan pada semua anak-anaknya.
  • Anak-anak melipatgandakan transformasi lokal mereka dengan transformasi global orangtua mereka . Hasilnya adalah apa yang Anda gunakan saat rendering.

Dengan ini Anda akan dapat melakukan apa yang Anda minta tanpa modifikasi kode Anda. Ini sebuah contoh:

Sprite tank = new Sprite(tankTexture);
tank.Children.Add(new Sprite(turretTexture) {Position = new Vector2(26, 16) });

spriteBatch.Begin();
tank.Draw(spriteBatch);
spriteBatch.End();

Penerapan

Sebagai permulaan saya hanya akan memasukkan proyek sampel dengan teknik ini diimplementasikan, jika Anda lebih suka hanya melihat kode dan mencari tahu:

masukkan deskripsi gambar di sini

Catatan: Saya memilih kejelasan daripada kinerja di sini. Dalam implementasi yang serius, ada banyak optimisasi yang bisa dilakukan, yang sebagian besar melibatkan transformasi caching dan hanya menghitung ulang mereka sesuai kebutuhan (misalnya, cache transformasi lokal dan global pada setiap sprite, dan hitung ulang hanya ketika sprite atau salah satu leluhurnya berubah). Juga, versi operasi matriks dan vektor XNA yang mengambil nilai dengan referensi sedikit lebih cepat daripada yang saya gunakan di sini.

Tetapi saya akan menjelaskan prosesnya secara lebih rinci di bawah ini, jadi baca terus untuk informasi lebih lanjut.


Langkah 1 - Buat beberapa penyesuaian ke kelas Sprite

Dengan asumsi Anda sudah memiliki Spritekelas di tempat (dan Anda harus) maka Anda harus membuat beberapa modifikasi. Khususnya Anda perlu menambahkan daftar sprite anak-anak, matriks transformasi lokal, dan cara untuk menyebarkan transformasi ke hierarki sprite. Saya menemukan cara termudah untuk melakukannya hanya dengan memberikannya sebagai parameter saat menggambar. Contoh:

public class Sprite
{
    public Vector2 Position { get; set; } 
    public float Rotation { get; set; }
    public Vector2 Scale { get; set; }
    public Texture2D Texture { get; set; }

    public List<Sprite> Children { get; }
    public Matrix LocalTransform { get; }
    public void Draw(SpriteBatch spriteBatch, Matrix parentTransform);
}

Langkah 2 - Menghitung matriks LocalTransform

The LocalTransformmatriks hanya matriks dunia biasa dibangun dari posisi, rotasi dan skala nilai-nilai sprite. Untuk asalnya, saya mengasumsikan pusat sprite:

public Matrix LocalTransform
{
    get 
    {
        // Transform = -Origin * Scale * Rotation * Translation
        return Matrix.CreateTranslation(-Texture.Width/2f, -Texture.Height/2f, 0f) *
               Matrix.CreateScale(Scale.X, Scale.Y, 1f) *
               Matrix.CreateRotationZ(Rotation) *
               Matrix.CreateTranslation(Position.X, Position.Y, 0f);
   }
}

Langkah 3 - Mengetahui cara mengirimkan Matrix ke SpriteBatch

Satu masalah dengan SpriteBatchkelas adalah Drawmetodenya tidak tahu bagaimana mengambil matriks dunia secara langsung. Berikut adalah metode pembantu untuk menjembatani masalah ini:

public static void DecomposeMatrix(ref Matrix matrix, out Vector2 position, out float rotation, out Vector2 scale)
{
    Vector3 position3, scale3;
    Quaternion rotationQ;
    matrix.Decompose(out scale3, out rotationQ, out position3);
    Vector2 direction = Vector2.Transform(Vector2.UnitX, rotationQ);
    rotation = (float) Math.Atan2(direction.Y, direction.X);
    position = new Vector2(position3.X, position3.Y);
    scale = new Vector2(scale3.X, scale3.Y);
}

Langkah 4 - Rendering the Sprite

Catatan: DrawMetode ini mengambil transformasi global induk sebagai parameter. Ada cara lain untuk menyebarkan informasi ini, tetapi saya menemukan ini mudah digunakan.

  1. Hitung transformasi global dengan mengalikan transformasi lokal dengan transformasi global induknya.
  2. Adaptasikan transformasi global ke SpriteBatch dan render sprite saat ini.
  3. Jadikan semua anak yang lulus sebagai global transform sebagai parameter.

Menerjemahkannya ke dalam kode Anda akan mendapatkan sesuatu seperti:

public void Draw(SpriteBatch spriteBatch, Matrix parentTransform)
{
    // Calculate global transform
    Matrix globalTransform = LocalTransform * parentTransform;

    // Get values from GlobalTransform for SpriteBatch and render sprite
    Vector2 position, scale;
    float rotation;
    DecomposeMatrix(ref globalTransform, out position, out rotation, out scale);
    spriteBatch.Draw(Texture, position, null, Color.White, rotation, Vector2.Zero, scale, SpriteEffects.None, 0.0f);

    // Draw Children
    Children.ForEach(c => c.Draw(spriteBatch, globalTransform));
}

Saat menggambar root sprite tidak ada transformasi induk, jadi Anda meneruskannya Matrix.Identity. Anda dapat membuat kelebihan untuk membantu dengan kasus ini:

public void Draw(SpriteBatch spriteBatch) { Draw(spriteBatch, Matrix.Identity); }

Saya memiliki masalah yang sedang saya hadapi. Dalam pengaturan saya saat ini, saya membuat transformasi Matrix ketika memanggil spriteBatch, karena saya menggunakan kamera. Jadi dalam hal kamera saya di 500,50 dan sprite pemain saya di 500,50, pemain harus di tengah. Namun, dalam hal ini, itu tidak menggambar di mana pun
Martin.

@ Martin Apakah Anda menggunakan matriks kamera atau tidak, itu harus bekerja sama saja. Dalam contoh ini saya tidak menggunakan kamera, tetapi pada mesin saya menggunakan kamera dan berfungsi normal. Apakah Anda melewatkan matriks kamera (dan hanya matriks kamera) ke SpriteBatch.Begin? Dan jika Anda ingin segala sesuatu menjadi terpusat, apakah Anda memperhitungkan setengah dari ukuran layar saat membuat matriks kamera?
David Gouveia

Saya menggunakan kelas yang sama seperti di sini untuk kamera, mendapatkan itu metrix kamera, ya. Sekarang berfungsi, saya akan mencoba untuk men-tweak sekarang dan memberi tahu Anda
Martin.

1
SEMPURNA! Luar biasa SEMPURNA! Dan seperti dalam kebanyakan kasus saya punya masalah, itu salah saya. Lihatlah ! i.imgur.com/2CDRP.png
Martin.

Saya tahu saya terlambat ke pesta di sini karena pertanyaan ini sudah sangat lama. Tetapi sementara ini bekerja, itu mengacaukan posisi ketika skala x dan y tidak sama: /
Erik Skoglund

2

Anda harus dapat mengelompokkan mereka ke dalam SpriteBatch dan mereka memindahkannya ke tempatnya dan memutarnya menggunakan matriks.

var matrix = 
    Matrix.CreateRotationZ(radians) *
    Matrix.CreateTranslation(new Vector3(x, y, 0));

SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null, matrix);
SpriteBatch.Draw(player, Vector2.Zero, null, Color.White);
SpriteBatch.Draw(gun, Vector2(handDistance, 0), null, Color.White);
SpriteBatch.End();

Kode tidak diuji dan perkalian matriks saya sangat berkarat tetapi teknik ini berlaku.


Apa yang harus dilakukan jika saya benar-benar MENGGUNAKAN Matriks dalam spriteBatch untuk kamera? spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, null, null, cam.get_transformation(graphics.GraphicsDevice));
Martin.

Saya pikir Anda harus dapat menggunakan Mulai dengan matriks * cam.get_transformation (graphics.GraphicsDevice).
ClassicThunder

SEMPURNA. Saya tidak menjawab dengan sangat cepat.
Martin.

Solusi sederhana, tapi itu agak mengalahkan seluruh tujuan batching sprite yaitu untuk menggambar sprite sebanyak mungkin dalam satu panggilan. Saya mungkin menggunakan ini dalam demo sederhana, tetapi pasti tidak dalam permainan intensif sprite.
David Gouveia

@ Martin Dalam jawaban saya, saya punya contoh bagaimana membuat matriks dunia lengkap untuk sprite. Pada dasarnya urutannya harus -Origin * Scale * Rotation * Translation(di mana semua nilai ini berasal dari sprite).
David Gouveia

0

Saya akan menerapkan ini sedikit berbeda.

Beri kelas sprite Anda daftar untuk anak-anaknya. Kemudian ketika Anda memperbarui posisi dan transformasi sprite, juga terapkan terjemahan yang sama untuk anak-anak dengan setiap loop. Sprite anak harus memiliki koordinat yang didefinisikan dalam ruang model daripada ruang dunia.

Saya suka menggunakan dua rotasi - satu di sekitar asal anak (untuk memutar sprite di tempat) dan satu di sekitar asal orangtua (untuk membuat anak dasarnya mengorbit induknya).

Untuk menggambar, mulailah spritebatch Anda, panggil player Anda. Draw () yang akan menggambar kemudian lewati semua anak-anak dan gambar mereka juga.

Dengan hierarki semacam ini, ketika Anda memindahkan orang tua, semua anak-anaknya ikut serta - juga memungkinkan Anda untuk memindahkan anak-anak secara mandiri.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.