تحية! تتناول هذه المقالة إنشاء مكونات مرئية خاصة بك في واجهة المستخدم باستخدام مثال مكون لتصور نظام جزيئي في
Canvas 'e.
ستكون هذه المعلومات مفيدة لتنفيذ التأثيرات المختلفة في واجهة المستخدم ، ويمكن استخدامها أيضًا لإنشاء شبكة أو تحسينها.
قليلا من الناحية النظرية أو من أين تبدأ إنشاء المكون
أساس Unity's UI هو
Canvas . هو الذي يستخدمه نظام التقديم لعرض هندسة "متعدد الطبقات" ، وفقًا للتسلسل الهرمي الداخلي لعناصر واجهة المستخدم.
يجب أن يرث أي مكون لواجهة المستخدم المرئية من فئة
الرسوم (أو فئة
MaskableGraphic المشتقة منه) ، والتي تقوم بتمرير جميع البيانات اللازمة إلى مكون
CanvasRenderer لتقديمها . يتم إنشاء البيانات في طريقة
OnPopulateMesh ، والتي تسمى في كل مرة يحتاج فيها المكون إلى تحديث شكله الهندسي (على سبيل المثال ، عند تغيير حجم عنصر).
يتم تمرير
VertexHelper كمعلمة ، مما يساعد في إنشاء شبكة لواجهة المستخدم.
إنشاء مكون
مؤسسة
نبدأ التنفيذ عن طريق إنشاء برنامج نصي
UIParticleSystem يرث من فئة
MaskableGraphic .
MaskableGraphic هو مشتق من الفئة
الرسومية ، وبالإضافة إلى ذلك يوفر العمل مع الأقنعة. تجاوز الأسلوب
OnPopulateMesh .
سيبدو أساس العمل مع
VertexHelper لإنشاء رؤوس نظام الجسيمات الشبكية كما يلي:
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); } } }
أولاً ، تحتاج إلى مسح
VertextHelper من البيانات الموجودة عن طريق استدعاء الأسلوب
مسح . بعد ذلك ، يمكنك البدء في ملء البيانات الجديدة حول القمم. لهذا ، سيتم استخدام الأسلوب
AddUIVertexQuad ، والذي يسمح لك بإضافة معلومات حول 4 رؤوس في وقت واحد. تم اختيار هذه الطريقة لسهولة الاستخدام ، كما كل جسيم مستطيل. يتم وصف كل قمة بواسطة كائن
UIVertex . من بين جميع المعلمات ، نحتاج إلى ملء الموضع واللون وبعض إحداثيات
الأشعة فوق البنفسجية فقط .
VertexHelperلدى VertexHelper مجموعة كاملة من الأساليب لإضافة معلومات vertex ، بالإضافة إلى زوج لتلقي البيانات الحالية. بالنسبة إلى هندسة أكثر تعقيدًا ، فإن أفضل حل هو اختيار طريقة AddUIVertexStream ، التي تقبل قائمة من الرؤوس وقائمة من المؤشرات.
نظرًا لأن كل إطار يوضع موضع الجسيمات ولونها ومعلماتها الأخرى سوف يتغير ، يجب أيضًا تحديث شبكة التقديم.
للقيام بذلك ، سوف يستدعي كل إطار الأسلوب
SetVerticesDirty ، والذي سيضع العلامة على الحاجة لإعادة فرز البيانات الجديدة ، مما سيؤدي إلى استدعاء الأسلوب
OnPopulateMesh . وبالمثل بالنسبة للمادة ، إذا تغيرت خصائصها ، فأنت بحاجة إلى استدعاء الأسلوب
SetMaterialDirty .
protected void Update() { SetVerticesDirty(); }
تجاوز خاصية
mainTexture . يشير إلى أي مادة سيتم تمريرها إلى
CanvasRenderer وتستخدم في المادة ،
خاصية تظليل
_MainTex . للقيام بذلك ، قم بإنشاء حقل
ParticleImage ، والذي سيتم إرجاعه بواسطة خاصية
mainTexture .
public Texture ParticleImage; public override Texture mainTexture { get { return ParticleImage; } }
نظام الجسيمات
سيتم الحصول على البيانات الخاصة بتكوين رؤوس الشبكات من مكون نظام
الجسيمات ، والذي يشارك في جميع العمليات الحسابية على موقع الجزيئات ، وحجمها ، ولونها ، إلخ.
سوف يشارك مكون ParticleSystemRenderer في تقديم الجسيمات ، والذي سيتعين تعطيله ، لذلك ستكون المكونات الأخرى مسؤولة أيضًا عن إنشاء الشبكة
وتقديمها في UI -
UIParticleSystem و
CanvasRenderer .
قم بإنشاء الحقول اللازمة للتشغيل وتهيئتها في طريقة
الاستيقاظ .
UIBehaviourتحتاج Awake ، مثل معظم الطرق ، إلى إعادة تعريفها هنا ، لأنها مدرجة على أنها افتراضية في UIBehaviour . فئة UIBehaviour نفسها مجردة وعملياً لا تحتوي على أي منطق عمل ، لكنها أساسية لفئة الرسوم .
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]; }
سيتم استخدام حقل
_ الجسيمات لتخزين
جسيمات نظام الجسيمات ، و
يتم استخدام
_main للراحة مع وحدة
MainModule .
دعونا نضيف طريقة OnPopulateMesh ، مع أخذ جميع البيانات اللازمة مباشرة من نظام الجسيمات. قم
بإنشاء المتغيرات المساعدة
Vector3 [] _quadCorners و
Vector2 [] _simpleUV .
_quadCorners يحتوي على إحداثيات الزوايا الأربع للمستطيل ، بالنسبة إلى مركز الجسيم. يعتبر الحجم الأولي لكل جسيم مربعًا ذو جوانب 1x1.
_simpleUV - إحداثيات
الأشعة فوق البنفسجية ، في هذه الحالة تستخدم جميع الجسيمات نفس النسيج دون أي تشريد.
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); } }
vertexPositionأولاً ، يتم حساب الموضع المحلي للقمة بالنسبة إلى مركز الجسيمات ، مع مراعاة حجمها (العملية Vector3.Scale (particleSize ، _quadCorners [j]) ) والدوران (ضرب دوران رباعي بواسطة ناقل). بعد أن يضاف موضع الجسيم نفسه إلى النتيجة
الآن لنقم بإنشاء واجهة مستخدم بسيطة للاختبار باستخدام مكونات قياسية.

أضف نظام
UIParticleSystem إلى مكون
نظام الجسيمات
قم بتشغيل المشهد وتحقق من نتيجة المكون.

يتم عرض الجزيئات وفقًا لموضعها في التسلسل الهرمي وتأخذ في الاعتبار الأقنعة المستخدمة. عند تغيير دقة الشاشة ونسبها ، وكذلك عند تغيير
خاصية Canvas Rendere Mode ، تتصرف الجزيئات بشكل مشابه لأي مكون مرئي آخر في
Canvas ويتم عرضها فقط.
SimulationSpace
لأن نضع نظام الجسيمات داخل واجهة المستخدم ، هناك مشكلة في المعلمة
SimulationSpace . عندما يتم محاكاة الجسيمات في الفضاء العالمي ، لا تظهر الجسيمات التي يجب أن تظهر فيها. لذلك ، نضيف حساب موضع الجسيمات حسب قيمة المعلمة.
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; } ... }
محاكاة خصائص particleSystemRenderer
الآن ننفذ جزءًا من وظيفة
ParticleSystemRenderer . وهي خصائص
RenderMode ،
SortMode ،
Pivot .
RenderMode
نحن نقتصر على حقيقة أن الجسيمات سوف تكون دائما موجودة فقط في الطائرة من القماش. لذلك ، نحن نطبق قيمتين فقط:
Billboard و
StretchedBillboard .
دعونا إنشاء التعداد
CanvasParticleSystemRenderMode لدينا لهذا الغرض.
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; } ... }
إذا قمت بتحديد المعلمة
StretchedBillboard ، فسوف يعتمد حجم الجسيم على
معلمات LengthScale و
SpeedScale ، وسيتم توجيه دورانه فقط في اتجاه الحركة.

SortMode
وبالمثل ، قم بإنشاء تعداد
CanvasParticlesSortMode . ونحن ننفذ فقط الفرز حسب عمر الجسيمات.
public enum CanvasParticlesSortMode { None = 0, OldestInFront = 1, YoungestInFront = 2 }
public CanvasParticlesSortMode SortMode;
للفرز ، نحتاج إلى تخزين البيانات على عمر الجسيمات ، والتي سيتم تخزينها في متغير
_particleElapsedLifetime . يتم تطبيق الفرز باستخدام طريقة
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 لتعويض النقطة المركزية للجسيم.
public Vector3 Pivot = Vector3.zero;
وعند حساب موضع قمة الرأس ، نضيف هذه القيمة.
Vector3 cornerPosition = Vector3.Scale(particleSize, _quadCorners[j] + Pivot); Vector3 vertexPosition = rotation * cornerPosition + particlePosition; vertexPosition.z = 0;
حجم قابل للتعديل
إذا كان العنصر الذي يرتبط به نظام الجسيمات لا يحتوي على أحجام ثابتة أو يمكن أن يتغير في وقت التشغيل ، فسيكون من الجيد تكييف حجم نظام الجسيمات. لنجعل المصدر - يتناسب مع حجم العنصر.
يتم
استدعاء الأسلوب
OnRectTransformDimensionsChange عندما يتم
تغيير حجم المكون
RectTransform . نقوم بإعادة تعريف هذه الطريقة من خلال تطبيق تغيير الحجم على الشكل لتناسب أبعاد
RectTransform .
أولاً ، قم بإنشاء المتغيرات لمكون
RectTransform والوحدة النمطية لـ
ShapeModule . لتعطيل تغيير حجم
الأشكال ، قم بإنشاء متغير
ScaleShapeByRectTransform .
أيضًا ، يجب إجراء القياس عند تنشيط المكون لضبط مقياسه الأولي.
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; }
عند الحساب ، يجدر النظر في دوران
الشكل . يجب أن تؤخذ قيم النتيجة النهائية بشكل نمطي ، حيث يمكن أن تكون سالبة ، مما سيؤثر على اتجاه حركة الجزيئات.
لاختبار العملية ، قم بتشغيل
RectTransform بتغيير حجم الرسوم المتحركة مع نظام الجسيمات المرتبطة به.

التهيئة
لكي يتم تنفيذ البرنامج النصي بشكل صحيح في المحرر وتجنب الأخطاء عند استدعاء أسلوب
OnRectTransformDimensionsChange ، نضع تهيئة المتغيرات في طريقة منفصلة. وإضافة دعوته إلى
أساليب OnPopulateMesh و
OnRectTransformDimensionsChange .
ExecuteInEditModeلا تحتاج إلى تحديد سمة ExecuteInEditMode ، لأن يقوم تطبيق Graphic بالفعل بتنفيذ هذا السلوك ويتم تنفيذ البرنامج النصي في المحرر.
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 ... }
يمكن
استدعاء الأسلوب
OnRectTransformDimensionsChange أقدم من
Awake . لذلك ، في كل مرة يتم استدعاء ذلك ، من الضروري تهيئة المتغيرات.
الأداء والتحسين
يعد تقديم الجزيئات هذا أغلى قليلاً من استخدام تطبيق
ParticleSystemRenderer ، والذي يتطلب استخدامًا أكثر حكمة ، خاصة على الأجهزة المحمولة.
تجدر الإشارة أيضًا إلى أنه إذا تم وضع علامة واحدة على الأقل من العناصر
Canvas على أنها
Dirty ، فسيؤدي ذلك إلى إعادة حساب كامل هندسة
Canvas وإنشاء أوامر تجسيد جديدة. إذا كانت واجهة المستخدم تحتوي على الكثير من الأشكال الهندسية المعقدة وحساباتها ، فمن الجدير تقسيمها إلى العديد من اللوحات المدمجة.

ملاحظة: كل شفرة المصدر والعروض التجريبية هي
رابط بوابة .
تم إطلاق المقال منذ عام تقريبًا بعد أن كان مطلوبًا استخدام نظام ParticleSystem في واجهة المستخدم. في ذلك الوقت ، لم أجد حلاً مماثلاً ، ولم تكن الحلول المتاحة هي الحل الأمثل للمهمة الحالية. لكن قبل يومين من نشر هذه المقالة ، أثناء جمع المواد ، وجدت بالصدفة حلاً مماثلاً باستخدام طريقة Graphic.OnPopulateMesh. لذلك ، أعتقد أنه من الضروري تحديد
رابط إلى المستودع .