Valori di velocità non interi: esiste un modo più pulito per farlo?


21

Spesso vorrò usare un valore di velocità come 2.5 per spostare il mio personaggio in un gioco basato sui pixel. Tuttavia, il rilevamento delle collisioni sarà generalmente più difficile se lo faccio. Quindi finisco per fare qualcosa del genere:

moveX(2);
if (ticks % 2 == 0) { // or if (moveTime % 2 == 0)
    moveX(1);
}

Mi agito all'interno ogni volta che devo scriverlo, c'è un modo più pulito per spostare un personaggio con valori di velocità non interi o rimarrò bloccato a farlo per sempre?


11
Hai preso in considerazione l'idea di rendere più piccola la tua unità intera (es. 1/10 di un'unità di visualizzazione), quindi 2,5 si traduce in 25 e puoi comunque trattarla come un numero intero per tutti i controlli e trattare ogni frame in modo coerente.
DMGregory

6
Puoi prendere in considerazione l'adozione dell'algoritmo di linea di Bresenham che può essere implementato usando solo numeri interi.
n

1
Questo è comunemente fatto su vecchie console a 8 bit. Vedi articoli come questo per un esempio di come viene implementato il movimento subpixel con metodi a virgola fissa: tasvideos.org/GameResources/NES/BattleOfOlympus.html
Lucas

Risposte:


13

Bresenham

Ai vecchi tempi, quando le persone stavano ancora scrivendo le proprie routine video di base per disegnare linee e cerchi, non era inusuale utilizzare l'algoritmo di linea di Bresenham per quello.

Bresenham risolve questo problema: si desidera tracciare una linea sullo schermo che sposta i dxpixel in direzione orizzontale e contemporaneamente si estende su dypixel in direzione verticale. C'è un carattere intrinseco "floaty" nelle linee; anche se hai pixel interi, finisci con inclinazioni razionali.

L'algoritmo deve essere veloce, il che significa che può usare solo l'aritmetica intera; e si allontana anche senza alcuna moltiplicazione o divisione, solo addizioni e sottrazioni.

Puoi adattarlo al tuo caso:

  • La tua "x direction" (in termini di algoritmo di Bresenham) è il tuo orologio.
  • La tua "direzione y" è il valore che vuoi incrementare (cioè la posizione del tuo personaggio - attenzione, questa non è in realtà la "y" del tuo sprite o qualunque cosa sullo schermo, più un valore astratto)

"x / y" qui non sono la posizione sullo schermo, ma il valore di una delle tue dimensioni nel tempo. Ovviamente, se il tuo sprite corre in una direzione arbitraria attraverso lo schermo, avrai più Bresenhams in esecuzione separatamente, 2 per 2D, 3 per 3D.

Esempio

Supponiamo che tu voglia spostare il tuo personaggio con un semplice movimento da 0 a 25 lungo uno dei tuoi assi. Mentre si muove con la velocità 2.5, arriverà lì al frame 10.

È lo stesso di "tracciare una linea" da (0,0) a (10,25). Prendi l'algoritmo di linea di Bresenham e lascialo correre. Se lo fai nel modo giusto (e quando lo studi, diventerà molto rapidamente chiaro come lo fai nel modo giusto), quindi genererà 11 "punti" per te (0,0), (1,2), (2, 5), (3,7), (4,10) ... (10,25).

Suggerimenti sull'adattamento

Se cerchi quell'algoritmo su Google e trovi del codice (Wikipedia ha un trattato piuttosto ampio su di esso), ci sono alcune cose a cui devi fare attenzione:

  • Funziona ovviamente per tutti i tipi di dxe dy. Sei interessato a un caso specifico (ad esempio, non lo avrai mai dx=0).
  • La normale implementazione avrà diversi casi per i quadranti sullo schermo, a seconda se dxe dysono positivi, negativi e anche se abs(dx)>abs(dy). Ovviamente scegli anche quello che ti serve qui. Devi assicurarti in particolare che la direzione che viene aumentata da 1ogni tick è sempre la direzione del tuo "orologio".

Se applichi queste semplificazioni, il risultato sarà davvero molto semplice e ti libererai completamente di qualsiasi realtà.


1
Questa dovrebbe essere la risposta accettata. Avendo programmato giochi sul C64 negli anni '80 e frattali su PC negli anni '90, continuo a rabbrividire nell'usare il virgola mobile ovunque posso evitarlo. Ovviamente, con le FPU onnipresenti nei processori di oggi, l'argomento delle prestazioni è per lo più controverso, ma l'aritmetica in virgola mobile ha ancora bisogno di molti più transistor, assorbendo più potenza e molti processori spegneranno completamente le loro FPU mentre non sono in uso. Quindi, evitando il virgola mobile, i tuoi utenti mobili ti ringrazieranno per non aver succhiato le batterie così rapidamente.
Guntram Blohm supporta Monica il

@GuntramBlohm La risposta accettata funziona perfettamente anche se si utilizza anche il punto fisso, che penso sia un buon modo per farlo. Come ti senti riguardo ai numeri a virgola fissa?
leetNightshade,

Modificato questo con la risposta accettata dopo aver scoperto che è così che hanno fatto nei giorni a 8 e 16 bit.
Accumulatore,

26

C'è un ottimo modo per fare esattamente quello che vuoi.

Oltre a una floatvelocità dovrai avere una seconda floatvariabile che conterrà e accumulerà una differenza tra velocità reale e velocità arrotondata . Questa differenza viene quindi combinata con la velocità stessa.

#include <iostream>
#include <cmath>

int main()
{
    int pos = 10; 
    float vel = 0.3, vel_lag = 0;

    for (int i = 0; i < 20; i++)   
    {
        float real_vel = vel + vel_lag;
        int int_vel = std::lround(real_vel);
        vel_lag = real_vel - int_vel;

        std::cout << pos << ' ';
        pos += int_vel;
    }
}

Produzione:

10 10 11 11 11 12 12 12 12 13 13 13 14 14 14 15 15 15 15 16 16


5
Perché preferisci questa soluzione piuttosto che usare float (o fixedpoint) sia per la velocità che per la posizione e arrotondare la posizione a interi interi alla fine?
CodesInCos

@CodesInChaos Non preferisco la mia soluzione a quella. Quando stavo scrivendo questa risposta non lo sapevo.
HolyBlackCat

16

Utilizzare valori mobili per i valori di movimento e interi per collisione e rendering.

Ecco un esempio:

class Character {
    float position;
public:
    void move(float delta) {
        this->position += delta;
    }
    int getPosition() const {
        return lround(this->position);
    }
};

Quando ti muovi usi ciò move()che accumula le posizioni frazionarie. Ma la collisione e il rendering possono gestire posizioni integrali utilizzando la getPosition()funzione.


Si noti che nel caso di un gioco in rete, l'utilizzo di tipi in virgola mobile per la simulazione mondiale potrebbe essere complicato. Vedi ad esempio gafferongames.com/networking-for-game-programmers/… .
liori,

@liori Se hai una classe a virgola fissa che si comporta come un sostituto drop-in per float, questo non risolve principalmente questi problemi?
leetNightshade,

@leetNightshade: dipende dall'implementazione.
liori,

1
Direi che il problema non esiste in pratica, quale hardware in grado di eseguire moderni giochi in rete non ha float IEEE 754 ???
Sopel,
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.