In Pong, come si calcola la direzione della palla quando rimbalza dalla paletta?


28

Sto cercando di avvolgere la mia testa attorno a questo problema di Hello World-y nello sviluppo del gioco. Ho creato un gioco TicTacToe in XNA, quindi immagino che il prossimo passo sarebbe un clone di Breakout .

Tieni presente che non ho alcuna conoscenza sulla programmazione del gioco o anche su quali matematica dovrei applicare dove. Ecco perché sto facendo questa domanda.


Alla domanda: come posso determinare dove la palla dovrebbe rimbalzare quando colpisce la paletta nella parte inferiore dello schermo?

Immagino che sarebbe qualcosa del tipo:

  1. Cattura la velocità e l'angolazione dalla palla in arrivo.
  2. Rileva dove ha toccato la barra (all'estrema sinistra, all'estrema destra, al centro) e in base a ciò, dagli una velocità maggiore se ha toccato le aree esterne.
  3. Questo è dove sono bloccato. Hehe.

Qualche intuizione? Mi rendo conto che questa non è una domanda semplice, ma sono sicuro che a un certo punto tutti si troveranno ad affrontare.

Sto leggendo il libro Linear Algebra consigliato su questo sito Web, ma non ho ancora idea di applicarlo qui.


Scrivi ping prima del breakout, puoi quindi esportare le classi di palla, muro e paddle ed estenderle in modo che funzionino con i diversi tipi di mattoni e power-up. Inoltre, considererei il pong più semplice del breakout.
Incognito,

Risposte:


30

Ecco la logica pertinente che ho usato sul pong sulla mia homepage : (per favore vai a giocarci prima di leggere, in modo che tu sappia l'effetto che sto ottenendo con il seguente codice)

In sostanza, quando la palla si scontra con la paletta, la sua direzione viene completamente ignorata; gli viene data una nuova direzione in base alla distanza dal centro della paletta. Se la palla colpisce la paletta proprio al centro, viene espulsa esattamente in orizzontale; se colpisce proprio sul bordo, vola via con un angolo estremo (75 gradi). E viaggia sempre a velocità costante.

var relativeIntersectY = (paddle1Y+(PADDLEHEIGHT/2)) - intersectY;

Prendi il valore Y centrale della paletta e sottrai l'intersezione Y della palla. Se la paletta è alta 10 pixel, questo numero sarà compreso tra -5 e 5. Lo chiamo "incrocio relativo" perché ora si trova nello "spazio della paletta", l'intersezione della palla rispetto al centro della paletta.

var normalizedRelativeIntersectionY = (relativeIntersectY/(PADDLEHEIGHT/2));
var bounceAngle = normalizedRelativeIntersectionY * MAXBOUNCEANGLE;

Prendi l'intersezione relativa e dividila per metà dell'altezza della paletta. Ora il nostro numero da -5 a 5 è un decimale da -1 a 1; è normalizzato . Quindi moltiplicalo per l'angolo massimo con cui vuoi far rimbalzare la palla. L'ho impostato su 5 * Pi / 12 radianti (75 gradi).

ballVx = BALLSPEED*Math.cos(bounceAngle);
ballVy = BALLSPEED*-Math.sin(bounceAngle);

Infine, calcola le nuove velocità della palla, usando una semplice trigonometria.

Questo potrebbe non essere l'effetto che stai cercando o potresti anche determinare una velocità moltiplicando l'intersezione relativa normalizzata per una velocità massima; questo renderebbe la palla più veloce se colpisce vicino al bordo di una paletta, o più lenta se colpisce vicino al centro.


Vorrei forse un po 'di codice su come sarebbe un vettore o su come potrei salvare la variabile del vettore che hanno le palle (velocità e direzione).

Un vettore contiene sia la velocità che la direzione, implicitamente. Conservo il mio vettore come "vx" e "vy"; cioè, la velocità nella direzione xe la velocità nella direzione y. Se non hai seguito un corso introduttivo di fisica, questo potrebbe sembrare in qualche modo estraneo a te.

Il motivo per cui lo faccio è perché riduce i calcoli per frame necessari; ogni fotogramma, lo fai x += vx * time;e y += vy * time;dove il tempo è il tempo dall'ultimo fotogramma, in millisecondi (quindi le velocità sono in pixel per millisecondo).


Per quanto riguarda l'implementazione della capacità di curvare la palla:

Prima di tutto, dovresti conoscere la velocità della paletta nel momento in cui la palla colpisce; ciò significa che dovresti tenere traccia della cronologia della paletta, in modo da poter conoscere una o più delle posizioni passate della paletta in modo da poterle confrontare con la sua posizione attuale per vedere se si è mossa. (cambio di posizione / cambio di tempo = velocità; quindi sono necessarie 2 o più posizioni e i tempi di tali posizioni)

Ora devi anche tenere traccia di una velocità angolare della palla, che rappresenta praticamente la curva lungo la quale sta viaggiando, ma equivale alla rotazione reale della palla. In modo simile all'interpolazione dell'angolo di rimbalzo dalla posizione relativa della sfera in caso di collisione con la paletta, è necessario interpolare questa velocità angolare (o rotazione) dalla velocità della paletta in caso di collisione. Invece di impostare semplicemente la rotazione come fai con l'angolo di rimbalzo, potresti voler aggiungere o sottrarre alla rotazione esistente della palla, perché tende a funzionare bene nei giochi (il giocatore può notare che la palla gira e fa girare la palla) ancora più selvaggiamente, o contrastare la rotazione nel tentativo di farla viaggiare dritta).

Si noti, tuttavia, che mentre questo è il senso più comune e probabilmente il modo più semplice per implementarlo, la fisica effettiva di un rimbalzo non si basa esclusivamente sulla velocità dell'oggetto che colpisce; un oggetto senza velocità angolare (senza rotazione) che colpisce una superficie ad angolo, avrà una rotazione impartita su di essa. Questo potrebbe portare a una migliore meccanica di gioco, quindi potresti voler esaminare questo, ma non sono sicuro della fisica che sta dietro, quindi non proverò a spiegarlo.


Quell'effetto è quello che sto cercando; la velocità maggiore quando colpisce i bordi della barra. Grazie mille per aver dedicato del tempo a scrivere questo. Ho qualche problema a capire alcune cose però; ad esempio, nel primo frammento, che cos'è "intersectY"? Inoltre, 'paddle1Y' è l'altezza della barra corretta?

intersectY è la posizione della palla in cui interseca la paletta. Faccio un calcolo complicato che onestamente non capisco nemmeno in questo momento, ma essenzialmente è il valore Y della palla nel momento in cui si scontra. paddle1Y è il valore Y della paletta, dalla parte superiore dello schermo; PADDLEHEIGHT è l'altezza della paletta ("barra").
Ricket,

Cosa dovresti aggiungere per consentire le palle "curve"? Ad esempio, quando la palla sta per colpire la paletta, la si sposta per fare la curva della palla. Qualcosa del genere: en.wikipedia.org/wiki/Curve_ball
Zolomon

Guarda la modifica e fammi sapere cosa ne pensi (se hai bisogno di maggiori informazioni su qualcosa, non ottenere qualcosa, ecc.)
Ricket,

Grazie! Risposta superba, mi sono sempre chiesto come ottenere questo effetto!
Zolomon,

8

È passato un po 'di tempo da quando l'ho fatto, ma penso di aver capito bene.

Data una collisione perfetta, l'angolo di riflessione è uguale all'angolo di incidenza.

Conosci la normale della tua paletta (presumendo una superficie piana): N Conosci la tua posizione originale della palla (all'inizio del tuo timestep): P Conosci la tua nuova posizione della palla (alla fine del timestep): P 'Conosci il tuo punto di collisione: C Supponendo che tu abbia calcolato che il segmento P -> P' passi attraverso la tua paletta, la tua nuova posizione riflessa (P '') sarebbe:

P '+ 2 * (N * (P' punto -N))

La sottoespressione N * (P 'punto -N) calcola la profondità lungo la normale collisione percorsa dalla palla. Il segno meno è quello di correggere il fatto che stiamo verificando la profondità opposta alla direzione del normale.

P '+ 2 * la parte della sottoespressione sposta la palla indietro sopra il piano di collisione di 2 volte la profondità della collisione.

Se si desidera una collisione non perfetta, modificare il fattore 2 in (1 + (1-k)) dove k è il coefficiente di attrito. Una collisione perfetta ha un valore di ak pari a 0, facendo sì che l'angolo di riflessione sia esattamente quello dell'angolo in entrata. Un valore k di 1 provoca una collisione in cui la palla rimarrebbe sulla superficie del piano di collisione.

Il tuo nuovo vettore di velocità, V '', direzione sarebbe P '' - C. Normalizzalo e moltiplicalo per la tua velocità in entrata e la magnitudine della velocità risultante sarebbe la stessa, ma nella nuova direzione. Puoi scimmiottare con quella velocità moltiplicandola per un coefficiente, l, che aumenterebbe (l> 1) o ridurrebbe (l <1) la velocità risultante.

Riassumere:

P '' = P '+ (1-k) * (N * (P punto -N)) V' '= l * V * ((P' '- C) / | P' '- C |)

Dove k e l sono coefficienti di tua scelta.


5

La riflessione può essere fatta "a destra" o "facile".

Il modo "giusto" è calcolare i vettori perpendicolari alle pareti. In 2D è abbastanza facile e probabilmente potresti semplicemente codificarli. Quindi, il passaggio di riflessione essenzialmente lascia intatta la componente "parallela" del movimento e inverte la componente "perpendicolare". Probabilmente ci sono informazioni dettagliate sul web per questo, forse anche a MathWorld.

Il modo "semplice" è semplicemente annullare il movimento X o Y quando si colpisce un muro. Se colpisci le pareti laterali, negheresti X. Se colpisci la cima negherai Y. Se vuoi accelerare la palla, aumenta semplicemente quello che vuoi; puoi accelerarlo nella sua direzione attuale moltiplicando entrambe le velocità X e Y oppure puoi accelerare solo su un asse.


Il modo "facile" e il modo "giusto" sopra descritti essenzialmente non sono gli stessi ??
Tom,

Sono esattamente gli stessi se le pareti sono lungo gli assi maggiori. Se i muri non sono tutti lungo gli assi X, Y e Z, allora no, i due sono completamente diversi.
dash-tom-bang,

5

Sto realizzando anch'io un gioco arcanoide-ish e penso che la soluzione su come dovrebbe comportarsi la palla quando si colpisce la paletta è abbastanza più semplice e veloce rispetto all'approccio sin / cos ... funziona benissimo ai fini di un gioco come questo. Ecco cosa faccio:

  • Naturalmente, poiché la velocità della sfera aumenta nel tempo, interpolo i passi prima / dopo x, y per mantenere un rilevamento accurato delle collisioni, eseguendo il ciclo attraverso tutti i "stepX" e "stepY" che vengono calcolati dividendo ogni componente di velocità per il modulo del vettore formato dalle posizioni attuali e future della palla.

  • Se si verifica una collisione contro la paletta, divido la velocità Y per 20. Questo "20" è il valore più conveniente che ho trovato per ottenere il mio angolo massimo risultante quando la palla colpisce sui lati della paletta, ma puoi cambiarla in qualsiasi cosa le tue esigenze sono, basta giocare con alcuni valori e scegliere il meglio per te. Dividendo, diciamo una velocità di 5, che è la mia velocità di gioco iniziale per questo numero (20), ottengo un "fattore di rimbalzo" di 0,25. Questo calcolo mantiene i miei angoli abbastanza proporzionali quando la velocità aumenta nel tempo fino al mio valore di velocità massima che, ad esempio, potrebbe essere 15 (in quel caso: 15/20 = 0,75). Considerando che i miei paddle x, y coords sono gestiti a metà (xey rappresentano il centro del paddle), quindi moltiplico questo risultato per la differenza tra la posizione della palla e la posizione della paletta. Maggiore è la differenza, il grande angolo risultante. Inoltre, usando un paddle con manico centrale, ottieni il segno corretto per l'incremento x a seconda del lato colpito dalla palla senza doversi preoccupare di calcolare il centro. In pseudo-codice:

Per n = 0 al modulo ...

se collision_detected allora speedX = - (speedY / 20) * (paddleX - ballX); speedY = -speedY;
Uscita; finisci se

...

x = x + stepX; y = y + stepY;

fine per

Ricorda, cerca sempre di mantenere le cose SEMPLICI. Spero possa essere d'aiuto!


4

La paletta in Breakout, quando segue lo stile che stai descrivendo, è di solito modellata come una superficie curva. L'angolo di incidenza cambia in base a dove colpisce la paletta. Nel punto morto la linea tangente alla curva è assolutamente orizzontale e la palla si riflette come previsto. Mentre ti allontani dal centro, la tangente alla curva diventa sempre più angolata e, di conseguenza, la palla si riflette in modo diverso.

Il punto chiave è che l'angolo di riflessione, non la velocità della palla, è ciò che cambia. La velocità della palla generalmente aumenta lentamente nel tempo.


1
Dici come una superficie curva e questo sembra logico nella mia testa, ma una volta che provo a pensarci in termini di codice, le cose diventano sfocate velocemente. Come potrei persino dichiararlo nel codice come una variabile o qualcosa del genere.

Qualcosa del genere angle = 1 - 2 * (ball.x - paddle.left) / paddle.widthti darà un numero compreso tra 1 e -1; questo (a volte qualche valore ottimizzato per le tue meccaniche di gioco) è la pendenza della linea tangente nel punto in cui la palla si è scontrata. Rifletti su quella linea piuttosto che su quella strettamente orizzontale.

4

Nolan Bushnell ha tenuto un keynote al SIEGE lo scorso fine settimana e ha parlato di un problema simile con il pong originale. Non devi fare molti calcoli complicati. Se colpisci verso la parte sinistra del pannello hte, manda la palla a sinistra. Fai lo stesso per il lato destro.

Per iniziare con te puoi rendere l'angolo per i lati sinistro e destro di 45 gradi. Una volta terminato il gioco, puoi tornare indietro e renderlo più complicato, ma rendilo il più semplice possibile per iniziare.


1
Ho visto anche quel keynote, il suo punto era che questa era una decisione progettuale non una decisione matematica: "l'angolo di incidenza = l'angolo di riflessione" sarebbe corretto ma avrebbe comportato un gameplay debole. Inoltre, nell'originale Pong e Breakout, la velocità era una funzione di quante collisioni palla / paddle c'erano (quindi accelera nel tempo). Hanno anche ridotto le dimensioni della paletta anche dopo un certo numero di colpi. Eviterei però di lasciare andare la palla verso l'alto, quindi potresti lasciare la paletta lì a tempo indeterminato.
Ian Schreiber,

4

Breakout è un classico lavoro per principianti che inizia a immergersi nel mondo della programmazione di giochi basata sulla fisica. Fondamentalmente la palla ha un movimento di rimbalzo quando colpisce sul muro. Come qualcuno sopra suggerito l'angolo di incidenza è uguale all'angolo di riflessione. Ma quando consideri la palla che colpisce la paletta. La logica è divisa in 3 sezioni. 1.) La palla che colpisce la parte centrale della paletta. 2.) La palla che colpisce la parte sinistra della paletta. 3.) La palla colpisce la posizione verso destra della paletta.

Quando si considera la parte centrale: non è necessario differenziare l'effetto di rimbalzo di quello che viene applicato quando si colpisce la palla. La palla viene deviata normalmente ma, quando viene colpita una delle due direzioni, il caso è diverso.

Quando la palla viene colpita sul lato sinistro, vale a dire considerare la palla proveniente dal lato sinistro dello schermo e si arriva con la paletta dal lato destro. Quindi quando colpisci la palla con la parte sinistra, la palla dovrebbe riflettere nella direzione da cui proviene .ie. Verso sinistra con lo stesso angolo da cui proviene. Lo stesso è il caso viceversa. Nella parte destra vale anche la stessa cosa.

Questo movimento della palla verso la direzione sinistra o destra quando viene colpito la rende più credibile.

Spero che tu abbia avuto l'idea, almeno per quanto riguarda la logica. Grazie


1

Immagina di calcolare la distanza tra il centro della paletta e il punto in cui la palla Y colpisce e la chiama d. Supponiamo che dabbia un valore positivo quando la palla colpisce sopra il centro della paletta. Ora puoi aggiungere d * -0.1alla velocità Y della tua palla e inizierà a cambiare direzione. Ecco un esempio in javascript che può essere facilmente tradotto in c #!

var canvas = document.querySelector('canvas');
var resize = function () {
  canvas.width = innerWidth;
  canvas.height = innerHeight;
};
resize();
var ctx = canvas.getContext('2d');
var ball = {
  size: 3,
  x: 1,
  y: canvas.height/2,
  vx: 2,
  vy: 0
};
var paddle = {
  height: 40,
  width: 3,
  x: canvas.width/2,
  y: canvas.height/2
};
addEventListener('mousemove', function (e) {
  paddle.y = e.clientY - (paddle.height/2);
});
var loop = function () {
  resize();
  ball.x += ball.vx;
  ball.y += ball.vy;
  if (ball.x > canvas.width || ball.x < 0) ball.vx *= -1; // horiz wall hit
  if (ball.y > canvas.height || ball.y < 0) ball.vy *= -1; // vert wall hit
  if (ball.x >= paddle.x && ball.x <= paddle.x + paddle.width && ball.y >= paddle.y && ball.y <= paddle.y + paddle.height) {
    // paddle hit
    var paddleCenter = paddle.y + (paddle.height/2);
    var d = paddleCenter - ball.y;
    ball.vy += d * -0.1; // here's the trick
    ball.vx *= -1;
  }
  ctx.fillRect(ball.x,ball.y,ball.size,ball.size);
  ctx.fillRect(paddle.x,paddle.y,paddle.width,paddle.height);
  requestAnimationFrame(loop);
};
loop();
body {overflow: hidden; margin: 0}
canvas {width: 100vw; height: 100vh}
<canvas></canvas>



0

Ciao, sono stato recentemente tentato di creare un gioco con la palla e ho trovato una soluzione per questo. Quindi quello che ho fatto: la paletta si muove mentre stiamo giocando. Il mio sistema di coordinate è lasciato com'è, il punto in alto a sinistra della tela è 0,0. La paletta si sta muovendo in questo sistema di coordinate. L'asse x punta da 0 alla larghezza della tela e l'asse y punta su 0 all'altezza della tela. Ho creato una paletta con dimensioni fisse 100 larghezza e 20 altezza. E poi disegno un cerchio immaginario attorno ad esso. Quando la palla colpisce la paletta computo il punto centrale della paletta

double paddleCenter=Squash.paddle.getXpos()+Squash.paddle.getPaddleWidth()/2;

Quindi sottraggo il centro dalla posizione corrente della palla in questo modo il sistema di coordinate sarà al centro della paletta, ballCenter è il punto in cui la palla ha colpito la paletta (- (paddlewidth + r) .. 0 .. (paddlewidth + r )) non è altro che riscalare il punto di colpire sulla paletta

double x0 = ballCenterX-paddleCenter;

calcola il punto di intersezione del cerchio con l'aiuto del punto di colpire della palla (x0) questa è una ricomputazione, chiediamo la coordinata y sul cerchio con la coordinata x0 già nota e c'era bisogno di capovolgere l'asse y

double y0 = -Math.sqrt(paddleRadius*paddleRadius-x0*x0);

calcola la derivata dell'equazione normale del cerchio che è definita attorno alla paletta con la paletta del radio Radius f (x, y) = x ^ 2 + y ^ 2-r ^ 2

double normalX=2*x0;
double normalY=2*y0;

normalizzare il vettore N, per ottenere un vettore unitario per la normale alla superficie

double normalizer=Math.sqrt(normalX*normalX + normalY*normalY);
normalX=normalX/normalizer;
normalY=normalY/normalizer;

ora abbiamo le normali (unitarie) di superficie normalizzate per la paletta. Calcola la nuova direzione con queste normali alla superficie, questo sarà calcolato con l'aiuto della formula del vettore di riflessione: new_direction = old_direction-2 * dot (N, old_direction) * N, ma invece con la normale della superficie che punta sempre verso l'alto, la volontà della normale cambiando da punto a punto in cui la palla colpisce la paletta

double eta=2; //this is the constant which gives now perfect reflection but with different normal vectors, for now this set to 2, to give perfect reflection
double dotprod=vX*normalX+vY*normalY;
vX=vX-eta*dotprod*normalX;//compute the reflection and get the new direction on the x direction
vY=-vY;//y direction is remain the same (but inverted), as we just want to have a change in the x direction

Ho pubblicato la mia soluzione a questo problema. Per maggiori dettagli e per il gioco completo puoi vedere il mio repository github:

https://github.com/zoli333/BricksGame

scritto in java con eclissi. C'è un'altra soluzione per questo commentato in Ball.java, in cui il riscalaggio non avviene. Non sposto il sistema di coordinate delle coordinate sul punto centrale della paletta, invece le calcolo tutte sopra dal sistema di coordinate 0,0 di topleft rispetto a il punto centrale della paletta. Funziona anche questo.

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.