¿Cómo evito que el personaje de mi juego de plataformas se recorte en los azulejos de la pared?

9

Actualmente, tengo un juego de plataformas con mosaicos para terreno (gráficos tomados de Cave Story). El juego está escrito desde cero usando XNA, así que no estoy usando un motor existente o un motor de física.

Las colisiones de mosaicos se describen exactamente como se describe en esta respuesta (con SAT simple para rectángulos y círculos), y todo funciona bien.

Excepto cuando el jugador se encuentra con una pared mientras cae / salta. En ese caso, se agarrarán de un azulejo y comenzarán a pensar que han golpeado un piso o techo que en realidad no está allí.

Imagen de captura de personajes

En esta captura de pantalla, el jugador se mueve hacia la derecha y cae hacia abajo. Entonces, después del movimiento, se comprueban las colisiones, y primero, resulta que el personaje del jugador colisiona con la ficha 3 del piso y se empuja hacia arriba. En segundo lugar, se encuentra colisionando con el mosaico a su lado y empujado hacia un lado: el resultado final es que el personaje del jugador piensa que está en el suelo y no se cae, y se 'atrapa' en el mosaico mientras se encuentre con él. .

Podría resolver esto definiendo las fichas de arriba a abajo, lo que lo hace caer suavemente, pero luego ocurre el caso inverso y golpeará un techo que no está allí cuando salta hacia arriba contra la pared.

¿Cómo debería abordar la resolución de esto, para que el personaje del jugador pueda caer a lo largo de la pared como debería?

doppelgreener
fuente
Similar a esta pregunta? gamedev.stackexchange.com/questions/14486/…
MichaelHouse
@ Byte56 La pregunta no es la misma, pero su respuesta podría ayudar.
doppelgreener
Una nota: he tenido muy poco tiempo en las últimas semanas para experimentar con las respuestas aquí. He implementado la resolución de colisión mundial como respondí en la pregunta @ Byte56 enlazada, que tiene errores separados a altas velocidades, y aún no he podido probar las respuestas a esta pregunta. Los probaré cuando pueda y aceptaré una respuesta cuando pueda, o publicaré la mía si la resuelvo yo mismo.
doppelgreener

Respuestas:

8

El enfoque más simple y a prueba de fallas es simplemente no verificar colisiones en bordes ocultos. Si tiene dos fichas de pared, una directamente encima de la otra, entonces el borde inferior de la ficha superior y el borde superior de la ficha inferior no deben verificarse por colisión contra el jugador.

Puede determinar qué bordes son válidos durante una pasada simple sobre el mapa de mosaicos, almacenando los bordes externos como banderas en cada ubicación de mosaico. También puede hacer esto en tiempo de ejecución comprobando los mosaicos adyacentes durante la detección de colisión.

Hay versiones más avanzadas de esta misma técnica que también hacen que sea más fácil y rápido admitir mosaicos no cuadrados.

Te recomiendo que leas los artículos a continuación. Son introducciones simples y decentes para hacer física básica y detección de colisiones en plataformas modernas (como Cave Story). Si buscas una sensación de Mario de la vieja escuela, hay algunos trucos más de los que preocuparte, pero la mayoría de ellos involucran manejar saltos y animaciones en lugar de una colisión.

http://www.metanetsoftware.com/technique/tutorialA.html http://www.metanetsoftware.com/technique/tutorialB.html

Sean Middleditch
fuente
¿Puedes aclarar cómo ignorar algunos bordes durante la colisión? Por lo general, veo colisiones detectadas como la intersección de dos rectángulos (los límites del jugador y los límites del mosaico). Cuando dice que no se debe verificar la colisión contra bordes individuales, ¿significa que el algoritmo de colisión no es una intersección rectángulo-rectángulo sino algo más? Su descripción tiene sentido, pero no estoy seguro de cómo sería la implementación.
David Gouveia
Sí, solo haz una colisión de línea rectangular. Solo simplificado porque la línea siempre está alineada a los ejes. Simplemente puede descomponer su casilla de verificación.
Sean Middleditch
5

Aquí el orden que haría las cosas ...

1) Guardar X, Y actual del personaje

2) Mueve la dirección X

3) Verifique todos los rincones del carácter obteniendo los datos del mosaico y verifique si es sólido haciendo lo siguiente: (X e Y es la posición del carácter)

  • arriba a la izquierda: piso (X / tileWidth), piso (Y / tileHeight);
  • arriba a la derecha: piso ((X + ancho de caracteres) / ancho de mosaico), piso (Y / altura de mosaico);
  • para abajo a la izquierda: piso (X + / tileWidth), piso ((Y + characterHeight) / tileHeight);
  • para abajo a la derecha: floor ((X + characterWidth) / tileWidth), floor ((Y + characterHeight) / tileHeight);

... para obtener datos de mosaico correctos de los datos del mapa

4) Si alguna de las fichas es sólida, entonces debes forzar a X a la mejor posición posible restaurando la X guardada y ...

  • si se movió a la izquierda: X = piso (X / tileWidth) * tileWidth;
  • si se movía a la derecha: X = ((piso ((X + ancho de caracteres) / ancho de mosaico) + 1) * ancho de mosaico) - ancho de carácter;

5) Repita 2-4 usando Y

Si tu personaje es más alto que un mosaico, debes verificar más mosaicos. Para hacer esto, debe realizar un bucle y agregar tileHeight a la posición del carácterY para obtener el mosaico

int characterBlockHeight = 3;

// check all tiles and intermediate tiles down the character body
for (int y = 0; y < characterBlockHeight - 1; y++) {
    tile = getTile(floor(characterX / tileWidth), floor((characterY + (y * tileHeight)) / tileHeight));
    ... check tile OK....
}

// finally check bottom
tile = getTile(floor(characterX / tileWidth), floor((characterY + characterHeight) / tileHeight);
... check tile OK....

Si el carácter es más ancho que 1 bloque, haga lo mismo pero reemplace characterY, characterHeight, tileHeight, etc. con characterX, characterWidth, etc.

Juan
fuente
2

¿Qué sucede si se verifica la posibilidad de una colisión como parte del método de movimiento del jugador y se rechaza el movimiento que hizo que el jugador se deslizara hacia otro objeto? Esto evitaría que el jugador ingrese "dentro" de un bloque y, por lo tanto, no podría estar "encima" del bloque debajo de él.

Algún chico
fuente
1

Me encontré con casi el mismo problema en un juego en el que estoy trabajando actualmente. La forma en que lo resolví fue almacenar el área de la parte superpuesta cuando se realizaban pruebas de colisiones, y luego manejar esas colisiones en función de su prioridad (primero el área más grande), luego verificar dos veces cada colisión para ver si ya había sido resuelta por una anterior. manejo.

En su ejemplo, el área de colisión del eje x sería mayor que el eje y, por lo que se manejaría primero. Luego, antes de intentar manejar la colisión del eje y, verificaría y vería que ya no está colisionando después de moverse sobre el eje x. :)

Nick Badal
fuente
1

Una solución simple, pero no perfecta, es cambiar la forma del cuadro de colisión del jugador para que no sea un cuadrado. Corta las esquinas para hacerlo octágono o algo similar. De esta manera, cuando choca con un azulejo como los que están en la pared, se deslizará y no quedará atrapado. Esto no es perfecto porque todavía puedes notar que se queda un poco atrapado en las esquinas, pero no se atasca y es muy fácil de implementar.

Amplify91
fuente
1

En este tutorial se cubrió un problema muy similar: Introducción al desarrollo de juegos . La parte relevante del video es a las 1:44 , explicando con diagramas y fragmentos de código.

El aviso 14-tilemap.py muestra el problema de recorte, pero se soluciona en 15-blocker-sides.py

No puedo explicar exactamente por qué el último ejemplo de tutorial no se recorta mientras que su código sí, pero creo que tiene algo que ver con:

  • Respuesta de colisión condicional basada en el tipo de mosaico (qué bordes están realmente activos).
  • El jugador siempre está "cayendo". Aunque el jugador parece estar descansando sobre un mosaico, el jugador en realidad cae repetidamente a través del mosaico y luego lo empujan hacia la parte superior. (El self.restingmiembro en el código solo se usa para verificar si el jugador puede saltar. A pesar de esto, no puedo hacer que el jugador haga un 'doble' salto desde una pared. En teoría, podría ser posible)
Leftium
fuente
0

Otro método (al que respondí la pregunta que Byte56 enlazó) sería verificar si la resolución de colisión coloca al personaje en un lugar vacío o no. Entonces, en su problema, obtiene una colisión desde el techo del rectángulo interior para moverlo hacia arriba, lo que sería ilegal (ya que todavía está en colisión con otra baldosa). En cambio, solo lo mueve si se mueve a un espacio libre (como la forma en que la colisión desde el mosaico superior lo movería hacia la izquierda), y una vez que encuentre esa colisión, habrá terminado.

La respuesta que di tenía código, pero se volvió demasiado complicada. Llevaría un registro de todas las colisiones dentro de ese plazo y solo tomaría la que conduzca a un espacio libre. Sin embargo, si no hubiera ninguno, creo que recurrí a restablecer la posición del personaje a su última posición, sin embargo, eso es realmente feo y tal vez prefieras intentar implementar algo como el Mario original donde simplemente se mueve en una dirección o algo cuando no hay resolución de espacio libre es posible. También puede ordenar esa lista de resoluciones de colisión y solo moverse al espacio libre con la distancia más corta (creo que esa solución sería la más preferible, aunque no codifiqué para eso).

Jeff
fuente
0

Solo he hecho 3 SAT en 3D, así que tal vez no estoy haciendo esto bien. Si entiendo su problema, podría deberse a tener contactos con un eje de contacto que no debería ser posible, lo que da como resultado que el jugador actúe como si hubiera un piso. Una solución alternativa podría ser usar una forma circular para su caracter, entonces solo obtendrá ejes de contacto en la dirección del eje x cuando golpee una pared.

Mikael Högström
fuente