Los ganchos para gatos agregan mecánicas divertidas e interesantes al juego. Puedes usarlos para moverte por niveles, luchar en arenas y conseguir objetos. ¡Pero a pesar de la aparente simplicidad, la física del manejo de la cuerda y la creación de un comportamiento realista puede ser un desafío!
En la primera parte de este tutorial, implementamos nuestro propio sistema de gancho bidimensional y aprendemos lo siguiente:
- Crea un sistema de puntería.
- Use el renderizador de línea y la unión de distancia para crear la cuerda.
- Le enseñaremos a la soga a envolver objetos del juego.
- Calcule el ángulo de giro de la cuerda y agregue fuerza en esa dirección.
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 . Dado que DistanceJoint2D se usa en este tutorial, también debe consultar Physics Joints en Unity 2D , y solo luego volver a este tutorial.
Llegar al trabajo
Descargue el 
borrador de este tutorial y luego ábralo en el editor de Unity. Se requiere la unidad 2017.1 o superior para la operación.
Abra la escena del 
juego desde la carpeta 
Escenas y vea dónde comenzaremos:
Por ahora, tenemos un personaje jugador simple (babosa) y piedras colgando en el aire.
Los componentes importantes de GameObject 
Player hasta ahora son el colisionador de cápsulas y el cuerpo rígido, que le permiten interactuar con objetos físicos en el nivel. Además, se adjunta un guión de movimiento simple ( 
PlayerMovement ) al personaje, lo que le permite deslizarse por el suelo y realizar saltos simples.
Presiona el botón 
Reproducir para iniciar el juego e intenta controlar al personaje. 
A y 
D lo mueven hacia la izquierda / derecha, y cuando presiona la 
barra espaciadora, salta. Trate de no resbalarse y caerse del precipicio, de lo contrario, ¡morirá!
Ya tenemos los conceptos básicos de administración, pero el mayor problema ahora es la falta de ganchos para gatos.
Crear ganchos y cuerdas
Al principio, el sistema cat-hook parece bastante simple, pero para su implementación de alta calidad es necesario tener en cuenta muchos aspectos. Estos son algunos de los requisitos para la mecánica bidimensional de gancho de gato:
- Renderizador de línea para mostrar la cuerda. Cuando la cuerda se envuelve alrededor de los objetos, podemos agregar más segmentos al renderizador de línea y colocar los vértices en los puntos correspondientes a las roturas de la cuerda.
- DistanceJoint2D. Se puede usar para unir el punto de anclaje actual del gancho de gato para que nuestra bala pueda balancearse. También nos permite ajustar la distancia que se puede usar para alargar y reducir la cuerda.
- Child GameObject con RigidBody2D, que se puede mover según la ubicación actual del punto de anclaje del gancho. En esencia, será el punto de suspensión / anclaje de la cuerda.
- Raycast para lanzar un gancho y sujetarlo a objetos.
Seleccione el objeto 
Player en la Jerarquía y agregue un nuevo GameObject hijo llamado 
RopeHingeAnchor . Este GameObject se usará para colocar el punto de suspensión / anclaje del gancho de gato.
Agregue los componentes 
SpriteRenderer y 
RigidBody2D a 
RopeHingeAnchor .
Para SpriteRenderer, configure la propiedad 
Sprite para usar el valor 
UISprite y cambie el 
Orden en la Capa a 
2 . Deshabilite el componente 
desmarcando la casilla junto a su nombre.
Para el componente 
RigidBody2D, establezca la propiedad Tipo de cuerpo en 
Cinemática . Este punto será movido no por el motor físico, sino por el código.
Seleccione la capa de 
cuerda y establezca los valores de escala X e Y del componente Transformar en 
4 .
Seleccione 
Player nuevamente y conecte el nuevo componente 
DistanceJoint2D .
Arrastre 
RopeHingeAnchor desde la Jerarquía a la propiedad 
Cuerpo rígido conectado del componente 
DistanceJoint2D y desactive la 
Configuración automática de distancia .
Cree un nuevo script C # llamado 
RopeSystem en la carpeta del proyecto 
Scripts y ábralo en el editor de código.
Eliminar el método de 
Update .
En la parte superior del script dentro de la 
RopeSystem clase 
RopeSystem agregue nuevas variables, el método 
Awake() y el nuevo método 
Update :
 
Analicemos cada parte en orden:
- Usamos estas variables para rastrear los diversos componentes con los que interactuará el script RopeSystem.
- El método Awakecomienza al comienzo del juego y deshabilitaropeJoint(el componente DistanceJoint2D). También estableceplayerPositionen la posición actual del jugador.
- Esta es la parte más importante del ciclo principal Update(). Primero, obtenemos la posición del cursor del mouse en el mundo usando elScreenToWorldPointcámaraScreenToWorldPoint. Luego calculamos la dirección de nuestra mirada restando la posición del jugador de la posición del mouse en el mundo. Luego lo usamos para crearaimAngle, que es una representación del ángulo de puntería del cursor. El valor almacena un valor positivo en la construcción if.
- aimDirectiones un giro que resulta útil más adelante. Solo estamos interesados en el valor Z, ya que estamos usando una cámara 2D, y este es el único sistema operativo correspondiente. Pasamos- aimAngle * Mathf.Rad2Deg, que convierte el ángulo en radianes en un ángulo en grados.
- La posición del jugador se controla mediante una variable conveniente que le permite no referirse constantemente a transform.Position.
- Finalmente, tenemos la construcción if..else, que usaremos pronto para determinar si la cuerda está unida al punto de anclaje.
Guarde el script y regrese al editor.
Adjunte el componente 
RopeSystem al objeto Player y cuelgue los diversos componentes en los campos públicos que creamos en el script 
RopeSystem . Arrastre 
Player , 
Crosshair y 
RopeHingeAnchor a los campos apropiados:
- Anclaje de bisagra de cuerda : Anclaje de bisagra
- Conjunto de cuerda : jugador
- Punto de mira : punto de mira
- Punto de mira Sprite : Punto de mira
- Movimiento del jugador: jugador
Ahora solo estamos haciendo todos estos cálculos complejos, pero hasta ahora no hay visualización que pueda mostrarlos en acción. Pero no se preocupe, lo haremos pronto.
Abra el script 
RopeSystem y agregue un nuevo método:
 private void SetCrosshairPosition(float aimAngle) { if (!crosshairSprite.enabled) { crosshairSprite.enabled = true; } var x = transform.position.x + 1f * Mathf.Cos(aimAngle); var y = transform.position.y + 1f * Mathf.Sin(aimAngle); var crossHairPosition = new Vector3(x, y, 0); crosshair.transform.position = crossHairPosition; } 
Este método posiciona la vista según el 
aimAngle transmitido (el valor flotante que calculamos en 
Update() ) para que gire alrededor del jugador con un radio de 1 unidad. También incluimos un alcance de sprite en caso de que aún no esté hecho.
En Update() cambiamos la construcción !ropeAttached para verificar !ropeAttached para que se vea así:
 if (!ropeAttached) { SetCrosshairPosition(aimAngle); } else { crosshairSprite.enabled = false; } 
Guarda el script y ejecuta el juego. Ahora nuestra babosa debería poder apuntar con la vista.
La siguiente pieza de lógica que debe implementarse es un tiro de gancho de gato. Ya hemos determinado la dirección del objetivo, por lo que necesitamos un método que lo reciba como parámetro.
Agregue las siguientes variables debajo de las variables en el script 
RopeSystem :
 public LineRenderer ropeRenderer; public LayerMask ropeLayerMask; private float ropeMaxCastDistance = 20f; private List<Vector2> ropePositions = new List<Vector2>(); 
LineRenderer contendrá un enlace a un renderizador de línea que dibuja la cuerda. 
LayerMask le permite personalizar las capas físicas con las que el gancho puede interactuar. El valor 
ropeMaxCastDistance establece la distancia máxima que el "raycast" puede "disparar".
Finalmente, la lista de posiciones de Vector2 se utilizará para rastrear los puntos de envoltura de la cuerda, que discutiremos más adelante.
Agregue los siguientes métodos nuevos:
 
Esto es lo que hace el código anterior:
- Se llama a HandleInput desde el bucle Update()y simplemente sondea la entrada de los botones izquierdo y derecho del mouse.
- Cuando se registra un clic izquierdo, se activa la línea de la cuerda y se dispara un rayo 2D desde la posición del jugador en la dirección de puntería. La distancia máxima se establece para que el gato de gancho no se pueda disparar a una distancia infinita, y se aplica una máscara para que sea posible seleccionar las capas de física con las que los rayos pueden chocar.
- Si se detecta un impacto de emisión de rayos, entonces ropeAttachedestruey se verifica una lista de las posiciones de los vértices de la cuerda para asegurarse de que no haya ningún punto allí.
- Si el cheque devuelve verdadero, entonces se agrega un pequeño impulso de fuerza a la babosa para que rebote sobre el suelo, se ropeJoint(DistanceJoint2D), que se establece en la distancia igual a la distancia entre la babosa y el punto de impacto de la emisión de rayos. También se incluye un sprite de punto de anclaje.
- Si raycast no golpea nada, entonces el renderizador de línea y ropeJoint están deshabilitados, y el indicador ropeAttachedes falso.
- Si se presiona el botón derecho del mouse, se ResetRope()métodoResetRope(), que deshabilita y restablece todos los parámetros relacionados con la cuerda / gancho a los valores que deberían ser si no se usa el gancho.
En la parte inferior de nuestro método de 
Update , agregue una llamada al nuevo método 
HandleInput() y 
aimDirection valor 
aimDirection :
 HandleInput(aimDirection); 
Guarde los cambios en 
RopeSystem.cs y regrese al editor.
Agregar una soga
Nuestra babosa no podrá volar por el aire sin una cuerda, por lo que es hora de darle algo que sea una representación visual de la cuerda y que tenga la capacidad de "girar" en las esquinas.
El renderizador de líneas es ideal para esto, ya que nos permite transferir el número de puntos y su posición en el espacio del mundo.
La idea aquí es que siempre almacenamos el primer vértice de la cuerda (0) en la posición del jugador, y todos los demás vértices se colocan dinámicamente cuando la cuerda debe envolverse alrededor de algo, incluida la posición actual de la bisagra, que es el siguiente punto a lo largo de la cuerda. del jugador
Seleccione 
Player y agregue el componente 
LineRenderer . Establezca 
Ancho en 
0.075 . Expanda la lista 
Materiales y, como 
Elemento 0, seleccione el material Material de 
cuerda ubicado en la carpeta 
Materiales del proyecto. Finalmente, para el Renderizador de línea, para el 
Modo de textura, seleccione 
Distribuir por segmento .
Arrastre el componente Renderizador de línea al campo 
Renderizador de 
cuerda del componente 
Sistema de cuerda .
Haga clic en la lista desplegable de 
Máscara de capa de 
cuerda y seleccione como capas con las que pueden interactuar Raycast 
Default, Rope y Pivot . Debido a esto, cuando se "dispara" una emisión de rayos, colisionará solo con estas capas, pero no con otros objetos, como un jugador.
Si comienzas el juego ahora, notarás un comportamiento extraño. Cuando apuntamos a una piedra sobre la cabeza de la babosa y disparamos con un gancho, obtenemos un pequeño salto hacia arriba, después de lo cual nuestro amigo comienza a comportarse de manera bastante aleatoria.
Todavía no hemos establecido la distancia para la unión de distancia, además, los vértices de la representación de línea no están configurados. Por lo tanto, no vemos la cuerda, y dado que la junta de distancia está directamente por encima de la posición de la bala, el valor actual de la distancia de la junta de distancia la empuja hacia las piedras debajo de ella.
Pero no se preocupe, ahora resolveremos este problema.
En el script 
RopeSystem.cs, agregue un nuevo operador al comienzo de la clase:
 using System.Linq; 
Esto nos permite usar consultas LINQ, que en nuestro caso simplemente nos permiten encontrar convenientemente el primer o el último elemento de la lista 
ropePositions .
Nota : Language-Integrated Query (LINQ) es el nombre de un conjunto de tecnologías basadas en la incorporación de capacidades de consulta directamente en C #. Puedes leer más sobre esto aquí .
Agregue una nueva variable privada bool llamada 
distanceSet debajo de las otras variables:
 private bool distanceSet; 
Utilizaremos esta variable como una bandera para que el guión pueda reconocer que la distancia de la cuerda (para el punto entre el jugador y el punto de referencia actual donde está conectado el gancho de gato) está configurada correctamente.
Ahora agregue un nuevo método que usaremos para establecer las posiciones de los vértices de la cuerda para representar la línea y establecer la distancia de la articulación de distancia en la lista almacenada de posiciones con cuerda ( 
ropePositions ):
 private void UpdateRopePositions() {  
Explique el código que se muestra arriba:
- Salga del método si la cuerda no está unida.
- Asignamos el valor de los puntos de renderizado de la línea de la cuerda al número de posiciones almacenadas en las posiciones de la ropePositions, más 1 más (para la posición del jugador).
- Hacemos un bucle alrededor de la lista de ropePositionsyropePositionspara cada posición (excepto la última), asignamos la posición del vértice del renderizador de línea al valor de la posición Vector2 almacenada por el índice de bucle en la lista deropePositions.
- Asigne al punto de anclaje de la cuerda el segundo desde la posición final de la cuerda, en la que debería estar el punto de anclaje / bisagra actual, o si solo tenemos una posición de la cuerda, entonces conviértalo en el punto de anclaje. Entonces, establecemos la distancia ropeJointigual a la distancia entre el jugador y la posición actual de la cuerda, que damos laropeJointen el bucle.
- La construcción if maneja el caso donde la posición actual de la cuerda en el bucle es la segunda desde el final; es decir, el punto en el que la cuerda se conecta al objeto, es decir bisagra / punto de anclaje actual.
- Este elsebloque maneja la asignación de la posición del último vértice de la cuerda al valor de la posición actual del jugador.
Recuerde agregar la llamada 
UpdateRopePositions() al final de 
Update() UpdateRopePositions() :
 UpdateRopePositions(); 
Guarde los cambios en el script y vuelva a ejecutar el juego. Haga un "pequeño espacio" un pequeño salto apuntando y disparando con un gancho a una piedra sobre el personaje. Ahora puede disfrutar de los frutos de su trabajo: la babosa se balancea tranquilamente sobre las piedras.
Ahora puede ir a la ventana de escena, seleccionar Jugador, usar la herramienta de movimiento (por defecto, la tecla 
W ) para moverlo y ver cómo los dos vértices de la línea de la cuerda siguen la posición del gancho y la posición del jugador para dibujar la cuerda. Después de liberar el reproductor, DistanceJoint2D calcula correctamente la distancia y la bala continuará balanceándose en la bisagra conectada.
Manejo de puntos de envoltura
Jugar con una babosa oscilante no es más útil que una toalla repelente al agua, por lo que definitivamente debemos complementarlo.
La buena noticia es que el método recientemente agregado para procesar las posiciones de la cuerda se puede utilizar en el futuro. Hasta ahora estamos usando solo dos posiciones de cuerda. Uno está conectado a la posición del jugador, y el segundo a la posición actual del punto de anclaje del gancho cuando se le dispara.
El único problema es que, si bien no rastreamos todas las posiciones potenciales de la cuerda, esto requiere un poco de trabajo.
Para reconocer las posiciones en las piedras alrededor de las cuales debe envolverse la cuerda, agregando una nueva posición de vértice al renderizador de línea, necesitamos un sistema que determine si el punto de vértice del colisionador está entre la línea recta entre la posición actual de la bala y el punto de anclaje / bisagra actual de la cuerda.
¡Parece que esto es un trabajo nuevo para el buen y viejo Raycast!
Primero, necesitamos crear un método que pueda encontrar el punto más cercano en el colisionador basado en el punto de impacto de la emisión de rayos y los bordes del colisionador.
Agregue un nuevo método al script 
RopeSystem.cs :
 
Si no está familiarizado con las consultas LINQ, entonces este código puede parecer una especie de magia C # complicada.
Si es así, entonces no tengas miedo. LINQ hace mucho trabajo por nosotros:
- Este método toma dos parámetros: el objeto RaycastHit2D y el objeto PolygonCollider2D . Todas las piedras en el nivel tienen colisionadores PolygonCollider2D, por lo que si siempre usamos formas PolygonCollider2D, funcionará bien.
- ¡Aquí es donde comienza la magia de consulta LINQ! Aquí transformamos la colección de puntos del colisionador poligonal en un diccionario de posiciones Vector2 (el valor de cada elemento del diccionario es la posición misma), y a la clave de cada elemento se le asigna el valor de la distancia desde este punto al jugador de posición del jugador (valor flotante). A veces sucede algo más aquí: la posición resultante se convierte en espacio mundial (de forma predeterminada, las posiciones de los vértices del colisionador se almacenan en el espacio local, es decir, local en relación con el objeto al que pertenece el colisionador, y necesitamos posiciones en el espacio mundial).
- El diccionario está ordenado por clave. En otras palabras, por la distancia más cercana a la posición actual del jugador. ¡Se devuelve la distancia más cercana, es decir, cualquier punto devuelto por este método es el punto de colisión entre el jugador y el punto actual de la bisagra de la cuerda!
Volvamos al script 
RopeSystem.cs y agreguemos una nueva variable de campo privado en la parte superior:
 private Dictionary<Vector2, int> wrapPointsLookup = new Dictionary<Vector2, int>(); 
Lo usaremos para rastrear las posiciones alrededor de las cuales se puede enrollar la cuerda.
Al final del método 
Update() , busque la construcción 
else que contiene 
crosshairSprite.enabled = false; y agregue lo siguiente:
 
Explica este fragmento de código:
- Si algunas posiciones se almacenan en la lista de ropePositions, entonces ...
- Disparamos desde la posición del jugador en la dirección del jugador que está mirando la última posición de la cuerda de la lista, el punto de anclaje en el que se une el gancho de gato a la piedra, con una distancia de emisión de rayos igual a la distancia entre el jugador y la posición del punto de anclaje de la cuerda.
- Si Raycast choca con algo, entonces el colisionador de este objeto se convierte de forma segura en el tipo PolygonCollider2D . Si bien es un verdadero PolygonCollider2D, la posición de vértice más cercana de este colisionador se devuelve utilizando el método que escribimos anteriormente como Vector2 .
- Se comprueba mediante wrapPointsLookuppara asegurarse de que la misma posición no se vuelva a comprobar. Si está marcada, descartamos la cuerda y la cortamos, dejando caer al jugador.
- Luego, se ropePositionslistaropePositions: se agrega la posición alrededor de la cual la cuerda debe envolverse. El diccionariowrapPointsLookuptambién se actualiza. Finalmente, el indicadordistanceSetse restablece para que el métodoUpdateRopePositions()pueda redefinir la distancia de la cuerda con la nueva longitud de la cuerda y los segmentos.
En 
ResetRope() agregue la siguiente línea para que el diccionario 
wrapPointsLookup cada vez que un jugador desconecte una cuerda:
 wrapPointsLookup.Clear(); 
Guarda y lanza el juego. Dispara el gancho de gato en la piedra sobre la babosa y usa la herramienta Mover en la ventana de escena para mover la babosa sobre varias repisas de piedra.
¡Así es como enseñamos a la soga a envolver objetos!
Añadir capacidad de balanceo
La babosa que cuelga de la cuerda es bastante estática. Para solucionar esto, podemos agregar la habilidad de balanceo.
Para hacer esto, necesitamos obtener una posición perpendicular a la posición de balanceo hacia adelante (de lado), independientemente del ángulo en el que esté mirando.
Abra 
PlayerMovement.cs y agregue las siguientes dos variables públicas a la parte superior del script:
 public Vector2 ropeHook; public float swingForce = 4f; 
A la variable 
ropeHook se le asignará cualquier posición en la que el gancho de la cuerda se encuentre actualmente, y 
swingForce es el valor que usamos para agregar el movimiento de oscilación.
Reemplace el método 
FixedUpdate() uno nuevo:
 void FixedUpdate() { if (horizontalInput < 0f || horizontalInput > 0f) { animator.SetFloat("Speed", Mathf.Abs(horizontalInput)); playerSprite.flipX = horizontalInput < 0f; if (isSwinging) { animator.SetBool("IsSwinging", true);  
Los principales cambios aquí son que la bandera se verifica primero isSwingingpara que las acciones se realicen solo cuando la babosa cuelga de la cuerda, y también agregamos un perpendicular a la esquina de la babosa, indicando su punto de anclaje actual en la parte superior de la cuerda, pero perpendicular a la dirección de su oscilación.- Obtenemos el vector de dirección normalizado del jugador al punto de conexión del gancho.
- Dependiendo de si la bala se balancea hacia la izquierda o hacia la derecha, la dirección perpendicular se calcula usando playerToHookDirection. También se ha agregado una llamada de extracción de depuración para que pueda verla en el editor si lo desea.
Abra RopeSystem.cs y agregue lo siguiente en la parte superior del bloque else dentro delif(!ropeAttached)método Update(): playerMovement.isSwinging = true; playerMovement.ropeHook = ropePositions.Last(); 
En el bloque if del mismo diseño, if(!ropeAttached)agregue lo siguiente: playerMovement.isSwinging = false; 
Por lo tanto, informamos al script PlayerMovement que el jugador está balanceándose, y también determinamos la última posición (excepto la posición del jugador) de la cuerda, en otras palabras, el punto de anclaje de la cuerda. Esto es necesario para calcular el ángulo perpendicular que acabamos de agregar al script PlayerMovement.Esto es lo que parece si enciende los artilugios en un juego en ejecución y presiona A o D para girar hacia la izquierda / derecha:Agregar descenso de cuerda
Si bien no tenemos la capacidad de subir y bajar la cuerda. Aunque en la vida real, la babosa no podía levantarse y caer fácilmente a lo largo de la cuerda, pero este es un juego en el que puede pasar cualquier cosa, ¿verdad?En la parte superior del script RopeSystem, agregue dos nuevas variables de campo: public float climbSpeed = 3f; private bool isColliding; 
climbSpeedestablecerá la velocidad a la cual la bala puede moverse hacia arriba y hacia abajo de la cuerda, y isCollidingse usará como una bandera para determinar si la propiedad de unión de distancia de la cuerda de unión de distancia se puede aumentar o disminuir.Agregue este nuevo método: private void HandleRopeLength() {  
Este bloque if..elseiflee la entrada a lo largo del eje vertical (arriba / abajo o W / S en el teclado) y, teniendo en cuenta las banderas, ropeAttached iscCollidingaumenta o disminuye la distancia ropeJoint, creando el efecto de alargar o acortar la cuerda.Enganchamos este método, agregando su llamada al final Update(): HandleRopeLength(); 
También necesitamos una forma de establecer la bandera isColliding.Agregue los siguientes dos métodos al final del script: void OnTriggerStay2D(Collider2D colliderStay) { isColliding = true; } private void OnTriggerExit2D(Collider2D colliderOnExit) { isColliding = false; } 
Estos dos métodos son métodos nativos de la clase base de los scripts MonoBehaviour.Si Collider actualmente toca otro objeto físico en el juego, el método se disparará constantemente OnTriggerStay2D, asignando un isCollidingvalor a la bandera true. Esto significa que cuando la babosa toca la piedra, se le asigna un valor a la bandera isColliding true.El método se OnTriggerExit2Dactiva cuando un colisionador abandona el área de otro colisionador, configurando la bandera en falso.Recuerde: el método OnTriggerStay2Dpuede ser muy costoso desde el punto de vista computacional, así que úselo con cuidado.¿A dónde ir después?
Comienza el juego nuevamente y esta vez presiona las teclas de flecha o W / S para subir y bajar la cuerda.El proyecto terminado de esta parte del tutorial se puede descargar aquí .¡Hemos recorrido un largo camino, desde la babosa que no se balancea hasta el molusco gasterópodo sin caparazón acrobático!¡Aprendiste a crear un sistema de puntería que puede disparar un gancho de gato a cualquier objeto que tenga un colisionador, aferrarse a él y simultáneamente balancearse sobre él, girando con una cuerda dinámica alrededor de los bordes de los objetos! Buen trabajoSin embargo, aquí falta una función importante: la cuerda no puede "desenrollarse" cuando es necesario.En la segunda parte del tutorial resolveremos este problema.Pero si estás dispuesto a arriesgarte, ¿por qué no intentas hacerlo tú mismo? Puedes usar un diccionario para esto wrapPointsLookup.