Extrapolação interrompe a detecção de colisão

10

Antes de aplicar extrapolação ao movimento do meu sprite, minha colisão funcionou perfeitamente. No entanto, após aplicar extrapolação ao movimento do meu sprite (para suavizar as coisas), a colisão não funciona mais.

É assim que as coisas funcionavam antes da extrapolação:

insira a descrição da imagem aqui

No entanto, depois de implementar minha extrapolação, a rotina de colisão é interrompida. Suponho que isso ocorre porque ele está agindo sobre a nova coordenada produzida pela rotina de extrapolação (que está situada na minha chamada de renderização).

Depois de aplicar minha extrapolação

insira a descrição da imagem aqui

Como corrigir esse comportamento?

Eu tentei colocar uma verificação de colisão extra logo após a extrapolação - isso parece esclarecer muitos problemas, mas eu descartei isso porque colocar a lógica na minha renderização está fora de questão.

Eu também tentei fazer uma cópia da posição spritesX, extrapolando e desenhando usando isso em vez do original, deixando o original intacto para a lógica - essa parece ser uma opção melhor, mas ainda produz alguns efeitos estranhos ao colidir com paredes. Tenho certeza de que essa também não é a maneira correta de lidar com isso.

Encontrei algumas perguntas semelhantes aqui, mas as respostas não me ajudaram.

Este é o meu código de extrapolação:

public void onDrawFrame(GL10 gl) {


        //Set/Re-set loop back to 0 to start counting again
        loops=0;

        while(System.currentTimeMillis() > nextGameTick && loops < maxFrameskip){

        SceneManager.getInstance().getCurrentScene().updateLogic();
        nextGameTick+=skipTicks;
        timeCorrection += (1000d/ticksPerSecond) % 1;
        nextGameTick+=timeCorrection;
        timeCorrection %=1;
        loops++;
        tics++;

     }

        extrapolation = (float)(System.currentTimeMillis() + skipTicks - nextGameTick) / (float)skipTicks; 

        render(extrapolation);
}

Aplicando extrapolação

            render(float extrapolation){

            //This example shows extrapolation for X axis only.  Y position (spriteScreenY is assumed to be valid)
            extrapolatedPosX = spriteGridX+(SpriteXVelocity*dt)*extrapolation;
            spriteScreenPosX = extrapolationPosX * screenWidth;

            drawSprite(spriteScreenX, spriteScreenY);           


        }

Editar

Como mencionei acima, tentei fazer uma cópia das coordenadas do sprite especificamente para desenhar ... isso tem seus próprios problemas.

Em primeiro lugar, independentemente da cópia, quando o sprite está em movimento, é super suave, quando para, balança levemente para a esquerda / direita - pois ainda está extrapolando sua posição com base no tempo. Esse é um comportamento normal e podemos 'desligá-lo' quando o sprite parar?

Tentei ter sinalizadores para esquerda / direita e extrapolar apenas se um deles estiver ativado. Também tentei copiar a última e a atual posição para ver se há alguma diferença. No entanto, no que diz respeito à colisão, isso não ajuda.

Se o usuário estiver pressionando, digamos, o botão direito e o sprite estiver se movendo para a direita, quando atingir uma parede, se o usuário continuar pressionando o botão direito, o sprite continuará animado para a direita, enquanto estiver parado na parede ( portanto, não está realmente se movendo), no entanto, porque a bandeira certa ainda está definida e também porque a rotina de colisão está constantemente movendo o sprite para fora da parede, ainda parece para o código (não para o jogador) que o sprite ainda está se movendo e, portanto, a extrapolação continua. Então, o que o jogador veria é o sprite 'estático' (sim, está animando, mas na verdade não está se movendo pela tela), e de vez em quando treme violentamente enquanto a extrapolação tenta fazer isso ..... .. Espero que ajude

BungleBonce
fonte
1
Isso vai levar algum tempo para entender completamente ('interpolação' tem aparentemente uma dúzia de significados diferentes e não é totalmente claro à primeira vista exatamente o que você quer dizer com isso aqui), mas meu primeiro instinto é 'você não deve fazer nada para afetar sua posição do objeto na sua rotina de renderização '. Seu renderizador deve desenhar seu objeto na posição especificada e, ao manipular o objeto, há uma receita para problemas, pois ele inerentemente acopla o renderizador à física do jogo. Em um mundo ideal, seu renderizador deve ser capaz de levar ponteiros constantes para os objetos do jogo.
Steven Stadnicki
Olá @StevenStadnicki, muito obrigado pelo seu comentário, há vários exemplos mostrando um valor de interpolação sendo passado para o renderizador, veja o seguinte: mysecretroom.com/www/programming-and-software/…, que é de onde eu adaptei meu código. Meu entendimento limitado é que isso interpola a posição entre as últimas e as próximas atualizações com base no tempo gasto desde a última atualização - eu concordo que é um pesadelo! Ficaria muito grato se você pudesse propor uma alternativa para que seja mais fácil trabalhar com
felicidades
6
Bem, direi 'só porque você pode encontrar código para algo não faz dele uma prática recomendada'. :-) Nesse caso, porém, suspeito que a página à qual você se vinculou use o valor de interpolação para descobrir onde exibir seus objetos, mas na verdade não atualiza as posições dos objetos com eles; você também pode fazer isso apenas com uma posição específica do desenho que é calculada em cada quadro, mas mantendo essa posição separada da posição "física" real do objeto.
Steven Stadnicki
Olá @StevenStadnicki, conforme descrito na minha pergunta (o parágrafo que começa com "Eu também tentei fazer uma cópia"), na verdade, eu já tentei usar uma posição 'apenas empate' :-) Você pode propor um método de interpolação onde eu não precisa fazer ajustes na posição do sprite dentro da rotina de renderização? Obrigado!
BungleBonce 22/05
Olhando para o seu código, parece que você está fazendo extrapolação em vez de interpolação.
Durza007

Respostas:

1

Ainda não posso postar um comentário, por isso vou postar isso como resposta.

Se eu entendi o problema corretamente, é mais ou menos assim:

  1. primeiro você tem uma colisão
  2. então a posição do objeto é corrigida (presumivelmente pela rotina de detecção de colisões)
  3. a posição atualizada do objeto é enviada para a função de renderização
  4. a função de renderização atualiza a localização do objeto usando extrapolação
  5. a posição do objeto extrapolado agora quebra a rotina de detecção de colisão

Eu posso pensar em 3 soluções possíveis. Vou listá-los na ordem que é mais desejável para menos IMHO.

  1. Mova a extrapolação para fora da função de renderização. Extrapole a posição do objeto e teste uma colisão.
  2. ou se você quiser manter a extrapolação na função render, defina um sinalizador para indicar que uma colisão ocorreu. Faça com que a detecção de colisão corrija a posição do objeto como você já faz, mas antes de calcular o valor da extrapolação, verifique primeiro o sinalizador de colisão. Como o objeto já está onde precisa estar, não há necessidade de movê-lo.
  3. A última possibilidade, que para mim é mais uma solução alternativa do que uma correção, seria compensar excessivamente a detecção de colisão. Após uma colisão, afaste o objeto da parede, para que após a extrapolação o objeto volte à parede.

Código de exemplo para # 2.

if (collisionHasOccured)
    extrpolation = 0.0f;
else
    extrapolation = (float)(System.currentTimeMillis() + skipTicks - nextGameTick) / (float)skipTicks;

Eu acho que o número 2 provavelmente seria o mais rápido e fácil de começar uma corrida, embora o número 1 pareça ser uma solução mais lógica. Dependendo de como você lida com a sua solução de tempo delta nº 1, pode ser quebrada da mesma maneira por um delta grande; nesse caso, você pode precisar usar os nºs 1 e 2 juntos.

EDIT: Eu entendi mal o seu código anteriormente. Os loops devem renderizar o mais rápido possível e atualizar em um intervalo definido. É por isso que você interpolará a posição do sprite, para lidar com o caso em que você está desenhando mais do que atualizando. No entanto, se o loop ficar para trás, você fará uma pesquisa de atualização até ser pego ou pular o número máximo de quadros desenhados.

Dito isto, o único problema é que o objeto está se movendo após uma colisão. Se houver uma colisão, o objeto deve parar de se mover nessa direção. Portanto, se houver uma colisão, defina sua velocidade como 0. Isso deve impedir que a função render mova o objeto ainda mais.

Aholio
fonte
Oi @Aholio Eu tentei a opção 2 e funciona, mas causa algumas falhas, talvez eu tenha feito errado, vou revisar isso. No entanto, estou muito interessado na opção 1, embora não encontre informações sobre como fazer isso. Como posso extrapolar dizer na minha rotina lógica? BTW, meu delta é fixo, assim como 1/60 ou 0,01667 (esse é o valor que eu uso para integrar, embora a quantidade de tempo que cada iteração do meu jogo leve possa obviamente variar dependendo do que está acontecendo, mas nunca deve levar mais que 0,01667 ), portanto, qualquer ajuda seria ótimo.
BungleBonce
Talvez eu não entenda completamente o que você está fazendo. Algo que me parece um pouco estranho é que você não está apenas fazendo movimentos de posição na sua função de empate; você também está fazendo cálculos de tempo. Isso é feito para todos os objetos? A ordem correta deve ser: cálculo do tempo feito no código do loop do jogo. O loop do jogo passa o delta para uma função de atualização (dt). Atualizar (dt) atualizará todos os objetos do jogo. Ele deve lidar com qualquer movimento, extrapolação e detecção de colisão. Depois que update () retorna ao loop do jogo, ele chama uma função render () que desenha todos os objetos do jogo atualizados recentemente.
Aholio
Hmmmmm no momento minha função de atualização faz tudo. O movimento (por movimento, quero dizer, calcula a nova posição dos sprites - isso é calculado a partir do meu tempo delta). A única coisa que estou fazendo na minha função de renderização (além de realmente desenhar) é extrapolar a posição 'nova', mas é claro que isso usa o tempo, pois é necessário levar em consideração o tempo desde a última atualização lógica até a chamada de renderização. Essa é a minha compreensão de qualquer maneira :-) Como você faria isso? Não entendo como usar extrapolação apenas dentro da atualização lógica. Obrigado!
precisa saber é o seguinte
Atualizei minha resposta. Espero que ajude
Aholio 4/14
Obrigado, veja no meu Q original "quando ele pára, está oscilando ligeiramente para a esquerda / direita" - portanto, mesmo que eu pare meu sprite, a extrapolação ainda mantém o sprite 'em movimento' (oscilante) - isso acontece porque eu não estou usando o As posições antiga e atual do sprite para calcular minha extrapolação; portanto, mesmo que o sprite seja estático, ele ainda tem esse efeito de oscilação com base no valor de 'extrapolação' que é calculado a cada iteração de quadro. Eu até tentei dizer 'se pos antigo e atual são os mesmos, então não extrapole', mas isso ainda parece não funcionar, vou voltar e dar uma outra olhada!
BungleBonce
0

Parece que você precisa separar totalmente a renderização e a atualização da física. Normalmente, a simulação subjacente será executada em intervalos de tempo discretos e a frequência nunca mudará. Por exemplo, você pode simular o movimento da sua bola a cada 1/60 de segundo, e é isso.

Para permitir uma taxa de quadros variável, o código de renderização deve operar com uma frequência variável, mas qualquer simulação ainda deve estar em um intervalo de tempo fixo. Isso permite que os gráficos leiam a memória da simulação como somente leitura e configure interpolação em vez de extrapolação.

Como a extrapolação está tentando prever onde serão os valores futuros, mudanças repentinas no movimento podem gerar enormes erros de extrapolação. É melhor renderizar sua cena sobre um quadro por trás da simulação e interpolar entre posições conhecidas discretas.

Se você quiser ver alguns detalhes da implementação, já escrevi uma seção curta sobre esse tópico em um artigo aqui . Por favor, consulte a seção chamada "Timestepping".

Aqui está o código psuedo importante do artigo:

const float fps = 100
const float dt = 1 / fps
float accumulator = 0

// In units seconds
float frameStart = GetCurrentTime( )

// main loop
while(true)
  const float currentTime = GetCurrentTime( )

  // Store the time elapsed since the last frame began
  accumulator += currentTime - frameStart( )

  // Record the starting of this frame
  frameStart = currentTime

  // Avoid spiral of death and clamp dt, thus clamping
  // how many times the UpdatePhysics can be called in
  // a single game loop.
  if(accumulator > 0.2f)
    accumulator = 0.2f

  while(accumulator > dt)
    UpdatePhysics( dt )
    accumulator -= dt

  const float alpha = accumulator / dt;

  RenderGame( alpha )

void RenderGame( float alpha )
  for shape in game do
    // calculate an interpolated transform for rendering
    Transform i = shape.previous * alpha + shape.current * (1.0f - alpha)
    shape.previous = shape.current
    shape.Render( i )

A RenderGamefunção é de maior interesse. A idéia é usar a interpolação entre posições discretas de simulação. O código de renderização pode fazer suas próprias cópias dos dados somente leitura da simulação e usar um valor interpolado temporário para renderizar. Isso lhe dará um movimento muito suave, sem problemas tolos como o que você parece estar tendo!

RandyGaul
fonte