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.
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 .
VertexHelperVertexHelper 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 UIBSedarlah , 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); } }
vertexPosisiPertama, 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.

Tambahkan
UIParticleSystem ke komponen
ParticleSystem
Jalankan adegan dan periksa hasil komponen.

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.

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.

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 .
ExecuteInEditModeAnda 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.

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 .