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
Sprite
toko aList<Sprite> Children
dan menyediakan metode untuk menambah anak baru.
- Masing-masing
Sprite
tahu cara menghitung Matrix LocalTransform
yang didefinisikan relatif terhadap induknya.
- Memanggil
Draw
aSprite
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:
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 Sprite
kelas 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 LocalTransform
matriks 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 SpriteBatch
kelas adalah Draw
metodenya 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: Draw
Metode ini mengambil transformasi global induk sebagai parameter. Ada cara lain untuk menyebarkan informasi ini, tetapi saya menemukan ini mudah digunakan.
- Hitung transformasi global dengan mengalikan transformasi lokal dengan transformasi global induknya.
- Adaptasikan transformasi global ke
SpriteBatch
dan render sprite saat ini.
- 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); }