Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Platformer

Física básica de plataformas en 2D, parte 8: pendientes

by
Difficulty:IntermediateLength:LongLanguages:

Spanish (Español) translation by Luis Chiabrera (you can also view the original English article)

Final product image
What You'll Be Creating

Demostración

La demostración muestra el resultado final de la implementación de la pendiente. Usa WASD para mover el personaje. El botón derecho del mouse crea un mosaico. Puede usar la rueda de desplazamiento o las teclas de flecha para seleccionar un mosaico que desea colocar. Los controles deslizantes cambian el tamaño del personaje del jugador.

La demostración se publicó bajo Unity 5.5.2f1, y el código fuente también es compatible con esta versión de Unity.

Antes de empezar...

Al igual que en las partes anteriores de la serie, continuaremos nuestro trabajo donde lo dejamos en la última parte. La última vez calculamos y almacenamos en caché los datos necesarios para mover los objetos fuera de la colisión de las pendientes y cambiamos la forma en que se verifican las colisiones contra el mapa de mosaicos. En esta parte necesitaremos la misma configuración desde el final de la última parte.

Puede descargar los archivos del proyecto de la parte anterior y escribir el código junto con este tutorial.

En esta parte implementaremos la colisión con pendientes u otras fichas personalizadas, añadiremos pendientes de un solo sentido y posibilitará que el objeto del juego se desplace suavemente por las laderas.

Implementación de pendientes

Control vertical de la pendiente

¡Finalmente podemos llegar a las pistas! En primer lugar, trataremos de manejar cuando el borde inferior del objeto se encuentre dentro de un mosaico de pendiente.

Vamos a echar un vistazo a nuestra función CollidesWithTileBottom, particularmente la parte donde manejamos las fichas.

Para poder ver si nuestro objeto colisiona con la pendiente, primero debemos obtener los desplazamientos de la función que creamos anteriormente, que hace la mayor parte de nuestro trabajo.

Dado que estamos comprobando un píxel debajo de nuestro personaje, tenemos que ajustar el desplazamiento.

La condición para la colisión es que el freeUp es mayor o igual a 0, lo que significa que o movemos el personaje hacia arriba o el personaje está de pie en la pendiente.

Sin embargo, no deberíamos olvidarnos del caso cuando queremos que el personaje se adhiera a la pendiente. Esto significa que, aunque el personaje abandone la pendiente, queremos que se comporte como si estuviera en la pendiente de todos modos. Para esto, necesitamos agregar una nueva constante que contendrá el valor de qué tan pronunciada debe ser una pendiente para poder ser considerada una pared vertical en lugar de una pendiente.

Si el desplazamiento está por debajo de esta constante, debería ser posible que el objeto avance suavemente a lo largo de la curva de la pendiente. Si es igual o mayor, debe tratarse como una pared, y saltar sería necesario para subir.

Ahora tenemos que agregar otra condición a nuestra declaración. Esta condición verificará si se supone que el personaje se adhiere a pendientes, ya sea en el último cuadro de una pendiente, y si debe presionarse hacia abajo o hacia arriba en menos píxeles que nuestra constante cSlopeWallHeight.

Si la condición es verdadera, debemos guardar este mosaico como posible colisión con el objeto. Todavía necesitaremos iterar a través de todos los otros mosaicos a lo largo del eje X. Primero, crea las variables que mantendrán la coordenada X y el valor de compensación para el mosaico que colisiona.

Ahora guarde los valores, si la condición definida es verdadera. Si ya encontramos un mosaico que colisiona, debemos comparar los desplazamientos, y el mosaico que colisionará será el que más necesita compensar al personaje.

Finalmente, después de iterar a través de todos los mosaicos y encontrar un mosaico con el que colisiona el objeto, necesitamos compensar el objeto.

Eso es más o menos para el control de fondo, así que ahora vamos a hacer el más alto. Este será un poco más simple, ya que ni siquiera necesitamos manejar la adherencia.

Eso es todo.

Control de pendiente horizontal

La verificación horizontal será un poco más complicada, ya que es aquí donde manejaremos los casos más problemáticos.

Comencemos con el manejo de las pendientes a la derecha. Hay un par de cosas que tendremos que tener en cuenta, sobre todo en cuanto a subir las pendientes. Consideremos las siguientes situaciones.

Different shaped slopes

Tendremos que manejar esos casos con especial cuidado porque en algún momento, cuando avancemos por la pendiente, llegaremos al techo. Para evitar eso, necesitaremos hacer más controles en caso de que el personaje se mueva horizontalmente.

Tendremos que manejar estos casos con especial cuidado porque en algún momento, cuando lleguemos por la pendiente, lleguemos al techo. Para evitar eso, necesitaremos hacer más controles en caso de que el personaje se mueva horizontalmente. Para las verificaciones horizontales, es un poco diferente, porque este es el lugar donde manejaremos el movimiento a lo largo de la pendiente, por lo que, naturalmente, el ajuste de la altura tendrá lugar principalmente aquí.

Para hacer la respuesta de colisión adecuada para los casos ilustrados arriba, será más fácil verificar si podemos entrar en un espacio horizontalmente, y si eso es posible, entonces verifique si el objeto no se superpone con ningún píxel sólido si tuviera que ser movido verticalmente debido a moverse a lo largo de una pendiente. Si no logramos encontrar el espacio, sabemos que es imposible avanzar hacia la dirección marcada, y podemos establecer el indicador de pared horizontal.

Pasemos a la función CollidesWithTileRight, a la parte donde manejamos las pendientes.

Obtenemos la compensación de forma similar a la de los controles verticales, pero la compensación que nos importa es la que es más grande.

Ahora, veamos si nuestro personaje debe tratar el mosaico marcado como una pared. Hacemos esto si el desplazamiento de la pendiente es mayor o igual a nuestra constante cSlopeWallHeight o para salir de la colisión necesitamos compensar el personaje hacia arriba o hacia abajo mientras estamos colisionando con una ficha en la misma dirección, lo que significa que nuestro el objeto se aprieta entre los azulejos superior e inferior.

Si ese no es el caso y el desplazamiento es mayor que 0, entonces golpeamos una pendiente. Un problema aquí es que no sabemos si topamos con una pared en otras teselas que aún no hemos comprobado, de modo que por ahora solo guardaremos el desplazamiento de talud y el tipo de colisión de teselas en caso de que necesitemos utilizarlas más adelante.

Ahora, en lugar de ver si el desplazamiento de la pendiente es mayor que cero, vamos a compararlo con el desplazamiento de la pendiente de otro mosaico, en caso de que ya hayamos encontrado una pendiente colisionante en iteraciones previas.

Manejar el apretar entre las baldosas

Después de terminar de recorrer todas las fichas de interés, veamos si necesitamos mover el objeto. Manejemos el caso donde el desplazamiento de la pendiente terminó siendo distinto de cero.

Tendremos que manejar dos casos aquí, y tenemos que hacer cosas ligeramente diferentes dependiendo de si tenemos que compensar nuestro objeto hacia arriba o hacia abajo.

En primer lugar, debemos verificar si podemos encajar en el espacio después de compensar el objeto. Si ese es el caso, entonces estamos manejando uno de los casos ilustrados arriba. Donde el personaje está tratando de moverse hacia la derecha, el desplazamiento es positivo, pero si desplazamos el objeto, entonces será empujado hacia la pared superior, así que en su lugar solo marcaremos que está colisionando con la pared del lado derecho para bloquear el movimiento en esa dirección.

Si encajamos en el espacio, marcaremos que chocamos con la loseta inferior y compensamos la posición del objeto de manera apropiada.

Manejamos el caso en el cual el objeto necesita ser compensado de manera similar.

Objeto en movimiento en el control de colisión

Ahora esta función desplazará el objeto hacia arriba o hacia abajo según sea necesario si queremos pisar el mosaico a la derecha, pero ¿qué ocurre si queremos usar esta función solo como una comprobación, y realmente no queremos mover el personaje? llamándolo? Para resolver este problema, agreguemos una variable adicional llamada 'mover' para marcar si la función puede mover el objeto o no.

Y mueva el objeto solo si este nuevo indicador está establecido en verdadero.

Controlar la inclinación de pendientes

Ahora manejemos pegados a las pendientes. Es bastante sencillo, pero necesitaremos manejar todas las casillas de las esquinas correctamente, para que el personaje se quede pegado a la pendiente sin ningún contratiempo en el camino.

Sin embargo, antes de manejar las cajas de esquina, podemos manejar fácilmente la inclinación de pendiente dentro de una sola loseta en la verificación de colisión vertical. Será suficiente si agregamos la siguiente condición en la función CollidesWithTileBottom.

Esta condición lo hace de modo que si la distancia entre la posición del objeto y el terreno más cercano está entre 0 y cSlopeWallHeight, el personaje será empujado hacia abajo también, además de la condición original. Desafortunadamente, esto funciona solo dentro de un solo mosaico; la siguiente ilustración identifica el problema que debemos resolver.

Slope with three squares marked on it

El caso límite que estamos hablando es sólo esto: el personaje se mueve hacia abajo ya la izquierda de baldosas número uno al número de baldosas de dos. Baldosas número dos está vacía, por lo que necesitamos para comprobar la baldosa debajo de ella y ver si el desplazamiento desde el carácter de baldosa número 3 es adecuada para seguir caminando a lo largo de la pendiente allí.

Maneje los casos de esquina

Será más fácil manejar estos casos de esquina en las comprobaciones de colisión horizontal, así que volvamos a la función CollidesWithTileRight. Vayamos al final de la función y manejemos los casos problemáticos aquí.

En primer lugar, para manejar la inclinación de la pendiente, se necesita establecer el indicador mSticksToSlope, el objeto debe haber estado en el suelo del cuadro anterior, y el indicador de movimiento debe estar activado.

Ahora tenemos que encontrar la baldosa a la que debemos pegarnos. Como esta función verifica la colisión en el borde derecho del objeto, manejaremos la inclinación de la pendiente para la esquina inferior izquierda del personaje.

Ahora tenemos que encontrar una forma de comparar la altura en la que se encuentra el objeto actualmente al que quiere pisar. Si la siguiente altura es más baja que la actual, pero aún más alta que nuestra constante cSlopeWallHeight, empujaremos nuestro objeto hacia abajo en el suelo.

Obtener la altura de la pendiente

Volvamos a nuestra clase Slope para hacer una función que devuelva la altura de una pendiente en una posición particular.

Los parámetros para la función son el valor de x en la pendiente y el tipo de pendiente. Si la pendiente está vacía, podemos devolver inmediatamente 0, y si está lleno, devolveremos el tamaño del mosaico.

Podemos obtener fácilmente la altura de una pendiente mediante el uso de nuestras compensaciones en caché. Si el mosaico no se transforma de ninguna manera, obtenemos un desplazamiento para un objeto que tiene un píxel de ancho en la posición x, y su altura es igual al tamaño del mosaico.

Manejemos esto para diferentes transformaciones. Si se inclina una pendiente en el eje X, solo tenemos que duplicar el argumento x.

Si la pendiente se voltea en el eje Y, necesitamos devolver la CollidingTop en lugar de collidingBottom de la parte inferior. Como collidingTop en este caso será negativo, también tendremos que darle la vuelta al signo.

Finalmente, si la baldosa se gira 90 grados, necesitaremos regresar collidingLeft o collidingRight. Además de eso, para obtener una compensación en caché adecuada, tendremos que cambiar las posiciones y el tamaño x e y.

Esa es la función final.

Volver a los casos de esquina

Volvamos a la función CollidesWithTileRight, justo donde terminamos de determinar los tipos de pendiente para las fichas en las que se mueve el personaje.

Para usar la función que acabamos de crear, necesitamos determinar la posición en la que queremos obtener la altura de un mosaico.

Ahora calculemos la altura entre esos dos puntos.

Si el desplazamiento está entre 0 y la constante cSlopeWallHeight, vamos a empujar el objeto hacia abajo, pero primero debemos verificar si realmente podemos empujar el objeto hacia abajo. Esta es exactamente la misma rutina que hicimos antes.

En general, la función debería verse así.

Ahora tenemos que hacer todo analógicamente para la función CollidesWithTileLeft. La versión final de la misma debe tomar la siguiente forma.

Eso es. El código debería ser capaz de manejar todas las formas de pendientes no traducidas.

Animation of character moving on slope

Manejar tipos de traslación

Antes de comenzar a manipular los mosaicos trasladables, hagamos algunas funciones que devolverán si un TileCollisionType particular se traduce de una manera particular. Nuestra enumeración de tipo colisión está estructurada de esta manera:

Podemos usar estos patrones para decir solo por el valor de la enumeración de cómo se traduce un tipo de colisión particular. Comencemos identificando el giro en el eje X.

Primero, obtengamos la id de la pendiente. Lo haremos calculando el desplazamiento desde la primera losa de pendiente definida hasta la que queremos identificar.

Tenemos ocho tipos de traducciones, así que ahora todo lo que necesitamos es obtener el resto de dividir el tipeId por 8.

Entonces ahora las traslaciones tienen un número asignado para ellos.

El giro en el eje X está presente en los tipos igual a 1, 3, 5 y 7, por lo que si es igual a uno de ellos, entonces la función debería devolver verdadero, de lo contrario devolvería falso.

De la misma manera, creemos una función que indique si un tipo está volteado en el eje Y.

Y finalmente, si se gira el tipo de colisión.

Eso es todo lo que necesitamos.

Transformar el desplazamiento

Regresemos a la clase Slopes y hagamos que nuestra función GetOffset soporte los mosaicos traducidos.

Como es habitual, dado que no tenemos datos almacenados en caché para las pendientes traducidas, traduciremos la posición y el tamaño del objeto para que el resultado sea idéntico al del traductor traducido. Comencemos con el giro en el eje X. Todo lo que tenemos que hacer aquí es voltear el objeto a lo largo del centro del mosaico.

Del mismo modo para el giro en el eje Y.

Ahora, en caso de que volteemos el mosaico en el eje y, las compensaciones que recibimos se intercambian en realidad. Vamos a traducirlos para que funcionen de la misma manera que los desplazamientos de la tesela no traducida, lo que significa que arriba está arriba y abajo.

Ahora manejemos la rotación de 90 grados.

Aquí todo debe girarse 90 grados, por lo que en lugar de basar nuestra posX y sizeX en los bordes izquierdo y derecho del objeto, los basaremos en la parte superior e inferior.

Ahora tenemos que hacer algo similar a lo que hicimos anteriormente si el mosaico se volteó en el eje Y, pero esta vez tenemos que hacerlo tanto para la rotación de 90 grados como para el giro Y.

Eso es todo. Dado que nuestros desplazamientos finales hacia arriba y hacia abajo se ajustan para que tengan sentido en el espacio mundial, nuestros ajustes fuera de los límites de las teselas siguen funcionando correctamente.

Eso es todo; ahora también podemos usar pendientes trasladadas.

Animation of character moving on slope

En la animación anterior, hay pendientes de 45, 22, 15 y 11 grados. Gracias a las rotaciones de 90 grados, también podemos obtener pendientes de 79, 75 y 68 grados sin definir losetas de pendiente adicionales. También puede ver que la pendiente de 79 grados es demasiado empinada para avanzar sin problemas con nuestro valor de cSlopeWallHeight.

Maneje plataformas de una vía

En todo este problema, hemos roto nuestro soporte para plataformas de una vía. Necesitamos arreglar eso, y extender la funcionalidad a las pendientes también. Las plataformas unidireccionales son tan importantes o incluso más importantes que las fichas sólidas, por lo que no podemos permitirnos perderlas.

Agregue los tipos de One-Way

Lo primero que debemos hacer es agregar nuevos tipos de colisión para plataformas de una vía. Los agregaremos más allá de los tipos de colisión no unidireccional y también marcaremos dónde comienzan, por lo que más adelante nos resultará fácil determinar si un tipo de colisión en particular es unidireccional o no.

Ahora todas las plataformas de una vía se encuentran entre las encripciones OneWayStart y OneWayEnd, por lo que podemos crear fácilmente una función que devuelva esta información.

Las variantes unidireccionales de las pendientes deben apuntar a los mismos datos que las plataformas que no son de una sola vía, por lo que no se preocupe por ampliar aún más los requisitos de memoria.

Cubra los Datos Adicionales

Ahora agreguemos variables que nos permitan hacer que un objeto ignore las plataformas unidireccionales. Una será una bandera de objeto, que básicamente será para establecer ignorar permanentemente las plataformas de una sola vía; esto será útil para volar monstruos y otros objetos que no necesitan utilizar las plataformas, y otra bandera para desactivar temporalmente la colisión con plataformas de una vía, solo por el hecho de caer a través de ellas.

La primera variable estará dentro de la clase MovingObject.

El segundo está dentro de la estructura PositionState.

También agregaremos otra variable aquí que mantendrá la coordenada Y de la plataforma que queremos omitir.

Para hacer que las plataformas de una vía funcionen, simplemente ignoraremos una sola capa horizontal de plataformas. Cuando ingresamos a otra capa, es decir, la posición Y de nuestro personaje ha cambiado en las coordenadas del mapa, luego configuramos el personaje para colisionar nuevamente con las plataformas de una vía.

Modificar los controles de colisión

Vayamos a nuestra función CollidesWithTileBottom. Antes que nada, mientras iteramos a través de las teselas, veamos si es una plataforma de un solo sentido, y si es así, si debemos considerar colisionar con este mosaico o no.

Deberíamos colisionar con plataformas unidireccionales solo si la distancia hasta la parte superior de la plataforma es menor que cSlopeWallHeightConstant, por lo que podemos llegar a la cima. Agreguemos esto a la condición ya establecida, y también debemos asignar los valores adecuados a state.onOneWay y state.oneWayY.

Para la función CollidesWithTileTop, simplemente ignoramos las plataformas unidireccionales.

Para la verificación de colisión horizontal, habrá un poco más de trabajo. En primer lugar, creemos dos booleanos adicionales al principio, que servirán como información sobre si el mosaico actualmente procesado es unidireccional, y si el mosaico de la iteración anterior ha sido una plataforma unidireccional.

Ahora estamos interesados en iterar a través de una plataforma unidireccional si nos movemos a lo largo de ella. Realmente no podemos colisionar con plataformas de una vía desde la derecha o la izquierda, pero si el personaje se mueve a lo largo de una pendiente que también es una plataforma de una vía, entonces debe manejarse de la misma manera que lo haría una pendiente normal.

Ahora asegúrate de que no podamos colisionar con una pendiente como si fuera una pared.

Y si ese no es el caso y el desplazamiento es lo suficientemente pequeño como para escalarlo, entonces recuerde que ahora nos estamos moviendo a lo largo de una plataforma unidireccional.

Ahora lo que queda aquí es asegurarnos de que cada vez que cambiemos el estado de la posición también necesitemos actualizar la variable onOneWay.

Saltando hacia abajo

Tenemos que dejar de ignorar las plataformas de dirección única una vez que cambiemos la posición Y en las coordenadas del mapa. Vamos a configurar nuestra condición después del movimiento en el eje Y en la función Mover. Necesitamos agregarlo al final del segundo caso.

Y también al final del tercer caso.

Deberias hacer eso. Ahora, lo único que debemos hacer para que un personaje caiga desde una plataforma unidireccional es establecer su tmpIgnoresOneWay en verdadero.

Veamos cómo se ve esto en acción.

New animation of character moving on slope

Resumen

Vaya, eso fue mucho trabajo, pero valió la pena. El resultado es muy flexible y robusto. Podemos definir cualquier tipo de pendiente gracias a nuestro manejo de mapas de bits de colisión, traducir los mosaicos y convertirlos en plataformas de una vía.

Esta implementación aún no está optimizada, y estoy seguro de haber perdido muchas oportunidades para nuestro nuevo método de integración de un píxel. También estoy bastante seguro de que se podrían omitir muchos controles de colisión adicionales, así que si mejoras esta implementación, ¡házmelo saber en la sección de comentarios!

Gracias por quedarte conmigo hasta ahora, y espero que este tutorial te sea útil.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.