Costruire un Plattformer - Come determinare se un giocatore può saltare?


16

Sto costruendo un semplice gioco Jump n 'Run Style Plattformer. Non uso le tessere, invece ho forme geometriche per le mie entità di livello (e anche il giocatore è uno). Ho finito il mio codice di rilevamento delle collisioni e tutto funziona bene finora.

Successivamente, volevo implementare il salto. Basta controllare se il giocatore preme il tasto appropriato e aggiungere un po 'di velocità verso l'alto. Funziona bene. Ma funziona anche se il giocatore è in aria, il che non è quello che voglio. ;-)

Quindi, devo controllare se il giocatore si trova su qualcosa. La mia prima idea è stata quella di verificare se ci fosse una collisione nell'ultimo fotogramma e contrassegnare il giocatore come "in grado di saltare", ma ciò si innescherebbe anche se il giocatore colpisce un muro in aria. Poiché le mie abilità matematiche non sono così buone, chiedo aiuto - anche i suggerimenti farebbero come implementarlo.

Grazie!

Risposte:


14

Mi vengono in mente due opzioni:

  • Il primo pensiero è di contrassegnare la geometria con un ID e quindi verificare se la collisione è con la geometria contrassegnata come pavimento. Ciò offre il massimo controllo delle superfici saltabili ma a un costo in termini di tempo di creazione del livello.
  • Il secondo è controllare la normale della collisione, se è rivolta verso l'alto, quindi consentire il salto. O entro un certo margine, dipende da se si hanno piani inclinati. Questo è flessibile e non richiede alcun tag, ma se hai pavimenti e pareti inclinati potresti saltare un po 'dove non lo desideri.

Il controllo normale mi sembra un'ottima idea. Grazie per averlo pubblicato.
Christopher Horenstein,

+1 per il controllo normale. Ciò garantisce che un pavimento possa passare senza soluzione di continuità in un muro senza causare problemi.
Soviut,

1
+1 Controlla sicuramente la collisione normale. Avere diversi tipi di geometria del mondo va bene e consente una progettazione di livello più flessibile, ma non risolve il problema principale. Un "muro" potrebbe anche essere di tipo "terra", a seconda che ci si trovi o ci si trovi sopra. Ecco dove la collisione normale aiuterà.
Bummzack,

Trovo la soluzione normale molto bella e risolverei il mio problema. Anche l'altra risposta di alto livello è buona e così - ma questo risponde meglio alla mia domanda. Grazie al tuo wkerslake!
Malax,

8

Dovresti sicuramente implementare un qualche tipo di tipo di superficie. Pensaci, come farai se riesci a salire una scala se non sai se il tuo personaggio si è appena scontrato contro un muro o una scala? Potresti semplicemente usare OOP per gestire una gerarchia di tipi usando heritage, ma ti suggerirei di usare le "categorie" implementate usando un tipo enumerato:

Ecco l'idea: un'enumerazione "Collisioni" ha un flag per ogni categoria. Per esempio:

namespace Collisions
{
    enum Type
    {
        None   = 0,
        Floor  = 1 << 0,
        Ladder = 1 << 1,
        Enemy  = 1 << 2,
        ... // And whatever else you need.

        // Then, you can construct named groups of flags.
        Player = Floor | Ladder | Enemy
    };
}

Con questo metodo, sarai in grado di verificare se il juste del giocatore ha scontrato qualcosa che dovresti gestire, in modo che il tuo motore possa chiamare un metodo "Collided" dell'entità:

void Player::Collided( Collisions::Type group )
{
   if ( group & Collisions::Ladder )
   {
      // Manage Ladder Collision
   }
   if ( group & Collisions::Floor )
   {
      // Manage Floor Collision
   }
   if ( group & Collisions::Enemy )
   {
      // Manage Enemy Collision
   }
}

Il metodo usa flag bit a bit e l'operatore bit a bit "Or" per assicurare che ogni gruppo abbia un valore diverso, basato sul valore binario della categoria. Questo metodo funziona bene ed è facilmente scalabile in modo da poter creare gruppi di collisioni doganali. Ogni Entità (Giocatore, Nemico, ecc.) Nel tuo gioco ha alcuni bit chiamati "filtro", che sono usati per determinare con cosa possono scontrarsi. Il codice di collisione dovrebbe verificare se i bit corrispondono e reagiscono di conseguenza, con un codice che potrebbe apparire come:

void PhysicEngine::OnCollision(...)
{
    mPhysics.AddContact( body1, body1.GetFilter(), body2, body2.GetFilter() );
}

C'è un motivo per usare const ints invece di un enum? Nel caso degenerato di un singolo flag o gruppo denominato, il tipo enumerato è più leggibile e si ottengono ulteriori capacità di controllo del tipo nella maggior parte dei compilatori.

Bene sì, l'enum non può (credo) usare operatori binari per creare nuovi gruppi personalizzati. Il motivo per cui ho reso costanti queste tesi è che uso una classe Config con membri statici pubblici per motivi di leggibilità, mantenendo la sicurezza dei parametri di configurazione.
Frédérick Imbeault,

Può. I "valori" di enumerazione possono essere operazioni bit per bit su altri valori costanti (penso che in realtà possa essere qualsiasi espressione costante, ma sono troppo pigro per controllare lo standard). I valori non statici vengono raggiunti esattamente come nell'esempio, ma vengono digitati in modo più accurato. Non ho idea di cosa intendi per "sicurezza", ma con lo spazio dei nomi è possibile ottenere esattamente la stessa sintassi senza l'overhead associato a una classe.

Ho aggiornato i tuoi esempi di codice per utilizzare un tipo elencato.

Forse rende le cose più facili da leggere. Approvo le modifiche che hai apportato, il metodo che ho usato era corretto ma non avevo modificato il codice per la spiegazione, grazie per le correzioni. Per lo spazio dei nomi, hai ragione, ma questo non è vero per tutte le lingue (ad esempio le lingue di scripting). La "Sicurezza" è che le mie opzioni di configurazione sono statiche, quindi non possono essere modificate, ma l'utilizzo di un enum impedisce anche questo.
Frédérick Imbeault,

1

Se consideri il piede del tuo personaggio sempre sotto il personaggio di una certa distanza e se non ti stai allontanando dalla superficie, il tuo personaggio è a terra.

In pseudo-codice approssimativo:

bool isOnGround(Character& chr)
{
   // down vector is opposite from your characters current up vector.
   // if you want to support wall jumps, animate the vecToFoot as appropriate.
   vec vecToFoot = -chr.up * chr.footDistanceFromCentre;

// if feet are moving away from any surface, can't be on the ground if (dot(chr.velocity, down) < 0.0f) return false;

// generate line from character centre to the foot position vec linePos0 = chr.position; vec linePos1 = chr.position + vecToFoot;

// test line against world. If it returns a hit surface, we're on the ground if (testLineAgainstWorld(line0, line1, &surface) return true;

return false; }


Questo non funzionerà se si desidera aggiungere funzionalità come i salti doppi o i salti a parete, penso?
Frédérick Imbeault,

Non credo che l'OP abbia menzionato i salti doppi o quelli a parete, vero?
jpaver,

Ho modificato il codice per mostrare come sottrarre il vettore al tuo piede dal centro del personaggio. Se lo animi e i piedi finiscono lateralmente e colpiscono un muro, sarai in grado di supportare il salto del muro.
jpaver,

Questo è fondamentalmente un modo meno flessibile di controllare la collisione normale. (Meno flessibile perché puoi distinguere banalmente un salto dal muro da un salto dal pavimento da un salto dal soffitto solo controllando la direzione del normale.)

2
Non chiamare mai una variabile char, nemmeno in pseudo-codice.
bendaggio
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.