In questo suggerimento rapido, ti mostrerò come usare il seno
funzione per dare agli oggetti del gioco un movimento fluido avanti e indietro - niente più duri zig-zag dove i tuoi nemici galleggianti sembrano rimbalzare contro un muro invisibile!
Per prima cosa, lascia che ti mostri il tipo di movimento continuo avanti e indietro, intendo. (I grafici provengono dal nostro pacchetto di sprite shoot-'em-up completamente gratuito.)
Questo nemico si muove su e giù, sparando proiettili a intervalli regolari mentre va:
Questo nemico tesse attraverso lo schermo:
Entrambi i tipi di movimento sono utili per i giochi sparatutto. Nota come il movimento è fluido e graduale - nessun movimento improvviso, nessun "sobbalzo" quando il nemico cambia direzione. Questo è in netto contrasto con ...
Un primo tentativo comune di creare un movimento avanti e indietro è di fare qualcosa del genere:
var goingUp = false; // La funzione viene eseguita ogni pochi millisecondi. // Vedi: http://gamedev.tutsplus.com/articles/glossary/quick-tip-what-is-the-game-loop/ function gameLoop () if (ufo.y> = bottomOfRange) goingUp = true ; altro se (ufo.y <= topOfRange) goingUp = false; if (goingUp) ufo.y -= ufo.ySpeed; else ufo.y += ufo.ySpeed; ufo.x += ufo.xSpeed;
Fondamentalmente, questo dice al nemico di scendere a una velocità costante (vale a dire lo stesso numero di pixel ogni volta) fino a raggiungere il punto più basso nella sua gamma consentita, quindi di salire a quella stessa velocità costante fino a raggiungere il punto più alto in il suo intervallo consentito, più e più volte.
Il nemico può essere fatto muovere orizzontalmente impostando il suo xSpeed
a qualsiasi numero diverso da zero: un numero negativo lo fa andare a sinistra, e un numero positivo lo fa muovere a destra.
Questi esempi mostrano come si presenta questo tipo di movimento. Innanzitutto, senza movimento orizzontale:
Ora, con movimento orizzontale:
Raggiunge l'obiettivo di spostarsi avanti e indietro, ma certamente non è così fluido come il nostro precedente esempio.
La ragione di questo movimento accidentato è che la velocità verticale del nemico fa un enorme cambiamento improvviso - anche se il valore di ufo.ySpeed
resta lo stesso.
supporre ufo.ySpeed
è 10
. Durante l'ascesa, il nemico si muove verso l'alto a 10px / tick (pixel per tick, dove un "tick" è la lunghezza di un loop di gioco). Una volta che il nemico raggiunge la cima, inverte la direzione e si muove improvvisamente a 10px / tick verso il basso. Il passaggio da + 10px / tick a -10px / tick è una differenza di 20px / tick, ed è ciò che è così evidente.
Quando la causa è spiegata in questo modo, la soluzione sembra ovvia: rallenta il nemico vicino ai punti più alti e più bassi! In questo modo, il cambiamento nella sua velocità non sarà così grande quando invertirà la direzione.
Un primo tentativo potrebbe essere simile a questo:
var goingUp = false; var movingSlowly = false; // La funzione viene eseguita ogni pochi millisecondi. // Vedi: http://gamedev.tutsplus.com/articles/glossary/quick-tip-what-is-the-game-loop/ function gameLoop () if (ufo.y> = bottomOfRange) goingUp = true ; altro se (ufo.y <= topOfRange) goingUp = false; if (ufo.y <= bottomOfRange + 100) movingSlowly = true; else if (ufo.y >= topOfRange - 100) movingSlowly = true; else movingSlowly = false; if (movingSlowly) if (goingUp) ufo.y - = ufo.ySpeed / 2; else ufo.y + = ufo.ySpeed / 2; else if (goingUp) ufo.y - = ufo.ySpeed; else ufo.y + = ufo.ySpeed; ufo.x + = ufo.xSpeed;
Questo codice è disordinato, ma ti viene l'idea: se il nemico si trova entro 100px dei suoi limiti più alti o più bassi, si sposta a metà della sua velocità normale.
Funziona, anche se non è perfetto. Il nemico avrà ancora un "salto" in velocità quando cambierà direzione, ma almeno non sarà così evidente. Tuttavia, il nemico ora avrà ulteriori salti di velocità quando si muoverà dalla sua andatura regolare alla più lenta velocità! Dang.
Noi poteva risolvi questo problema dividendo l'intervallo in sezioni più piccole o rendendo la velocità un po 'più precisa della distanza esatta dal nemico ai suoi confini ... ma c'è un modo più semplice.
Pensa a un treno modello che gira su una pista perfettamente circolare. Il treno cambia continuamente direzione, eppure si muove ad un ritmo costante, senza "salti".
Ora immagina un muro su un lato della pista circolare e una grande luce brillante sul lato opposto (quindi, la pista e il treno sono tra i due). Il treno proietterà un'ombra sul muro. Ma ovviamente l'ombra non si muoverà in circolo, perché il muro è piatto: si muoverà avanti e indietro, in linea retta, ma con quel movimento regolare e senza sobbalzi del treno!
Questo è esattamente quello che vogliamo. E per fortuna c'è una funzione che ce la darà: il seno funzione. Questa GIF animata da Wikipedia dimostra:
La linea rossa è la curva di y = sin (x)
. Così, peccato (0,5 * pi)
è 1, sin (pi)
è 0 e così via.
È un po 'scomodo che pi (π) sia l'unità di base utilizzata per questa funzione, ma possiamo gestirla. Possiamo usarlo in questo modo:
var numberOfTicks = 0; function gameLoop () numberOfTicks ++; ufo.y = sin (numberOfTicks * pi); ufo.x + = ufo.xSpeed;
Vedi cosa sta succedendo qui? Dopo un segno di spunta, ufo.y
sarà impostato su peccato (1 * pi)
, che è 0
. Dopo due zecche, ufo.y
sarà impostato su peccato (2 * pi)
, che è… 0
, ancora. Oh. Un attimo.
var numberOfTicks = 0; function gameLoop () numberOfTicks ++; ufo.y = sin (numberOfTicks * 0.5 * pi); ufo.x + = ufo.xSpeed;
Ora, dopo un tick, ufo.y
sarà impostato su peccato (0,5 * pi)
, che è 1
. Dopo due zecche, ufo.y
sarà impostato su peccato (1 * pi)
, che è 0
. Dopo tre tick, ufo.y
sarà impostato su peccato (1,5 * pi)
, che è -1
, e così via. (La funzione sinusoidale si ripete, quindi sin (a) == sin (a + (2 * pi))
, sempre - non devi preoccuparti di assicurarti che un
è sotto un certo numero!)
Ovviamente andando da 1
a 0
a -1
e così via non è quello che vogliamo. Primo, vogliamo che i valori al contorno siano qualcosa di diverso allora 1
e -1
. È facile: noi moltiplichiamo il tutto peccato
funzione dal limite massimo desiderato:
var numberOfTicks = 0; function gameLoop () numberOfTicks ++; ufo.y = 250 * sin (numberOfTicks * 0.5 * pi); ufo.x + = ufo.xSpeed;
Ora il nemico andrà da y = +250
a y = -250
. Se vogliamo che vada 100
a 600
, possiamo solo aggiungere un extra 350
su questo valore (dal 250 + 350 = 600
e -250 + 350 = 100
):
var numberOfTicks = 0; function gameLoop () numberOfTicks ++; ufo.y = (250 * sin (numberOfTicks * 0.5 * pi)) + 350; ufo.x + = ufo.xSpeed;
Ma il valore sta ancora saltando 100
a 350
a 600
, perché il sin (numberOfTicks * 0.5 * pi)
sta ancora saltando da -1
a 0
a 1
.
Ma diamine, sappiamo perché quello è accadendo: è perché il valore di numberOfTicks * 0.5 * pi
sta saltando da 0,5 * pi
a 1 * pi
a 1,5 * pi
. Guarda di nuovo il GIF se non vedi perché questo lo causerebbe:
Quindi tutto ciò che dobbiamo fare è scegliere un divario diverso tra il numero che forniamo al peccato()
funzione, invece di numberOfTicks * 0.5 * pi
. Se vuoi che il movimento avanti e indietro richieda dieci volte più a lungo, usa numberOfTicks * 0.5 * pi / 10
. Se vuoi che duri 25 volte, usa numberOfTicks * 0.5 * pi / 25
, e così via.
Puoi usare questa regola per far durare esattamente il movimento per tutto il tempo che vuoi. Se il tuo ciclo di gioco viene eseguito una volta ogni 25 millisecondi (40 volte al secondo), puoi utilizzarlo numberOfTicks * 0,5 * pi / 40
per far muovere il nemico dal centro verso l'alto precisamente una volta al secondo, o numberOfTicks * 0.5 * pi / (40 * 2)
per farlo muovere dall'alto verso il basso parte inferiore precisamente una volta al secondo.
Certo, puoi semplicemente dimenticarti di tutto ciò e sperimentare con numeri diversi per vedere cosa sembra giusto. Questa demo utilizza sin (numberOfTicks / 50)
, e mi piace il risultato:
Sperimenta e divertiti!