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 movementDirection
Parameter 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.