Udało mi się osiągnąć to, czego potrzebowałem, przede wszystkim przy pomocy tego wpisu na blogu dotyczącego łamigłówki na powierzchni i wymyśliłem własne pomysły na ruch gracza i kamerę.
Przyciąganie odtwarzacza do powierzchni obiektu
Podstawowa konfiguracja składa się z dużej kuli (świata) i mniejszej kuli (gracza), oba z dołączonymi zderzakami kul.
Większość wykonywanych prac polegała na dwóch następujących metodach:
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;
}
The Vector3 movementDirection
Parametr jest tak jak się wydaje, w kierunku będziemy zmierzać nasz zawodnik w tej ramce, a wyliczenia tego wektora, a skończyło się stosunkowo prosta w tym przykładzie był nieco trudne dla mnie, aby dowiedzieć się, w pierwszej kolejności. Więcej na ten temat później, ale pamiętaj, że jest to znormalizowany wektor w kierunku, w którym gracz przesuwa tę ramkę.
Przechodząc, najpierw sprawdzamy, czy promień pochodzący z hipotetycznej przyszłej pozycji skierowanej w stronę wektora w dół graczy (-transform.up) uderza w świat za pomocą WorldLayerMask, która jest publiczną właściwością LayerMask skryptu. Jeśli chcesz bardziej złożonych kolizji lub wielu warstw, będziesz musiał zbudować własną maskę warstwy. Jeśli raycast z powodzeniem trafi w coś, hitInfo służy do odzyskania normalnego i hit point, aby obliczyć nową pozycję i obrót gracza, który powinien znajdować się bezpośrednio na obiekcie. Przesunięcie pozycji gracza może być wymagane w zależności od wielkości i pochodzenia danego obiektu gracza.
Wreszcie, tak naprawdę zostało to przetestowane i prawdopodobnie działa dobrze tylko na prostych obiektach, takich jak kule. Ponieważ post na blogu, na którym oparłem swoje rozwiązanie, sugeruje, prawdopodobnie będziesz chciał wykonać wiele raycastów i uśrednić je dla swojej pozycji i rotacji, aby uzyskać znacznie lepsze przejście podczas poruszania się w bardziej złożonym terenie. Mogą być też inne pułapki, o których w tym momencie nie myślałem.
Kamera i ruch
Gdy gracz przyklejał się do powierzchni obiektu, kolejnym zadaniem do wykonania był ruch. Początkowo zaczynałem od ruchu względem gracza, ale zacząłem napotykać problemy na biegunach kuli, w których nagle zmieniły się kierunki, powodując, że mój gracz szybko zmieniał kierunek w kółko, nie pozwalając mi nigdy przekroczyć biegunów. Skończyło się na tym, że moi gracze poruszali się względem kamery.
To, co zadziałało dobrze dla moich potrzeb, to mieć kamerę, która ściśle śledziłaby gracza w oparciu wyłącznie o pozycję gracza. W rezultacie, mimo że kamera technicznie się obraca, naciśnięcie w górę zawsze przesuwało odtwarzacz w kierunku góry ekranu, w dół w dół i tak dalej z lewej i prawej strony.
Aby to zrobić, w kamerze, w której obiektem docelowym był gracz, wykonano:
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);
}
Wreszcie, aby przesunąć gracza, wykorzystaliśmy transformację kamery głównej, aby za pomocą naszych kontrolek ruchy w górę, ruchy w dół itp. I tu właśnie wywołujemy UpdatePlayerTransform, który unieruchamia naszą pozycję do obiektu świata.
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);
}
Aby zaimplementować bardziej interesującą kamerę, ale elementy sterujące będą w przybliżeniu takie same, jak tutaj, możesz łatwo zaimplementować kamerę, która nie jest renderowana, lub po prostu inny atrapa obiektu, aby wyłączyć ruch, a następnie użyć bardziej interesującej kamery do renderowania tego, co chcesz, żeby gra wyglądała. Umożliwi to ładne przejścia z kamery podczas poruszania się po obiektach bez zrywania elementów sterujących.