En Pong, ¿cómo calculas la dirección de la pelota cuando rebota en la pala?

28

Estoy tratando de entender este problema de Hello World-y en el desarrollo de juegos. He creado un juego TicTacToe en XNA, así que supongo que el siguiente paso sería un clon de Breakout .

Tenga en cuenta que no tengo ningún conocimiento de programación de juegos de matemáticas o incluso lo que debería aplicarse a dónde. Por eso estoy haciendo esta pregunta.


A la pregunta: ¿Cómo puedo determinar dónde debe rebotar la pelota cuando golpea la paleta en la parte inferior de la pantalla?

Me imagino que sería algo así como:

  1. Capture la velocidad y el ángulo de la pelota entrante.
  2. Detecta dónde tocó la barra (extremo izquierdo, extremo derecho, centro) y de acuerdo con eso dale una mayor velocidad si tocó las áreas externas.
  3. Aquí es donde estoy atrapado. Jeje.

Alguna idea? Me doy cuenta de que esta no es una pregunta del tipo de respuesta directa, pero estoy seguro de que es una pregunta que todos enfrentan en algún momento.

Estoy leyendo el libro Álgebra lineal que se recomendó en este sitio web, pero todavía no tengo idea de si debo aplicarlo aquí.

Anko
fuente
Escriba pong antes del breakout, luego puede exportar las clases de pelota, pared y pádel y extenderlas de modo que funcionen con los diferentes tipos de ladrillos y potenciadores. Además, consideraría pong más simple que breakout.
Incognito

Respuestas:

30

Aquí está la lógica relevante que usé en el pong en mi página de inicio : (por favor, juegue antes de leer, para que sepa el efecto que estoy logrando con el siguiente código)

Esencialmente, cuando la pelota choca con la pala, su dirección se ignora por completo; se le da una nueva dirección de acuerdo a qué tan lejos del centro de la pala chocó. Si la pelota golpea la paleta justo en el centro, se envía exactamente horizontal; Si golpea justo en el borde, vuela en un ángulo extremo (75 grados). Y siempre viaja a una velocidad constante.

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

Tome el valor medio Y de la pala y reste la intersección Y de la pelota. Si la paleta tiene una altura de 10 píxeles, este número estará entre -5 y 5. Yo lo llamo la "intersección relativa" porque ahora está en el "espacio de la paleta", la intersección de la pelota con respecto al centro de la paleta.

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

Tome la intersección relativa y divídala por la mitad de la altura de la pala. Ahora nuestro número de -5 a 5 es un decimal de -1 a 1; se normalizó . Luego multiplíquelo por el ángulo máximo por el cual desea que la pelota rebote. Lo configuré en 5 * Pi / 12 radianes (75 grados).

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

Finalmente, calcule las nuevas velocidades de la pelota, usando trigonometría simple.

Es posible que este no sea el efecto que busca, o que también desee determinar una velocidad multiplicando la intersección relativa normalizada por una velocidad máxima; esto haría que la pelota vaya más rápido si golpea cerca del borde de una pala, o más lenta si golpea cerca del centro.


Posiblemente me gustaría algún código sobre cómo se vería un vector o cómo podría guardar la variable del vector que tienen las bolas (velocidad y dirección).

Un vector contiene velocidad y dirección, implícitamente. Almaceno mi vector como "vx" y "vy"; es decir, la velocidad en la dirección x y la velocidad en la dirección y. Si no ha tomado un curso introductorio de física, esto puede parecerle algo extraño.

La razón por la que hago esto es porque reduce los cálculos necesarios por cuadro; cada cuadro, simplemente lo hace x += vx * time;y y += vy * time;donde el tiempo es el tiempo desde el último cuadro, en milisegundos (por lo tanto, las velocidades están en píxeles por milisegundo).


Con respecto a la implementación de la capacidad de curvar la pelota:

En primer lugar, necesitaría saber la velocidad de la pala en el momento en que golpea la pelota; lo que significa que necesitaría hacer un seguimiento del historial de la pala, para poder conocer una o más de las posiciones pasadas de la pala para poder compararlas con su posición actual para ver si se movió. (cambio de posición / cambio en el tiempo = velocidad; por lo que necesita 2 o más posiciones, y los tiempos de esas posiciones)

Ahora también necesita rastrear una velocidad angular de la pelota, que prácticamente representa la curva a lo largo de la que viaja, pero es equivalente al giro de la pelota en el mundo real. De manera similar a cómo interpolaría el ángulo de rebote desde la posición relativa de la pelota en colisión con la pala, también necesitaría interpolar esta velocidad angular (o giro) desde la velocidad de la pala en colisión. En lugar de simplemente establecer el giro como lo hace con el ángulo de rebote, es posible que desee sumar o restar el giro existente de la pelota, porque eso tiende a funcionar bien en los juegos (el jugador puede notar que la pelota está girando y hacer que gire) incluso más salvajemente, o contrarresta el giro en un intento de hacer que viaje en línea recta).

Sin embargo, tenga en cuenta que, si bien este es el sentido más común y probablemente la forma más fácil de implementarlo, la física real de un rebote no depende únicamente de la velocidad del objeto que golpea; un objeto sin velocidad angular (sin giro) que golpea una superficie en ángulo tendrá un giro impartido sobre él. Esto podría conducir a una mejor mecánica de juego, por lo que es posible que desee investigar esto, pero no estoy seguro de la física detrás de esto, así que no voy a tratar de explicarlo.

Ricket
fuente
Ese efecto es lo que busco; la velocidad más rápida cuando toca los bordes de la barra. Muchas gracias por tomarse el tiempo para escribir esto. Sin embargo, estoy teniendo problemas para entender algunas cosas; por ejemplo, en el primer snipper, ¿qué es 'intersección'? Además, ¿'paddle1Y' es correcta la altura de la barra?
intersectY es la posición de la pelota donde se cruza con la pala. Hago un cálculo demasiado complicado que honestamente ni siquiera entiendo en este momento, pero esencialmente es el valor Y de la pelota en el momento en que colisiona. paddle1Y es el valor Y de la pala, desde la parte superior de la pantalla; PADDLEHEIGHT es la altura de la pala ("barra").
Ricket
¿Qué tendrías que agregar para permitir bolas "curvas"? Por ejemplo, cuando la pelota está a punto de golpear la paleta, la mueves para hacer que la pelota se curve. Algo como esto: en.wikipedia.org/wiki/Curve_ball
Zolomon
Vea la edición y hágame saber lo que piensa (si necesita más información sobre algo, no obtenga algo, etc.)
Ricket
¡Gracias! Excelente respuesta, ¡siempre me he preguntado cómo lograr ese efecto!
Zolomon
8

Ha pasado un tiempo desde que hice esto, pero creo que lo he hecho bien.

Dada una colisión perfecta, el ángulo de reflexión es igual al ángulo de incidencia.

Conoces la normalidad de tu remo (suponiendo una superficie plana): N Conoces la posición original de tu pelota (al comienzo de tu paso de tiempo): P Conoces tu nueva posición de la pelota (al final del paso de tiempo): P 'Conoces tu punto de colisión: C Suponiendo que calculaste que el segmento P -> P' pasa a través de tu pala, tu nueva posición reflejada (P '') sería:

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

La subexpresión N * (P 'dot -N) calcula la profundidad a lo largo de la colisión normal que recorrió la pelota. El signo menos es corregir el hecho de que estamos verificando la profundidad opuesta a la dirección de lo normal.

El P '+ 2 *, la parte de la subexpresión, mueve la pelota hacia atrás por encima del plano de colisión 2 veces la profundidad de la colisión.

Si desea una colisión menos que perfecta, cambie el factor 2 para que sea (1 + (1-k)) donde k es su coeficiente de fricción. Una colisión perfecta tiene un valor k de 0, lo que hace que el ángulo de reflexión sea exactamente el del ángulo entrante. Un valor k de 1 provoca una colisión donde la bola permanecería en la superficie del plano de colisión.

Su nuevo vector de velocidad, V '', dirección sería P '' - C. Normalícelo y multiplíquelo por su velocidad entrante y su magnitud de velocidad resultante sería la misma, pero en la nueva dirección. Puede simular con esa velocidad multiplicándola por un coeficiente, l, que aumentaría (l> 1) o disminuiría (l <1) la velocidad resultante.

Para resumir:

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

Donde k y l son coeficientes de su elección.

nanómetro
fuente
5

La reflexión puede hacerse "bien" o "fácil".

La forma "correcta" es calcular vectores perpendiculares a las paredes. En 2D eso es bastante fácil y probablemente puedas codificarlos. Luego, el paso de reflexión esencialmente deja intacto el componente "paralelo" del movimiento e invierte el componente "perpendicular". Probablemente hay información detallada en la web para esto, incluso en MathWorld.

La forma "fácil" es negar el movimiento X o Y cuando golpeas una pared. Si golpeas las paredes laterales, negarías X. Si golpeas la parte superior niegas Y. Si quieres acelerar la pelota, solo aumenta lo que quieras; puede acelerarlo en su dirección actual multiplicando las velocidades X e Y o puede acelerar solo en un eje.

dash-tom-bang
fuente
¿No son la forma "fácil" y la forma "correcta" descritas anteriormente esencialmente las mismas?
Tom
Son exactamente iguales si las paredes están a lo largo de los ejes principales. Si las paredes no están a lo largo de los ejes X, Y y Z, entonces no, los dos son completamente diferentes.
dash-tom-bang
5

También estoy haciendo un juego arkanoid-ish y creo que la solución sobre cómo debe comportarse la pelota al golpear la pala es bastante más simple y más rápida que entrar en el enfoque de pecado / cos ... funciona bien para los propósitos de un juego como este Esto es lo que hago:

  • Por supuesto, dado que la velocidad de la bola aumenta en el tiempo, interpolo los pasos x / y antes / después para mantener una detección de colisión precisa, recorriendo todos los "pasos X" y "pasos Y" que se calculan dividiendo cada componente de velocidad por el módulo del vector formado por las posiciones actuales y futuras de la pelota.

  • Si ocurre una colisión contra la pala, divido la velocidad Y entre 20. Este "20" es el valor más conveniente que encontré para obtener mi ángulo máximo resultante cuando la pelota golpea a los lados de la pala, pero puede cambiarlo a lo que sea sus necesidades son, solo juegue con algunos valores y elija el mejor para usted. Al dividir, digamos una velocidad de 5, que es mi velocidad inicial del juego por este número (20), obtengo un "factor de rebote" de 0.25. Este cálculo mantiene mis ángulos bastante proporcionales cuando la velocidad aumenta en el tiempo hasta mi valor de velocidad máxima que, por ejemplo, podría ser 15 (en ese caso: 15/20 = 0,75). Teniendo en cuenta que mis palas x, y coords están en el medio (x e y representan el centro de la pala), entonces multiplico este resultado por la diferencia entre la posición de la pelota y la posición de la pala. Cuanto mayor es la diferencia, cuanto mayor sea el ángulo resultante. Además, al usar una paleta intermedia, obtienes el signo correcto para el incremento x dependiendo del lado donde golpee la pelota sin tener que preocuparte por calcular el centro. En pseudocódigo:

Para n = 0 al módulo ...

si collision_detected entonces speedX = - (speedY / 20) * (paddleX - ballX); velocidadY = -velocidadY;
salida; terminara si

...

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

fin para

Recuerde, siempre trate de mantener las cosas SIMPLES. ¡Espero que ayude!

co_Opernicus
fuente
4

La paleta en Breakout, cuando sigue el estilo que está describiendo, generalmente se modela como una superficie curva. El ángulo de incidencia cambia en función de dónde golpea la paleta. En el punto muerto, la línea tangente a la curva es absolutamente horizontal, y la pelota se refleja como se esperaba. A medida que te alejas del centro, la tangente a la curva se vuelve cada vez más angulada y, como resultado, la pelota se refleja de manera diferente.

El punto clave es que el ángulo de reflexión, no la velocidad de la pelota, es lo que cambia. La velocidad de la pelota generalmente solo aumenta lentamente con el tiempo.


fuente
1
Dices como una superficie curva y eso suena lógico en mi cabeza, pero una vez que trato de pensarlo en términos de código, las cosas se ponen borrosas rápidamente. ¿Cómo podría incluso declarar eso en el código como una variable o algo así?
Algo así angle = 1 - 2 * (ball.x - paddle.left) / paddle.widthle dará un número entre 1 y -1; esto (multiplicado por un valor ajustado para la mecánica del juego) es la pendiente de la línea tangente en el punto en que colisionó la pelota. Refleje esa línea en lugar de la estrictamente horizontal.
4

Nolan Bushnell dio una conferencia magistral en SIEGE el pasado fin de semana y habló sobre un problema similar con el pong original. No tiene que hacer muchos cálculos complicados. Si golpeas hacia la parte izquierda del panel, envía la pelota hacia la izquierda. Haz lo mismo para el lado derecho.

Para comenzar, puede hacer que el ángulo para los lados izquierdo y derecho sea de 45 grados. Una vez que termine el juego, podría hacerlo si desea volver y hacer esto más complicado, pero para empezar, hágalo lo más simple posible.

lathomas64
fuente
1
También vi esa nota clave, su punto era que esta era una decisión de diseño, no una decisión matemática: "ángulo de incidencia = ángulo de reflexión" sería correcto, pero generaría un juego débil. Además, en el Pong and Breakout original, la velocidad era una función de cuántas colisiones de bolas / paletas había (por lo que se acelera con el tiempo). También redujeron el tamaño de la pala después de un cierto número de golpes, también. Sin embargo, evitaría permitir que la pelota vaya directamente hacia arriba, entonces podrías dejar la pala allí indefinidamente.
Ian Schreiber
4

Breakout es un trabajo clásico para principiantes para comenzar a sumergirse en el mundo de la programación de juegos basada en la física. Básicamente, la pelota tiene un movimiento de rebote cuando golpea la pared. Como alguien sugirió anteriormente, el ángulo de incidencia es igual al ángulo de reflexión. Pero cuando consideras que la pelota golpea la pala. La lógica se divide en 3 secciones. 1.) La pelota golpea la parte central de la pala. 2.) La pelota golpea la parte izquierda de la pala. 3.) La pelota golpea la posición derecha de la pala.

Cuando considera la parte central: no necesita diferir el efecto de rebote de lo que se aplica al golpear la pelota. La pelota simplemente se desvía normalmente, pero cuando se golpea en cualquier dirección, el caso es diferente.

Cuando la pelota es golpeada en el lado izquierdo, es decir, considere la pelota que viene del lado izquierdo de la pantalla y usted viene con la pala desde el lado derecho. Luego, cuando golpeas la pelota con la porción hacia la izquierda, la pelota debe reflejarse en la dirección de donde vino. Igual es el caso al revés. En la parte derecha también se aplica lo mismo.

Este movimiento de la pelota hacia la izquierda o hacia la derecha cuando está siendo golpeada la hace más creíble.

Espero que tengas la idea, al menos lógicamente, gracias.

Vishnu
fuente
1

Imagine que calcula la distancia entre el centro de la pala y el punto donde la bola Y golpeó y la llamó d. Supongamos que dtiene un valor positivo cuando la pelota golpea por encima del centro de la pala. Ahora puede agregar d * -0.1a la velocidad Y de su bola y comenzará a cambiar de dirección. ¡Aquí hay un ejemplo en javascript que se puede traducir fácilmente a 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>

rafaelcastrocouto
fuente
0

Hola, recientemente he intentado hacer un juego de pelota y encontré una solución para esto. Entonces, lo que hice: la paleta se mueve mientras jugamos. Mi sistema de coordenadas se deja como está, el punto superior izquierdo del lienzo es 0,0. La paleta se mueve en este sistema de coordenadas. El eje x apunta de 0 al ancho del lienzo, y el eje y apunta a 0 a la altura del lienzo. Creé una pala con un tamaño fijo de 100 de ancho y 20 de altura. Y luego dibujo un círculo imaginario a su alrededor. Cuando la pelota golpea la pala, calculo el punto central de la pala

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

Luego resto el centro de la posición actual de la pelota de esta manera el sistema de coordenadas estará en el centro de la paleta, ballCenter es el punto donde la pelota golpeó la paleta (- (ancho de paleta + r) .. 0 .. (ancho de paleta + r )) esto no es más que reescalar el punto de golpe en la pala

double x0 = ballCenterX-paddleCenter;

calcule el punto de intersección del círculo con la ayuda del punto de golpe de la bola (x0), esto es una nueva calculación, pedimos la coordenada y en el círculo con la coordenada x0 ya conocida y se necesitaba un giro para el eje y

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

calcule la derivada de la ecuación normal del círculo que se define alrededor de la paleta con radis paddleRadius f (x, y) = x ^ 2 + y ^ 2-r ^ 2

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

normalizar el vector N, para obtener un vector unitario para la superficie normal

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

ahora tenemos las normales de superficie normalizadas (unidad) para la pala. Calcule la nueva dirección con estas normales de superficie, esto se calculará con la ayuda de la fórmula del vector de reflexión: new_direction = old_direction-2 * dot (N, old_direction) * N, pero en cambio con la superficie normal apuntando siempre hacia arriba, la normal estar cambiando de un punto a otro donde la pelota golpea la pala

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

He publicado mi solución a este problema. Para más detalles y para el juego completo puedes ver mi repositorio de github:

https://github.com/zoli333/BricksGame

escrito en java con eclipse. Hay otra solución para esto comentada en Ball.java, donde el cambio de escala no ocurre. No muevo el sistema de coordenadas al punto central de la paleta, sino que calculo todo esto desde el sistema de coordenadas superior 0.0 relativo a El punto central de la pala. Esto tambien funciona.

zoli333
fuente