Buat komponen visual dalam Unity UI. Sistem partikel

Hai Artikel ini adalah tentang membuat komponen visual Anda sendiri di UI menggunakan contoh komponen untuk memvisualisasikan sistem partikel di Canvas 'e.

Informasi ini akan berguna untuk mengimplementasikan berbagai efek dalam antarmuka pengguna, dan juga dapat digunakan untuk menghasilkan mesh atau mengoptimalkannya.

gambar


Sedikit teori atau di mana untuk memulai pembuatan komponen


Dasar untuk Unity's UI adalah Canvas . Dialah yang digunakan oleh sistem render untuk menampilkan geometri "multilayer", sesuai dengan hierarki internal elemen UI.
Setiap komponen antarmuka pengguna visual harus mewarisi dari kelas Grafik (atau kelas MaskableGraphic yang diturunkan), yang meneruskan semua data yang diperlukan ke komponen CanvasRenderer untuk membuatnya. Data dibuat dalam metode OnPopulateMesh , yang dipanggil setiap kali komponen perlu memperbarui geometri (misalnya, ketika mengubah ukuran elemen). VertexHelper dilewatkan sebagai parameter, yang membantu dalam menghasilkan mesh untuk UI.

Pembuatan Komponen


Base


Kami memulai implementasi dengan membuat skrip UIParticleSystem yang mewarisi dari kelas MaskableGraphic . MaskableGraphic adalah turunan dari kelas Grafik dan sebagai tambahan menyediakan pekerjaan dengan topeng. Ganti metode OnPopulateMesh . Dasar untuk bekerja dengan VertexHelper untuk menghasilkan simpul dari sistem partikel mesh akan terlihat seperti ini:

public class UIParticleSystem : MaskableGraphic { protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); ... int particlesCount = ... ; for (int i = 0; i < particlesCount; i++) { Color vertexColor = ... ; Vector2[] vertexUV = ... ; UIVertex[] quadVerts = new UIVertex[4]; for (int j = 0; j < 4; j++) { Vector3 vertixPosition = ... ; quadVerts[j] = new UIVertex() { position = vertixPosition, color = vertexColor, uv0 = vertexUV }; } vh.AddUIVertexQuad(quadVerts); } } } 

Pertama, Anda perlu menghapus VertextHelper dari data yang ada dengan memanggil metode Hapus . Setelah itu, Anda dapat mulai mengisinya dengan data baru tentang puncak. Untuk ini, metode AddUIVertexQuad akan digunakan, yang memungkinkan Anda untuk menambahkan informasi tentang 4 simpul sekaligus. Metode ini telah dipilih untuk kemudahan penggunaan, seperti setiap partikel berbentuk segi empat. Setiap dhuwur dijelaskan oleh objek UIVertex . Dari semua parameter, kita hanya perlu mengisi posisi, warna, dan beberapa koordinat pindaian uv .

VertexHelper
VertexHelper memiliki serangkaian metode untuk menambahkan informasi titik, serta pasangan untuk menerima data saat ini. Untuk geometri yang lebih kompleks, solusi terbaik adalah memilih metode AddUIVertexStream , yang menerima daftar simpul dan daftar indeks.

Karena setiap frame posisi partikel, warna dan parameter lainnya akan berubah, mesh untuk rendering juga harus diperbarui.
Untuk melakukan ini, setiap frame akan memanggil metode SetVerticesDirty , yang akan mengatur bendera pada kebutuhan untuk menghitung kembali data baru, yang akan mengarah pada panggilan ke metode OnPopulateMesh . Demikian pula untuk material, jika propertinya berubah, Anda perlu memanggil metode SetMaterialDirty .

 protected void Update() { SetVerticesDirty(); } 

Override properti mainTexture . Ini menunjukkan tekstur mana yang akan diteruskan ke CanvasRenderer dan digunakan dalam materi, properti _MainTex shader. Untuk melakukan ini, buat bidang ParticleImage , yang akan dikembalikan oleh properti mainTexture .

 public Texture ParticleImage; public override Texture mainTexture { get { return ParticleImage; } } 

Sistem partikel


Data untuk menghasilkan simpul jala akan diambil dari komponen ParticleSystem , yang terlibat dalam semua perhitungan di lokasi partikel, ukurannya, warna, dll.
Komponen ParticleSystemRenderer, yang perlu dinonaktifkan, terlibat dalam rendering partikel, sehingga komponen lain, UIParticleSystem dan CanvasRenderer, akan bertanggung jawab untuk membuat mesh dan merendernya di UI.

Buat bidang yang diperlukan untuk operasi dan inisialisasi mereka dalam metode Sedarlah .

Perilaku UIB
Sedarlah , seperti kebanyakan metode, perlu didefinisikan ulang di sini, karena mereka terdaftar sebagai virtual di UIBehaviour . Kelas UIBehaviour itu sendiri abstrak dan praktis tidak mengandung logika kerja, tetapi dasar untuk kelas Grafik .

 private ParticleSystem _particleSystem; private ParticleSystemRenderer _particleSystemRenderer; private ParticleSystem.MainModule _main; private ParticleSystem.Particle[] _particles; protected override void Awake() { base.Awake(); _particleSystem = GetComponent<ParticleSystem>(); _main = _particleSystem.main; _particleSystemRenderer = GetComponent<ParticleSystemRenderer>(); _particleSystemRenderer.enabled = false; int maxCount = _main.maxParticles; _particles = new ParticleSystem.Particle[maxCount]; } 

Bidang _particles akan digunakan untuk menyimpan partikel ParticleSystem , dan
_main digunakan untuk kenyamanan dengan modul MainModule .

Mari kita tambahkan metode OnPopulateMesh, mengambil semua data yang diperlukan langsung dari sistem partikel. Buat variabel pembantu Vector3 [] _quadCorners dan Vector2 [] _simpleUV .

_quadCorners berisi koordinat 4 sudut persegi panjang, relatif terhadap pusat partikel. Ukuran awal setiap partikel dianggap sebagai kotak dengan sisi 1x1.
_simpleUV - koordinat pemindaian uv , dalam hal ini semua partikel menggunakan tekstur yang sama tanpa perpindahan apa pun.

 private Vector3[] _quadCorners = new Vector3[] { new Vector3(-.5f, -.5f, 0), new Vector3(-.5f, .5f, 0), new Vector3(.5f, .5f, 0), new Vector3(.5f, -.5f, 0) }; private Vector2[] _simpleUV = new Vector2[] { new Vector2(0,0), new Vector2(0,1), new Vector2(1,1), new Vector2(1,0), }; 

 protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); int particlesCount = _particleSystem.GetParticles(_particles); for (int i = 0; i < particlesCount; i++) { var particle = _particles[i]; Vector3 particlePosition = particle.position; Color vertexColor = particle.GetCurrentColor(_particleSystem) * color; Vector3 particleSize = particle.GetCurrentSize3D(_particleSystem); Vector2[] vertexUV = _simpleUV; Quaternion rotation = Quaternion.AngleAxis(particle.rotation, Vector3.forward); UIVertex[]quadVerts = new UIVertex[4]; for (int j = 0; j < 4; j++) { Vector3 cornerPosition = rotation * Vector3.Scale(particleSize, _quadCorners[j]); Vector3 vertexPosition = cornerPosition + particlePosition; vertexPosition.z = 0; quadVerts[j] = new UIVertex(); quadVerts[j].color = vertexColor; quadVerts[j].uv0 = vertexUV[j]; quadVerts[j].position = vertexPosition; } vh.AddUIVertexQuad(quadVerts); } } 

vertexPosisi
Pertama, posisi lokal verteks relatif terhadap pusat partikel dihitung, dengan mempertimbangkan ukurannya (operasi Vector3.Scale (particleSize, _quadCorners [j]) ) dan rotasi (mengalikan rotasi angka empat dengan vektor). Setelah itu posisi partikel itu sendiri ditambahkan ke hasilnya

Sekarang mari kita buat UI sederhana untuk pengujian menggunakan komponen standar.

gambar

Tambahkan UIParticleSystem ke komponen ParticleSystem

gambar

Jalankan adegan dan periksa hasil komponen.

gambar

Partikel ditampilkan sesuai dengan posisi mereka dalam hierarki dan memperhitungkan topeng yang digunakan. Ketika Anda mengubah resolusi layar dan proporsinya, serta ketika mengubah properti Mode Rendere dari Kanvas , partikel berperilaku mirip dengan komponen visual lainnya di Kanvas dan hanya ditampilkan di dalamnya.

Ruang Simulasi


Karena kami menempatkan sistem partikel di dalam UI, ada masalah dengan parameter SimulationSpace . Ketika disimulasikan di ruang dunia, partikel tidak ditunjukkan di mana mereka seharusnya. Oleh karena itu, kami menambahkan perhitungan posisi partikel tergantung pada nilai parameter.

 protected override void OnPopulateMesh(VertexHelper vh) { ... Vector3 particlePosition; switch (_main.simulationSpace) { case ParticleSystemSimulationSpace.World: particlePosition = _rectTransform.InverseTransformPoint(particle.position); break; case ParticleSystemSimulationSpace.Local: particlePosition = particle.position; break; case ParticleSystemSimulationSpace.Custom: if (_main.customSimulationSpace != null) particlePosition = _rectTransform.InverseTransformPoint( _main.customSimulationSpace.TransformPoint(particle.position) ); else particlePosition = particle.position; break; default: particlePosition = particle.position; break; } ... } 

Simulasikan Properti ParticleSystemRenderer


Sekarang kita mengimplementasikan bagian dari fungsionalitas ParticleSystemRenderer . Yakni, sifat-sifat RenderMode , SortMode , Pivot .

Renderder


Kami membatasi diri pada fakta bahwa partikel akan selalu terletak hanya di bidang kanvas. Oleh karena itu, kami hanya menerapkan dua nilai: Billboard dan StretchedBillboard .
Mari kita buat enumerasi kami CanvasParticleSystemRenderMode untuk ini.

 public enum CanvasParticleSystemRenderMode { Billboard = 0, StretchedBillboard = 1 } 

 public CanvasParticleSystemRenderMode RenderMode; public float SpeedScale = 0f; public float LengthScale = 1f; protected override void OnPopulateMesh(VertexHelper vh) { ... Quaternion rotation; switch (RenderMode) { case CanvasParticleSystemRenderMode.Billboard: rotation = Quaternion.AngleAxis(particle.rotation, Vector3.forward); break; case CanvasParticleSystemRenderMode.StretchedBillboard: rotation = Quaternion.LookRotation(Vector3.forward, particle.totalVelocity); float speed = particle.totalVelocity.magnitude; particleSize = Vector3.Scale(particleSize, new Vector3(LengthScale + speed * SpeedScale, 1f, 1f)); rotation *= Quaternion.AngleAxis(90, Vector3.forward); break; default: rotation = Quaternion.AngleAxis(particle.rotation, Vector3.forward); break; } ... } 

Jika Anda memilih parameter StretchedBillboard , ukuran partikel akan bergantung pada parameter LengthScale dan SpeedScale , dan putarannya akan diarahkan hanya ke arah gerakan.

gambar

Metode penyortiran


Demikian pula, buat enumerasi CanvasParticlesSortMode . dan kami hanya menerapkan pengurutan berdasarkan umur partikel.

 public enum CanvasParticlesSortMode { None = 0, OldestInFront = 1, YoungestInFront = 2 } 

 public CanvasParticlesSortMode SortMode; 

Untuk mengurutkan, kita perlu menyimpan data pada masa hidup partikel, yang akan disimpan dalam variabel _particleElapsedLifetime . Penyortiran diimplementasikan menggunakan metode Array.Sort .

 private float[] _particleElapsedLifetime; protected override void Awake() { ... _particles = new ParticleSystem.Particle[maxCount]; _particleElapsedLifetime = new float[maxCount]; } protected override void OnPopulateMesh(VertexHelper vh) { vh.Clear(); int particlesCount = _particleSystem.GetParticles(_particles); for (int i = 0; i < particlesCount; i++) _particleElapsedLifetime[i] = _particles[i].startLifetime - _particles[i].remainingLifetime; switch (SortMode) { case CanvasParticlesSortMode.None: break; case CanvasParticlesSortMode.OldestInFront: Array.Sort(_particleElapsedLifetime, _particles, 0, particlesCount,Comparer<float>.Default); Array.Reverse(_particles, 0, particlesCount); break; case CanvasParticlesSortMode.YoungestInFront: Array.Sort(_particleElapsedLifetime, _particles, 0, particlesCount, Comparer<float>.Default); break; } ... } 

Pivot


Buat bidang Pivot untuk mengimbangi titik tengah partikel.

 public Vector3 Pivot = Vector3.zero; 

Dan ketika menghitung posisi titik, kami menambahkan nilai ini.

 Vector3 cornerPosition = Vector3.Scale(particleSize, _quadCorners[j] + Pivot); Vector3 vertexPosition = rotation * cornerPosition + particlePosition; vertexPosition.z = 0; 

Ukuran disesuaikan


Jika elemen yang dilekatkan sistem partikel tidak memiliki ukuran tetap atau dapat berubah pada waktu berjalan, alangkah baiknya menyesuaikan ukuran sistem partikel. Mari kita buat sumbernya - bentuknya proporsional dengan ukuran elemen.

Metode OnRectTransformDimensionsChange dipanggil ketika komponen RectTransform diubah ukurannya . Kami mendefinisikan kembali metode ini dengan menerapkan perubahan skala ke bentuk agar sesuai dengan dimensi RectTransform .

Pertama, buat variabel untuk komponen RectTransform dan modul ShapeModule . Untuk menonaktifkan penskalaan bentuk, buat variabel ScaleShapeByRectTransform .

Selain itu, penskalaan harus dilakukan ketika komponen diaktifkan untuk mengatur skala awalnya.

 private RectTransform _rectTransform; private ParticleSystem.ShapeModule _shape; public bool ScaleShapeByRectTransform; protected override void Awake() { ... _rectTransform = GetComponent<RectTransform>(); _shape = _particleSystem.shape; ... } protected override void OnEnable() { base.OnEnable(); ScaleShape(); } protected override void OnRectTransformDimensionsChange() { base.OnRectTransformDimensionsChange(); ScaleShape(); } protected void ScaleShape() { if (!ScaleShapeByRectTransform) return; Rect rect = _rectTransform.rect; var scale = Quaternion.Euler(_shape.rotation) * new Vector3(rect.width, rect.height, 0); scale = new Vector3(Mathf.Abs(scale.x), Mathf.Abs(scale.y), Mathf.Abs(scale.z)); _shape.scale = scale; } 

Saat menghitung, ada baiknya mempertimbangkan rotasi Bentuk . Nilai-nilai hasil akhir harus diambil modulo, karena mereka bisa berubah menjadi negatif, yang akan mempengaruhi arah gerak partikel.

Untuk menguji operasi, jalankan animasi mengubah ukuran RectTransform dengan sistem partikel yang melekat padanya.

gambar

Inisialisasi


Agar skrip dijalankan dengan benar di editor dan menghindari kesalahan saat memanggil metode OnRectTransformDimensionsChange , kami mengeluarkan inisialisasi variabel dalam metode terpisah. Dan tambahkan panggilannya ke metode OnPopulateMesh dan OnRectTransformDimensionsChange .

ExecuteInEditMode
Anda tidak perlu menentukan atribut ExecuteInEditMode , karena Grafik sudah mengimplementasikan perilaku ini dan skrip dieksekusi di editor.

 private bool _initialized; protected void Initialize() { if (_initialized) return; _initialized = true; _rectTransform = GetComponent<RectTransform>(); _particleSystem = GetComponent<ParticleSystem>(); _main = _particleSystem.main; _textureSheetAnimation = _particleSystem.textureSheetAnimation; _shape = _particleSystem.shape; _particleSystemRenderer = GetComponent<ParticleSystemRenderer>(); _particleSystemRenderer.enabled = false; _particleSystemRenderer.material = null; var maxCount = _main.maxParticles; _particles = new ParticleSystem.Particle[maxCount]; _particlesLifeProgress = new float[maxCount]; _particleRemainingLifetime = new float[maxCount]; } protected override void Awake() { base.Awake(); Initialize(); } protected override void OnPopulateMesh(VertexHelper vh) { Initialize(); ... } protected override void OnRectTransformDimensionsChange() { #if UNITY_EDITOR Initialize(); #endif ... } 

Metode OnRectTransformDimensionsChange dapat dipanggil lebih awal dari Sedarlah . Oleh karena itu, setiap kali dipanggil, perlu menginisialisasi variabel.

Performa dan Optimasi


Render partikel ini sedikit lebih mahal daripada menggunakan ParticleSystemRenderer , yang membutuhkan penggunaan yang lebih hati-hati, khususnya pada perangkat seluler.
Perlu juga dicatat bahwa jika setidaknya satu dari elemen Canvas ditandai sebagai Dirty , ini akan mengarah pada penghitungan ulang seluruh geometri Canvas dan pembuatan perintah rendering baru. Jika UI berisi banyak geometri kompleks dan kalkulasinya, maka ada baiknya Anda membaginya menjadi beberapa kanvas tertanam.

gambar

PS: Semua kode sumber dan demo adalah tautan git .
Artikel ini diluncurkan hampir setahun yang lalu setelah diharuskan menggunakan ParticleSystem di UI. Saat itu, saya tidak menemukan solusi yang sama, dan yang tersedia tidak optimal untuk tugas saat ini. Tetapi beberapa hari sebelum publikasi artikel ini, saat mengumpulkan materi, saya tidak sengaja menemukan solusi yang sama menggunakan metode Graphic.OnPopulateMesh. Oleh karena itu, saya menganggap perlu untuk menentukan tautan ke repositori .

Source: https://habr.com/ru/post/id464961/


All Articles