Erstellen eines Katzenhakens in Unity. Teil 1

Bild

Katzenhaken verleihen dem Spiel Spaß und interessante Mechanik. Sie können sie verwenden, um sich durch Level zu bewegen, in Arenen zu kämpfen und Gegenstände zu erhalten. Trotz der scheinbaren Einfachheit kann die Physik des Seilmanagements und die Schaffung eines realistischen Verhaltens eine Herausforderung sein!

Im ersten Teil dieses Tutorials implementieren wir unser eigenes zweidimensionales Hook-Cat-System und lernen Folgendes:

  • Erstellen Sie ein Zielsystem.
  • Verwenden Sie den Linienrenderer und das Distanzgelenk, um das Seil zu erstellen.
  • Wir werden dem Seil beibringen, sich um Spielobjekte zu wickeln.
  • Berechnen Sie den Schwenkwinkel am Seil und erhöhen Sie die Stärke in dieser Richtung.

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 . Da DistanceJoint2D in diesem Lernprogramm verwendet wird, sollten Sie auch die Physikverbindungen in Unity 2D durchsehen und erst dann zu diesem Lernprogramm zurückkehren.

An die Arbeit gehen


Laden Sie den Entwurf für dieses Tutorial herunter und öffnen Sie ihn im Unity-Editor. Für den Betrieb ist Unity 2017.1 oder höher erforderlich.

Öffnen Sie die Spielszene aus dem Ordner " Szenen " und sehen Sie, wo wir anfangen werden:


Im Moment haben wir einen einfachen Spielercharakter (Schnecke) und Steine, die in der Luft hängen.

Wichtige Komponenten von GameObject Player sind bisher Capsule Collider und Rigidbody, die es ihm ermöglichen, mit physischen Objekten auf der Ebene zu interagieren. Außerdem ist dem Charakter ein einfaches Bewegungsskript ( PlayerMovement ) beigefügt, mit dem er auf dem Boden gleiten und einfache Sprünge ausführen kann.

Drücken Sie die Play- Taste, um das Spiel zu starten, und versuchen Sie, den Charakter zu steuern. A und D bewegen es nach links / rechts, und wenn Sie die Leertaste drücken , springt es. Versuche nicht zu rutschen und von der Klippe zu fallen, sonst wirst du sterben!


Wir haben bereits die Grundlagen des Managements, aber das größte Problem ist jetzt das Fehlen von Katzenhaken.

Haken und Seile erstellen


Das Cat-Hook-System scheint zunächst recht einfach zu sein, aber für seine qualitativ hochwertige Implementierung müssen viele Aspekte berücksichtigt werden. Hier sind einige der Anforderungen für die zweidimensionale Cat-Hook-Mechanik:

  • Line Renderer zur Anzeige des Seils. Wenn sich das Seil um Objekte wickelt, können wir dem Linienrenderer weitere Segmente hinzufügen und die Eckpunkte an den Punkten platzieren, die den Seilbrüchen entsprechen.
  • DistanceJoint2D. Es kann verwendet werden, um den aktuellen Ankerpunkt des Katzenhakens so zu befestigen, dass unsere Schnecke schwingen kann. Außerdem können wir den Abstand einstellen, mit dem das Seil verlängert und verkleinert werden kann.
  • Untergeordnetes GameObject mit RigidBody2D, das abhängig von der aktuellen Position des Ankerpunkts des Hakens verschoben werden kann. Im Wesentlichen ist dies der Aufhänge- / Ankerpunkt des Seils.
  • Raycast zum Werfen eines Hakens und zum Befestigen an Objekten.

Wählen Sie das Player- Objekt in der Hierarchie aus und fügen Sie ein neues untergeordnetes GameObject mit dem Namen RopeHingeAnchor hinzu . Dieses GameObject wird verwendet, um den Aufhängungs- / Ankerpunkt des Katzenhakens zu positionieren.

Fügen Sie dem RopeHingeAnchor SpriteRenderer- und RigidBody2D- Komponenten hinzu .

Legen Sie für SpriteRenderer die Sprite- Eigenschaft so fest, dass der UISprite- Wert verwendet wird, und ändern Sie die Reihenfolge in Layer auf 2 . Deaktivieren Sie die Komponente, indem Sie das Kontrollkästchen neben ihrem Namen deaktivieren .

Setzen Sie für die RigidBody2D- Komponente die Body Type-Eigenschaft auf Kinematic . Dieser Punkt wird nicht von der physischen Engine, sondern vom Code verschoben.

Wählen Sie die Seilebene aus und setzen Sie die X- und Y-Skalierungswerte der Transformationskomponente auf 4 .


Wählen Sie Player erneut aus und hängen Sie die neue DistanceJoint2D- Komponente an.

Ziehen Sie den RopeHingeAnchor aus der Hierarchie auf die Eigenschaft Connected Rigid Body der DistanceJoint2D- Komponente und deaktivieren Sie Auto Configure Distance .


Erstellen Sie ein neues C # -Skript mit dem Namen RopeSystem im Projektordner " Skripte " und öffnen Sie es im Code-Editor.

Entfernen Sie die Update Methode.

RopeSystem oben im Skript in der RopeSystem Klassendeklaration neue Variablen, die Awake() -Methode und die neue Update Methode hinzu:

 // 1 public GameObject ropeHingeAnchor; public DistanceJoint2D ropeJoint; public Transform crosshair; public SpriteRenderer crosshairSprite; public PlayerMovement playerMovement; private bool ropeAttached; private Vector2 playerPosition; private Rigidbody2D ropeHingeAnchorRb; private SpriteRenderer ropeHingeAnchorSprite; void Awake() { // 2 ropeJoint.enabled = false; playerPosition = transform.position; ropeHingeAnchorRb = ropeHingeAnchor.GetComponent<Rigidbody2D>(); ropeHingeAnchorSprite = ropeHingeAnchor.GetComponent<SpriteRenderer>(); } void Update() { // 3 var worldMousePosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0f)); var facingDirection = worldMousePosition - transform.position; var aimAngle = Mathf.Atan2(facingDirection.y, facingDirection.x); if (aimAngle < 0f) { aimAngle = Mathf.PI * 2 + aimAngle; } // 4 var aimDirection = Quaternion.Euler(0, 0, aimAngle * Mathf.Rad2Deg) * Vector2.right; // 5 playerPosition = transform.position; // 6 if (!ropeAttached) { } else { } } 

Lassen Sie uns jedes Teil der Reihe nach analysieren:

  1. Wir verwenden diese Variablen, um die verschiedenen Komponenten zu verfolgen, mit denen das RopeSystem-Skript interagieren wird.
  2. Die Awake Methode beginnt zu Beginn des Spiels und deaktiviertopeJoint (die DistanceJoint2D-Komponente). Außerdem wird playerPosition auf die aktuelle Position des Players gesetzt.
  3. Dies ist der wichtigste Teil der Hauptschleife von Update() . Zuerst erhalten wir die Mauszeigerposition in der Welt mithilfe der ScreenToWorldPoint Kameramethode. Dann berechnen wir die Blickrichtung, indem wir die Position des Spielers von der Position der Maus in der Welt abziehen. Anschließend erstellen wir damit aimAngle , eine Darstellung des aimAngle des Cursors. Der Wert speichert einen positiven Wert im if-Konstrukt.
  4. aimDirection ist eine Wendung, die später nützlich sein wird. Der Z-Wert interessiert uns nur, da wir eine 2D-Kamera verwenden und dies das einzige entsprechende Betriebssystem ist. Wir übergeben aimAngle * Mathf.Rad2Deg , das den Bogenmaßwinkel in einen Winkel in Grad umwandelt.
  5. Die Position des Spielers wird mithilfe einer praktischen Variablen überwacht, mit der Sie nicht ständig auf transform.Position verweisen können.
  6. Schließlich haben wir das if..else Konstrukt, mit dem wir bald feststellen werden, ob das Seil am Ankerpunkt befestigt ist.

Speichern Sie das Skript und kehren Sie zum Editor zurück.

Hängen Sie die RopeSystem- Komponente an das Player-Objekt an und hängen Sie die verschiedenen Komponenten an die öffentlichen Felder, die wir im RopeSystem- Skript erstellt haben. Ziehen Sie Player , Crosshair und RopeHingeAnchor in die entsprechenden Felder:

  • Seilscharnieranker : RopeHingeAnchor
  • Seilverbindung : Spieler
  • Fadenkreuz : Fadenkreuz
  • Fadenkreuz-Sprite : Fadenkreuz
  • Spielerbewegung : Spieler


Jetzt führen wir nur all diese komplizierten Berechnungen durch, aber bisher gibt es keine Visualisierung, die sie in Aktion zeigen könnte. Aber keine Sorge, wir werden es bald tun.

Öffnen Sie das RopeSystem- Skript und fügen Sie eine neue Methode hinzu:

 private void SetCrosshairPosition(float aimAngle) { if (!crosshairSprite.enabled) { crosshairSprite.enabled = true; } var x = transform.position.x + 1f * Mathf.Cos(aimAngle); var y = transform.position.y + 1f * Mathf.Sin(aimAngle); var crossHairPosition = new Vector3(x, y, 0); crosshair.transform.position = crossHairPosition; } 

Diese Methode positioniert das Visier basierend auf dem übertragenen aimAngle (dem Float-Wert, den wir in Update() berechnet haben) so, dass es sich mit einem Radius von 1 Einheit um den Spieler dreht. Wir fügen auch einen Sprite-Bereich hinzu, falls dies noch nicht geschehen ist.

In Update() ändern wir das Konstrukt !ropeAttached , um !ropeAttached so zu überprüfen, dass es folgendermaßen aussieht:

 if (!ropeAttached) { SetCrosshairPosition(aimAngle); } else { crosshairSprite.enabled = false; } 

Speichern Sie das Skript und führen Sie das Spiel aus. Jetzt sollte unsere Schnecke in der Lage sein, mit einem Anblick zu zielen.


Die nächste Logik, die implementiert werden muss, ist ein Cat-Hook-Schuss. Wir haben die Zielrichtung bereits festgelegt, daher benötigen wir eine Methode, die sie als Parameter erhält.

Fügen Sie die folgenden Variablen unter den Variablen im RopeSystem- Skript hinzu:

 public LineRenderer ropeRenderer; public LayerMask ropeLayerMask; private float ropeMaxCastDistance = 20f; private List<Vector2> ropePositions = new List<Vector2>(); 

LineRenderer enthält einen Link zu einem Linienrenderer, der das Seil zeichnet. Mit LayerMask können Sie die Physik-Layer anpassen, mit denen der Hook interagieren kann. Der Wert für ropeMaxCastDistance legt die maximale Entfernung fest, die Raycast "schießen" kann.

Schließlich wird die Positionsliste von Vector2 verwendet, um die Seilwickelpunkte zu verfolgen, auf die wir später noch eingehen werden.

Fügen Sie die folgenden neuen Methoden hinzu:

 // 1 private void HandleInput(Vector2 aimDirection) { if (Input.GetMouseButton(0)) { // 2 if (ropeAttached) return; ropeRenderer.enabled = true; var hit = Physics2D.Raycast(playerPosition, aimDirection, ropeMaxCastDistance, ropeLayerMask); // 3 if (hit.collider != null) { ropeAttached = true; if (!ropePositions.Contains(hit.point)) { // 4 //    ,    -  . transform.GetComponent<Rigidbody2D>().AddForce(new Vector2(0f, 2f), ForceMode2D.Impulse); ropePositions.Add(hit.point); ropeJoint.distance = Vector2.Distance(playerPosition, hit.point); ropeJoint.enabled = true; ropeHingeAnchorSprite.enabled = true; } } // 5 else { ropeRenderer.enabled = false; ropeAttached = false; ropeJoint.enabled = false; } } if (Input.GetMouseButton(1)) { ResetRope(); } } // 6 private void ResetRope() { ropeJoint.enabled = false; ropeAttached = false; playerMovement.isSwinging = false; ropeRenderer.positionCount = 2; ropeRenderer.SetPosition(0, transform.position); ropeRenderer.SetPosition(1, transform.position); ropePositions.Clear(); ropeHingeAnchorSprite.enabled = false; } 

Der obige Code bewirkt Folgendes:

  1. HandleInput wird von der Update() Schleife aufgerufen und fragt einfach die Eingabe der linken und rechten Maustaste ab.
  2. Wenn ein Linksklick registriert wird, wird die Seillinie eingeschaltet und ein 2D-Raycast von der Position des Spielers in Zielrichtung abgefeuert. Die maximale Entfernung wird so eingestellt, dass die Hakenkatze nicht in unendlicher Entfernung geschossen werden kann, und eine Maske wird angewendet, damit die Schichten der Physik ausgewählt werden können, mit denen Raycast kollidieren kann.
  3. Wenn ein Raycast-Treffer erkannt wird, ropeAttached true , und eine Liste der Positionen der Eckpunkte des Seils wird überprüft, um sicherzustellen, dass dort kein Punkt vorhanden ist.
  4. Wenn die Prüfung wahr zurückgibt, wird dem Butzen ein kleiner Kraftimpuls hinzugefügt, so dass er über dem Boden abprallt. Das ropeJoint (DistanceJoint2D) wird eingeschaltet, das auf den Abstand eingestellt ist, der dem Abstand zwischen dem Schneckenpunkt und dem Raycast-Trefferpunkt entspricht. Ein Ankerpunkt-Sprite ist ebenfalls enthalten.
  5. Wenn Raycast nichts trifft, sind Line Renderer und RopeJoint deaktiviert und das Flag " ropeAttached ist falsch.
  6. Wenn die rechte Maustaste gedrückt wird, wird die ResetRope() -Methode ResetRope() , mit der alle mit dem Seil / Haken verbundenen Parameter deaktiviert und auf die Werte zurückgesetzt werden, die gelten sollten, wenn der Haken nicht verwendet wird.

HandleInput() ganz unten in unserer Update Methode einen Aufruf der neuen HandleInput() -Methode hinzu und übergeben Sie den aimDirection Wert an aimDirection :

 HandleInput(aimDirection); 

Speichern Sie die Änderungen in RopeSystem.cs und kehren Sie zum Editor zurück.

Ein Seil hinzufügen


Unsere Schnecke kann ohne Seil nicht durch die Luft fliegen, daher ist es an der Zeit, ihr etwas zu geben, das eine visuelle Darstellung des Seils darstellt und sich um Ecken "drehen" kann.

Der Linienrenderer ist dafür ideal, da er es uns ermöglicht, die Anzahl der Punkte und ihre Position im Raum der Welt zu übertragen.

Die Idee dabei ist, dass wir immer den ersten Scheitelpunkt des Seils (0) in der Position des Spielers speichern und alle anderen Scheitelpunkte dynamisch positioniert werden, wenn sich das Seil um etwas wickeln soll, einschließlich der aktuellen Position des Scharniers, das der nächste Punkt entlang des Seils ist vom Spieler.

Wählen Sie Player und fügen Sie die LineRenderer- Komponente hinzu. Stellen Sie die Breite auf 0,075 ein . Erweitern Sie die Materialliste und wählen Sie als Element 0 das RopeMaterial- Material aus, das sich im Materialordner des Projekts befindet. Wählen Sie schließlich für Line Renderer für Texture Mode die Option Pro Segment verteilen .


Ziehen Sie die Line Renderer-Komponente in das Feld Rope Renderer der Rope System- Komponente.

Klicken Sie auf die Dropdown-Liste der Rope Layer Mask und wählen Sie als Ebenen aus, mit denen Raycast Default, Rope und Pivot interagieren können. Aus diesem Grund kollidiert ein Raycast beim „Aufnehmen“ nur mit diesen Ebenen, nicht jedoch mit anderen Objekten, z. B. einem Player.


Wenn Sie das Spiel jetzt starten, werden Sie ein merkwürdiges Verhalten bemerken. Wenn wir auf einen Stein über dem Kopf der Schnecke zielen und mit einem Haken schießen, bekommen wir einen kleinen Sprung nach oben, wonach sich unser Freund eher zufällig verhält.


Wir haben den Abstand für das Distanzgelenk noch nicht festgelegt, außerdem sind die Eckpunkte des Linienrenderings nicht konfiguriert. Daher sehen wir das Seil nicht, und da sich das Distanzgelenk direkt über der Position der Schnecke befindet, drückt der aktuelle Wert des Distanzgelenkabstands es zu den Steinen darunter.

Aber keine Sorge, jetzt werden wir dieses Problem lösen.

Fügen Sie im Skript RopeSystem.cs am Anfang der Klasse einen neuen Operator hinzu:

 using System.Linq; 

Auf diese Weise können wir LINQ-Abfragen verwenden, die es uns in unserem Fall einfach ermöglichen, das erste oder letzte Element der Liste der ropePositions bequem zu finden.

Hinweis : Language Integrated Query (LINQ) ist der Name einer Reihe von Technologien, die auf der Einbettung von Abfragefunktionen direkt in C # basieren. Hier können Sie mehr darüber lesen.

Fügen Sie unter den anderen Variablen eine neue private Bool-Variable namens distanceSet :

 private bool distanceSet; 

Wir werden diese Variable als Flag verwenden, damit das Skript erkennen kann, dass der Abstand des Seils (für den Punkt zwischen dem Spieler und dem aktuellen Referenzpunkt, an dem der Katzenhaken angebracht ist) korrekt eingestellt ist.

Fügen Sie nun eine neue Methode hinzu, mit der wir die Positionen der Seilscheitelpunkte für das Rendern der Linie und den Abstand der Distanzverbindung in der gespeicherten Liste der Positionen mit Seil ( ropePositions ) ropePositions :

 private void UpdateRopePositions() { // 1 if (!ropeAttached) { return; } // 2 ropeRenderer.positionCount = ropePositions.Count + 1; // 3 for (var i = ropeRenderer.positionCount - 1; i >= 0; i--) { if (i != ropeRenderer.positionCount - 1) // if not the Last point of line renderer { ropeRenderer.SetPosition(i, ropePositions[i]); // 4 if (i == ropePositions.Count - 1 || ropePositions.Count == 1) { var ropePosition = ropePositions[ropePositions.Count - 1]; if (ropePositions.Count == 1) { ropeHingeAnchorRb.transform.position = ropePosition; if (!distanceSet) { ropeJoint.distance = Vector2.Distance(transform.position, ropePosition); distanceSet = true; } } else { ropeHingeAnchorRb.transform.position = ropePosition; if (!distanceSet) { ropeJoint.distance = Vector2.Distance(transform.position, ropePosition); distanceSet = true; } } } // 5 else if (i - 1 == ropePositions.IndexOf(ropePositions.Last())) { var ropePosition = ropePositions.Last(); ropeHingeAnchorRb.transform.position = ropePosition; if (!distanceSet) { ropeJoint.distance = Vector2.Distance(transform.position, ropePosition); distanceSet = true; } } } else { // 6 ropeRenderer.SetPosition(i, transform.position); } } } 

Erläutern Sie den oben gezeigten Code:

  1. Beenden Sie die Methode, wenn das Seil nicht befestigt ist.
  2. Wir weisen den Wert der ropePositions der Seillinie der Anzahl der in ropePositions gespeicherten ropePositions , plus 1 weiteren (für die Position des Spielers).
  3. Wir ropePositions Liste der ropePositions und führen eine ropePositions für jede Position (mit Ausnahme der letzten) durch. ropePositions die Position des Scheitelpunkts des Linienrenderers dem Wert der Position Vector2 zu, die vom Schleifenindex in der Liste der Seilpositionen gespeichert wird.
  4. Weisen Sie dem Ankerpunkt des Seils die zweite von der Endposition des Seils zu, in der sich der aktuelle Scharnier- / Ankerpunkt befinden sollte, oder machen Sie ihn zum Ankerpunkt, wenn wir nur eine Position des Seils haben. Also setzen wir das DistanzseilJoint gleich dem Abstand zwischen dem Spieler und der aktuellen Position des Seils, die wir in der Schleife ropeJoint .
  5. Das if-Konstrukt behandelt den Fall, in dem die aktuelle Position des Seils in der Schleife vom Ende an zweiter Stelle steht. d.h. der Punkt, an dem das Seil mit dem Objekt verbunden ist, d.h. aktueller Scharnier- / Ankerpunkt.
  6. Dieser else Block behandelt das Zuweisen der Position des letzten Scheitelpunkts des Seils zum Wert der aktuellen Position des Spielers.

Denken Sie daran, den UpdateRopePositions() am Ende von Update() UpdateRopePositions() hinzuzufügen:

 UpdateRopePositions(); 

Speichern Sie die Änderungen am Skript und führen Sie das Spiel erneut aus. Machen Sie aus einem „kleinen Raum“ einen kleinen Sprung, indem Sie mit einem Haken auf einen Stein über dem Charakter zielen und schießen. Jetzt können Sie die Früchte Ihrer Arbeit genießen - die Schnecke schwankt ruhig über den Steinen.


Jetzt können Sie zum Szenenfenster gehen, Player auswählen, das Verschieben-Werkzeug (standardmäßig die W- Taste) verwenden, um es zu verschieben, und beobachten, wie die beiden Eckpunkte des Renderings der Seillinie der Position des Hakens und der Position des Players zum Zeichnen des Seils folgen. Nachdem wir den Player losgelassen haben, berechnet DistanceJoint2D die Entfernung korrekt und der Butzen schwingt weiter am angeschlossenen Scharnier.


Umgang mit Wickelpunkten


Das Spielen mit einer schwingenden Schnecke ist bisher nicht nützlicher als ein wasserabweisendes Handtuch, daher müssen wir es definitiv ergänzen.


Die gute Nachricht ist, dass die neu hinzugefügte Methode zur Verarbeitung von Seilpositionen in Zukunft verwendet werden kann. Bisher verwenden wir nur zwei Seilpositionen. Einer ist mit der Position des Spielers verbunden und der zweite mit der aktuellen Position des Ankerpunkts des Hakens, wenn er auf ihn schießt.

Das einzige Problem ist, dass wir zwar nicht alle möglichen Positionen des Seils verfolgen, dies jedoch ein wenig Arbeit erfordert.

Um die Positionen auf den Steinen zu erkennen, um die sich das Seil wickeln soll, und dem Linienrenderer eine neue Scheitelpunktposition hinzuzufügen, benötigen wir ein System, das bestimmt, ob der Scheitelpunkt des Kolliders zwischen der geraden Linie zwischen der aktuellen Position der Schnecke und dem aktuellen Scharnier- / Ankerpunkt des Seils liegt.

Sieht so aus, als wäre dies wieder Arbeit für den guten alten Raycast!


Zuerst müssen wir eine Methode erstellen, die den nächstgelegenen Punkt im Collider basierend auf dem Trefferpunkt des Raycasts und den Kanten des Colliders findet.

Fügen Sie dem Skript RopeSystem.cs eine neue Methode hinzu :

 // 1 private Vector2 GetClosestColliderPointFromRaycastHit(RaycastHit2D hit, PolygonCollider2D polyCollider) { // 2 var distanceDictionary = polyCollider.points.ToDictionary<Vector2, float, Vector2>( position => Vector2.Distance(hit.point, polyCollider.transform.TransformPoint(position)), position => polyCollider.transform.TransformPoint(position)); // 3 var orderedDictionary = distanceDictionary.OrderBy(e => e.Key); return orderedDictionary.Any() ? orderedDictionary.First().Value : Vector2.zero; } 

Wenn Sie mit LINQ-Abfragen nicht vertraut sind, scheint dieser Code eine Art komplizierte C # -Magie zu sein.


Wenn ja, dann haben Sie keine Angst. LINQ leistet viel Arbeit für uns:

  1. Diese Methode verwendet zwei Parameter - das RaycastHit2D- Objekt und das PolygonCollider2D-Objekt . Alle Steine ​​auf dem Level haben PolygonCollider2D-Collider. Wenn wir also immer PolygonCollider2D-Formen verwenden, funktioniert dies einwandfrei.
  2. Hier beginnt die Magie der LINQ-Abfrage! Hier transformieren wir die Punktesammlung des polygonalen Colliders in das Vector2- Positionswörterbuch (der Wert jedes Elements des Wörterbuchs ist die Position selbst), und dem Schlüssel jedes Elements wird der Abstand von diesem Punkt zum Positionsspieler des Spielers zugewiesen (Gleitkommawert). Manchmal passiert hier etwas anderes: Die resultierende Position wird in den Weltraum konvertiert (standardmäßig werden die Positionen der Kolliderscheitelpunkte im lokalen Raum gespeichert, d. H. Lokal relativ zu dem Objekt, zu dem der Kollider gehört, und wir benötigen Positionen im Weltraum).
  3. Das Wörterbuch ist nach Schlüsseln sortiert. Mit anderen Worten, um die Entfernung, die der aktuellen Position des Spielers am nächsten liegt. Die nächstgelegene Entfernung wird zurückgegeben, dh jeder Punkt, der mit dieser Methode zurückgegeben wird, ist der Kolliderpunkt zwischen dem Spieler und dem aktuellen Punkt des Seilscharniers!

Kehren wir zum Skript RopeSystem.cs zurück und fügen oben eine neue private Feldvariable hinzu:

 private Dictionary<Vector2, int> wrapPointsLookup = new Dictionary<Vector2, int>(); 

Wir werden es verwenden, um die Positionen zu verfolgen, um die sich das Seil wickeln kann.

Suchen Sie am Ende der Update() -Methode das else Konstrukt, das crosshairSprite.enabled = false; enthält crosshairSprite.enabled = false; und fügen Sie Folgendes hinzu:

 // 1 if (ropePositions.Count > 0) { // 2 var lastRopePoint = ropePositions.Last(); var playerToCurrentNextHit = Physics2D.Raycast(playerPosition, (lastRopePoint - playerPosition).normalized, Vector2.Distance(playerPosition, lastRopePoint) - 0.1f, ropeLayerMask); // 3 if (playerToCurrentNextHit) { var colliderWithVertices = playerToCurrentNextHit.collider as PolygonCollider2D; if (colliderWithVertices != null) { var closestPointToHit = GetClosestColliderPointFromRaycastHit(playerToCurrentNextHit, colliderWithVertices); // 4 if (wrapPointsLookup.ContainsKey(closestPointToHit)) { ResetRope(); return; } // 5 ropePositions.Add(closestPointToHit); wrapPointsLookup.Add(closestPointToHit, 0); distanceSet = false; } } } 

Erklären Sie diesen Code:

  1. Wenn einige Positionen in der ropePositions gespeichert sind, dann ...
  2. Wir schießen von der Position des Spielers in Richtung des Spielers, der die letzte Position des Seils aus der Liste betrachtet - den Ankerpunkt, an dem der Katzenhaken am Stein befestigt ist - mit einem Raycast-Abstand, der dem Abstand zwischen dem Spieler und der Position des Ankerpunkts des Seils entspricht.
  3. Wenn Raycast mit etwas kollidiert, wird der Collider dieses Objekts sicher in den Typ PolygonCollider2D umgewandelt . Während es sich um einen echten PolygonCollider2D handelt, wird die nächstgelegene Scheitelpunktposition dieses Colliders mit der Methode zurückgegeben, die wir zuvor als Vector2 geschrieben haben .
  4. Es wird von wrapPointsLookup überprüft, um sicherzustellen, dass dieselbe Position nicht erneut überprüft wird. Wenn es aktiviert ist, werfen wir das Seil weg, schneiden es ab und lassen den Spieler fallen.
  5. Dann wird die Liste der ropePositions : Die Position wird hinzugefügt, um die sich das Seil wickeln soll. Das wrapPointsLookup Wörterbuch wird ebenfalls aktualisiert. Schließlich wird das Flag distanceSet zurückgesetzt, damit die UpdateRopePositions() -Methode den Abstand des Seils mit der neuen Länge des Seils und der Segmente neu definieren kann.

ResetRope() in ResetRope() die folgende Zeile hinzu, damit das wrapPointsLookup Wörterbuch jedes Mal gelöscht wird, wenn ein Spieler ein Seil wrapPointsLookup :

 wrapPointsLookup.Clear(); 

Speichern und starten Sie das Spiel. Schießen Sie den Katzenhaken in den Stein über der Schnecke und bewegen Sie die Schnecke mit dem Verschieben-Werkzeug im Szenenfenster über mehrere Steinleisten.


So haben wir Seil gelehrt, um Gegenstände zu wickeln!

Schaukelfähigkeit hinzufügen


Die am Seil hängende Schnecke ist ziemlich statisch. Um dies zu beheben, können wir die Schwingfähigkeit hinzufügen.

Dazu müssen wir eine Position senkrecht zur Position des Vorwärtsschwingens (seitwärts) erhalten, unabhängig von dem Winkel, in den er schaut.

Öffnen Sie PlayerMovement.cs und fügen Sie die folgenden zwei öffentlichen Variablen oben im Skript hinzu:

 public Vector2 ropeHook; public float swingForce = 4f; 

Der ropeHook Variablen wird jede Position zugewiesen, an der sich der swingForce gerade befindet, und swingForce ist der Wert, den wir zum Hinzufügen der Swing-Bewegung verwenden.

Ersetzen Sie die FixedUpdate() -Methode FixedUpdate() neue:

 void FixedUpdate() { if (horizontalInput < 0f || horizontalInput > 0f) { animator.SetFloat("Speed", Mathf.Abs(horizontalInput)); playerSprite.flipX = horizontalInput < 0f; if (isSwinging) { animator.SetBool("IsSwinging", true); // 1 -          var playerToHookDirection = (ropeHook - (Vector2)transform.position).normalized; // 2 -  ,     Vector2 perpendicularDirection; if (horizontalInput < 0) { perpendicularDirection = new Vector2(-playerToHookDirection.y, playerToHookDirection.x); var leftPerpPos = (Vector2)transform.position - perpendicularDirection * -2f; Debug.DrawLine(transform.position, leftPerpPos, Color.green, 0f); } else { perpendicularDirection = new Vector2(playerToHookDirection.y, -playerToHookDirection.x); var rightPerpPos = (Vector2)transform.position + perpendicularDirection * 2f; Debug.DrawLine(transform.position, rightPerpPos, Color.green, 0f); } var force = perpendicularDirection * swingForce; rBody.AddForce(force, ForceMode2D.Force); } else { animator.SetBool("IsSwinging", false); if (groundCheck) { var groundForce = speed * 2f; rBody.AddForce(new Vector2((horizontalInput * groundForce - rBody.velocity.x) * groundForce, 0)); rBody.velocity = new Vector2(rBody.velocity.x, rBody.velocity.y); } } } else { animator.SetBool("IsSwinging", false); animator.SetFloat("Speed", 0f); } if (!isSwinging) { if (!groundCheck) return; isJumping = jumpInput > 0f; if (isJumping) { rBody.velocity = new Vector2(rBody.velocity.x, jumpSpeed); } } } 

Die wichtigsten Änderungen hierbei sind, dass die Flagge zuerst überprüft wird, isSwingingsodass Aktionen nur ausgeführt werden, wenn die Schnecke am Seil hängt. Außerdem fügen wir eine Senkrechte zur Ecke der Schnecke hinzu, die ihren aktuellen Ankerpunkt oben am Seil anzeigt, jedoch senkrecht zur Richtung ihres Schwingens.

  1. Wir erhalten den normalisierten Richtungsvektor vom Spieler zum Hakenbefestigungspunkt.
  2. Abhängig davon, ob die Schnecke nach links oder rechts schwankt, wird die senkrechte Richtung mit berechnet playerToHookDirection. Ein Debug-Draw-Aufruf wurde ebenfalls hinzugefügt, damit Sie ihn auf Wunsch im Editor anzeigen können.

Öffnen Sie RopeSystem.cs und fügen Sie oben im else-Block der if(!ropeAttached)Methode Update()Folgendes hinzu:

 playerMovement.isSwinging = true; playerMovement.ropeHook = ropePositions.Last(); 

Fügen Sie im if-Block desselben Designs if(!ropeAttached)Folgendes hinzu:

 playerMovement.isSwinging = false; 

Daher informieren wir das PlayerMovement-Skript, dass der Spieler schwingt, und bestimmen auch die letzte Position (mit Ausnahme der Position des Spielers) des Seils - mit anderen Worten den Ankerpunkt des Seils. Dies ist erforderlich, um den senkrechten Winkel zu berechnen, den wir gerade zum PlayerMovement-Skript hinzugefügt haben.

So sieht es aus, wenn Sie in einem laufenden Spiel Gizmos einschalten und A oder D drücken, um nach links / rechts zu schwingen:


Seilabstieg hinzufügen


Wir haben zwar nicht die Fähigkeit, das Seil auf und ab zu bewegen. Obwohl die Schnecke im wirklichen Leben nicht leicht am Seil entlang steigen und fallen konnte, ist dies ein Spiel, in dem alles passieren kann, oder? Fügen Sie

oben im RopeSystem- Skript zwei neue Feldvariablen hinzu:

 public float climbSpeed = 3f; private bool isColliding; 

climbSpeedLegt die Geschwindigkeit fest, mit der sich der Butzen auf dem Seil auf und ab bewegen kann, und isCollidingwird als Flagge verwendet, um zu bestimmen, ob die Distanzverbindungseigenschaft des Distanzgelenkseils erhöht oder verringert werden kann.

Fügen Sie diese neue Methode hinzu:

 private void HandleRopeLength() { // 1 if (Input.GetAxis("Vertical") >= 1f && ropeAttached && !isColliding) { ropeJoint.distance -= Time.deltaTime * climbSpeed; } else if (Input.GetAxis("Vertical") < 0f && ropeAttached) { ropeJoint.distance += Time.deltaTime * climbSpeed; } } 

Dieser Block if..elseifliest die Eingabe entlang der vertikalen Achse (auf / ab oder W / S auf der Tastatur) und ropeAttached iscCollidingvergrößert oder verkleinert unter Berücksichtigung der Flaggen den Abstand ropeJoint, wodurch der Effekt einer Verlängerung oder Verkürzung des Seils entsteht.

Wir verknüpfen diese Methode und fügen ihren Aufruf am Ende hinzu Update():

 HandleRopeLength(); 

Wir brauchen auch eine Möglichkeit, die Flagge zu setzen isColliding.

Fügen Sie am Ende des Skripts die folgenden zwei Methoden hinzu:

 void OnTriggerStay2D(Collider2D colliderStay) { isColliding = true; } private void OnTriggerExit2D(Collider2D colliderOnExit) { isColliding = false; } 

Diese beiden Methoden sind native Methoden der Basisklasse von MonoBehaviour-Skripten.

Wenn Collider derzeit ein anderes physisches Objekt im Spiel berührt, wird die Methode ständig OnTriggerStay2Dausgelöst und der Flagge einen isCollidingWert zugewiesen true. Dies bedeutet, dass dem isColliding-Flag ein Wert zugewiesen wird, wenn die Schnecke den Stein berührt true.

Die Methode wird OnTriggerExit2Dausgelöst, wenn ein Collider den Bereich eines anderen Colliders verlässt und das Flag auf false setzt.

Beachten Sie: Die Methode OnTriggerStay2Dkann sehr rechenintensiv sein. Gehen Sie daher vorsichtig vor.

Wohin als nächstes?


Starten Sie das Spiel erneut und drücken Sie diesmal die Pfeiltasten oder W / S, um das Seil auf und ab zu bewegen.


Das fertige Projekt dieses Teils des Tutorials kann hier heruntergeladen werden .

Wir haben einen langen Weg zurückgelegt - von der nicht schwingenden Schnecke bis zur akrobatischen muschelfreien Gastropodenmolluske!

Sie haben gelernt, wie Sie ein Zielsystem erstellen, das einen Katzenhaken auf jedes Objekt mit einem Kollider schießt, sich daran festhält und gleichzeitig darauf schwingt und sich an einem dynamischen Seil um die Kanten von Objekten dreht! Ausgezeichnete Arbeit.


Hier fehlt jedoch eine wichtige Funktion - das Seil kann sich bei Bedarf nicht „abwickeln“.

Im zweiten Teil des Tutorials werden wir dieses Problem lösen.

Aber wenn Sie bereit sind, ein Risiko einzugehen, warum versuchen Sie es dann nicht selbst? Sie können hierfür ein Wörterbuch verwenden wrapPointsLookup.

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


All Articles