Creando un gancho de gato en Unity. Parte 2

imagen

Nota : este tutorial está destinado a usuarios avanzados y experimentados, y no cubre temas como agregar componentes, crear nuevos scripts de GameObject y sintaxis de C #. Si necesita mejorar sus habilidades de Unity, consulte nuestros tutoriales de Introducción a Unity e Introducción a las secuencias de comandos de Unity .

En la primera parte del tutorial, aprendimos cómo crear un gancho para gatos con la mecánica de envolver una cuerda alrededor de los obstáculos. Sin embargo, queremos más: la cuerda puede envolver objetos a nivel, pero no se desprende cuando regresa.

Llegar al trabajo


Abra el proyecto terminado desde la primera parte en Unity o descargue el borrador para esta parte del tutorial, luego abra 2DGrapplingHook-Part2-Starter . Como en la primera parte, utilizaremos Unity versión 2017.1 o superior.

Abra la escena del juego en el editor desde la carpeta del proyecto Escenas .


Inicie la escena del juego e intente enganchar el gancho del gato en las piedras sobre el personaje, y luego balancee para que la cuerda se enrolle alrededor de un par de bordes de piedra.

Cuando regreses, notarás que los puntos de la piedra a través de los cuales la cuerda solía girar no se vuelven a desenganchar.


Piensa en el punto en el que la cuerda debe desplegarse. Para simplificar la tarea, es mejor usar el estuche cuando la cuerda se enrolla alrededor de los bordes.

Si la babosa, que se aferra a una piedra sobre su cabeza, se balancea hacia la derecha, entonces la cuerda se doblará después del umbral en el que cruza el ángulo de 180 grados con la costilla a la que está unida la babosa. En la figura siguiente, se muestra con un punto verde resaltado.


Cuando la babosa se balancea en la otra dirección, la cuerda debe desengancharse nuevamente en el mismo punto (resaltado en rojo en la figura anterior):


La lógica de desenrollar


Para calcular el momento en que necesita desenrollar la cuerda en los puntos sobre los que se envolvió anteriormente, necesitamos conocimientos de geometría. En particular, utilizaremos una comparación de ángulos para determinar cuándo la cuerda debe separarse del borde.

Esta tarea puede parecer un poco intimidante. Las matemáticas pueden inspirar horror y desesperación incluso en los más valientes.

Afortunadamente, Unity tiene excelentes funciones de ayuda matemática que pueden hacernos la vida un poco más fácil.

Abra el script RopeSystem en el IDE y cree un nuevo método llamado HandleRopeUnwrap() .

 private void HandleRopeUnwrap() { } 

Vaya a Update() y agregue al final una llamada a nuestro nuevo método.

 HandleRopeUnwrap(); 

Si bien HandleRopeUnwrap() no hace nada, ahora podemos procesar la lógica asociada con todo el proceso de separación de los bordes.

Como recordará en la primera parte del tutorial, almacenamos las posiciones de envoltura de la cuerda en una colección llamada ropePositions , que es una colección List<Vector2> . Cada vez que una cuerda se enrolla alrededor de un borde, mantenemos la posición de este punto de envoltura en esta colección.

Para que el proceso sea más eficiente, no ejecutaremos ninguna lógica en HandleRopeUnwrap() si el número de posiciones almacenadas en la colección es igual o menor que 1.

En otras palabras, cuando la bala se ha enganchado al punto de partida y su cuerda aún no se ha enrollado alrededor de los bordes, el número de ropePositions de la ropePositions será 1, y no seguiremos la lógica de procesamiento de desenrollado.

Agregue esta simple return a la parte superior de HandleRopeUnwrap() para guardar ciclos de CPU valiosos, porque este método se llama desde Update() muchas veces por segundo.

 if (ropePositions.Count <= 1) { return; } 

Agregar nuevas variables


Bajo esta nueva prueba, agregaremos algunas dimensiones y referencias a los diferentes ángulos necesarios para implementar la base de la lógica de desenrollado. Agregue el siguiente código a HandleRopeUnwrap() :

 // Hinge =       // Anchor =     Hinge // Hinge Angle =   anchor  hinge // Player Angle =   anchor  player // 1 var anchorIndex = ropePositions.Count - 2; // 2 var hingeIndex = ropePositions.Count - 1; // 3 var anchorPosition = ropePositions[anchorIndex]; // 4 var hingePosition = ropePositions[hingeIndex]; // 5 var hingeDir = hingePosition - anchorPosition; // 6 var hingeAngle = Vector2.Angle(anchorPosition, hingeDir); // 7 var playerDir = playerPosition - anchorPosition; // 8 var playerAngle = Vector2.Angle(anchorPosition, playerDir); 

Aquí hay muchas variables, por lo que explicaré cada una de ellas y agregaré una ilustración conveniente que ayudará a comprender su propósito.

  1. anchorIndex es el índice en la colección ropePositions en dos posiciones desde el final de la colección. Podemos considerarlo como un punto en dos posiciones de la cuerda desde la posición de la bala. En la figura siguiente, este es el primer punto de unión del gancho a la superficie. Al llenar la colección ropePositions nuevos puntos de envoltura, este punto siempre será el punto de envoltura a una distancia de dos posiciones de la bala.
  2. hingeIndex es el índice de la colección que almacena el punto de la bisagra actual; en otras palabras, la posición en la que la cuerda se enrolla actualmente alrededor del punto más cercano al extremo de la cuerda de la bala. Siempre está a una distancia de una posición de la bala, por eso usamos ropePositions.Count - 1 .
  3. anchorPosition calcula haciendo referencia al lugar anchorIndex en la colección ropePositions y es el valor simple de Vector2 de esa posición.
  4. hingePosition calcula haciendo referencia al lugar de hingeIndex en la colección ropePositions y es el valor simple de Vector2 de esa posición.
  5. hingeDir es un vector dirigido desde anchorPosition a hingePosition . Se usa en la siguiente variable para obtener el ángulo.
  6. hingeAngle : la útil función auxiliar Vector2.Angle() se usa aquí para calcular el ángulo entre anchorPosition y el punto de bisagra.
  7. playerDir es un vector dirigido desde anchorPosition a la posición actual de la babosa (playerPosition)
  8. Luego, usando el ángulo entre el punto de anclaje y el jugador (slug), playerAngle calcula playerAngle .


Todas estas variables se calculan utilizando las posiciones almacenadas como valores de Vector2 en la colección ropePositions y comparando estas posiciones con otras posiciones o la posición actual del jugador (slug).

Las dos variables importantes utilizadas para la comparación son hingeAngle y hingeAngle .

El valor almacenado en hingeAngle debe permanecer estático porque siempre es un ángulo constante entre el punto en los dos "pliegues de la cuerda" de la barra y el "doblez de la cuerda" más cercano a la barra que no se mueve hasta que la cuerda se desenrosca o después de doblar Se agregará un nuevo punto de curvatura.

Cuando la babosa se playerAngle cambia. Al comparar este ángulo con hingeAngle , y también verificar si la barra está a la izquierda o derecha de esta esquina, podemos determinar si el punto de plegado actual más cercano a la barra debe separarse.

En la primera parte de este tutorial, guardamos las posiciones de plegado en un diccionario llamado wrapPointsLookup . Cada vez que guardamos el punto de plegado, lo agregamos al diccionario con la posición como clave y con 0 como valor. Sin embargo, este valor de 0 era bastante misterioso, ¿verdad?

Utilizaremos este valor para almacenar la posición de la babosa en relación con su ángulo con el punto de bisagra (el punto de plegado actual más cercano a la babosa).

Si asigna un valor de -1 , entonces el ángulo de la babosa ( playerAngle ) es menor que el ángulo de la bisagra ( hingeAngle ), y con un valor de 1, el ángulo de playerAngle mayor que hingeAngle .

Debido al hecho de que guardamos los valores en el diccionario, cada vez que comparamos hingeAngle con hingeAngle , podemos entender si la bala acaba de pasar el límite después del cual la cuerda debe desengancharse.

Se puede explicar de manera diferente: si el ángulo de la bala acaba de comprobarse y es más pequeño que el ángulo de la bisagra, pero la última vez que se guardó en el diccionario de puntos de plegado se marcó con un valor que indica que estaba en el otro lado de esta esquina, entonces el punto debe eliminarse inmediatamente !

Cuerda de desacoplamiento


Echa un vistazo a la captura de pantalla a continuación con notas. Nuestra babosa se aferró a la roca, se balanceó hacia arriba, envolviendo una cuerda alrededor del borde de la roca en su camino.


Puede notar que en la posición de oscilación más alta, donde la bala es opaca, su punto de plegado más cercano actual (marcado con un punto blanco) se almacenará en el diccionario wrapPointsLookup con un valor de 1 .

En el camino hacia abajo, cuando playerAngle vuelve más pequeño que hingeAngle (dos líneas verdes discontinuas), como se muestra con la flecha azul, se realiza una verificación, y si el último valor (actual) del punto de curvatura era 1 , entonces el punto de curvatura debería eliminarse.

Ahora implementemos esta lógica en el código. Pero antes de comenzar, creemos un espacio en blanco del método que usaremos para desconectar. Debido a esto, después de crear la lógica, no dará lugar a un error.

Agregue un nuevo método UnwrapRopePosition(anchorIndex, hingeIndex) insertando las siguientes líneas:

 private void UnwrapRopePosition(int anchorIndex, int hingeIndex) { } 

Una vez hecho esto, regrese a HandleRopeUnwrap() . Bajo las variables recién agregadas, agregue la siguiente lógica, que manejará dos casos: playerAngle menos que hingeAngle y hingeAngle más que hingeAngle :

 if (playerAngle < hingeAngle) { // 1 if (wrapPointsLookup[hingePosition] == 1) { UnwrapRopePosition(anchorIndex, hingeIndex); return; } // 2 wrapPointsLookup[hingePosition] = -1; } else { // 3 if (wrapPointsLookup[hingePosition] == -1) { UnwrapRopePosition(anchorIndex, hingeIndex); return; } // 4 wrapPointsLookup[hingePosition] = 1; } 

Este código debe corresponder a la explicación de la lógica descrita anteriormente para el primer caso (cuando hingeAngle < hingeAngle ), pero también maneja el segundo caso (cuando hingeAngle > hingeAngle ).

  1. Si el punto de plegado actual más cercano a la babosa tiene un valor de 1 en el punto donde hingeAngle < hingeAngle , entonces eliminamos este punto y realizamos un retorno para que el resto del método no se ejecute.
  2. De lo contrario, si el punto de inflexión no se marcó por última vez con un valor de 1 , pero playerAngle menor que hingeAngle , entonces se asigna -1 .
  3. Si el punto de plegado actual más cercano a la babosa es -1 en el punto donde hingeAngle > hingeAngle , entonces elimine el punto y regrese.
  4. De lo contrario, asignamos las entradas en el diccionario de puntos de plegado en la posición de bisagra a 1 .

Este código garantiza que el diccionario wrapPointsLookup siempre se actualice, asegurando que el valor del punto de plegado actual (más cercano a la barra) coincida con el ángulo de barra actual en relación con el punto de curvatura.

No olvide que el valor es -1 cuando el ángulo del slug es menor que el ángulo de la bisagra (en relación con el punto de referencia) y 1 cuando el ángulo del slug es mayor que el ángulo de la bisagra.

Ahora UnwrapRopePosition() en el script RopeSystem con un código que se enganchará directamente, desacoplando la posición de referencia y asignando un nuevo valor de distancia al valor de distancia de la cuerda DistanceJoint2D. Agregue las siguientes líneas al disco de método creado anteriormente:

  // 1 var newAnchorPosition = ropePositions[anchorIndex]; wrapPointsLookup.Remove(ropePositions[hingeIndex]); ropePositions.RemoveAt(hingeIndex); // 2 ropeHingeAnchorRb.transform.position = newAnchorPosition; distanceSet = false; // Set new rope distance joint distance for anchor position if not yet set. if (distanceSet) { return; } ropeJoint.distance = Vector2.Distance(transform.position, newAnchorPosition); distanceSet = true; 

  1. El índice del punto de anclaje actual (la segunda posición de la cuerda desde la bala) se convierte en la nueva posición de la bisagra, y se elimina la posición anterior de la bisagra (la que antes estaba más cerca de la bala y que ahora estamos "desenroscando"). A la variable newAnchorPosition asigna el valor anchorIndex en la lista de posiciones de la cuerda. Luego se usará para posicionar la posición actualizada del punto de anclaje.
  2. La junta de cuerda RigidBody2D (a la que se une la cuerda DistanceJoint2D) cambia su posición a la nueva posición del punto de anclaje. Esto garantiza un movimiento continuo y suave de la bala en la cuerda cuando está conectada a DistanceJoint2D, y esta conexión debería permitirle seguir balanceándose en relación con la nueva posición, que se convirtió en la referencia, en otras palabras, en relación con el siguiente punto hacia abajo de la cuerda desde su posición.
  3. Luego, debe actualizar el valor de distancia distanceJoint2D para tener en cuenta un cambio brusco en la distancia desde la bala hasta el nuevo punto de referencia. Si esto aún no se ha hecho, se realiza una comprobación rápida del indicador distanceSet , y a la distancia se le asigna el valor de la distancia calculada entre el slug y la nueva posición del punto de anclaje.

Guarde el script y regrese al editor. ¡Comienza el juego nuevamente y observa cómo la cuerda se desprende de los bordes cuando la bala supera los valores de umbral de cada punto de curvatura!


Aunque la lógica está lista, agregaremos un código auxiliar a HandleRopeUnwrap() justo antes de comparar hingeAngle con hingeAngle ( if (playerAngle < hingeAngle) ).

 if (!wrapPointsLookup.ContainsKey(hingePosition)) { Debug.LogError("We were not tracking hingePosition (" + hingePosition + ") in the look up dictionary."); return; } 

En realidad, esto no debería suceder, porque redefinimos y desconectamos el gancho de gato cuando se enrolla alrededor de una costilla dos veces, pero si esto sigue sucediendo, podemos salir fácilmente de este método con una simple return y un mensaje de error en la consola

Además, gracias a esto, manejaremos más convenientemente tales casos limitantes; Además, recibimos nuestro propio mensaje de error en caso de que ocurra algo innecesario.

¿A dónde ir después?


Aquí hay un enlace al proyecto terminado de esta segunda y última parte del tutorial.

¡Felicitaciones por completar esta serie de tutoriales! Cuando se trataba de comparar ángulos y posiciones, todo se volvió bastante complicado, pero sobrevivimos a esto y ahora tenemos un maravilloso sistema de gancho y cuerda que puede terminar en objetos en el juego.


¿Sabías que nuestro equipo de desarrollo de Unity ha escrito un libro? Si no, echa un vistazo a Unity Games By Tutorials . Este juego te enseñará cómo crear cuatro juegos listos para usar desde cero:

  • Tirador de dos palos
  • Tirador en primera persona
  • Juego de defensa de la torre (con soporte VR)
  • Juego de plataformas 2D

¡Después de leer este libro, aprenderá cómo crear sus propios juegos para Windows, macOS, iOS y otras plataformas!

Este libro está destinado tanto a principiantes como a aquellos que desean actualizar sus habilidades de Unity a un nivel profesional. Para dominar el libro necesitas tener experiencia en programación (en cualquier idioma).

Source: https://habr.com/ru/post/es415217/


All Articles