التلاعب في الوقت الحقيقي للشبكات على الوحدة

الصورة

أحد مزايا Unity كمنصة لتطوير الألعاب هو محركها القوي ثلاثي الأبعاد. في هذا البرنامج التعليمي ، ستتعرف على عالم الكائنات ثلاثية الأبعاد ومعالجة الشبكة.

نظرًا لنمو تقنيات الواقع الافتراضي والمعزز (VR / AR) ، يواجه معظم المطورين مفاهيم رسومات ثلاثية الأبعاد معقدة. دع هذا البرنامج التعليمي يكون نقطة البداية بالنسبة لهم. لا تقلق ، لن تكون هناك رياضيات ثلاثية الأبعاد معقدة - فقط القلوب والرسومات والسهام والكثير من الأشياء المثيرة للاهتمام!

ملاحظة: هذا البرنامج التعليمي مخصص للمستخدمين المطلعين على Unity IDE ولديهم بعض الخبرة في البرمجة في C #. إذا لم يكن لديك هذه المعرفة ، فقم أولاً بدراسة البرامج التعليمية مقدمة إلى Unity UI ومقدمة إلى Unity Scripting .

ستحتاج إلى إصدار Unity لا يقل عن 2017.3.1. يمكن تنزيل أحدث إصدار من Unity من هنا . يستخدم هذا البرنامج التعليمي محررًا مخصصًا ، ويمكنك معرفة المزيد عنها من البرنامج التعليمي Extended the Unity Editor .

للوصول إلى العمل


للبدء ، تعرف على الشروط الأساسية للرسومات ثلاثية الأبعاد ، والتي ستسمح لك بفهم البرنامج التعليمي بشكل أفضل.

المصطلحات التقنية الأساسية للرسومات ثلاثية الأبعاد:

  • القمم : كل ​​قمة هي نقطة في الفضاء ثلاثي الأبعاد.
  • شبكة : تحتوي على جميع القمم والحواف والمثلثات والمعايير وبيانات الأشعة فوق البنفسجية للنموذج.
  • تصفية شبكة : يخزن بيانات شبكة النموذج.
  • Mesh Renderer : لعرض بيانات الشبكة في المشهد.
  • المعايير : ناقل الرأس أو السطح. يتم توجيهها إلى الخارج ، متعامدة على سطح الشبكة.
  • خطوط / حواف : خطوط غير مرئية تربط القمم ببعضها البعض.
  • المثلثات : تتكون من ربط ثلاث قمم.
  • خريطة الأشعة فوق البنفسجية : تعلق المواد على كائن ، مما يخلق نسيجًا ولونًا له.

يبدأ تشريح كائن ثلاثي الأبعاد بشبكته. يبدأ إنشاء هذه الشبكة في قمتها. تشكل الخطوط غير المرئية التي تربط هذه القمم مثلثات تحدد الشكل الأساسي للكائن.


ثم تقوم البيانات العادية وبيانات الأشعة فوق البنفسجية بتعيين التظليل واللون والملمس. يتم تخزين بيانات الشبكة في مرشح شبكة ، ويستخدم عارض الشبكة هذه البيانات لرسم كائن في المشهد.

أي أن الرمز الزائف لإنشاء نموذج ثلاثي الأبعاد يبدو كما يلي:

  • إنشاء شبكة جديدة تسمى "myMesh".
  • إضافة بيانات إلى خصائص القمم والمثلثات myMesh.
  • قم بإنشاء مرشح شبكة جديد يسمى "myMeshFilter".
  • قم بتعيين خاصية شبكة myMeshFilter إلى myMesh.

بمجرد أن تتقن الأساسيات ، قم بتنزيل المشروع وفك ضغط الملفات وتشغيل قطعة عمل المشروع في Unity. انظر إلى بنية المجلد في نافذة المشروع :


وصف المجلدات:

  • Prefabs : يحتوي على Sphere الجاهزة التي سيتم استخدامها لحفظ الشبكة ثلاثية الأبعاد أثناء تنفيذ التطبيق.
  • المشاهد : تحتوي على المشاهد الثلاثة التي نستخدمها في هذا البرنامج التعليمي.
  • المحرر : تمنحنا النصوص البرمجية الموجودة داخل هذا المجلد الميزات الفائقة في المحرر الذي نستخدمه في التطوير.
  • البرامج النصية : إليك البرامج النصية لوقت التشغيل التي ترتبط بـ GameObject ويتم تنفيذها عند النقر فوق تشغيل .
  • المواد : يحتوي هذا المجلد على مادة الشبكة.

في القسم التالي ، سننشئ محررًا مخصصًا لتصور إنشاء شبكة ثلاثية الأبعاد.

تغيير الشبكات باستخدام محرر مخصص


افتح العرض التوضيحي 01 Mesh Study الموجود في مجلد Scenes . في نافذة المشهد ، سترى مكعبًا ثلاثي الأبعاد:


قبل الدخول في الشبكة ، دعنا نلقي نظرة على البرنامج النصي المحرر المخصص.

تحرير برنامج نصي محرر


حدد مجلد Editor في نافذة Project . تضيف البرامج النصية في هذا المجلد وظائف إلى المحرر (المحرر) أثناء التطوير ولا تتوفر في وضع البناء.


افتح MeshInspector.cs وعرض التعليمات البرمجية المصدر. يجب أن تنفذ جميع البرامج النصية الخاصة Editor فئة Editor ، حيث CustomEditor سمة CustomEditor فئة Editor بنوع الكائن المخصص لها. OnSceneGUI() هي طريقة حدث تسمح OnSceneGUI() في نافذة المشهد ؛ يسمح لك OnInspectorGUI() بإضافة عناصر GUI إضافية إلى المفتش.

في MeshInspector.cs ، قبل بدء فئة MeshInspector أضف ما يلي:

 [CustomEditor(typeof(MeshStudy))] 

شرح الكود: CustomEditor السمة CustomEditor الوحدة بنوع الكائن الذي يمكن لفئة المحرر المخصص تعديله.

في OnSceneGUI() قبل EditMesh() أضف ما يلي:

 mesh = target as MeshStudy; Debug.Log("Custom editor is running"); 

شرح الكود: تحتوي فئة Editor على متغير target قياسي. target هنا هو التحويل إلى MeshStudy . الآن سوف يرسم المحرر المخصص جميع GameObjects في نافذة المشهد و MeshStudy.cs المرفقة بها. تسمح لك إضافة رسائل تصحيح الأخطاء بالتحقق في وحدة التحكم من أن المحرر المخصص يعمل بالفعل.

احفظ الملف وارجع إلى الوحدة. انتقل إلى مجلد البرامج النصية واسحب MeshStudy.cs إلى المكعب GameObject في التسلسل الهرمي لإرفاقه.


الآن يجب عرض الرسالة "محرر مخصص قيد التشغيل" في وحدة التحكم ، وهذا يعني أننا فعلنا كل شيء بشكل صحيح! يمكنك حذف رسالة تصحيح الأخطاء بحيث لا تزعجنا في وحدة التحكم.

الاستنساخ وإلقاء الشبكة


عند العمل مع شبكة ثلاثية الأبعاد في وضع التحرير باستخدام المحرر المخصص ، احرص على عدم استبدال شبكة الوحدة الافتراضية. إذا حدث ذلك ، فسيتعين عليك إعادة تشغيل الوحدة.

لنسخ الشبكة بأمان دون الكتابة فوق النموذج الأصلي ، قم بإنشاء نسخة من الشبكة من خاصية MeshFilter.sharedmesh وقم بتعيينها لمرشح الشبكة مرة أخرى.

للقيام بذلك ، انقر نقرًا مزدوجًا فوق MeshStudy.cs في مجلد البرامج النصية لفتح الملف في محرر التعليمات البرمجية. يرث هذا البرنامج النصي من فئة MonoBehaviour ، ولا يتم تنفيذ وظيفته Start() في وضع التحرير.

في MeshStudy.cs ، قبل بدء فئة MeshStudy أضف ما يلي:

 [ExecuteInEditMode] 

شرح الكود: بعد إضافة هذه السمة ، سيتم تنفيذ وظيفة Start() في وضع التشغيل وفي وضع التحرير. الآن يمكننا أولاً إنشاء كائن الشبكة واستنساخه.

في InitMesh() أضف التعليمات البرمجية التالية:

 oMeshFilter = GetComponent<MeshFilter>(); oMesh = oMeshFilter.sharedMesh; //1 cMesh = new Mesh(); //2 cMesh.name = "clone"; cMesh.vertices = oMesh.vertices; cMesh.triangles = oMesh.triangles; cMesh.normals = oMesh.normals; cMesh.uv = oMesh.uv; oMeshFilter.mesh = cMesh; //3 vertices = cMesh.vertices; //4 triangles = cMesh.triangles; isCloned = true; Debug.Log("Init & Cloned"); 

شرح الكود:

  1. الحصول على شبكة oMesh الأصلية من مكون MeshFilter .
  2. نسخ cMesh إلى cMesh شبكة جديد.
  3. يعين مرشح شبكة الشبكة المنسوخ مرة أخرى.
  4. تحديث المتغيرات المحلية.

احفظ الملف وارجع إلى الوحدة. يجب عرض الرسالة "Init & Cloned" في وحدة تحكم التصحيح. حدد GameObject Cube في التسلسل الهرمي وتحقق من خصائصه في المفتش . يجب أن يعرض " تصفية الشبكة" أصل شبكة يسمى استنساخ . عظيم! هذا يعني أننا نجحنا في استنساخ الشبكة.


في مجلد Editor ، انتقل إلى MeshInspector.cs . في OnInspectorGUI() ، بعد السطر الثاني من التعليمات البرمجية ، أضف ما يلي:

 if (GUILayout.Button("Reset")) //1 { mesh.Reset(); //2 } 

شرح الكود:

  1. يرسم هذا الرمز زر إعادة الضبط في المفتش .
  2. عند الضغط عليه ، فإنه يستدعي وظيفة Reset() في MeshStudy.cs .

احفظ الملف وافتح MeshStudy.cs وأضف الكود التالي إلى وظيفة Reset() :

 if (cMesh != null && oMesh != null) //1 { cMesh.vertices = oMesh.vertices; //2 cMesh.triangles = oMesh.triangles; cMesh.normals = oMesh.normals; cMesh.uv = oMesh.uv; oMeshFilter.mesh = cMesh; //3 vertices = cMesh.vertices; //4 triangles = cMesh.triangles; } 

شرح الكود:

  1. التحقق من وجود المصدر وشبكة مستنسخة.
  2. إعادة cMesh إلى الشبكة الأصلية.
  3. التعيين إلى cMesh oMeshFilter .
  4. تحديث المتغيرات المحلية.

احفظ الملف وارجع إلى الوحدة. في المفتش ، انقر على زر اختبار التحرير لتشويه شبكة المكعب. بعد ذلك ، انقر فوق زر إعادة الضبط ؛ يجب أن يعود المكعب إلى شكله الأصلي.


شرح القمم والمثلثات في الوحدة


تتكون الشبكة من القمم المتصلة بحواف مثلثات. تحدد المثلثات الشكل الأساسي للكائن.

فئة الشبكة:

  • يتم تخزين القمم Vector3 من قيم Vector3 .
  • يتم تخزين المثلثات كمصفوفة صحيحة تتوافق مع مؤشرات مجموعة القمة.

بمعنى ، في شبكة رباعية بسيطة تتكون من أربعة رؤوس ومثلثين ، ستبدو بيانات الشبكة كما يلي:


رسم خرائط الرأس


هنا نريد عرض رؤوس المكعب كنقاط زرقاء.

في MeshInspector.cs سنذهب إلى وظيفة EditMesh() ونضيف ما يلي:

 handleTransform = mesh.transform; //1 handleRotation = Tools.pivotRotation == PivotRotation.Local ? handleTransform.rotation : Quaternion.identity; //2 for (int i = 0; i < mesh.vertices.Length; i++) //3 { ShowPoint(i); } 

شرح الكود:

  1. يحصل handleTransform على قيم التحويل من mesh .
  2. يحصل handleRotation على وضع التدوير للمفصل الحالي.
  3. اجتياز رؤوس الشبكة وارسم النقاط باستخدام ShowPoint() .

في ShowPoint() ، مباشرةً بعد //draw dot ، أضف ما يلي:

 Vector3 point = handleTransform.TransformPoint(mesh.vertices[index]); 

شرح الكود: هذا الخط يحول الموقع المحلي للرأس إلى إحداثيات في الفضاء العالمي.

في نفس الوظيفة ، في كتلة if ، بعد سطر التعليمات البرمجية الذي تمت إضافته للتو ، أضف ما يلي:

 Handles.color = Color.blue; point = Handles.FreeMoveHandle(point, handleRotation, mesh.handleSize, Vector3.zero, Handles.DotHandleCap); 

شرح الكود:

  1. يضبط لون النقطة وحجمها Handles باستخدام فئة المساعد Handles .
  2. Handles.FreeMoveHandle() ينشئ مناور حركة غير محدود يبسط عملية السحب والإفلات ، وهو أمر مفيد لنا في القسم التالي.

احفظ الملف وارجع إلى الوحدة. تحقق من خاصية المكعب في المفتش وتأكد من تمكين الخيار Move Vertex Point . يجب أن ترى الآن أن الشبكة على الشاشة مميزة بالعديد من النقاط الزرقاء. ها هم - قمم شبكة المكعب! حاول القيام بذلك مع كائنات ثلاثية الأبعاد أخرى ولاحظ النتائج.


تحريك قمة واحدة


لنبدأ بالخطوة الأبسط للتلاعب بالشبكة - تحريك قمة واحدة.

انتقل إلى MeshInspector.cs . داخل ShowPoint() ، مباشرة بعد //drag التعليق مباشرة قبل أقواس الإغلاق للكتلة if ، أضف ما يلي:

 if (GUI.changed) //1 { mesh.DoAction(index, handleTransform.InverseTransformPoint(point)); //2 } 

شرح الكود:

  1. GUI.changed جميع التغييرات التي تحدث مع النقاط ، ويعمل بشكل جيد مع Handles.FreeMoveHandle() للتعرف على عملية السحب والإفلات.
  2. بالنسبة mesh.DoAction() القابل للسحب ، تتلقى الدالة mesh.DoAction() الفهرس وقيم التحويل كمعلمات. نظرًا لأن قيم تحويل الرأس في الفضاء العالمي ، فإننا نحولها إلى مساحة محلية باستخدام InverseTransformPoint() .

احفظ ملف البرنامج النصي وانتقل إلى MeshStudy.cs . في DoAction() ، بعد أقواس الفتح ، أضف ما يلي:

 PullOneVertex(index, localPos); 

ثم أضف ما يلي إلى PullOneVertex() :

 vertices[index] = newPos; //1 cMesh.vertices = vertices; //2 cMesh.RecalculateNormals(); //3 

شرح الكود:

  1. نقوم بتحديث قمة الهدف بالقيمة newPos .
  2. cMesh.vertices قيم قمة محدثة مرة أخرى إلى cMesh.vertices .
  3. في RecalculateNormals() بإعادة حساب الشبكة وإعادة رسمها بحيث تتطابق مع التغييرات.

احفظ الملف وارجع إلى الوحدة. حاول سحب النقاط على المكعب. هل رأيت شبكة مكسورة؟


يبدو أن بعض القمم لها نفس الوضع ، لذلك عندما نسحب واحدة فقط ، تبقى القمم المتبقية خلفها ، وتتكسر الشبكة. في القسم التالي ، سنقوم بإصلاح هذه المشكلة.

إيجاد جميع القمم المتشابهة


بصريا ، تتكون شبكة المكعب من ثمانية قمم وستة جوانب و 12 مثلثات. دعونا نتحقق مما إذا كان الأمر كذلك.


افتح MeshStudy.cs ، وألق نظرة أمام الدالة Start() وابحث عن متغير vertices . سنرى ما يلي:

 [HideInInspector] public Vector3[] vertices; 

شرح الكود: [HideInInspector] يخفي متغير مشترك من نافذة المفتش .

التعليق على هذه السمة:

 //[HideInInspector] public Vector3[] vertices; 

ملاحظة: يساعد إخفاء قيم الذروة [HideInInspector] شبكات أكثر تعقيدًا ثلاثية الأبعاد. نظرًا لأن حجم صفيف الرأس يمكن أن يصل إلى آلاف العناصر ، فقد يؤدي ذلك إلى تثبيط الوحدة عند محاولة عرض قيمة الصفيف في المفتش.

احفظ الملف وارجع إلى الوحدة. انتقل إلى المفتش . الآن ، تحت مكون البرنامج النصي لدراسة شبكة ، ظهرت خاصية القمم . انقر على أيقونة السهم المجاورة لها ؛ حتى تقوم Vector3 مجموعة عناصر Vector3 .


يمكنك أن ترى أن حجم المصفوفة هو 24 ، أي أن القمم لها نفس الموضع! قبل المتابعة ، تأكد من عدم [HideInInspector] .

لماذا يوجد 24 قمة؟
هناك العديد من النظريات حول هذا الموضوع. لكن أبسط إجابة هي: المكعب له ستة جوانب ، وكل جانب يتكون من أربعة قمم تشكل مستوى.

لذلك ، يكون الحساب كما يلي: 6 × 4 = 24 قمة.

يمكنك البحث عن إجابات أخرى. ولكن في الوقت الحالي ، من السهل معرفة أن بعض الشبكات سيكون لها رؤوس لها نفس الوضع.

في MeshStudy.cs ، استبدل جميع التعليمات البرمجية الموجودة داخل الدالة DoAction() بما يلي:

 PullSimilarVertices(index, localPos); 

دعنا PullSimilarVertices() إلى وظيفة PullSimilarVertices() وإضافة ما يلي:

 Vector3 targetVertexPos = vertices[index]; //1 List<int> relatedVertices = FindRelatedVertices(targetVertexPos, false); //2 foreach (int i in relatedVertices) //3 { vertices[i] = newPos; } cMesh.vertices = vertices; //4 cMesh.RecalculateNormals(); 

شرح الكود:

  1. نحصل على موضع قمة الهدف ، والتي سيتم استخدامها كوسيطة FindRelatedVertices() .
  2. تُرجع هذه الطريقة قائمة الفهارس (المقابلة للقمم) التي لها نفس موضع الرأس المستهدف.
  3. newPos الحلقة القائمة بالكامل newPos القمم المقابلة لـ newPos .
  4. cMesh.vertices vertices المحدثة مرة أخرى إلى cMesh.vertices . ثم نسمي RecalculateNormals() لإعادة رسم الشبكة بالقيم الجديدة.

احفظ الملف وارجع إلى الوحدة. اسحب أي من القمم ؛ الآن يجب أن تحتفظ الشبكة بشكلها ولا تنهار.


الآن بعد أن أكملنا الخطوة الأولى في معالجة الشبكات ، احفظ المشهد وانتقل إلى القسم التالي.

معالجة شبكة


في هذا القسم ، ستتعلم كيفية معالجة الشبكات في الوقت الفعلي. هناك العديد من الطرق ، ولكن في هذا البرنامج التعليمي سنلقي نظرة على أبسط نوع من معالجة الشبكة ، أي تحريك رؤوس الشبكات التي تم إنشاؤها سابقًا.

جمع الفهارس المختارة


لنبدأ باختيار الذروات التي سننقلها في الوقت الفعلي.

افتح Scene 02 Create Heart Mesh من مجلد Scenes . في نافذة المشهد ، سترى كرة حمراء. حدد المجال في التسلسل الهرمي وانتقل إلى المفتش . سترى أن مكون البرنامج النصي Heart Mesh مرفق بالكائن.

نحتاج الآن إلى البرنامج النصي Editor لهذا الكائن لعرض رؤوس الشبكة في نافذة Scene. انتقل إلى مجلد Editor وانقر نقرًا مزدوجًا فوق HeartMeshInspector.cs .

في ShowHandle() ، داخل كتلة if ، أضف ما يلي:

 Handles.color = Color.blue; if (Handles.Button(point, handleRotation, mesh.pickSize, mesh.pickSize, Handles.DotHandleCap)) //1 { mesh.selectedIndices.Add(index); //2 } 

شرح الكود:

  1. يضبط ويعرض رؤوس الشبكة كنوع Handles.Button .
  2. عند النقر فوقه ، فإنه يضيف الفهرس المحدد إلى قائمة الضغط على mesh.selectedIndices .

في OnInspectorGUI() ، قبل قوس الإغلاق ، أضف ما يلي:

 if (GUILayout.Button("Clear Selected Vertices")) { mesh.ClearAllData(); } 

شرح الكود: هذه هي الطريقة التي نضيف بها زر إعادة الضبط إلى المفتش لاستدعاء mesh.ClearAllData() .

احفظ الملف وافتح HeartMesh.cs من مجلد البرامج النصية . في دالة ClearAllData() ، أضف ما يلي:

 selectedIndices = new List<int>(); targetIndex = 0; targetVertex = Vector3.zero; 

شرح الكود: يمحو الكود القيم الموجودة في targetIndex selectedIndices و targetIndex . كما يعيد تعيين targetVertex .

احفظ الملف وارجع إلى الوحدة. حدد المجال وانتقل إلى المفتش لمكون البرنامج النصي HeartMesh . قم بتوسيع المؤشرات المحددة بالضغط على أيقونة السهم المجاورة لها. سيسمح لنا هذا بتتبع كل قمة يتم إضافتها إلى القائمة.

تمكين وضع التعديل باستخدام مربع الاختيار المجاور له. ونتيجة لذلك ، سيتم رسم رؤوس الشبكة في نافذة المشهد. النقر على النقاط الزرقاء في المؤشرات المحددة يجب أن يغير القيم وفقًا لذلك. اختبر أيضًا زر مسح القمم المحددة للتأكد من أنه يمسح جميع القيم.


ملاحظة: في المفتش المخصص المعدل ، لدينا خيار إظهار / إخفاء معالج التحويل باستخدام إظهار مقبض التحويل . لذلك لا داعي للذعر إذا لم تجد معالج التحويل في مشاهد أخرى! قم بتشغيله قبل الخروج.

تحويل المجال إلى قلب


يتكون تغيير رؤوس الشبكات في الوقت الفعلي بشكل أساسي من ثلاث خطوات:

  1. انسخ رؤوس الشبكة الحالية (قبل الرسم المتحرك) إلى mVertices .
  2. mVertices الحسابات وتغيير القيم في mVertices .
  3. قم بتحديث رؤوس الشبكة الحالية باستخدام mVertices عند التغيير في كل خطوة ودع الوحدة تقوم بحساب القيم الطبيعية تلقائيًا.

افتح HeartMesh.cs والمتغيرات التالية قبل وظيفة Start() :

 public float radiusofeffect = 0.3f; //1 public float pullvalue = 0.3f; //2 public float duration = 1.2f; //3 int currentIndex = 0; //4 bool isAnimate = false; float starttime = 0f; float runtime = 0f; 

شرح الكود:

  1. نصف قطر المنطقة المتضررة من قمة الهدف.
  2. قوة السحب.
  3. مدة الرسوم المتحركة.
  4. الفهرس الحالي لقائمة المؤشرات selectedIndices .

في دالة Init() ، قبل كتلة if ، أضف ما يلي:

 currentIndex = 0; 

شرح الكود: في بداية اللعبة ، currentIndex على 0 ، وهو أول فهرس لقائمة المؤشرات selectedIndices .

في نفس دالة Init() ، قبل قوس الإغلاق للكتلة else ، أضف ما يلي:

 StartDisplacement(); 

شرح الكود: قم بتشغيل StartDisplacement() إذا كان isEditMode خطأ.

داخل StartDisplacement() ، أضف ما يلي:

 targetVertex = oVertices[selectedIndices[currentIndex]]; //1 starttime = Time.time; //2 isAnimate = true; 

شرح الكود:

  1. حدد targetVertex لبدء الرسوم المتحركة.
  2. قم بتعيين وقت البدء وتغيير قيمة isAnimate إلى true.

بعد وظيفة StartDisplacement() ، قم بإنشاء وظيفة FixedUpdate() بالكود التالي:

 void FixedUpdate() //1 { if (!isAnimate) //2 { return; } runtime = Time.time - starttime; //3 if (runtime < duration) //4 { Vector3 targetVertexPos = oFilter.transform.InverseTransformPoint(targetVertex); DisplaceVertices(targetVertexPos, pullvalue, radiusofeffect); } else //5 { currentIndex++; if (currentIndex < selectedIndices.Count) //6 { StartDisplacement(); } else //7 { oMesh = GetComponent<MeshFilter>().mesh; isAnimate = false; isMeshReady = true; } } } 

شرح الكود:

  1. يتم تنفيذ الوظيفة FixedUpdate() في حلقة FPS ثابتة.
  2. إذا كانت isAnimate خاطئة ، isAnimate بتخطي الرمز التالي.
  3. تغيير الرسوم المتحركة runtime .
  4. إذا كان runtime ضمن duration ، فإننا نحصل على إحداثيات العالم من targetVertex و DisplaceVertices() ، والتي تغطي الرأس المستهدف pullvalue و radiusofeffect .
  5. خلاف ذلك ، انتهى الوقت. أضف واحدًا إلى currentIndex .
  6. تحقق مما إذا كان currentIndex بين currentIndex selectedIndices . انتقل إلى القمة التالية في القائمة باستخدام StartDisplacement() .
  7. خلاف ذلك ، في نهاية القائمة ، قم بتغيير بيانات oMesh إلى الشبكة الحالية isAnimate إلى false لإيقاف الرسوم المتحركة.

في DisplaceVertices() أضف ما يلي:

 Vector3 currentVertexPos = Vector3.zero; float sqrRadius = radius * radius; //1 for (int i = 0; i < mVertices.Length; i++) //2 { currentVertexPos = mVertices[i]; float sqrMagnitute = (currentVertexPos - targetVertexPos).sqrMagnitude; //3 if (sqrMagnitute > sqrRadius) { continue; //4 } float distance = Mathf.Sqrt(sqrMagnitute); //5 float falloff = GaussFalloff(distance, radius); Vector3 translate = (currentVertexPos * force) * falloff; //6 translate.z = 0f; Quaternion rotation = Quaternion.Euler(translate); Matrix4x4 m = Matrix4x4.TRS(translate, rotation, Vector3.one); mVertices[i] = m.MultiplyPoint3x4(currentVertexPos); } oMesh.vertices = mVertices; //7 oMesh.RecalculateNormals(); 

شرح الكود:

  1. مربع نصف القطر.
  2. نحن ندور عبر كل قمة للشبكة.
  3. sqrMagnitude بين currentVertexPos و targetVertexPos .
  4. إذا تجاوز sqrRadius ، فانتقل إلى القمة التالية.
  5. خلاف ذلك ، استمر في تحديد قيمة falloff ، والتي تعتمد على distance القمة الحالية من نقطة مركز النطاق.
  6. Vector3 موضع Vector3 الجديد Vector3 بتطبيق تحويله على قمة الرأس الحالية.
  7. عند الخروج من الحلقة ، نقوم بتعيين قيم mVertices تغييرها إلى mVertices ، mVertices الوحدة على إعادة حساب القيم الطبيعية.

مصدر تكنولوجيا Falloff
يتم أخذ الصيغة الأصلية من ملف حزمة الأصول الأمثلة الإجرائية ، والذي يمكن تنزيله مجانًا من Unity Asset Store.

احفظ الملف وارجع إلى الوحدة. حدد المجال ، انتقل إلى مكون HeartMesh وحاول إضافة بعض القمم إلى خاصية المؤشرات المحددة . قم بتعطيل وضع Is Edit وانقر فوق Play لإلقاء نظرة على نتيجة عملك.


اختبر قيم Radiusofeffect و Pullvalue و Duration للحصول على نتائج مختلفة. عندما تكون جاهزًا ، قم بتغيير الإعدادات بما يتوافق مع لقطة الشاشة أدناه.


انقر فوق تشغيل . هل تحول مجالك إلى قلب؟


مبروك! في القسم التالي ، سنحفظ الشبكة كإعداد مسبق للاستخدام المستقبلي.

حفظ الشبكة في الوقت الحقيقي


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

في نافذة Project ، ابحث عن CustomHeart في مجلد Prefabs . انقر فوق رمز السهم لتوسيع محتوياته وحدد الطفل . ترى الآن كائن Sphere في نافذة معاينة المفتش . هذا هو الجاهزة التي ستقوم بتخزين البيانات للشبكة الجديدة.


افتح HeartMeshInspector.cs . داخل OnInspectorGUI() ، قبل قوس الإغلاق ، أضف ما يلي:

 if (!mesh.isEditMode && mesh.isMeshReady) { string path = "Assets/Prefabs/CustomHeart.prefab"; //1 if (GUILayout.Button("Save Mesh")) { mesh.isMeshReady = false; Object pfObj = AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)); //2 Object pfRef = AssetDatabase.LoadAssetAtPath (path, typeof(GameObject)); GameObject gameObj = (GameObject)PrefabUtility.InstantiatePrefab(pfObj); Mesh pfMesh = (Mesh)AssetDatabase.LoadAssetAtPath(path, typeof(Mesh)); //3 if (!pfMesh) { pfMesh = new Mesh(); } else { pfMesh.Clear(); } pfMesh = mesh.SaveMesh(); //4 AssetDatabase.AddObjectToAsset(pfMesh, path); gameObj.GetComponentInChildren<MeshFilter>().mesh = pfMesh; //5 PrefabUtility.ReplacePrefab(gameObj, pfRef, ReplacePrefabOptions.Default); //6 Object.DestroyImmediate(gameObj); //7 } } 

شرح الكود:

  1. لتعيين path إلى المسار إلى كائن CustomHeart.
  2. إنشاء كائنين من الإعداد المسبق CustomHeart ، أحدهما لإنشاء مثيل مثل GameObject ( pfObj ) ، والثاني كروابط ( pfRef ).
  3. إنشاء مثيل لأصل شبكة pfMesh . إذا لم يتم العثور عليه ، ينشئ شبكة جديدة ، وإلا فإنه ينظف البيانات الموجودة.
  4. pfMesh ببيانات شبكة جديدة ، ثم يضيفها كأصل إلى CustomHeart .
  5. يملأ أصل شبكة في gameObj بقيمة pfMesh .
  6. يستبدل CustomHeart بـ gameObj مطابقة الاتصالات الموجودة مسبقًا.
  7. يدمر على الفور gameObj

احفظ الملف وانتقل إلى HeartMesh.cs . في طريقة SaveMesh() العامة ، بعد إنشاء مثيل nMesh أضف ما يلي:

 nMesh.name = "HeartMesh"; nMesh.vertices = oMesh.vertices; nMesh.triangles = oMesh.triangles; nMesh.normals = oMesh.normals; 

شرح الرمز: إرجاع أصل شبكة مع قيم من شبكة على شكل قلب.

احفظ الملف وارجع إلى الوحدة. انقر فوق تشغيل . بعد اكتمال الرسم المتحرك ، سيظهر زر Save Mesh في المفتش . انقر فوق الزر لحفظ الشبكة الجديدة ، ثم أوقف المشغل.

انتقل إلى مجلد Prefabs وانظر إلى CustomHeart الجاهزة . يجب أن ترى أنه الآن في كائن CustomHeart الجاهزة ، توجد شبكة جديدة تمامًا على شكل قلب.


عمل رائع!

ضع كل ذلك معًا


في المشهد السابق ، استخدمت الدالة DisplaceVertices() صيغة Falloff لتحديد قوة السحب التي تم تطبيقها على كل قمة داخل نصف قطر معين. تعتمد نقطة "السقوط" ، التي تبدأ عندها قوة السحب في الانخفاض ، على نوع السقوط المستخدم: الخطي أو الغوسي أو الإبرة. ينتج كل نوع نتائج مختلفة في الشبكة.


في هذا القسم ، سننظر في طريقة أخرى للتعامل مع القمم: باستخدام منحنى معين. مع الأخذ في الاعتبار أن السرعة تساوي المسافة مقسومة على الوقت (d = (v / t)) ، يمكننا تحديد موضع المتجه ، بالإشارة إلى المسافة مقسومة على الوقت.


باستخدام طريقة المنحنى


احفظ المشهد الحالي وافتح 03 Customize Heart Mesh من مجلد Scenes . سترى نسخة هرمية من الإعداد CustomHeart. انقر فوق رمز السهم المجاور له لتوسيع محتوياته وحدد الطفل .

عرض خصائصه في المفتش . Mesh Filter Heart Mesh . Child Custom Heart . HeartMesh clone .


CustomHeart.cs Scripts . Start() :

 public enum CurveType { Curve1, Curve2 } public CurveType curveType; Curve curve; 

: (enum) CurveType , Inspector .

CurveType1() :

 Vector3[] curvepoints = new Vector3[3]; //1 curvepoints[0] = new Vector3(0, 1, 0); curvepoints[1] = new Vector3(0.5f, 0.5f, 0); curvepoints[2] = new Vector3(1, 0, 0); curve = new Curve(curvepoints[0], curvepoints[1], curvepoints[2], false); //2 

:

  1. . .
  2. نقوم بإنشاء المنحنى الأول بمساعدة Curve()وتعيين قيمه curve. يمكن عرض المنحنى المرسوم في المعاينة إذا حددت true كمعلمة أخيرة.

انتقل إلى CurveType2()وقم بإضافة ما يلي:

 Vector3[] curvepoints = new Vector3[3]; //1 curvepoints[0] = new Vector3(0, 0, 0); curvepoints[1] = new Vector3(0.5f, 1, 0); curvepoints[2] = new Vector3(1, 0, 0); curve = new Curve(curvepoints[0], curvepoints[1], curvepoints[2], false); //2 

شرح الكود:

  1. حدد النقاط للمنحنى الثاني.
  2. نقوم بإنشاء المنحنى الثاني Curve()وتعيين قيمه curve. يمكن عرض المنحنى المرسوم في المعاينة إذا حددت true كمعلمة أخيرة.

ب StartDisplacement()، قبل قوس الإغلاق ، يضاف ما يلي:

 if (curveType == CurveType.Curve1) { CurveType1(); } else if (curveType == CurveType.Curve2) { CurveType2(); } 

شرح الكود: هنا نتحقق من الخيار الذي حدده المستخدم curveTypeوننشئه وفقًا لذلك curve.

ب DisplaceVertices()، داخل بيان الحلقة forقبل أقواس الإغلاق ، أضف ما يلي:

 float increment = curve.GetPoint(distance).y * force; //1 Vector3 translate = (vert * increment) * Time.deltaTime; //2 Quaternion rotation = Quaternion.Euler(translate); Matrix4x4 m = Matrix4x4.TRS(translate, rotation, Vector3.one); mVertices[i] = m.MultiplyPoint3x4(mVertices[i]); 

شرح الكود:

  1. distance y force , increment .
  2. Vector3 Transform.

Unity. CustomHeart Child . , Curve Type . Edit Type Add Indices Remove Indices , .


, :


Curve Type Curve1 , , Edit Type None Play . , . , , . , Curve Type .



هذا كل شيء! Clear Selected Vertices , Selected Indices . , , , :

  • .
  • .
  • .
  • , .

?


.

! , « Unity» .

, , . Catlike Coding , .

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


All Articles