Come potrei limitare il movimento del giocatore sulla superficie di un oggetto 3D usando Unity?


16

Sto cercando di creare un effetto simile a quello di Mario Galaxy o Geometry Wars 3 in cui mentre il giocatore cammina intorno al "pianeta" la gravità sembra adattarsi e non cadono dal bordo dell'oggetto come farebbero se la gravità è stato risolto in una sola direzione.

inserisci qui la descrizione dell'immagine
(fonte: gameskinny.com )

Geometry Wars 3

Sono riuscito a implementare qualcosa di simile a quello che sto cercando usando un approccio in cui l'oggetto che dovrebbe avere la gravità attira altri corpi rigidi verso di esso, ma utilizzando il motore fisico incorporato per Unity, applicando il movimento con AddForce e simili, Non riuscivo proprio a far sentire il movimento giusto. Non riuscivo a far muovere il giocatore abbastanza velocemente senza che il giocatore iniziasse a volare via dalla superficie dell'oggetto e non riuscivo a trovare un buon equilibrio tra forza applicata e gravità per adattarlo a questo. La mia attuale implementazione è un adattamento di ciò che è stato trovato qui

Penso che la soluzione probabilmente userebbe ancora la fisica per mettere il giocatore a terra sull'oggetto se dovesse lasciare la superficie, ma una volta che il giocatore è stato messo a terra ci sarebbe un modo per far scattare il giocatore in superficie e spegnere la fisica e controlla il giocatore con altri mezzi ma non ne sono davvero sicuro.

Che tipo di approccio dovrei adottare per far scattare il giocatore sulla superficie degli oggetti? Si noti che la soluzione dovrebbe funzionare nello spazio 3D (rispetto al 2D) e dovrebbe essere in grado di essere implementata utilizzando la versione gratuita di Unity.



Non ho nemmeno pensato di cercare camminando sui muri. Dare un'occhiata e vedere se questi aiutano.
SpartanDonut,

1
Se questa domanda riguarda specificamente questa operazione in 3D in Unity, dovrebbe essere resa più chiara con le modifiche. (Non sarebbe un duplicato esatto di quello esistente allora.)
Anko

Anche questo è il mio sentimento generale: vedrò se posso adattare quella soluzione al 3D e pubblicarla come risposta (o se qualcun altro può battermi al pugno, anche io sto bene). Proverò ad aggiornare la mia domanda per essere più chiaro al riguardo.
SpartanDonut,

Potenzialmente utile: youtube.com/watch?v=JMWnufriQx4
ssb

Risposte:


7

Sono riuscito a realizzare ciò di cui avevo bisogno, principalmente con l'aiuto di questo post sul blog per il pezzo del puzzle scattante in superficie e ho avuto le mie idee per il movimento del giocatore e la telecamera.

Snapping Player sulla superficie di un oggetto

L'impostazione di base è composta da una sfera grande (il mondo) e una sfera più piccola (il giocatore) entrambe con collettori di sfere attaccati ad esse.

La maggior parte del lavoro svolto era nei seguenti due metodi:

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;
}

Il Vector3 movementDirection parametro è proprio come sembra, la direzione in cui stiamo andando a spostare il nostro giocatore in questo frame, e calcolare quel vettore, anche se è risultato relativamente semplice in questo esempio, è stato un po 'complicato da capire all'inizio. Ne parleremo più avanti, ma tieni presente che è un vettore normalizzato nella direzione in cui il giocatore sta spostando questo frame.

Facendo un passo avanti, la prima cosa che facciamo è controllare se un raggio, originato dall'ipotetica posizione futura diretta verso il vettore verso il basso dei giocatori (-transform.up), colpisce il mondo usando WorldLayerMask che è una proprietà pubblica LayerMask della sceneggiatura. Se vuoi collisioni più complesse o più livelli dovrai costruire la tua maschera di livello. Se il raycast colpisce con successo qualcosa, hitInfo viene utilizzato per recuperare il normale e il punto ferita per calcolare la nuova posizione e rotazione del giocatore che dovrebbe essere proprio sull'oggetto. La compensazione della posizione del giocatore può essere richiesta a seconda delle dimensioni e dell'origine dell'oggetto giocatore in questione.

Infine, questo è stato davvero testato e probabilmente funziona bene solo su oggetti semplici come le sfere. Come suggerisce il post sul blog su cui ho basato la mia soluzione, probabilmente vorrai eseguire più raycast e fare una media per la tua posizione e rotazione per ottenere una transizione molto più piacevole quando ti muovi su terreni più complessi. Potrebbero esserci anche altre insidie ​​a cui non ho pensato a questo punto.

Macchina fotografica e movimento

Una volta che il giocatore si è attaccato alla superficie dell'oggetto, il compito successivo da affrontare era il movimento. Inizialmente avevo iniziato con i movimenti relativi al giocatore, ma ho iniziato a incorrere in problemi ai poli della sfera in cui le direzioni improvvisamente cambiarono, facendo sì che il mio giocatore cambiasse rapidamente direzione più e più volte non facendomi passare mai i poli. Quello che ho finito per fare è stato il movimento dei miei giocatori rispetto alla telecamera.

Ciò che ha funzionato bene per le mie esigenze è stato quello di avere una videocamera che seguisse rigorosamente il giocatore basandosi esclusivamente sulla posizione dei giocatori. Di conseguenza, anche se la telecamera stava tecnicamente ruotando, premendo su si spostava sempre il lettore verso la parte superiore dello schermo, verso il basso, e così via con sinistra e destra.

Per fare ciò, è stato eseguito quanto segue sulla telecamera in cui l'oggetto target era il giocatore:

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);
}

Infine, per muovere il giocatore, abbiamo sfruttato la trasformazione della telecamera principale in modo che con i nostri controlli su si muova su, giù si muova giù, ecc. Ed è qui che chiamiamo UpdatePlayerTransform che farà scattare la nostra posizione sull'oggetto mondo.

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);
}

Per implementare una fotocamera più interessante, ma i controlli per essere quasi identici a quelli che abbiamo qui, potresti facilmente implementare una fotocamera che non è renderizzata o solo un altro oggetto fittizio su cui basare il movimento e quindi utilizzare la fotocamera più interessante per rendere ciò che vuoi che il gioco assomigli. Ciò consentirà piacevoli transizioni della videocamera mentre vai in giro per gli oggetti senza rompere i controlli.


3

Penso che questa idea funzionerà:

Conserva una copia lato CPU della mesh del pianeta. Avere vertici mesh significa anche che hai vettori normali per ogni punto del pianeta. Quindi disabilita completamente la gravità per tutte le entità, applicando invece una forza esattamente nella direzione opposta di un vettore normale.

Ora, in base a quale punto dovrebbe essere calcolato quel vettore normale del pianeta?

La risposta più semplice (che sono abbastanza sicuro funzionerà bene) è approssimare in modo simile al metodo di Newton : quando gli oggetti si generano per la prima volta, conosci tutte le loro posizioni iniziali sul pianeta. Utilizzare quella posizione iniziale per determinare il upvettore di ciascun oggetto . Ovviamente la gravità sarà nella direzione opposta (verso down). Nel fotogramma successivo, prima di applicare la gravità, lancia un raggio dalla nuova posizione dell'oggetto verso il suo vecchio downvettore. Usa l'intersezione di quel raggio con il pianeta come nuovo riferimento per determinare ilup vettore. Il raro caso in cui il raggio non colpisca nulla significa che qualcosa è andato terribilmente storto e dovresti spostare il tuo oggetto indietro nel punto in cui era nella cornice precedente.

Nota anche che usando questo metodo, più l'origine del giocatore proviene dal pianeta, peggiore è l'approssimazione. Quindi è meglio usare da qualche parte intorno ai piedi di ogni giocatore come origine. Sto indovinando, ma penso che usare i piedi come origine provocherà anche una più facile gestione e navigazione del giocatore.


Un'ultima nota: per risultati migliori, puoi anche fare quanto segue: tenere traccia dei movimenti del giocatore in ogni fotogramma (ad es. Usando current_position - last_position). Quindi serralo in movement_vectormodo che la sua lunghezza verso l'oggetto upsia zero. Chiamiamo questo nuovo vettore reference_movement. Sposta il precedentereference_point di reference_movemente utilizzare questo nuovo punto come origine del ray tracing. Dopo (e se) il raggio colpisce il pianeta, spostati reference_pointin quel punto colpito. Infine, calcola il nuovo upvettore, da questo nuovo reference_point.

Alcuni pseudo-codice per riassumere:

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);
}

2

Questo post potrebbe essere utile. La sua sostanza è che non usi i controller dei personaggi, ma ne fai uno tuo usando il motore fisico. Quindi si utilizzano le normali rilevate sotto il lettore per orientarle verso la superficie della mesh.

Ecco una bella panoramica della tecnica. Ci sono molte più risorse con termini di ricerca web come "unità a piedi su oggetti 3d mario galaxy".

C'è anche un progetto demo dell'unità 3.x che aveva un motore ambulante, con un soldato e un cane e in una delle scene. Ha dimostrato di camminare su oggetti 3d in stile Galaxy . Si chiama il sistema di locomozione per runevision.


1
A prima vista la demo non funziona molto bene e il pacchetto Unity presenta errori di compilazione. Vedrò se riesco a farlo funzionare, ma una risposta più completa sarebbe apprezzata.
SpartanDonut,

2

Rotazione

Controlla la risposta su questa domanda su answer.unity3d.com (in realtà chiesto da me stesso). Citazione:

Il primo compito è ottenere un vettore che lo definisca. Dal tuo disegno, puoi farlo in due modi. Puoi considerare il pianeta come una sfera e usare (object.position - planet.position). Il secondo modo è usare Collider.Raycast () e usare 'hit.normal' che restituisce.

Ecco il codice che mi ha suggerito:

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

Chiama ogni aggiornamento e dovresti farlo funzionare. (nota che il codice è in UnityScript ).
Lo stesso codice in C # :

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

Gravità

Per la gravità, puoi usare la mia "tecnica di fisica del pianeta", che ho descritto nella mia domanda, ma non è davvero ottimizzata. Era solo qualcosa che mi veniva in mente.
Ti suggerisco di creare il tuo sistema per la gravità.

Ecco un tutorial su Youtube di Sebastian Lague . Questa è una soluzione molto ben funzionante.

MODIFICA: In unità, vai su Modifica > Impostazioni progetto > Fisica e imposta tutti i valori per Gravità su 0 (O rimuovi il Corpo Rigido da tutti gli oggetti), per evitare che la gravità incorporata (che tira semplicemente il giocatore verso il basso) in conflitto con la soluzione personalizzata. (spegnilo)


Gravitare il giocatore al centro di un pianeta (e ruotarlo di conseguenza) funziona bene per i pianeti quasi sferici, ma posso immaginare che vada davvero storto per oggetti meno sferici, come quel Mario a forma di razzo che salta nella prima GIF.
Anko,

È possibile creare un cubo che è limitato a muoversi solo attorno all'asse X, ad esempio all'interno dell'oggetto non sferico, e spostarlo quando il giocatore si muove, in modo che sia sempre dritto sotto il giocatore, quindi tirare il giocatore fino al cubo. Provalo.
Daniel Kvist,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.