O exemplo fornecido pela Microsoft parece que a detecção de colisão (pelo que posso ver) terá um pequeno erro. Quando o usuário colide com um bloco Impossível, a profundidade da interseção é calculada. O menor dos valores de profundidade X e Y é usado para fixar a posição do usuário para que ele não colide mais com o bloco. Mas se o usuário estivesse viajando na diagonal, isso poderia resultar no final do ponto exatamente no ponto em que o personagem colidiria primeiro com o bloco?
Provavelmente estou errado, mas é assim que eu vejo.
private void HandleCollisions()
{
// Get the player's bounding rectangle and find neighboring tiles.
Rectangle bounds = BoundingRectangle;
int leftTile = (int)Math.Floor((float)bounds.Left / Tile.Width);
int rightTile = (int)Math.Ceiling(((float)bounds.Right / Tile.Width)) - 1;
int topTile = (int)Math.Floor((float)bounds.Top / Tile.Height);
int bottomTile = (int)Math.Ceiling(((float)bounds.Bottom / Tile.Height)) - 1;
// Reset flag to search for ground collision.
isOnGround = false;
// For each potentially colliding tile,
for (int y = topTile; y <= bottomTile; ++y)
{
for (int x = leftTile; x <= rightTile; ++x)
{
// If this tile is collidable,
TileCollision collision = Level.GetCollision(x, y);
if (collision != TileCollision.Passable)
{
// Determine collision depth (with direction) and magnitude.
Rectangle tileBounds = Level.GetBounds(x, y);
Vector2 depth = RectangleExtensions.GetIntersectionDepth(bounds, tileBounds);
if (depth != Vector2.Zero)
{
float absDepthX = Math.Abs(depth.X);
float absDepthY = Math.Abs(depth.Y);
// Resolve the collision along the shallow axis.
if (absDepthY < absDepthX || collision == TileCollision.Platform)
{
// If we crossed the top of a tile, we are on the ground.
if (previousBottom <= tileBounds.Top)
isOnGround = true;
// Ignore platforms, unless we are on the ground.
if (collision == TileCollision.Impassable || IsOnGround)
{
// Resolve the collision along the Y axis.
Position = new Vector2(Position.X, Position.Y + depth.Y);
// Perform further collisions with the new bounds.
bounds = BoundingRectangle;
}
}
else if (collision == TileCollision.Impassable) // Ignore platforms.
{
// Resolve the collision along the X axis.
Position = new Vector2(Position.X + depth.X, Position.Y);
// Perform further collisions with the new bounds.
bounds = BoundingRectangle;
}
}
}
}
}
// Save the new bounds bottom.
previousBottom = bounds.Bottom;
}
xna
c#
collision-detection
platformer
collision-resolution
PriestVallon
fonte
fonte
Respostas:
Você está absolutamente certo . Eu tive minha parcela de problemas com as rotinas de colisão na amostra de plataformas XNA. Mas consegui começar com o código, conforme fornecido na amostra, e modifiquei-o um pouco até obter resultados consistentes em todos os cenários de teste que pude lançar nele.
Em particular, o tipo de problema que eu estava tendo era tentar deslizar ao longo de uma parede movendo-se na diagonal contra ela. Devido à suposição que a amostra faz para resolver colisões com base no menor eixo de deslocamento, isso fez com que o personagem não pudesse se mover ao empurrar contra uma parede em alguma direção. Por exemplo, usando um sinal, eu ficava preso ao abraçar o teto e tentar me mover da esquerda para a direita (não consigo me lembrar dos detalhes). Mudar o sinal resolveria essa situação, mas um problema apareceria no cenário oposto. O ponto principal é que, com a implementação fornecida, eu não conseguia fazê-lo funcionar corretamente em todos os lados e em todas as direções - sempre falhava em pelo menos um caso.
Portanto, o núcleo das mudanças que eu fiz foi começar a lidar com o movimento no eixo X, independentemente do movimento no eixo Y, em duas etapas separadas. Eu escrevi sobre isso antes nesta resposta, então vá para os detalhes.
Aqui está a versão limpa do código: http://pastie.org/3152377
E aqui está um vídeo desse exemplo em ação: http://www.youtube.com/watch?v=5-D0PGdoDDY
E se bem me lembro, o motivo real era algo como isto:
fonte
Quando você tiver várias colisões, se as retificar da mais próxima para a mais distante do centro de cada retângulo envolvido, você não terá o problema de "travar".
1) Encontre todos os retângulos em colisão
2) Se houver mais de um (dependendo do seu caso de uso, isso pode ser frequente ou pouco frequente), encontre o mais próximo.
3) Resolva colisões uma de cada vez e verifique se as outras ainda são colisões válidas
Na resposta aceita, a lógica de colisão e entrada é enlameada; possui verificações para determinar o rumo, etc. A implementação da maneira descrita mantém a lógica de colisão separada da lógica de entrada, ao custo de calcular a distância, quando necessário.
fonte