Posso saltare da A a B?


10

Sto realizzando un AI rudimentale per il mio scroller laterale e ho bisogno di sapere se un'unità AI può raggiungere il punto B dal punto A semplicemente facendo un salto.

La traiettoria di volo dei miei personaggi è un po 'insolita in quanto possono applicare la forza a mezz'aria (come ad esempio in Jazz Jackrabbit 2), quindi a differenza della classica traiettoria di un proiettile che riguarda ...

percorso che un proiettile lanciato o lanciato prenderà (...) senza propulsione.

... Suppongo che il mio problema riguardi più un proiettile con propulsione (es. Razzo).

Per illustrare questo, ecco come appare la curva di volo per il mio personaggio se salto e premo continuamente il "pulsante sinistro" (sembra diverso all'estremità sinistra, qui è dove stavo realizzando alcuni manuevers a mezz'aria): inserisci qui la descrizione dell'immagine

La forza applicata durante il volo è sempre parallela all'asse X, quindi è F = (-f, 0) se tengo "sinistra" ed è F = (f, 0) se tengo "destra".

Può muoversi molto come un maglione da sci:

inserisci qui la descrizione dell'immagine

Quindi differisce molto dalla traiettoria classica che è semplicemente una parabola (fonte: wikipedia ):

inserisci qui la descrizione dell'immagine

Per renderlo più difficile, sto simulando una semplice resistenza all'aria in modo che i miei personaggi possano accelerare solo fino a un valore di velocità massima.

Questo viene fatto applicando una piccola forza nella direzione opposta del viaggio :

b2Vec2 vel = body->GetLinearVelocity();
float speed = vel.Normalize(); //normalizes vector and returns length
body->ApplyForce( AIR_RESISTANCE_MULT * speed * speed * -vel, body->GetWorldCenter() );

AIR_RESISTANCE_MULT è una costante che nel mio caso è pari a 0,1.

Supponiamo che il mio personaggio sia un punto infinitamente piccolo.

E NON sto prendendo in considerazione gli ostacoli, quindi la mia domanda va così ...

Come determinare (almeno in modo attendibile indovinare), data la velocità iniziale V, un impulso J = (0, -j) che applico al personaggio al salto, gravità G = (0, g) , forza F = (+ -f , 0) continuamente applicato durante il volo e AIR_RESISTANCE_MULT se decidiamo davvero di prendere in considerazione la resistenza dell'aria (questo è facoltativo) , se un punto si trova sotto la curva tracciata dal percorso che il mio personaggio prenderà?

Non ho letteralmente idea da dove cominciare con i calcoli e in effetti non sono necessariamente interessato a una risposta esatta; un hack / approssimazione ben funzionante sarebbe ottimo in quanto l'IA non deve assolutamente agire perfettamente.

modifica: ho deciso di risolvere questo problema usando la simulazione come suggerisce Jason, ma come gestire un caso del genere? inserisci qui la descrizione dell'immagine

Devo disegnare un segmento da C a D e verificare se il punto desiderato si trova al di sotto di questo segmento?

O dovrei cercare binariamente i timestep tra C e D per cercare il punto che è abbastanza vicino in distanza orizzontale al punto desiderato, e solo allora controllare la differenza verticale? (mi sembra un po 'eccessivo)


Penso di aver trovato una soluzione per il caso in cui non prendiamo in considerazione la resistenza dell'aria: gamedev.stackexchange.com/questions/37916/…
Patryk Czachurski,

Risposte:


4

Come dici, la scelta migliore è approssimare, in questo caso usando uno schema numerico. Dividi il tempo in grandi timestep (diciamo 100-300ms) e usa l'approssimazione parabolica per ogni timestep. Le forze sono le stesse ovunque tranne la resistenza dell'aria. Il percorso parabolico è sostanzialmente per l'accelerazione costante, ma con la resistenza all'aria l'accelerazione cambia perché la forza dipende dalla velocità. Un'approssimazione ragionevole è quella di considerare la resistenza dell'aria costante per ogni intervallo di tempo. Ma l'utilizzo di un'approssimazione quadratica (cioè parabolica) durante l'integrazione consente di gestire timestep molto più grandi. Quindi basta calcolare fino a quando una parabola attraversa il punto desiderato in direzione orizzontale, quindi confrontare le altezze.

EDIT: un po 'più di dettagli sul confronto. Lo sai che nel tempo (che potrebbe essere molti nei frame di gioco), che il giocatore attraversa il bersaglio <targetx,targety>. Il loro percorso è descritto dalla posizione in <ax*t^2 + bx*t + cx, ay*t^2 + by*t + cy>cui:

ax = 1/2 * accel.x
bx = velocity.x
cx = position.x

tè il tempo attraverso il timestep ( 0 <= t <= dt) e similmente per y. Quindi, quando t=0il personaggio è nella posizione precedente e quando t=dt, sono nella posizione successiva. Si noti che questo è sostanzialmente l'aggiornamento di Euler con dtsostituito da in tmodo che possiamo calcolare ovunque lungo la traiettoria. Ora sappiamo che la posizione x è una funzione quadratica, quindi possiamo risolvere ax*t^2 + bx*t + cx = targetx e ottenere (fino a) due volte durante il passaggio in cui il personaggio è direttamente sopra o sotto il bersaglio. Quindi eliminiamo qualsiasi soluzione che non rientri nell'intervallo [0,dt], poiché questi non sono nel timestep corrente. (Per maggiore robustezza, aggiungi una piccola costante alle estremità dell'intervallo in modo da non avere problemi di arrotondamento). Ora non potremmo avere soluzioni (dopo il filtraggio), nel qual caso non raggiungiamo l'obiettivo in questo momento. Altrimenti, valutiamo ay*t^2 + by*t + cyle soluzioni e confrontiamo questo y con targety. Nota che potresti essere al di sopra del bersaglio in un punto della traiettoria e al di sotto di esso in seguito (o viceversa). Dovrai interpretare tali situazioni in base a ciò che vuoi fare.

Considerare un sacco di timestep è molto più facile che trovare una soluzione analitica al problema originale, e molto più flessibile in quanto è possibile modificare il modello di movimento e questo funzionerà ancora all'incirca.

Punti bonus per l'utilizzo di passaggi variabili, ad esempio 100 ms per il primo secondo (dieci punti), 200 ms per i successivi due (altri dieci punti), 400 ms in 4 secondi, ecc. Infatti, quando il tuo personaggio si avvicina alla velocità terminale, la variazione in la resistenza scende e non hai bisogno di timestep più grandi comunque. In questo modo è possibile gestire salti molto lunghi senza troppa elaborazione, poiché la complessità per T secondi è O (registro T) anziché O (T).

Puoi anche simulare cosa succede quando il personaggio smette di potenziare a metà del suo salto o inizia a potenziare dall'altra parte. Con il trucco di cui sopra la complessità è O ((log T) ^ 2), che non è poi così male.


+1, ottima risposta! Come potrei non considerare la simulazione reale. Potresti per favore approfondire "l'approssimazione parabolica" (non capisco del tutto)? Intendi semplicemente il metodo di integrazione delle velocità, come ad esempio RK4 ed Euler? In tal caso, potresti spiegarlo o almeno collegare ad alcune informazioni su come eseguirlo?
Patryk Czachurski,

1
Normalmente lo fai x'= x + v*dt. Invece usa x' = x + v*dt + 1/2*a*dt*dt. Quando dtè piccolo, dt^2è piccolo, quindi generalmente viene escluso dalla tradizionale integrazione di Euler nei giochi. Qui dtnon è piccolo, quindi è necessario il termine di accelerazione. Poiché dtviene elevato alla seconda potenza, si tratta di un'integrazione quadratica e il percorso è una parabola, quindi approssimazione parabolica. L'RK4 calcola essenzialmente derivati ​​più alti e quindi potrebbe fornire approssimazioni cubiche, quartiche, quintiche, ecc. RK4 è eccessivo per questo, molto probabilmente, poiché la stabilità non è importante.

e suppongo che la stessa velocità dovrebbe essere integrata come nel tradizionale Eulero? v' = v + a*dt
Patryk Czachurski,

1
Sì. Non hai coglioni, stai assumendo che sia zero.

Dai un'occhiata alla modifica.
Patryk Czachurski,

4

Sìì! L'ho fatto!

Sto usando una simulazione semplice che prende la prima posizione per atterrare dietro l'asse verticale del punto target - da lì prendo la posizione simulata precedente e faccio un segmento. Ora controllo se il punto target è al di sotto di questo segmento. Se lo è, possiamo saltare lì.

inserisci qui la descrizione dell'immagine

È un personaggio controllato dal giocatore sulla gif. Il rosa è il percorso previsto, i segmenti gialli sono previsti nelle posizioni di stepping successive e il segmento finale diventa bianco se il punto target si trova sotto di esso, rosso altrimenti. La curva rossa è il percorso di volo effettivo. Ci sono alcune piccole imprecisioni dovute all'interpolazione dello stato di fisica attivata.

I calcoli si sono rivelati sorprendentemente facili, tuttavia far funzionare il mio ambiente allo stesso modo di questi calcoli puri ... è stato un dolore enorme nel sedere. Almeno ho risolto alcuni bug gravi là fuori, quindi dopo tutto è stato un esercizio utile.

Ecco il codice completo in Lua utilizzato per risolvere il problema originale (il codice presuppone che tu abbia la tua routine "debug_draw" e la tua classe vettoriale con metodi di base come "length_sq" (lunghezza al quadrato), "normalizzare" o operatori +, * :

function simple_integration(p, dt)
    local new_p = {}

    new_p.acc = p.acc
    new_p.vel = p.vel + p.acc * dt 
    new_p.pos = p.pos + new_p.vel * dt
    -- uncomment this if you want to use quadratic integration
    -- but with small timesteps even this is an overkill since Box2D itself uses traditional Euler
    -- and I found that for calculations to be accurate I either way must keep the timesteps very low at the beginning of the jump
     --+ p.acc * dt * dt * 0.5

    return new_p
end

function point_below_segment(a, b, p)
    -- make sure a is to the left
    if a.x > b.x then a,b = b,a end

    return ((b.x - a.x)*(p.y - a.y) - (b.y - a.y)*(p.x - a.x)) < 0
end

-- returns true or false
function can_point_be_reached_by_jump
(
gravity, -- vector (meters per seconds^2)
movement_force, -- vector (meters per seconds^2)
air_resistance_mult, -- scalar
queried_point, -- vector (meters)
starting_position, -- vector (meters)
starting_velocity, -- vector (meters per seconds)
jump_impulse, -- vector (meters per seconds)
mass -- scalar (kilogrammes)
)

    local my_point = {
        pos = starting_position,
        vel = starting_velocity + jump_impulse/mass
    }

    local direction_left = movement_force.x < 0
    local step = 1/60

    while true do           
        -- calculate resultant force
        my_point.acc = 
        -- air resistance (multiplier * squared length of the velocity * opposite normalized velocity)
        (vec2(my_point.vel):normalize() * -1 * air_resistance_mult * my_point.vel:length_sq()) / mass
        -- remaining forces
        + gravity + movement_force/mass

        -- I discard any timestep optimizations at the moment as they are very context specific
        local new_p = simple_integration(my_point, step)

        debug_draw(my_point.pos, new_p.pos, 255, 0, 255, 255)
        debug_draw(new_p.pos, new_p.pos+vec2(0, -1), 255, 255, 0, 255)

        if (direction_left and new_p.pos.x < queried_point.x) or (not direction_left and new_p.pos.x > queried_point.x) then
            if point_below_segment(new_p.pos, my_point.pos, queried_point) then
                debug_draw(new_p.pos, my_point.pos, 255, 0, 0, 255)
                return true
            else
                debug_draw(new_p.pos, my_point.pos, 255, 255, 255, 255)
                return false
            end
        else 
            my_point = new_p
        end
    end

    return false
end

Accetta va a Jason per avermi messo nella giusta direzione! Grazie!


2

Potresti voler "solo calcolare" la risposta, ma sono sicuro che la troverai insufficiente una volta ottenuta a causa della natura altamente interattiva della tua fisica di "caduta libera".

Prendi in considerazione l'utilizzo di un approccio diverso: la ricerca. Ecco come viene fatto per Super Mario AI: http://aigamedev.com/open/interview/mario-ai/

La ricerca di possibili percorsi per andare da A a B consente un'interattività illimitata a mezz'aria pur essendo ancora computazionalmente efficiente.


1
Questo è pratico solo per alcuni mondi. In particolare, Mario limita la dimensione del grafico di ricerca essendo approssimativamente lineare, con un numero limitato di velocità e con un'euristica eccellente. A seconda del gioco, questo potrebbe non essere vero. Anche un'efficienza computazionale è relativa, poiché questa IA dovrebbe probabilmente funzionare per più di un personaggio / nemico, mentre in Mario ce n'è solo uno da controllare.
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.