Erstellen eines Katzenhakens in Unity. Teil 2

Bild

Hinweis : Dieses Lernprogramm richtet sich an fortgeschrittene und erfahrene Benutzer und behandelt keine Themen wie das Hinzufügen von Komponenten, das Erstellen neuer GameObject-Skripts und die C # -Syntax. Wenn Sie Ihre Unity-Kenntnisse verbessern müssen, lesen Sie unsere Tutorials Erste Schritte mit Unity und Einführung in Unity Scripting .

Im ersten Teil des Tutorials haben wir gelernt, wie man einen Katzenhaken mit der Mechanik des Wickelns eines Seils um Hindernisse erstellt. Wir wollen jedoch mehr: Das Seil kann sich um Objekte auf der Ebene wickeln, löst sich jedoch nicht, wenn Sie zurückkehren.

An die Arbeit gehen


Öffnen Sie das fertige Projekt aus dem ersten Teil von Unity oder laden Sie den Entwurf für diesen Teil des Tutorials herunter und öffnen Sie dann 2DGrapplingHook-Part2-Starter . Wie im ersten Teil werden wir Unity Version 2017.1 oder höher verwenden.

Öffnen Sie die Spielszene im Editor aus dem Projektordner " Szenen ".


Starten Sie die Spielszene und versuchen Sie, den Katzenhaken an den Steinen über dem Charakter zu befestigen. Schwingen Sie dann, damit sich das Seil um ein Paar Steinkanten wickelt.

Wenn Sie zurückkehren, werden Sie feststellen, dass sich die Punkte des Steins, durch die sich das Seil gedreht hat, nicht wieder lösen.


Denken Sie an den Punkt, an dem sich das Seil entfalten soll. Um die Aufgabe zu vereinfachen, ist es besser, den Fall zu verwenden, wenn sich das Seil um die Kanten wickelt.

Wenn die Schnecke, die an einem Stein über ihrem Kopf haftet, nach rechts schwingt, biegt sich das Seil nach der Schwelle, an der es den 180-Grad-Winkelpunkt mit der Rippe kreuzt, an der die Schnecke gerade befestigt ist. In der folgenden Abbildung wird dies durch einen hervorgehobenen grünen Punkt angezeigt.


Wenn die Schnecke in die andere Richtung zurückschwingt, sollte sich das Seil an derselben Stelle wieder aushaken (in der obigen Abbildung rot hervorgehoben):


Die Logik des Aufdrehens


Um den Moment zu berechnen, in dem Sie das Seil an den Stellen, um die es zuvor gewickelt wurde, aufdrehen müssen, benötigen wir Kenntnisse der Geometrie. Insbesondere werden wir einen Winkelvergleich verwenden, um zu bestimmen, wann sich das Seil von der Kante lösen soll.

Diese Aufgabe mag ein wenig einschüchternd wirken. Mathematik kann selbst bei den Mutigsten Entsetzen und Verzweiflung hervorrufen.

Glücklicherweise hat Unity einige großartige mathematische Hilfsfunktionen, die unser Leben ein bisschen einfacher machen können.

Öffnen Sie das RopeSystem- Skript in der IDE und erstellen Sie eine neue Methode namens HandleRopeUnwrap() .

 private void HandleRopeUnwrap() { } 

Gehen Sie zu Update() und fügen Sie ganz am Ende einen Aufruf unserer neuen Methode hinzu.

 HandleRopeUnwrap(); 

Während HandleRopeUnwrap() nichts tut, können wir jetzt die Logik verarbeiten, die mit dem gesamten Prozess des Ablösens von den Kanten verbunden ist.

Wie Sie sich aus dem ersten Teil des Tutorials erinnern, haben wir die ropePositions in einer Sammlung ropePositions , bei der es sich um eine List<Vector2> -Sammlung handelt. Jedes Mal, wenn sich ein Seil um eine Kante wickelt, behalten wir die Position dieses Wickelpunkts in dieser Sammlung bei.

Um den Prozess effizienter zu gestalten, führen wir in HandleRopeUnwrap() keine Logik aus, wenn die Anzahl der in der Auflistung gespeicherten Positionen gleich oder kleiner als 1 ist.

Mit anderen Worten, wenn der Butzen am Startpunkt eingehakt ist und sein Seil noch nicht um die Kanten gewickelt ist, beträgt die Anzahl der ropePositions 1, und wir folgen nicht der Aufdrehverarbeitungslogik.

Fügen Sie diese einfache return oben in HandleRopeUnwrap() , um wertvolle CPU-Zyklen zu speichern, da diese Methode mehrmals pro Sekunde von Update() aufgerufen wird.

 if (ropePositions.Count <= 1) { return; } 

Neue Variablen hinzufügen


In diesem neuen Test werden wir einige Dimensionen und Verweise auf die verschiedenen Winkel hinzufügen, die zur Implementierung der Basis der Aufdrehlogik erforderlich sind. Fügen Sie HandleRopeUnwrap() den folgenden Code HandleRopeUnwrap() :

 // Hinge =       // Anchor =     Hinge // Hinge Angle =   anchor  hinge // Player Angle =   anchor  player // 1 var anchorIndex = ropePositions.Count - 2; // 2 var hingeIndex = ropePositions.Count - 1; // 3 var anchorPosition = ropePositions[anchorIndex]; // 4 var hingePosition = ropePositions[hingeIndex]; // 5 var hingeDir = hingePosition - anchorPosition; // 6 var hingeAngle = Vector2.Angle(anchorPosition, hingeDir); // 7 var playerDir = playerPosition - anchorPosition; // 8 var playerAngle = Vector2.Angle(anchorPosition, playerDir); 

Da es hier viele Variablen gibt, werde ich jede erklären und eine praktische Illustration hinzufügen, die hilft, ihren Zweck zu verstehen.

  1. anchorIndex ist der Index in der ropePositions Auflistung an zwei Positionen ab dem Ende der Auflistung. Wir können es als einen Punkt in zwei Positionen auf dem Seil von der Position der Schnecke aus betrachten. In der folgenden Abbildung ist dies der erste Befestigungspunkt des Hakens an der Oberfläche. Wenn Sie die Sammlung " ropePositions neuen ropePositions füllen, ropePositions dieser Punkt immer der ropePositions in einem Abstand von zwei Positionen von der Schnecke.
  2. hingeIndex ist der Index der Sammlung, in dem der Punkt des aktuellen Scharniers hingeIndex ist. Mit anderen Worten, die Position, in der sich das Seil gerade um den Punkt wickelt, der dem Ende des Seils von der Schnecke am nächsten liegt. Es befindet sich immer in einem Abstand von einer Position von der Schnecke, weshalb ropePositions.Count - 1 verwenden. ropePositions.Count - 1 .
  3. anchorPosition berechnet, indem auf die anchorIndex Stelle in der ropePositions Auflistung ropePositions , und ist der einfache Vector2-Wert dieser Position.
  4. hingePosition berechnet, indem auf die Stelle von hingeIndex in der Sammlungeilsektionen ropePositions , und ist der einfache Vector2-Wert dieser Position.
  5. hingeDir ist ein Vektor, der von anchorPosition zu hingePosition . Es wird in der folgenden Variablen verwendet, um den Winkel zu erhalten.
  6. hingeAngle - Die nützliche Vector2.Angle() wird hier verwendet, um den Winkel zwischen anchorPosition und dem Scharnierpunkt zu berechnen.
  7. playerDir ist ein Vektor, der von anchorPosition zur aktuellen Position des Slugs (playerPosition) geleitet wird.
  8. Dann wird unter Verwendung des Winkels zwischen dem Ankerpunkt und dem Spieler (Slug) playerAngle berechnet.


Alle diese Variablen werden unter Verwendung von Positionen berechnet, die als Vector2-Werte in der ropePositions Sammlung gespeichert sind, und unter Verwendung dieser Positionen mit anderen Positionen oder der aktuellen Position des Spielers (Slug).

Die beiden wichtigen Variablen, die zum Vergleich verwendet werden, sind hingeAngle und hingeAngle .

Der in hingeAngle gespeicherte hingeAngle muss statisch bleiben, da er immer einen konstanten Winkel zwischen dem Punkt an den beiden „Falten des Seils“ von der Schnecke und der aktuellen „Falte des Seils“ aufweist, die der Schnecke am nächsten liegt und sich erst bewegt, wenn das Seil gelöst oder nach dem Falten ist Ein neuer Biegepunkt wird hinzugefügt.

Wenn die Schnecke playerAngle ändert sich playerAngle . Durch Vergleichen dieses Winkels mit hingeAngle und Überprüfen, ob sich der Butzen links oder rechts von dieser Ecke befindet, können wir bestimmen, ob der aktuelle hingeAngle , der dem Butzen am nächsten liegt, entfernt werden soll.

Im ersten Teil dieses Tutorials haben wir die wrapPointsLookup in einem Wörterbuch namens wrapPointsLookup . Jedes Mal, wenn wir den Biegepunkt speichern, haben wir ihn dem Wörterbuch mit der Position als Schlüssel und mit 0 als Wert hinzugefügt. Dieser Wert von 0 war jedoch ziemlich mysteriös, oder?

Wir werden diesen Wert verwenden, um die Position der Schnecke relativ zu ihrem Winkel zum Scharnierpunkt (dem aktuellen Falzpunkt, der der Schnecke am nächsten liegt) zu speichern.

Wenn Sie einen Wert von -1 zuweisen, ist der Winkel der hingeAngle ( hingeAngle ) kleiner als der Winkel des Scharniers ( hingeAngle ), und bei einem Wert von 1 ist der Winkel von hingeAngle größer als der hingeAngle .

Aufgrund der Tatsache, dass wir die Werte im Wörterbuch hingeAngle , können wir jedes Mal, wenn wir hingeAngle mit hingeAngle vergleichen, nachvollziehen, ob die Schnecke gerade die Grenze überschritten hat, nach der sich das Seil lösen sollte.

Es kann anders erklärt werden: Wenn der Winkel der Schnecke gerade überprüft wurde und kleiner als der Scharnierwinkel ist, aber beim letzten Speichern im Wörterbuch der Biegepunkte mit einem Wert markiert wurde, der angibt, dass er sich auf der anderen Seite dieser Ecke befindet, sollte der Punkt entfernt werden !

Seil abkuppeln


Schauen Sie sich den Screenshot unten mit Notizen an. Unsere Schnecke klammerte sich an den Felsen, schwankte nach oben und wickelte auf dem Weg nach oben ein Seil um den Rand des Felsens.


Möglicherweise stellen Sie fest, dass an der obersten Schwenkposition, an der die Schnecke undurchsichtig ist, der aktuell nächstgelegene wrapPointsLookup (mit einem weißen Punkt markiert) im wrapPointsLookup Wörterbuch mit dem Wert 1 gespeichert wird.

Auf dem Weg nach unten wird eine Überprüfung durchgeführt, wenn hingeAngle kleiner als hingeAngle (zwei gestrichelte grüne Linien), wie durch den blauen Pfeil angezeigt. Wenn der letzte (aktuelle) Wert des Biegepunkts 1 war , sollte der Biegepunkt entfernt werden.

Lassen Sie uns diese Logik nun in Code implementieren. Bevor wir beginnen, erstellen wir ein Leerzeichen der Methode, mit der wir uns abwickeln. Aus diesem Grund führt das Erstellen der Logik nach dem Erstellen nicht zu einem Fehler.

Fügen Sie eine neue UnwrapRopePosition(anchorIndex, hingeIndex) Methode UnwrapRopePosition(anchorIndex, hingeIndex) indem Sie die folgenden Zeilen einfügen:

 private void UnwrapRopePosition(int anchorIndex, int hingeIndex) { } 

Nachdem Sie dies getan haben, HandleRopeUnwrap() zu HandleRopeUnwrap() . hingeAngle unter den neu hinzugefügten Variablen die folgende Logik hinzu, die zwei Fälle behandelt: playerAngle weniger als hingeAngle und hingeAngle mehr als hingeAngle :

 if (playerAngle < hingeAngle) { // 1 if (wrapPointsLookup[hingePosition] == 1) { UnwrapRopePosition(anchorIndex, hingeIndex); return; } // 2 wrapPointsLookup[hingePosition] = -1; } else { // 3 if (wrapPointsLookup[hingePosition] == -1) { UnwrapRopePosition(anchorIndex, hingeIndex); return; } // 4 wrapPointsLookup[hingePosition] = 1; } 

Dieser Code sollte der Erläuterung der oben beschriebenen Logik für den ersten Fall entsprechen (wenn hingeAngle < hingeAngle ), behandelt aber auch den zweiten Fall (wenn hingeAngle > hingeAngle ).

  1. Wenn der aktuelle hingeAngle , der dem Slug am nächsten liegt, an dem Punkt, an dem hingeAngle < hingeAngle , den Wert 1 hingeAngle , entfernen wir diesen Punkt und führen eine Rückgabe durch, damit der Rest der Methode nicht ausgeführt wird.
  2. Andernfalls wird -1 zugewiesen, wenn der hingeAngle nicht zuletzt mit dem Wert 1 markiert wurde, playerAngle kleiner als hingeAngle ist .
  3. Wenn der aktuelle hingeAngle , der dem Butzen am nächsten liegt, -1 an dem Punkt ist, an dem hingeAngle > hingeAngle , entfernen Sie den Punkt und kehren Sie zurück.
  4. Andernfalls ordnen wir die Einträge im Wörterbuch der Biegepunkte an der Scharnierposition 1 zu .

Dieser Code stellt sicher, dass das wrapPointsLookup Wörterbuch immer aktualisiert wird, und stellt sicher, dass der Wert des aktuellen wrapPointsLookup (der dem Butzen am nächsten liegt) mit dem aktuellen Butzenwinkel relativ zum wrapPointsLookup .

Vergessen Sie nicht, dass der Wert -1 ist, wenn der Schwenkwinkel kleiner als der Scharnierwinkel (relativ zum Referenzpunkt) ist, und 1, wenn der Schwenkwinkel größer als der Scharnierwinkel ist.

Jetzt UnwrapRopePosition() wir UnwrapRopePosition() im RopeSystem- Skript mit einem Code, der sich direkt mit dem Entkoppeln befasst , die Referenzposition verschiebt und dem Seilentfernungswert DistanceJoint2D einen neuen Abstandswert zuweist. Fügen Sie der zuvor erstellten Methoden-CD die folgenden Zeilen hinzu:

  // 1 var newAnchorPosition = ropePositions[anchorIndex]; wrapPointsLookup.Remove(ropePositions[hingeIndex]); ropePositions.RemoveAt(hingeIndex); // 2 ropeHingeAnchorRb.transform.position = newAnchorPosition; distanceSet = false; // Set new rope distance joint distance for anchor position if not yet set. if (distanceSet) { return; } ropeJoint.distance = Vector2.Distance(transform.position, newAnchorPosition); distanceSet = true; 

  1. Der Index des aktuellen Ankerpunkts (die zweite Position des Seils von der Schnecke) wird zur neuen Position des Scharniers, und die alte Position des Scharniers wird entfernt (die Position, die zuvor der Schnecke am nächsten war und die wir jetzt „aufdrehen“). Der Variablen newAnchorPosition in der Liste der newAnchorPosition der Wert anchorIndex zugewiesen. Es wird dann verwendet, um die aktualisierte Position des Ankerpunkts zu positionieren.
  2. Das Seilgelenk RigidBody2D (an dem das DistanceJoint2D-Seil befestigt ist) ändert seine Position in die neue Position des Ankerpunkts. Dies gewährleistet eine reibungslose kontinuierliche Bewegung des Butzens am Seil, wenn es mit DistanceJoint2D verbunden ist, und diese Verbindung sollte es ihm ermöglichen, weiter relativ zur neuen Position zu schwingen, die zur Referenz wurde - mit anderen Worten, relativ zum nächsten Punkt des Seils von seiner Position aus.
  3. Anschließend müssen Sie den Entfernungswert von distanceJoint2D aktualisieren, um eine starke Änderung der Entfernung von der Schnecke zum neuen Referenzpunkt zu berücksichtigen. Wenn dies noch nicht geschehen ist, wird eine schnelle Überprüfung des distanceSet Flags durchgeführt, und der Distanz wird der Wert der berechneten Distanz zwischen dem Butzen und der neuen Position des Ankerpunkts zugewiesen.

Speichern Sie das Skript und kehren Sie zum Editor zurück. Starten Sie das Spiel erneut und beobachten Sie, wie sich das Seil von den Kanten löst, wenn die Schnecke die Schwellenwerte jedes Biegepunkts überschreitet!


Obwohl die Logik fertig ist, fügen wir HandleRopeUnwrap() einen HandleRopeUnwrap() bevor wir HandleRopeUnwrap() mit hingeAngle ( if (playerAngle < hingeAngle) ).

 if (!wrapPointsLookup.ContainsKey(hingePosition)) { Debug.LogError("We were not tracking hingePosition (" + hingePosition + ") in the look up dictionary."); return; } 

Dies sollte eigentlich nicht passieren, da wir den Katzenhaken neu definieren und trennen, wenn er zweimal um eine Kante gewickelt wird. In diesem Fall ist es jedoch nicht schwierig, diese Methode mit einer einfachen return und einer Fehlermeldung zu beenden in der Konsole.

Dank dessen können wir solche Grenzfälle außerdem bequemer behandeln. Darüber hinaus erhalten wir eine eigene Fehlermeldung für den Fall, dass etwas Unnötiges auftritt.

Wohin als nächstes?


Hier ist ein Link zum fertigen Projekt dieses zweiten und letzten Teils des Tutorials.

Herzlichen Glückwunsch zum Abschluss dieser Tutorial-Reihe! Wenn es darum ging, Winkel und Positionen zu vergleichen, wurde alles ziemlich kompliziert, aber wir haben dies überlebt und jetzt haben wir ein wunderbares Hakenkatzen- und Seilsystem, das sich auf Objekten im Spiel aufwickeln kann.


Wussten Sie, dass unser Unity-Entwicklungsteam ein Buch geschrieben hat? Wenn nicht, lesen Sie Unity Games By Tutorials . In diesem Spiel lernen Sie, wie Sie vier vorgefertigte Spiele von Grund auf neu erstellen:

  • Zwei-Stock-Schütze
  • Ego-Shooter
  • Tower Defense-Spiel (mit VR-Unterstützung!)
  • 2D-Plattformer

Nachdem Sie dieses Buch gelesen haben, lernen Sie, wie Sie Ihre eigenen Spiele für Windows, MacOS, iOS und andere Plattformen erstellen!

Dieses Buch richtet sich sowohl an Anfänger als auch an diejenigen, die ihre Unity-Fähigkeiten auf ein professionelles Niveau bringen möchten. Um das Buch zu beherrschen, müssen Sie Programmiererfahrung (in jeder Sprache) haben.

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


All Articles