Em Pong, como você calcula a direção da bola quando ela bate na raquete?


28

Estou tentando entender esse problema do Hello World-y no desenvolvimento de jogos. Eu criei um jogo TicTacToe no XNA, então acho que o próximo passo seria um clone do Breakout .

Lembre-se de que eu não tenho conhecimento de programação de jogos ou mesmo de que matemática devo aplicar para onde. É por isso que estou fazendo essa pergunta.


À pergunta: como posso determinar para onde a bola deve saltar quando atinge a raquete na parte inferior da tela?

Eu imagino que seria algo como:

  1. Capture a velocidade e o ângulo da bola recebida.
  2. Detecte onde tocou a barra (extrema esquerda, extrema direita, centro) e, de acordo com isso, aumente a velocidade se tocar as áreas externas.
  3. É aqui que estou preso. Ele Ele.

Alguma ideia? Sei que essa não é uma pergunta direta, mas tenho certeza de que é uma pergunta que todo mundo enfrenta em algum momento.

Estou lendo o livro Álgebra Linear recomendado neste site, mas ainda não tenho idéia se devo aplicá-lo aqui.


Escreva pong antes do breakout, você poderá exportar as classes de bola, parede e raquete e ampliá-las para que funcionem com os diferentes tipos de tijolos e reforços. Além disso, eu consideraria o pong mais simples que o breakout.
Incognito

Respostas:


30

Aqui está a lógica relevante que usei no pong na minha página inicial : (por favor, jogue-a antes de ler, para que você saiba o efeito que estou conseguindo com o código a seguir)

Essencialmente, quando a bola colide com a raquete, sua direção é completamente desconsiderada; é dada uma nova direção de acordo com a distância do centro da raquete que colidiu. Se a bola acertar a raquete bem no centro, ela será enviada exatamente na horizontal; se atingir a borda, voa em um ângulo extremo (75 graus). E viaja sempre a uma velocidade constante.

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

Pegue o valor Y médio da raquete e subtraia a interseção Y da bola. Se a raquete tiver 10 pixels de altura, esse número estará entre -5 e 5. Eu chamo isso de "interseção relativa" porque agora está no "espaço da raquete", a interseção da bola em relação ao meio da raquete.

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

Pegue a interseção relativa e divida-a pela metade da altura da raquete. Agora nosso número de -5 a 5 é um decimal de -1 a 1; está normalizado . Em seguida, multiplique pelo ângulo máximo pelo qual você deseja que a bola salte. Defino-o como 5 * Pi / 12 radianos (75 graus).

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

Finalmente, calcule novas velocidades da bola, usando trigonometria simples.

Esse pode não ser o efeito que você está buscando ou também pode determinar uma velocidade multiplicando a interseção relativa normalizada por uma velocidade máxima; isso tornaria a bola mais rápida se acertar perto da borda de uma raquete ou mais lenta se acertar perto do centro.


Eu gostaria de algum código sobre como seria um vetor ou como eu poderia salvar a variável do vetor que as bolas têm (velocidade e direção).

Um vetor contém velocidade e direção, implicitamente. Eu armazeno meu vetor como "vx" e "vy"; isto é, a velocidade na direção x e a velocidade na direção y. Se você não fez um curso introdutório de física, isso pode parecer um pouco estranho para você.

A razão pela qual faço isso é porque reduz os cálculos por quadro necessários; cada quadro, você faz x += vx * time;e y += vy * time;onde o tempo é o tempo desde o último quadro, em milissegundos (portanto, as velocidades são em pixels por milissegundo).


Em relação à implementação da capacidade de curvar a bola:

Primeiro de tudo, você precisa saber a velocidade da raquete no momento em que a bola bate; o que significa que você precisa acompanhar o histórico da raquete, para poder conhecer uma ou mais posições anteriores da raquete, para poder compará-las à sua posição atual para ver se ela se moveu. (mudança de posição / mudança de tempo = velocidade; você precisa de 2 ou mais posições e os horários dessas posições)

Agora você também precisa rastrear uma velocidade angular da bola, que praticamente representa a curva ao longo da qual está viajando, mas é equivalente ao giro da bola no mundo real. Semelhante à maneira como você interpolava o ângulo de ressalto da posição relativa da bola em colisão com a raquete, também seria necessário interpolar essa velocidade angular (ou rotação) da velocidade da raquete em colisão. Em vez de simplesmente definir o giro como você faz com o ângulo de pulo, você pode adicionar ou subtrair o giro existente da bola, porque isso tende a funcionar bem nos jogos (o jogador pode perceber que a bola está girando e fazer com que ela gire ainda mais descontroladamente, ou contrarie o giro na tentativa de fazê-lo viajar reto).

Note, however, that while this is the most common sense and probably easiest way to implement it, the actual physics of a bounce doesn't rely solely on the velocity of the object it hits; an object with no angular velocity (no spin) which hits a surface at an angle, will have a spin imparted upon it. This might lead to a better game mechanic, so you may want to look into this, but I'm not certain of the physics behind it so I'm not going to try to explain it.


É esse o efeito que estou buscando; a velocidade mais rápida que atinge as bordas da barra. Muito obrigado por reservar um tempo para escrever isso. Estou tendo alguns problemas para entender algumas coisas; por exemplo, no primeiro snipper, o que é 'intersectY'? Além disso, 'paddle1Y' está correta a altura da barra?

intersectY é a posição da bola onde intercepta a raquete. Faço um cálculo muito complicado que sinceramente nem entendo no momento, mas essencialmente é o valor Y da bola no momento em que ela colide. paddle1Y é o valor Y da raquete, na parte superior da tela; PADDLEHEIGHT é a altura da raquete ("bar").
Ricket 2/10/10

O que você teria que adicionar para permitir bolas "curvas"? Por exemplo, quando a bola está prestes a bater na raquete, você a move para fazer a bola se curvar. Algo parecido com isto: en.wikipedia.org/wiki/Curve_ball
Zolomon

Veja a edição, e deixe-me saber o que você pensa (se precisar de mais informações sobre algo, não conseguir alguma coisa, etc.)
Ricket

Obrigado! Resposta soberba, eu sempre me perguntei como conseguir esse efeito!
Zolomon

8

Já faz um tempo desde que eu fiz isso, mas acho que entendi direito.

Dada uma colisão perfeita, o ângulo de reflexão é igual ao ângulo de incidência.

Você conhece o normal de sua raquete (presumindo uma superfície plana): N Você conhece sua posição original da bola (no início do seu timestep): P Você conhece sua nova posição da bola (no final do timestep): P 'Você sabe o seu ponto de colisão: C Supondo que você calculou que o segmento P -> P' passa por sua raquete, sua nova posição refletida (P '') seria:

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

A subexpressão N * (P 'ponto-N) calcula a profundidade ao longo do normal de colisão que a bola percorreu. O sinal de menos é corrigir o fato de que estamos verificando a profundidade oposta à direção do normal.

AP '+ 2 * a parte da subexpressão move a bola de volta acima do plano de colisão em 2 vezes a profundidade da colisão.

Se você deseja uma colisão menos que perfeita, altere o fator 2 para (1 + (1-k)), onde k é o seu coeficiente de atrito. Uma colisão perfeita tem um valor de k de 0, fazendo com que o ângulo de reflexão seja exatamente aquele do ângulo de entrada. Um valor de k igual a 1 causa uma colisão onde a bola fica na superfície do plano de colisão.

Seu novo vetor de velocidade, V '', a direção seria P '' - C. Normalize-o e multiplique pela sua velocidade de entrada e sua magnitude de velocidade resultante seria a mesma, mas na nova direção. Você pode usar essa velocidade multiplicando-a por um coeficiente, l, que aumentaria (l> 1) ou diminuiria (l <1) a velocidade resultante.

Para resumir:

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

Onde k e l são coeficientes de sua escolha.


5

A reflexão pode ser feita "correta" ou "fácil".

O caminho "certo" é calcular vetores perpendiculares às paredes. Em 2D, isso é muito fácil e você provavelmente poderia codificá-los com firmeza. Então, o passo de reflexão basicamente deixa intacto o componente "paralelo" do movimento e reverte o componente "perpendicular". Provavelmente, há informações detalhadas na Web para isso, talvez até no MathWorld.

A maneira "fácil" é simplesmente negar o movimento X ou Y quando você bate em uma parede. Se você acertar as paredes laterais, negaria X. Se você acertar o topo, nega Y. Se você quiser acelerar a bola, apenas aumente o que quiser; você pode acelerar na direção atual, multiplicando as velocidades X e Y ou pode acelerar apenas em um eixo.


As maneiras "fácil" e "correta" descritas acima não são essencialmente as mesmas?
Tom

Eles são exatamente iguais se as paredes estiverem ao longo dos eixos principais. Se as paredes não estiverem ao longo dos eixos X, Y e Z, então não, as duas serão completamente diferentes.
dash-tom-bang

5

Também estou fazendo um jogo arkanoid-ish e acho que a solução de como a bola deve se comportar ao acertar a raquete é muito mais simples e rápida do que entrar na abordagem sin / cos ... funciona bem para os propósitos de um jogo assim. Aqui está o que eu faço:

  • Obviamente, como a velocidade da bola aumenta com o tempo, interpolo as etapas x / y antes / depois para manter a detecção precisa de colisão, percorrendo todos os "stepX" e "stepY" que são calculados dividindo cada componente de velocidade pelo módulo do vetor formado pelas posições atuais e futuras da bola.

  • Se ocorrer uma colisão contra a raquete, divido a velocidade Y por 20. Esse "20" é o valor mais conveniente que obtive para obter o ângulo máximo resultante quando a bola bate nos lados da raquete, mas você pode alterá-la para qualquer suas necessidades são, apenas brinque com alguns valores e escolha o melhor para você. Ao dividir, digamos uma velocidade de 5, que é a minha velocidade inicial de jogo por esse número (20), recebo um "fator de rebote" de 0,25. Este cálculo mantém meus ângulos bastante proporcionais quando a velocidade aumenta no tempo até o meu valor máximo de velocidade que, por exemplo, pode ser 15 (nesse caso: 15/20 = 0,75). Considerando que minhas cordas de raquete x, y são de mão média (x e y representam o centro da raquete), multiplico esse resultado pela diferença entre a posição da bola e a posição da raquete. Quanto maior a diferença, o greatear o ângulo resultante. Além disso, usando uma raquete de mão média, você obtém o sinal correto para o incremento de x, dependendo do lado que a bola bate sem ter que se preocupar em calcular o centro. No pseudo-código:

Para n = 0 ao módulo ...

se for detectada uma colisão, speedX = - (speedY / 20) * (paddleX - ballX); speedY = -speedY;
Saída; fim se

...

x = x + passoX; y = y + passoY;

fim para

Lembre-se, sempre tente manter as coisas SIMPLES. Espero que ajude!


4

A raquete no Breakout, quando segue o estilo que você está descrevendo, geralmente é modelada como uma superfície curva. O ângulo de incidência muda de acordo com o local da raquete. No ponto morto, a linha tangente à curva é absolutamente horizontal e a bola reflete como esperado. À medida que você se move para fora do centro, a tangente à curva torna-se cada vez mais angulada, e a bola reflete diferentemente como resultado.

O ponto principal é que o ângulo de reflexão, não a velocidade da bola, é o que muda. A velocidade da bola geralmente aumenta lentamente ao longo do tempo.


1
Você diz como uma superfície curva e isso parece lógico na minha cabeça, mas uma vez que tento pensar nisso em termos de código, as coisas ficam confusas rapidamente. Como eu poderia declarar isso no código como uma variável ou algo assim.

Algo como angle = 1 - 2 * (ball.x - paddle.left) / paddle.widthfornecerá um número entre 1 e -1; isto (vezes que algum valor foi ajustado para a mecânica do jogo) é a inclinação da linha tangente no ponto em que a bola colidiu. Reflita sobre essa linha em vez da estritamente horizontal.

4

Nolan Bushnell fez uma palestra no SIEGE no fim de semana passado e falou sobre um problema semelhante com o pong original. Você não precisa fazer muitos cálculos complicados. Se você acertar a parte esquerda do painel, envie a bola para a esquerda. Faça o mesmo para o lado direito.

Para começar, você pode fazer o ângulo para os lados esquerdo e direito 45 graus. Depois de terminar o jogo, você pode se quiser voltar e tornar isso mais complicado, mas faça o mais simples possível.


1
Também vi essa palestra, o que ele queria dizer era que essa era uma decisão de design, não uma decisão matemática: "ângulo de incidência = ângulo de reflexão" estaria correto, mas resultaria em jogabilidade fraca. Além disso, no Pong e Breakout original, a velocidade era uma função de quantas colisões de bola / raquete havia (por isso, acelera com o tempo). Eles também reduziram o tamanho da raquete após um certo número de hits também. Eu evitaria deixar a bola ir para cima, então você poderia deixar a raquete lá indefinidamente.
Ian Schreiber

4

Breakout é um trabalho clássico para iniciantes para começar a mergulhar no mundo da programação de jogos baseada na física. Basicamente, a bola tem um movimento de salto quando bate na parede. Como alguém acima sugeriu, o ângulo de incidência é igual ao ângulo de reflexão. Mas quando você considera a bola bater na raquete. A lógica é dividida em 3 seções. 1.) A bola bate na parte central da raquete. 2.) A bola bate na parte esquerda da raquete. 3.) A bola bate na posição direita da raquete.

Quando você considera a parte central: Você não precisa diferenciar o efeito de ressalto do que é aplicado ao bater na bola. A bola é desviada normalmente. Mas, quando qualquer direção é atingida, o caso é diferente.

Quando a bola é atingida no lado esquerdo, ou seja, considere a bola vinda do lado esquerdo da tela e você está vindo com a raquete do lado direito. Então, quando você bate na bola com a parte esquerda, a bola deve refletir a direção de onde veio, ou seja, com o mesmo ângulo de onde veio. Mesmo é o caso vice-versa. Na parte direita também se aplica a mesma coisa.

Esse movimento da bola em direção à esquerda ou à direita quando está sendo atingido a torna mais crível.

Espero que você tenha entendido a ideia, pelo menos em termos de lógica.


1

Imagine que você calcula a distância entre o centro da raquete e o ponto em que a bola Y bate e a chama d. Vamos supor que dtenha um valor positivo quando a bola bater acima do centro da raquete. Agora você pode adicionar d * -0.1à velocidade Y da sua bola e ela começará a mudar de direção. Aqui está um exemplo em javascript que pode ser facilmente traduzido em 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

Oi Eu fui recentemente tentado fazer um jogo de bola e descobri uma solução para isso. Então o que eu fiz: a raquete está se movendo enquanto jogamos o jogo. Meu sistema de coordenadas é deixado como está, o ponto superior esquerdo da tela é 0,0. A raquete está se movendo neste sistema de coordenadas. O eixo x aponta de 0 para a largura da tela e o eixo y está apontando para 0 para a altura da tela. Criei uma raquete com tamanho fixo 100 de largura e 20 de altura. E então eu desenho um círculo imaginário ao redor dele. Quando a bola bate na raquete, eu calculo o ponto central da raquete

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

Então, subtraio o centro da posição atual da bola, para que o sistema de coordenadas fique no centro da raquete, ballCenter é o ponto em que a bola bate na raquete (- (largura da raquete + r) .. 0 .. (largura da raquete + r )) isso nada mais é do que redimensionar o ponto de batida na raquete

double x0 = ballCenterX-paddleCenter;

calcular o ponto de interseção do círculo com a ajuda do ponto de acerto da bola (x0), isso é uma recomputação; pedimos a coordenada y no círculo com a coordenada x0 já conhecida e foi necessário um giro para o eixo y

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

Calcule a derivada da equação normal do círculo que é definida ao redor da raquete com pá de raioRadius f (x, y) = x ^ 2 + y ^ 2-r ^ 2

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

normalizar o vetor N, para obter um vetor unitário para a superfície normal

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

agora temos as normais de superfície normalizadas (unitárias) para o remo. Calcule a nova direção com essas normais de superfície, isso será calculado com a ajuda da fórmula do vetor de reflexão: new_direction = old_direction-2 * ponto (N, old_direction) * N, mas, em vez disso, com a superfície normal apontando sempre para cima, a normal será estar mudando de ponto a ponto onde a bola bate na raquete

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

Publiquei minha solução para esse problema. Para mais detalhes e para o jogo completo, você pode ver meu repositório no github:

https://github.com/zoli333/BricksGame

escrito em java com eclipse. Existe outra solução para isso comentada em Ball.java, onde o redimensionamento não ocorre. Não movo o sistema de coordenadas de coordenadas para o ponto central da raquete, em vez disso, calculo tudo isso a partir do sistema de coordenadas topleft 0,0 em relação a o ponto central da raquete. Isso também funciona.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.