Echtzeit-Manipulation von Maschen auf Einheit

Bild

Einer der Vorteile von Unity als Plattform für die Spieleentwicklung ist die leistungsstarke 3D-Engine. In diesem Tutorial lernen Sie die Welt der 3D-Objekte und der Netzmanipulation kennen.

Aufgrund des Wachstums der VR- und AR-Technologien (Virtual and Augmented Reality) sind die meisten Entwickler mit komplexen Konzepten von 3D-Grafiken konfrontiert. Lassen Sie dieses Tutorial der Ausgangspunkt für sie sein. Keine Sorge, es wird keine komplizierte 3D-Mathematik geben - nur Herzen, Zeichnungen, Pfeile und viele interessante Dinge!

Hinweis: Dieses Lernprogramm richtet sich an Benutzer, die mit der Unity-IDE vertraut sind und über Programmiererfahrung in C # verfügen. Wenn Sie nicht über solche Kenntnisse verfügen, lesen Sie zuerst die Tutorials Einführung in die Unity-Benutzeroberfläche und Einführung in Unity Scripting .

Sie benötigen eine Version von Unity, die nicht niedriger als 2017.3.1 ist. Die neueste Version von Unity kann hier heruntergeladen werden . In diesem Lernprogramm wird ein benutzerdefinierter Editor verwendet. Weitere Informationen hierzu finden Sie im Lernprogramm zum Erweitern des Unity-Editors .

An die Arbeit gehen


Machen Sie sich zunächst mit den Grundbegriffen der 3D-Grafik vertraut, damit Sie das Tutorial besser verstehen.

Grundlegende Fachbegriffe für 3D-Grafiken:

  • Scheitelpunkte : Jeder Scheitelpunkt ist ein Punkt im 3D-Raum.
  • Netz : Enthält alle Eckpunkte, Kanten, Dreiecke, Normalen und UV-Daten des Modells.
  • Netzfilter : Speichert Modellnetzdaten.
  • Mesh Renderer : Rendert die Mesh-Daten in der Szene.
  • Normalen : Der Vektor eines Scheitelpunkts oder einer Oberfläche. Es ist senkrecht zur Oberfläche des Netzes nach außen gerichtet.
  • Linien / Kanten : Unsichtbare Linien, die Eckpunkte miteinander verbinden.
  • Dreiecke : gebildet durch Verbinden von drei Spitzen.
  • UV-Karte : Hängt Material an ein Objekt an und erstellt eine Textur und Farbe dafür.

Die Anatomie eines 3D-Objekts beginnt mit seinem Netz. Die Erstellung dieses Netzes beginnt an seinen Spitzen. Unsichtbare Linien, die diese Eckpunkte verbinden, bilden Dreiecke, die die Grundform des Objekts definieren.


Dann legen die Normalen und UV-Daten die Schattierung, Farbe und Textur fest. Netzdaten werden in einem Netzfilter gespeichert, und der Netzrenderer verwendet diese Daten, um ein Objekt in der Szene zu zeichnen.

Das heißt, der Pseudocode zum Erstellen eines 3D-Modells sieht folgendermaßen aus:

  • Erstellen Sie ein neues Netz mit dem Namen "myMesh".
  • Fügen Sie Daten zu den Eigenschaften der Eckpunkte und Dreiecke myMesh hinzu.
  • Erstellen Sie einen neuen Netzfilter mit dem Namen "myMeshFilter".
  • Setzen Sie die Mesh-Eigenschaft myMeshFilter auf myMesh.

Wenn Sie die Grundlagen beherrschen, laden Sie das Projekt herunter, entpacken Sie die Dateien und führen Sie das Werkstück des Projekts in Unity aus. Sehen Sie sich die Ordnerstruktur im Projektfenster an :


Beschreibung der Ordner:

  • Prefabs : Enthält das Sphere-Prefab, mit dem das 3D-Netz während der Anwendungsausführung gespeichert wird.
  • Szenen : Enthält die drei Szenen, die wir in diesem Tutorial verwenden.
  • Editor : Die Skripte in diesem Ordner geben uns die Superfunktionen im Editor, die wir in der Entwicklung verwenden.
  • Skripte : Hier sind die Laufzeitskripte, die an das GameObject angehängt und ausgeführt werden, wenn Sie auf " Spielen" klicken.
  • Materialien : Dieser Ordner enthält das Material für das Netz.

Im nächsten Abschnitt erstellen wir einen benutzerdefinierten Editor, um die Erstellung eines 3D-Netzes zu visualisieren.

Ändern Sie die Netze mit dem benutzerdefinierten Editor


Öffnen Sie die 01 Mesh Study Demo im Ordner " Scenes ". Im Szenenfenster sehen Sie einen 3D-Würfel:


Bevor wir uns mit dem Netz befassen, werfen wir einen Blick auf das benutzerdefinierte Editor-Skript.

Bearbeiten eines Editor-Skripts


Wählen Sie den Editor- Ordner im Projektfenster . Die Skripte in diesem Ordner erweitern den Editor (Editor) während der Entwicklung um Funktionen und sind im Erstellungsmodus nicht verfügbar.


Öffnen Sie MeshInspector.cs und zeigen Sie den Quellcode an. Alle Editor-Skripte müssen die Editor Klasse implementieren. Das CustomEditor Attribut teilt der Editor Klasse mit, für welchen Objekttyp sie bestimmt ist. OnSceneGUI() ist eine Ereignismethode, die das Rendern im OnSceneGUI() ermöglicht. OnInspectorGUI() können Sie dem Inspector zusätzliche GUI-Elemente hinzufügen.

Fügen Sie in MeshInspector.cs vor dem Starten der MeshInspector Klasse Folgendes hinzu:

 [CustomEditor(typeof(MeshStudy))] 

Code Erläuterung: Das CustomEditor Attribut teilt Unity mit, welchen Objekttyp die benutzerdefinierte CustomEditor ändern kann.

OnSceneGUI() in OnSceneGUI() vor EditMesh() Folgendes hinzu:

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

Codeerklärung: Die Editor Klasse verfügt über eine Standardzielvariable. target ist hier eine Konvertierung in MeshStudy . Jetzt zeichnet der benutzerdefinierte Editor alle GameObjects im Szenenfenster und die daran angehängten MeshStudy.cs . Durch Hinzufügen von Debugging-Meldungen können Sie in der Konsole überprüfen, ob der benutzerdefinierte Editor tatsächlich ausgeführt wird.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Gehen Sie zum Ordner " Scripts " und ziehen Sie MeshStudy.cs auf den GameObject- Cube in der Hierarchie , um ihn anzuhängen.


Jetzt sollte die Meldung "Benutzerdefinierter Editor läuft" in der Konsole angezeigt werden. Dies bedeutet, dass wir alles richtig gemacht haben! Sie können die Debug-Nachricht löschen, damit sie uns in der Konsole nicht stört.

Klonen und Dumping des Netzes


Achten Sie beim Arbeiten mit einem 3D-Netz im Bearbeitungsmodus mit dem benutzerdefinierten Editor darauf, das Standard-Unity-Netz nicht zu überschreiben. In diesem Fall müssen Sie Unity neu starten.

Um das Netz sicher zu klonen, ohne das ursprüngliche Formular zu überschreiben, erstellen Sie eine Kopie des Netzes aus der Eigenschaft MeshFilter.sharedmesh und weisen Sie es dem Netzfilter erneut zu.

Doppelklicken Sie dazu im Ordner Scripts auf MeshStudy.cs , um die Datei im Code-Editor zu öffnen. Dieses Skript erbt von der MonoBehaviour Klasse und seine Start() -Funktion wird im Bearbeitungsmodus nicht ausgeführt.

Fügen Sie in MeshStudy.cs vor dem Starten der MeshStudy Klasse Folgendes hinzu:

 [ExecuteInEditMode] 

Erläuterung des Codes: Nach dem Hinzufügen dieses Attributs wird die Funktion Start() sowohl im Wiedergabemodus als auch im Bearbeitungsmodus ausgeführt. Jetzt können wir zuerst das Netzobjekt instanziieren und es klonen.

InitMesh() in InitMesh() den folgenden Code hinzu:

 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"); 

Code Erläuterung:

  1. Ruft das ursprüngliche oMesh Netz aus der MeshFilter Komponente ab.
  2. Kopiert cMesh in eine neue Mesh- cMesh .
  3. Weist den kopierten Maschennetzfilter erneut zu.
  4. Aktualisiert lokale Variablen.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Die Meldung "Init & Cloned" sollte in der Debug-Konsole angezeigt werden. Wählen Sie den GameObject- Cube in der Hierarchie aus und überprüfen Sie seine Eigenschaften im Inspektor . Der Netzfilter sollte ein Netzelement namens Klon anzeigen. Großartig! Dies bedeutet, dass wir das Netz erfolgreich geklont haben.


Navigieren Sie im Editor-Ordner zu MeshInspector.cs . OnInspectorGUI() in OnInspectorGUI() nach der zweiten Codezeile Folgendes hinzu:

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

Code Erläuterung:

  1. Dieser Code zeichnet eine Schaltfläche zum Zurücksetzen im Inspektor .
  2. Wenn diese Taste gedrückt wird, wird die Funktion Reset() in MeshStudy.cs aufgerufen .

Speichern Sie die Datei, öffnen Sie MeshStudy.cs und fügen Sie der Funktion Reset() den folgenden Code hinzu:

 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; } 

Code Erläuterung:

  1. Überprüfen der Existenz der Quelle und des geklonten Netzes.
  2. cMesh auf das ursprüngliche Netz zurück.
  3. Zuordnung zu cMesh oMeshFilter .
  4. Lokale Variablen aktualisieren.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Klicken Sie im Inspektor auf die Schaltfläche Test bearbeiten , um das Würfelnetz zu verzerren. Klicken Sie anschließend auf die Schaltfläche Zurücksetzen . Der Würfel sollte in seine ursprüngliche Form zurückkehren.


Erklärung von Eckpunkten und Dreiecken in Unity


Ein Netz besteht aus Eckpunkten, die durch Kanten in Dreiecken verbunden sind. Dreiecke definieren die Grundform des Objekts.

Mesh-Klasse:

  • Die Eckpunkte werden als Array von Vector3 Werten gespeichert.
  • Dreiecke werden als ganzzahliges Array gespeichert, das den Indizes des Vertex-Arrays entspricht.

Das heißt, in einem einfachen Quad-Netz, das aus vier Eckpunkten und zwei Dreiecken besteht, sehen die Netzdaten folgendermaßen aus:


Vertex-Mapping


Hier wollen wir die Eckpunkte des Würfels als blaue Punkte anzeigen.

In MeshInspector.cs gehen wir in die Funktion EditMesh() und fügen Folgendes hinzu:

 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); } 

Code Erläuterung:

  1. handleTransform Transformationswerte aus dem mesh .
  2. handleRotation den Rotationsmodus des aktuellen Gelenks ab.
  3. Durchqueren Sie die Eckpunkte des Netzes und zeichnen Sie die Punkte mit ShowPoint() .

ShowPoint() Funktion ShowPoint() unmittelbar nach dem Kommentar //draw dot Folgendes hinzu:

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

Code Erläuterung: Diese Linie konvertiert die lokale Position des Scheitelpunkts in eine Koordinate im Weltraum.

Fügen Sie in derselben Funktion im if Block unmittelbar nach der gerade hinzugefügten Codezeile Folgendes hinzu:

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

Code Erläuterung:

  1. Legt die Farbe, Größe und Position eines Punkts mithilfe der Handles .
  2. Handles.FreeMoveHandle() erstellt einen unbegrenzten Bewegungsmanipulator, der das Ziehen und Ablegen vereinfacht, was für uns im nächsten Abschnitt nützlich ist.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Überprüfen Sie die Cube-Eigenschaft im Inspektor und stellen Sie sicher, dass die Option Vertexpunkt verschieben aktiviert ist. Sie sollten jetzt sehen, dass das Netz auf dem Bildschirm mit mehreren blauen Punkten markiert ist. Hier sind sie - die Spitzen des Würfelnetzes! Versuchen Sie dies mit anderen 3D-Objekten und beobachten Sie die Ergebnisse.


Verschieben Sie einen einzelnen Scheitelpunkt


Beginnen wir mit dem einfachsten Schritt der Manipulation des Netzes - dem Verschieben eines einzelnen Scheitelpunkts.

Gehen Sie zu MeshInspector.cs . ShowPoint() der ShowPoint() -Funktion unmittelbar nach dem //drag Kommentars und direkt vor den schließenden Klammern des if Blocks Folgendes hinzu:

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

Code Erläuterung:

  1. GUI.changed verfolgt alle Änderungen, die an Punkten auftreten, und arbeitet gut mit Handles.FreeMoveHandle() , um einen Drag & Drop-Vorgang zu erkennen.
  2. Für den ziehbaren Scheitelpunkt erhält die Funktion mesh.DoAction() ihre Index- und Transformationswerte als Parameter. Da sich die Transformationswerte des Scheitelpunkts im Weltraum befinden, konvertieren wir sie mit InverseTransformPoint() in den lokalen Raum.

Speichern Sie die Skriptdatei und gehen Sie zu MeshStudy.cs . DoAction() in DoAction() nach den öffnenden Klammern Folgendes hinzu:

 PullOneVertex(index, localPos); 

PullOneVertex() der PullOneVertex() Funktion Folgendes PullOneVertex() :

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

Code Erläuterung:

  1. Wir aktualisieren den Zielscheitelpunkt mit dem Wert newPos .
  2. cMesh.vertices aktualisierte Scheitelpunktwerte zu.
  3. In RecalculateNormals() berechnen RecalculateNormals() das Netz neu und zeichnen RecalculateNormals() neu, sodass es den Änderungen entspricht.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Versuchen Sie, Punkte auf den Würfel zu ziehen. Hast du ein gebrochenes Netz gesehen?


Es scheint, dass einige der Scheitelpunkte dieselbe Position haben. Wenn wir also nur einen ziehen, bleiben die verbleibenden Scheitelpunkte dahinter und das Netz bricht. Im nächsten Abschnitt werden wir dieses Problem beheben.

Alle ähnlichen Eckpunkte finden


Visuell besteht ein Würfelnetz aus acht Eckpunkten, sechs Seiten und 12 Dreiecken. Lassen Sie uns überprüfen, ob dies so ist.


Öffnen Sie MeshStudy.cs , werfen Sie einen Blick vor die Funktion Start() und suchen Sie die Variable vertices . Wir werden folgendes sehen:

 [HideInInspector] public Vector3[] vertices; 

Code Erläuterung: [HideInInspector] verbirgt eine gemeinsam genutzte Variable im Inspector- Fenster.

Kommentieren Sie dieses Attribut aus:

 //[HideInInspector] public Vector3[] vertices; 

Hinweis: Das Ausblenden von Scheitelpunktwerten hilft [HideInInspector] bei komplexeren 3D-Netzen. Da die Größe des Scheitelpunktarrays Tausende von Elementen erreichen kann, kann dies zu einer Hemmung der Einheit führen, wenn versucht wird, den Arraywert im Inspektor anzuzeigen.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Gehen Sie zum Inspektor . Jetzt wurde unter der Skriptkomponente "Netzstudie" die Eigenschaft " Vertices" angezeigt. Klicken Sie auf das Pfeilsymbol daneben. Sie Vector3 Array der Vector3 Elemente.


Sie können sehen, dass die Größe des Arrays 24 beträgt, dh es gibt Scheitelpunkte mit derselben Position! Bevor Sie fortfahren, müssen Sie [HideInInspector] .

Warum gibt es 24 Eckpunkte?
Es gibt viele Theorien zu diesem Thema. Die einfachste Antwort lautet jedoch: Der Würfel hat sechs Seiten, und jede Seite besteht aus vier Eckpunkten, die eine Ebene bilden.

Daher lautet die Berechnung wie folgt: 6 x 4 = 24 Eckpunkte.

Sie können nach anderen Antworten suchen. Im Moment ist es jedoch einfach zu wissen, dass einige Netze Scheitelpunkte haben, die dieselbe Position haben.

Ersetzen Sie in MeshStudy.cs den gesamten Code in der DoAction() Funktion durch Folgendes:

 PullSimilarVertices(index, localPos); 

Gehen wir in die PullSimilarVertices() -Funktion und fügen Folgendes hinzu:

 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(); 

Code Erläuterung:

  1. Wir erhalten die Position des Zielscheitelpunkts, der als Argument für die FindRelatedVertices() -Methode verwendet wird.
  2. Diese Methode gibt eine Liste von Indizes zurück (die Scheitelpunkten entsprechen), die dieselbe Position wie der Zielscheitelpunkt haben.
  3. Die Schleife durchläuft die gesamte Liste und setzt die entsprechenden Eckpunkte auf newPos .
  4. cMesh.vertices aktualisierte vertices zu. Dann rufen wir RecalculateNormals() auf, um das Netz mit den neuen Werten neu zu zeichnen.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Ziehen Sie einen der Scheitelpunkte. Jetzt sollte das Netz seine Form behalten und nicht kollabieren.


Nachdem wir den ersten Schritt zum Bearbeiten der Netze abgeschlossen haben, speichern Sie die Szene und fahren Sie mit dem nächsten Abschnitt fort.

Netzmanipulation


In diesem Abschnitt erfahren Sie, wie Sie Netze in Echtzeit bearbeiten. Es gibt viele Möglichkeiten, aber in diesem Tutorial werden wir uns die einfachste Art der Netzmanipulation ansehen, nämlich das Verschieben der zuvor erstellten Netzscheitelpunkte.

Ausgewählte Indizes sammeln


Beginnen wir mit der Auswahl der Eckpunkte, die wir in Echtzeit verschieben werden.

Öffnen Sie Szene 02 Herznetz aus dem Ordner Szenen erstellen . Im Szenenfenster sehen Sie eine rote Kugel. Wählen Sie Sphere in the Hierarchy und gehen Sie zu Inspector . Sie werden sehen, dass die Heart Mesh-Skriptkomponente an das Objekt angehängt ist.

Jetzt benötigen wir das Editor-Skript für dieses Objekt, um die Eckpunkte des Netzes im Szenenfenster anzuzeigen. Gehen Sie zum Editor- Ordner und doppelklicken Sie auf HeartMeshInspector.cs .

ShowHandle() in der ShowHandle() -Funktion im if Block Folgendes hinzu:

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

Code Erläuterung:

  1. Legt die Eckpunkte des Netzes fest und zeigt sie als Typ Handles.Button .
  2. Wenn Sie darauf mesh.selectedIndices , wird der ausgewählte Index zur mesh.selectedIndices der gedrückten mesh.selectedIndices .

OnInspectorGUI() in OnInspectorGUI() vor der schließenden Klammer Folgendes hinzu:

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

Erläuterung des Codes: Auf diese Weise fügen wir dem Inspektor eine Schaltfläche zum Zurücksetzen hinzu, um mesh.ClearAllData() .

Speichern Sie die Datei und öffnen Sie HeartMesh.cs im Ordner Scripts . ClearAllData() Funktion ClearAllData() Folgendes hinzu:

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

Code Erläuterung: Der Code löscht die Werte in selectedIndices und targetIndex . Außerdem wird targetVertex .

Speichern Sie die Datei und kehren Sie zu Unity zurück. Wählen Sie Sphere aus und rufen Sie den Inspector für die HeartMesh-Skriptkomponente auf . Erweitern Sie Ausgewählte Indizes, indem Sie auf das Pfeilsymbol daneben klicken. Auf diese Weise können wir jeden zur Liste hinzugefügten Scheitelpunkt verfolgen.

Aktivieren Sie Ist Bearbeitungsmodus über das Kontrollkästchen daneben. Aus diesem Grund werden die Eckpunkte des Netzes im Szenenfenster gezeichnet. Durch Klicken auf die blauen Punkte in den ausgewählten Indizes sollten die Werte entsprechend geändert werden. Testen Sie auch die Schaltfläche Ausgewählte Scheitelpunkte löschen , um sicherzustellen, dass alle Werte gelöscht werden.


Hinweis: Im geänderten benutzerdefinierten Inspektor haben wir die Möglichkeit, den Transformationsmanipulator mithilfe des Transformationshandles anzeigen ein- / auszublenden. Keine Panik, wenn Sie den Transformationsmanipulator in anderen Szenen nicht finden! Schalten Sie es vor dem Beenden ein.

Eine Kugel in ein Herz verwandeln


Das Ändern von Netzscheitelpunkten in Echtzeit besteht im Wesentlichen aus drei Schritten:

  1. Kopieren Sie die aktuellen mVertices (vor der Animation) nach mVertices .
  2. mVertices Berechnungen durch und ändern die Werte in mVertices .
  3. Aktualisieren Sie die aktuellen mVertices mit mVertices wenn Sie sie bei jedem Schritt ändern, und lassen Sie Unity die Normalen automatisch berechnen.

Öffnen Sie HeartMesh.cs und die folgenden Variablen vor der Funktion 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; 

Code Erläuterung:

  1. Der Radius des vom Zielscheitelpunkt betroffenen Bereichs.
  2. Kraft ziehen.
  3. Die Dauer der Animation.
  4. Der aktuelle Index der Liste selectedIndices .

Init() Funktion Init() vor dem if Block Folgendes hinzu:

 currentIndex = 0; 

Erläuterung des Codes: Zu Beginn des Spiels wird currentIndex auf 0 gesetzt, den ersten Index der Liste selectedIndices .

Init() derselben Init() Funktion vor der schließenden Klammer des else Blocks Folgendes hinzu:

 StartDisplacement(); 

StartDisplacement() : Führen Sie die Funktion StartDisplacement() aus, wenn isEditMode false ist.

StartDisplacement() Funktion StartDisplacement() Folgendes hinzu:

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

Code Erläuterung:

  1. Wählen Sie targetVertex , um die Animation zu starten.
  2. isAnimate Sie die Startzeit ein und ändern Sie den Wert von isAnimate in true.

Erstellen Sie nach der Funktion StartDisplacement() Funktion FixedUpdate() mit dem folgenden Code:

 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; } } } 

Code Erläuterung:

  1. Die Funktion FixedUpdate() wird in einer festen FPS-Schleife ausgeführt.
  2. Wenn isAnimate false ist, überspringen Sie den folgenden Code.
  3. runtime ändern.
  4. Wenn die runtime innerhalb der duration , erhalten wir die Weltkoordinaten von targetVertex und DisplaceVertices() , wobei der Zielscheitelpunkt mit den radiusofeffect pullvalue und radiusofeffect .
  5. Ansonsten ist die Zeit abgelaufen. Fügen Sie einen zu currentIndex .
  6. Überprüfen Sie, ob currentIndex zu selectedIndices . Gehen Sie mit StartDisplacement() zum nächsten Scheitelpunkt in der Liste.
  7. Andernfalls ändern oMesh am Ende der Liste die oMesh Daten in das aktuelle Netz und isAnimate auf false, um die Animation zu stoppen.

DisplaceVertices() in DisplaceVertices() Folgendes hinzu:

 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(); 

Code Erläuterung:

  1. Das Quadrat des Radius.
  2. Wir durchlaufen jeden Scheitelpunkt des Netzes.
  3. sqrMagnitude zwischen currentVertexPos und targetVertexPos .
  4. Wenn sqrMagnitude überschreitet, fahren Sie mit dem nächsten Scheitelpunkt fort.
  5. Andernfalls bestimmen Sie den falloff , der vom distance aktuellen Scheitelpunkts vom Mittelpunkt des falloff abhängt.
  6. Vector3 neue Vector3 Position und wenden Sie ihre Transformation auf den aktuellen Scheitelpunkt an.
  7. Wenn Sie die Schleife verlassen, weisen wir den mVertices die geänderten mVertices Werte zu und erzwingen, dass Unity die Normalen neu berechnet.

Quelle der Falloff-Technologie
Die ursprüngliche Formel stammt aus der Asset-Paketdatei " Verfahrensbeispiele" , die kostenlos aus dem Unity Asset Store heruntergeladen werden kann.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Wählen Sie Sphere aus , wechseln Sie zur HeartMesh- Komponente und versuchen Sie, der Eigenschaft Selected Indices einige Scheitelpunkte hinzuzufügen. Deaktivieren Sie den Bearbeitungsmodus und klicken Sie auf Wiedergabe , um das Ergebnis Ihrer Arbeit anzuzeigen.


Experimentieren Sie mit den Werten für Radiusofeffekt , Pullvalue und Dauer , um unterschiedliche Ergebnisse zu erhalten. Wenn Sie bereit sind, ändern Sie die Einstellungen gemäß dem folgenden Screenshot.


Klicken Sie auf Spielen . Hat sich deine Kugel in ein Herz verwandelt?


Glückwunsch! Im nächsten Abschnitt speichern wir das Netz als Fertighaus für die zukünftige Verwendung.

Speichern des Netzes in Echtzeit


Um ein herzförmiges prozedurales Netz im Wiedergabemodus zu speichern, müssen Sie ein Fertighaus vorbereiten, dessen Kind ein 3D-Objekt ist, und dann sein Netzelement mithilfe eines Skripts durch ein neues ersetzen.

Suchen Sie im Projektfenster CustomHeart im Ordner Prefabs . Klicken Sie auf das Pfeilsymbol, um den Inhalt zu erweitern, und wählen Sie Kind aus . Sie sehen jetzt ein Sphere-Objekt im Inspektor- Vorschaufenster. Dies ist das Fertighaus, in dem die Daten für das neue Netz gespeichert werden.


Öffnen Sie HeartMeshInspector.cs . OnInspectorGUI() Funktion OnInspectorGUI() vor der schließenden Klammer Folgendes hinzu:

 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 } } 

Code Erläuterung:

  1. Legt den path zum Pfad zum CustomHeart- Fertighausobjekt fest .
  2. Erstellt zwei Objekte aus dem CustomHeart- Fertighaus , eines zum Erstellen einer Instanz als GameObject ( pfObj ) und das zweite als Links ( pfRef ).
  3. Erstellt eine Instanz des pfMesh-Mesh- pfMesh . Wenn es nicht gefunden wird, wird ein neues Netz erstellt, andernfalls werden die vorhandenen Daten bereinigt.
  4. pfMesh mit neuen pfMesh und fügt sie dann CustomHeart als Asset hinzu .
  5. Füllt ein Mesh-Asset in gameObj Wert pfMesh .
  6. Ersetzt CustomHeart durch gameObj bereits vorhandene Verbindungen gameObj .
  7. gameObj .

Speichern Sie die Datei und gehen Sie zu HeartMesh.cs . SaveMesh() in der allgemeinen SaveMesh() -Methode nach dem Erstellen der nMesh Instanz Folgendes hinzu:

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

Code Erläuterung: Gibt ein Netzelement mit Werten aus einem herzförmigen Netz zurück.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Klicken Sie auf Wiedergabe . Nach Abschluss der Animation wird im Inspektor die Schaltfläche Netz speichern angezeigt . Klicken Sie auf die Schaltfläche, um das neue Netz zu speichern, und stoppen Sie den Player.

Gehen Sie zum Ordner Prefabs und sehen Sie sich das CustomHeart- Prefab an . Sie sollten sehen, dass es jetzt im CustomHeart- Fertighausobjekt ein völlig neues herzförmiges Netz gibt.


Ausgezeichnete Arbeit!

Alles zusammenfügen


In der vorherigen Szene verwendete die Funktion DisplaceVertices() die Falloff-Formel, um die Widerstandskraft zu bestimmen, die auf jeden Scheitelpunkt innerhalb eines bestimmten Radius angewendet wurde. Der Punkt des „Abfalls“, an dem die Widerstandskraft abzunehmen beginnt, hängt von der Art des verwendeten Abfalls ab: Linear, Gauß oder Nadel. Jeder Typ erzeugt unterschiedliche Ergebnisse im Netz.


In diesem Abschnitt sehen wir uns eine andere Möglichkeit an, Scheitelpunkte zu bearbeiten: die Verwendung einer bestimmten Kurve. Unter der Regel, dass die Geschwindigkeit gleich der durch die Zeit geteilten Entfernung (d = (v / t)) ist, können wir die Position des Vektors anhand seiner durch die Zeit geteilten Entfernung bestimmen.


Verwenden der Kurvenmethode


Speichern Sie die aktuelle Szene und öffnen Sie 03 Customize Heart Mesh aus dem Ordner Scenes . Sie sehen eine Hierarchieinstanz des CustomHeart- Fertighauses . Klicken Sie auf das Pfeilsymbol daneben, um den Inhalt zu erweitern, und wählen Sie Kind aus .

Zeigen Sie die Eigenschaften im Inspektor an .Sie sehen die Netzfilterkomponente mit dem Heart Mesh Asset . Hängen Sie ein benutzerdefiniertes Herzskript als Komponente an Child an . Jetzt sollte das Asset von HeartMesh zu Klon wechseln .


Öffnen Sie anschließend CustomHeart.cs im Ordner Scripts . Fügen Sie vor der Funktion Start()Folgendes hinzu:

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

Erläuterung des Codes: Hier wird eine allgemeine Aufzählung unter dem Namen erstellt CurveType, nach der sie vom Inspektor zur Verfügung gestellt wird .

Gehen Sie zu CurveType1()und fügen Sie Folgendes hinzu:

 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 

Code Erläuterung:

  1. Eine einfache Kurve besteht aus drei Punkten. Stellen Sie die Punkte für die erste Kurve ein.
  2. Mit Hilfe der Hilfe erzeugen wir die erste Kurve Curve()und weisen ihre Werte zu curve. Die gezeichnete Kurve kann in der Vorschau angezeigt werden, wenn Sie als letzten Parameter true angeben.

Gehen Sie zu CurveType2()und fügen Sie Folgendes hinzu:

 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 

Code Erläuterung:

  1. Stellen Sie die Punkte für die zweite Kurve ein.
  2. Wir erzeugen die zweite Kurve mit Curve()und weisen ihre Werte zu curve. Die gezeichnete Kurve kann in der Vorschau angezeigt werden, wenn Sie als letzten Parameter true angeben.

B StartDisplacement()Fügen Sie vor der schließenden Klammer Folgendes hinzu:

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

Codeerklärung: Hier überprüfen wir die vom Benutzer ausgewählte Option curveTypeund generieren sie entsprechend curve.

B DisplaceVertices(), forfügen Sie in der Schleifenanweisung vor den schließenden Klammern Folgendes hinzu:

 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]); 

Code Erläuterung:

  1. Wir erhalten die Position der Kurve bei der gegebenen distanceund multiplizieren ihren Wert ymit force, um zu erhalten increment.
  2. Erstellen Sie einen neuen Datentyp Vector3, um die neue Position des aktuellen Scheitelpunkts zu speichern, und wenden Sie dessen Transformation entsprechend an.

Speichern Sie die Datei und kehren Sie zu Unity zurück. Überprüfen Sie die Eigenschaften des Bauteils CustomHeart Spielobjekt Kind . Hier finden Sie eine Dropdown-Liste sehen , wo Sie auswählen können , die Kurve den Typ . In der Dropdown- Typen bearbeiten wählen hinzufügen Indizes oder den Remove - Indizes , die Liste der Eckpunkt zu aktualisieren und mit verschiedenen Einstellungen zu experimentieren.


Geben Sie die Werte gemäß dem Screenshot ein, um detaillierte Ergebnisse für verschiedene Kurventypen anzuzeigen:


Eine Liste der Kurventyp , wählen Sie Curve1 , stellen Sie sicher , zu bearbeiten Typ wählen Sie Keine , und klicken Sie auf den Play . Sie sollten sehen, dass das Netz in das Muster abweicht. Rollen Sie das Modell, um es in der Seitenansicht zu sehen, und vergleichen Sie die Ergebnisse für beide Kurventypen. Hier sehen Sie, wie sich der ausgewählte Kurventyp auf den Netzversatz auswirkt.



Das ist alles!Sie können auf Ausgewählte Scheitelpunkte löschen klicken , um Ausgewählte Indizes zurückzusetzen und mit Ihren eigenen Mustern zu experimentieren. Vergessen Sie jedoch nicht, dass es andere Faktoren gibt, die das Endergebnis des Netzes beeinflussen, nämlich:

  • Der Wert des Radius.
  • Die Verteilung der Eckpunkte im Bereich.
  • Die Musterposition der ausgewählten Scheitelpunkte.
  • Die für den Offset ausgewählte Methode.

Wohin als nächstes?


Dateien des fertigen Projekts befinden sich im Archiv des Lernprojekts.

Hör hier nicht auf! Probieren Sie die komplexeren Techniken aus, die im Lernprogramm zur Erstellung von Unity Procedural Maze verwendet werden .

Ich hoffe, Ihnen hat dieses Tutorial gefallen und Sie fanden die Informationen hilfreich. Ein besonderer Dank spricht ich Jasper Flick von Catlike Coding für seine hervorragend Tutorials , die mir geholfen , eine Demo für mein Projekt zusammenzustellen .

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


All Articles