Jak mogę ograniczyć ruch gracza do powierzchni obiektu 3D za pomocą Unity?

16

Staram się stworzyć efekt podobny do efektu Mario Galaxy lub Geometry Wars 3, w którym podczas spaceru po planecie grawitacja wydaje się dostosowywać i nie spada ona z krawędzi obiektu, tak jak gdyby grawitacja został naprawiony w jednym kierunku.

wprowadź opis zdjęcia tutaj
(źródło: gameskinny.com )

Geometry Wars 3

Udało mi się wdrożyć coś zbliżonego do tego, czego szukam, stosując podejście, w którym obiekt, który powinien mieć grawitację, przyciąga do siebie inne sztywne ciała, ale używając wbudowanego silnika fizyki dla Unity, stosując ruch z AddForce i podobne, Po prostu nie mogłem sprawić, by ruch był odpowiedni. Nie mogłem zmusić gracza do poruszania się wystarczająco szybko, gdyby gracz nie zaczął odlatywać z powierzchni obiektu i nie mogłem znaleźć odpowiedniej równowagi przyłożonej siły i grawitacji, aby to uwzględnić. Moje obecne wdrożenie jest adaptacją tego, co zostało znalezione tu

Wydaje mi się, że rozwiązanie prawdopodobnie nadal wykorzystywałoby fizykę, aby uziemić gracza na obiekcie, gdyby miał opuścić powierzchnię, ale gdy gracz zostanie uziemiony, istnieje sposób, aby zrzucić gracza na powierzchnię i wyłączyć fizykę i kontrolować gracza za pomocą innych środków, ale tak naprawdę nie jestem pewien.

Jakie podejście należy zastosować, aby przyciągnąć gracza do powierzchni przedmiotów? Pamiętaj, że rozwiązanie powinno działać w przestrzeni 3D (w przeciwieństwie do 2D) i powinno być możliwe do wdrożenia przy użyciu darmowej wersji Unity.

SpartanDonut
źródło
Nawet nie myślałem o poszukiwaniu chodzenia po ścianach. Zajrzę i zobaczę, czy te pomogą.
SpartanDonut
1
Jeśli to pytanie dotyczy konkretnie robienia tego w 3D w Unity, powinno być jaśniejsze dzięki edycjom. (Wtedy nie byłby to dokładny duplikat tego istniejącego .)
Anko
To też moje ogólne odczucie - zobaczę, czy mogę dostosować to rozwiązanie do 3D i opublikować je jako odpowiedź (lub jeśli ktoś inny może mnie pobić do ciosu, nie mam nic przeciwko). Spróbuję zaktualizować moje pytanie, aby było bardziej jasne na ten temat.
SpartanDonut
Potencjalnie pomocny: youtube.com/watch?v=JMWnufriQx4
ssb

Odpowiedzi:

7

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 movementDirectionParametr 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.

SpartanDonut
źródło
3

Myślę, że ten pomysł zadziała:

Zachowaj kopię siatki planety po stronie procesora. Posiadanie wierzchołków siatki oznacza również, że masz normalne wektory dla każdego punktu na planecie. Następnie całkowicie wyłącz grawitację dla wszystkich bytów, zamiast tego przykładając siłę dokładnie w przeciwnym kierunku niż normalny wektor.

Teraz, w oparciu o który punkt należy obliczyć ten normalny wektor planety?

Najłatwiejszą odpowiedzią (która, jestem pewien, że zadziała dobrze), jest zbliżenie w przybliżeniu do metody Newtona : kiedy obiekty pojawiają się po raz pierwszy, znasz wszystkie ich początkowe pozycje na planecie. Użyj tej pozycji początkowej, aby określić upwektor każdego obiektu . Oczywiście grawitacja będzie w przeciwnym kierunku (w kierunku down). W następnej klatce, przed zastosowaniem grawitacji, rzuć promień z nowej pozycji obiektu w kierunku jego starego downwektora. Użyj przecięcia tego promienia z planetą jako nowego odniesienia do określenia upwektora. Rzadki przypadek, gdy promień nie uderza w nic, oznacza, że ​​coś poszło strasznie nie tak i powinieneś przenieść swój obiekt z powrotem do miejsca, w którym był w poprzedniej klatce.

Należy również pamiętać, że przy użyciu tej metody im dalsze pochodzenie gracza pochodzi z planety, tym gorsze staje się przybliżenie. Dlatego lepiej jest użyć gdzieś wokół stóp każdego gracza jako źródła. Zgaduję, ale myślę, że użycie stóp jako źródła również spowoduje łatwiejszą obsługę i nawigację gracza.


Ostatnia uwaga: Aby uzyskać lepsze wyniki, możesz nawet wykonać następujące czynności: śledzić ruch gracza w każdej klatce (np. Używając current_position - last_position). Następnie zaciśnij to movement_vector, aby jego długość w kierunku obiektu upwynosiła zero. Nazwijmy ten nowy wektor reference_movement. Przenieś poprzednireference_point o reference_movementi użyj tego nowego punktu jako początku śledzenia promieni. Po tym, jak promień uderzy w planetę, przejdź reference_pointdo tego punktu wytrzymałości. Na koniec obliczyć nowy upwektor na podstawie tego nowego reference_point.

Niektóre pseudo-kod podsumowujący:

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
źródło
2

Ten post może być pomocny. Jego istotą jest to, że nie używasz kontrolerów postaci, ale stwórz własny, używając silnika fizyki. Następnie użyj normalnych wykrytych pod odtwarzaczem, aby ustawić je na powierzchni siatki.

Oto ładny przegląd techniki. Istnieje wiele innych zasobów z wyszukiwanymi hasłami, takimi jak „jedność po obiektach mario galaxy”.

Jest też projekt demonstracyjny z Unity 3.x, który miał chodzący silnik, z żołnierzem i psem oraz w jednej ze scen. Pokazał chodzenie po obiektach 3D w stylu Galaxy . Nazywa się to systemem ruchu przez runevision.

SpidermanSpidermanDWASC
źródło
1
Na pierwszy rzut oka demo nie działa bardzo dobrze, a pakiet Unity zawiera błędy kompilacji. Zobaczę, czy dam radę, ale bardziej kompletna odpowiedź będzie mile widziana.
SpartanDonut
2

Obrót

Sprawdź odpowiedź na to pytanie na stronie answer.unity3d.com (faktycznie zadane przeze mnie). Zacytować:

Pierwszym zadaniem jest uzyskanie wektora, który definiuje. Na podstawie rysunku możesz to zrobić na dwa sposoby. Możesz traktować planetę jako kulę i używać (object.position - planet.position). Drugim sposobem jest użycie Collider.Raycast () i użycie „hit.normal”, który zwraca.

Oto kod, który mi zasugerował:

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

Nazwij to każdą aktualizacją, a powinieneś ją uruchomić. (zauważ, że kod jest w języku UnityScript ).
Ten sam kod w C # :

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

Powaga

Do grawitacji możesz użyć mojej „techniki fizyki planet”, którą opisałem w moim pytaniu, ale tak naprawdę nie jest zoptymalizowana. To było coś, co przyszło mi do głowy.
Sugeruję stworzenie własnego systemu grawitacji.

Oto samouczek YouTube'a autorstwa Sebastiana Lague'a . To bardzo dobrze działające rozwiązanie.

EDYCJA: W jedności, przejdź do Edycja > Ustawienia projektu > Fizyka i ustaw wszystkie wartości Grawitacji na 0 (lub usuń Sztywne ciało ze wszystkich obiektów), aby zapobiec wbudowanej grawitacji (która po prostu pociąga gracza w dół) do konfliktu z niestandardowe rozwiązanie. (Wyłącz to)

Daniel Kvist
źródło
Przyciąganie gracza do środka planety (i obracanie ich odpowiednio) działa dobrze na planetach prawie sferycznych, ale mogę sobie wyobrazić, że źle się dzieje w przypadku mniej sferycznych obiektów, takich jak rakieta w kształcie Mario, w którą wskakuje podczas pierwszego GIF-a.
Anko,
Możesz stworzyć sześcian, który może poruszać się tylko wokół osi X, na przykład wewnątrz niesferycznego obiektu, i przesuwać go, gdy gracz się porusza, tak aby zawsze znajdował się prosto pod graczem, a następnie pociągnij gracza aż do sześcianu. Spróbuj.
Daniel Kvist,