Wie kann ich mit Unity die Bewegung des Spielers auf die Oberfläche eines 3D-Objekts beschränken?

16

Ich versuche, einen ähnlichen Effekt wie in Mario Galaxy oder Geometry Wars 3 zu erzielen, bei dem sich die Schwerkraft beim Umrunden des "Planeten" anzupassen scheint und sie nicht vom Rand des Objekts abfällt, wie dies bei der Schwerkraft der Fall wäre wurde in einer einzigen Richtung fixiert.

Bildbeschreibung hier eingeben
(Quelle: gameskinny.com )

Geometrie Kriege 3

Ich habe es geschafft, etwas zu implementieren, das meiner Suche nahe kommt, indem ich einen Ansatz gewählt habe, bei dem das Objekt, das die Schwerkraft haben soll, andere starre Körper anzieht, aber indem ich die eingebaute Physik-Engine für Unity verwende und Bewegung mit AddForce und ähnlichen Methoden anwende. Ich konnte es einfach nicht schaffen, dass sich die Bewegung richtig anfühlte. Ich konnte den Spieler nicht dazu bringen, sich schnell genug zu bewegen, ohne dass der Spieler anfing, von der Oberfläche des Objekts zu fliegen, und ich konnte kein gutes Gleichgewicht zwischen angewandter Kraft und Schwerkraft finden, um dies auszugleichen. Meine aktuelle Implementierung ist eine Anpassung dessen, was hier gefunden wurde

Ich glaube, die Lösung würde wahrscheinlich immer noch Physik verwenden, um den Spieler auf das Objekt zu bringen, wenn er die Oberfläche verlassen würde, aber sobald der Spieler geerdet ist, gibt es eine Möglichkeit, den Spieler an die Oberfläche zu schnappen und Physik und zu deaktivieren Kontrolliere den Spieler mit anderen Mitteln, aber ich bin mir nicht sicher.

Wie gehe ich vor, um den Player an der Oberfläche von Objekten zu befestigen? Beachten Sie, dass die Lösung im 3D-Raum funktionieren sollte (im Gegensatz zu 2D) und mit der kostenlosen Version von Unity implementiert werden kann.

SpartanDonut
quelle
Ich dachte nicht einmal daran, auf Mauern zu gehen. Ich werde nachsehen, ob diese helfen.
SpartanDonut
1
Wenn es in dieser Frage speziell darum geht, dies in 3D in Unity zu tun, sollte dies durch Bearbeitungen klarer gemacht werden. (Es wäre dann kein genaues Duplikat des existierenden .)
Anko
Das ist auch mein allgemeines Gefühl - ich werde sehen, ob ich diese Lösung an 3D anpassen und als Antwort posten kann (oder ob mich jemand anderes schlagen kann, damit bin ich auch einverstanden). Ich werde versuchen, meine Frage zu aktualisieren, um diesbezüglich klarer zu werden.
SpartanDonut
Möglicherweise hilfreich: youtube.com/watch?v=JMWnufriQx4
ssb

Antworten:

7

Ich habe es geschafft, das zu erreichen, was ich brauchte, hauptsächlich mit Hilfe dieses Blogposts für das oberflächenschnappende Teil des Puzzles und kam auf meine eigenen Ideen für Spielerbewegung und Kamera.

Player an der Oberfläche eines Objekts ausrichten

Der Grundaufbau besteht aus einer großen Kugel (der Welt) und einer kleineren Kugel (dem Spieler), die beide mit Kugelcollidern versehen sind.

Der Großteil der geleisteten Arbeit war in den folgenden zwei Methoden:

private void UpdatePlayerTransform(Vector3 movementDirection)
{                
    RaycastHit hitInfo;

    if (GetRaycastDownAtNewPosition(movementDirection, out hitInfo))
    {
        Quaternion targetRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
        Quaternion finalRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, float.PositiveInfinity);

        transform.rotation = finalRotation;
        transform.position = hitInfo.point + hitInfo.normal * .5f;
    }
}

private bool GetRaycastDownAtNewPosition(Vector3 movementDirection, out RaycastHit hitInfo)
{
    Vector3 newPosition = transform.position;
    Ray ray = new Ray(transform.position + movementDirection * Speed, -transform.up);        

    if (Physics.Raycast(ray, out hitInfo, float.PositiveInfinity, WorldLayerMask))
    {
        return true;
    }

    return false;
}

Der Vector3 movementDirectionParameter ist so, wie es sich anhört, die Richtung, in die wir unseren Player in diesem Frame bewegen werden, und die Berechnung dieses Vektors, obwohl in diesem Beispiel relativ einfach, war für mich zunächst etwas schwierig. Dazu später mehr, aber denken Sie daran, dass dies ein normalisierter Vektor in der Richtung ist, in die der Spieler diesen Frame bewegt.

Als Erstes prüfen wir, ob ein Strahl, der von der hypothetischen zukünftigen Position ausgeht und auf den Abwärtsvektor des Spielers (-transform.up) gerichtet ist, mit WorldLayerMask, einer öffentlichen LayerMask-Eigenschaft des Skripts, auf die Welt trifft. Wenn Sie komplexere Kollisionen oder mehrere Ebenen wünschen, müssen Sie Ihre eigene Ebenenmaske erstellen. Wenn der Raycast erfolgreich auf etwas trifft, wird mit hitInfo der Normal- und der Trefferpunkt abgerufen, um die neue Position und Drehung des Players zu berechnen, der sich direkt auf dem Objekt befinden sollte. Je nach Größe und Herkunft des betreffenden Spielerobjekts kann ein Versetzen der Position des Spielers erforderlich sein.

Schließlich wurde dies wirklich nur getestet und funktioniert wahrscheinlich nur bei einfachen Objekten wie Kugeln. Wie der Blog-Beitrag, auf dem meine Lösung basiert, andeutet, möchten Sie wahrscheinlich mehrere Raycasts durchführen und diese für Ihre Position und Rotation mitteln, um einen viel schöneren Übergang zu erhalten, wenn Sie sich über komplexeres Gelände bewegen. Es kann auch andere Fallstricke geben, an die ich noch nicht gedacht habe.

Kamera und Bewegung

Sobald der Spieler an der Oberfläche des Objekts klebte, war die nächste Aufgabe die Bewegung. Ich hatte ursprünglich mit Bewegungen relativ zum Spieler begonnen, aber ich fing an, auf Probleme an den Polen der Kugel zu stoßen, bei denen sich die Richtungen plötzlich änderten und mein Spieler immer wieder die Richtung änderte, ohne dass ich jemals an den Polen vorbeikam. Am Ende bewegte ich meine Spieler relativ zur Kamera.

Was für meine Bedürfnisse gut funktionierte, war, eine Kamera zu haben, die dem Spieler ausschließlich anhand der Position des Spielers folgt. Obwohl sich die Kamera technisch drehte, bewegte das Hochdrücken den Player daher immer in Richtung des oberen Bildschirmrandes, in Richtung des unteren Bildschirmrandes usw. mit Links und Rechts.

Dazu wurde auf der Kamera, auf der sich das Zielobjekt befand, Folgendes ausgeführt:

private void FixedUpdate()
{
    // Calculate and set camera position
    Vector3 desiredPosition = this.target.TransformPoint(0, this.height, -this.distance);
    this.transform.position = Vector3.Lerp(this.transform.position, desiredPosition, Time.deltaTime * this.damping);

    // Calculate and set camera rotation
    Quaternion desiredRotation = Quaternion.LookRotation(this.target.position - this.transform.position, this.target.up);
    this.transform.rotation = Quaternion.Slerp(this.transform.rotation, desiredRotation, Time.deltaTime * this.rotationDamping);
}

Um den Player zu bewegen, haben wir die Transformation der Hauptkamera so genutzt, dass sich mit unseren Steuerelementen nach oben, unten usw. bewegt. Hier rufen wir UpdatePlayerTransform auf, mit dem unsere Position auf dem Weltobjekt festgehalten wird.

void Update () 
{        
    Vector3 movementDirection = Vector3.zero;
    if (Input.GetAxisRaw("Vertical") > 0)
    {
        movementDirection += cameraTransform.up;
    }
    else if (Input.GetAxisRaw("Vertical") < 0)
    {
        movementDirection += -cameraTransform.up;
    }

    if (Input.GetAxisRaw("Horizontal") > 0)
    {
        movementDirection += cameraTransform.right;
    }
    else if (Input.GetAxisRaw("Horizontal") < 0)
    {
        movementDirection += -cameraTransform.right;
    }

    movementDirection.Normalize();

    UpdatePlayerTransform(movementDirection);
}

Um eine interessantere Kamera zu implementieren, die Steuerung jedoch in etwa der hier beschriebenen entspricht, können Sie problemlos eine Kamera implementieren, die nicht gerendert ist, oder nur ein anderes Dummy-Objekt, auf dessen Basis die Bewegung ausgeführt werden soll, und anschließend die interessantere Kamera zum Rendern der Objekte verwenden Sie möchten, dass das Spiel so aussieht. Dies ermöglicht schöne Kameraübergänge, wenn Sie um Objekte herumgehen, ohne die Steuerelemente zu beschädigen.

SpartanDonut
quelle
3

Ich denke, diese Idee wird funktionieren:

Behalten Sie eine CPU-seitige Kopie des Planetengitters. Wenn Sie Gitterpunkte haben, haben Sie auch normale Vektoren für jeden Punkt auf dem Planeten. Deaktivieren Sie dann die Schwerkraft für alle Objekte vollständig, und wenden Sie stattdessen eine Kraft in genau entgegengesetzter Richtung zu einem normalen Vektor an.

Nun, basierend auf welchem ​​Punkt sollte dieser Normalenvektor des Planeten berechnet werden?

Die einfachste Antwort (von der ich mir ziemlich sicher bin, dass sie in Ordnung ist) ist eine Annäherung an Newtons Methode : Wenn Objekte zum ersten Mal erscheinen, kennen Sie alle ihre Anfangspositionen auf dem Planeten. Verwenden Sie diese Anfangsposition, um den upVektor jedes Objekts zu bestimmen . Offensichtlich wird die Schwerkraft in die entgegengesetzte Richtung (Richtung down) gerichtet sein. Werfen Sie im nächsten Bild, bevor Sie die Schwerkraft anwenden, einen Strahl von der neuen Position des Objekts in Richtung seines alten downVektors. Verwenden Sie den Schnittpunkt dieses Strahls mit dem Planeten als neue Referenz für die Bestimmung des upVektors. Der seltene Fall, dass der Strahl auf nichts trifft, bedeutet, dass ein schrecklicher Fehler aufgetreten ist. Sie sollten Ihr Objekt an den Ort zurückbringen, an dem es sich im vorherigen Frame befunden hat.

Beachten Sie auch, dass bei dieser Methode die Annäherung umso schlechter wird, je weiter der Spieler vom Planeten entfernt ist. Daher ist es besser, irgendwo um die Füße eines jeden Spielers herum als Ursprung zu verwenden. Ich vermute, aber ich denke, dass die Verwendung von Füßen als Ursprung auch zu einer einfacheren Handhabung und Navigation des Players führen wird.


Ein letzter Hinweis: Für bessere Ergebnisse können Sie sogar Folgendes tun: Verfolgen Sie die Bewegung des Players in jedem Bild (z current_position - last_position. B. mit ). Dann klemmen Sie das movement_vectorso, dass die Länge zum Objekt upNull ist. Nennen wir diesen neuen Vektor reference_movement. Bewegen Sie den vorherigen Punkt reference_pointum reference_movementund verwenden Sie diesen neuen Punkt als Ursprung für die Strahlverfolgung. Nachdem (und wenn) der Strahl den Planeten getroffen hat, bewegen Sie sich reference_pointzu diesem Trefferpunkt. Berechnen Sie abschließend den neuen upVektor aus diesem neuen reference_point.

Ein paar Pseudocodes, um es zusammenzufassen:

update_up_vector(player:object, planet:mesh)
{
    up_vector        = normalize(planet.up);
    reference_point  = ray_trace (object.old_position, -up_vector, planet);
    movement         = object.position - object.old_position;
    movement_clamped = movement - up_vector * dot (movement, up_vector);

    reference_point  += movement_clamped;
    reference_point  = ray_trace (reference_point, -up_vector, planet);
    player.up        = normal_vector(mesh, reference_point);
}
Ali1S232
quelle
2

Dieser Beitrag könnte hilfreich sein. Das Wesentliche ist, dass Sie nicht die Charakter-Controller verwenden, sondern Ihre eigenen mithilfe der Physik-Engine erstellen. Anschließend orientieren Sie sich anhand der unter dem Player erkannten Normalen an der Oberfläche des Netzes.

Hier ist ein schöner Überblick über die Technik. Es gibt viel mehr Ressourcen mit Web-Suchbegriffen wie "Unity Walk auf 3D-Objekten Mario Galaxy".

Es gibt auch ein Demo-Projekt von Unity 3.x, das eine Walking-Engine hatte, mit einem Soldaten und einem Hund und in einer der Szenen. Es wurde das Gehen auf 3D-Objekten im Galaxy- Stil demonstriert . Es heißt das Fortbewegungssystem von runevision.

SpidermanSpidermanDWASC
quelle
1
Auf den ersten Blick funktioniert die Demo nicht sehr gut und das Unity-Paket hat Build-Fehler. Ich werde sehen, ob ich diese Arbeit machen kann, aber eine vollständigere Antwort würde geschätzt.
SpartanDonut
2

Drehung

Überprüfen Sie die Antwort auf diese Frage auf answers.unity3d.com (tatsächlich von mir gestellt). Zitat:

Die erste Aufgabe besteht darin, einen Vektor zu erhalten, der definiert. In Ihrer Zeichnung haben Sie zwei Möglichkeiten. Sie können den Planeten als Kugel behandeln und verwenden (object.position - planet.position). Die zweite Möglichkeit besteht darin, Collider.Raycast () zu verwenden und das zurückgegebene 'hit.normal' zu verwenden.

Hier ist der Code, den er mir vorgeschlagen hat:

var up : Vector3 = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

Nennen Sie das jedes Update, und Sie sollten es zum Laufen bringen. (Beachten Sie, dass der Code in UnityScript ist ).
Der gleiche Code in C # :

Vector3 up = transform.position - planet.position;
transform.rotation = Quaternion.FromToRotation(transform.up, up) * transform.rotation;

Schwere

Für die Schwerkraft kann man meine "Planetenphysik-Technik" verwenden, die ich in meiner Frage beschrieben habe, die aber nicht wirklich optimiert ist. Es war nur etwas, woran ich dachte.
Ich schlage vor, Sie erstellen Ihr eigenes System für die Schwerkraft.

Hier ist ein Youtube-Tutorial von Sebastian Lague . Das ist eine sehr gut funktionierende Lösung.

BEARBEITEN: Gehen Sie in Unity zu Bearbeiten > Projekteinstellungen > Physik und setzen Sie alle Werte für Schwerkraft auf 0 (oder entfernen Sie den starren Körper von allen Objekten), um zu verhindern, dass die eingebaute Schwerkraft (die den Spieler nur nach unten zieht) in Konflikt gerät die maßgeschneiderte lösung. (schalte es aus)

Daniel Kvist
quelle
Das Gravitieren des Spielers in die Mitte eines Planeten (und das entsprechende Drehen) funktioniert gut für nahezu kugelförmige Planeten, aber ich kann mir vorstellen, dass es bei weniger kugelförmigen Objekten, wie der Raketenform, auf die Mario im ersten GIF springt, wirklich schief geht.
Anko
Sie können einen Würfel erstellen, der sich beispielsweise nur um die X-Achse innerhalb des nicht kugelförmigen Objekts bewegen darf, und ihn verschieben, wenn sich der Spieler bewegt, sodass er immer gerade unter dem Spieler ist, und dann den Spieler ziehen runter zum Würfel. Versuch es.
Daniel Kvist