Creando un gancho de gato en Unity. Parte 1

imagen

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 :

 // 1 public GameObject ropeHingeAnchor; public DistanceJoint2D ropeJoint; public Transform crosshair; public SpriteRenderer crosshairSprite; public PlayerMovement playerMovement; private bool ropeAttached; private Vector2 playerPosition; private Rigidbody2D ropeHingeAnchorRb; private SpriteRenderer ropeHingeAnchorSprite; void Awake() { // 2 ropeJoint.enabled = false; playerPosition = transform.position; ropeHingeAnchorRb = ropeHingeAnchor.GetComponent<Rigidbody2D>(); ropeHingeAnchorSprite = ropeHingeAnchor.GetComponent<SpriteRenderer>(); } void Update() { // 3 var worldMousePosition = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 0f)); var facingDirection = worldMousePosition - transform.position; var aimAngle = Mathf.Atan2(facingDirection.y, facingDirection.x); if (aimAngle < 0f) { aimAngle = Mathf.PI * 2 + aimAngle; } // 4 var aimDirection = Quaternion.Euler(0, 0, aimAngle * Mathf.Rad2Deg) * Vector2.right; // 5 playerPosition = transform.position; // 6 if (!ropeAttached) { } else { } } 

Analicemos cada parte en orden:

  1. Usamos estas variables para rastrear los diversos componentes con los que interactuará el script RopeSystem.
  2. El método Awake comienza al comienzo del juego y deshabilita ropeJoint (el componente DistanceJoint2D). También establece playerPosition en la posición actual del jugador.
  3. Esta es la parte más importante del ciclo principal Update() . Primero, obtenemos la posición del cursor del mouse en el mundo usando el ScreenToWorldPoint cámara ScreenToWorldPoint . 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 crear aimAngle , que es una representación del ángulo de puntería del cursor. El valor almacena un valor positivo en la construcción if.
  4. aimDirection es 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.
  5. La posición del jugador se controla mediante una variable conveniente que le permite no referirse constantemente a transform.Position .
  6. 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:

 // 1 private void HandleInput(Vector2 aimDirection) { if (Input.GetMouseButton(0)) { // 2 if (ropeAttached) return; ropeRenderer.enabled = true; var hit = Physics2D.Raycast(playerPosition, aimDirection, ropeMaxCastDistance, ropeLayerMask); // 3 if (hit.collider != null) { ropeAttached = true; if (!ropePositions.Contains(hit.point)) { // 4 //    ,    -  . transform.GetComponent<Rigidbody2D>().AddForce(new Vector2(0f, 2f), ForceMode2D.Impulse); ropePositions.Add(hit.point); ropeJoint.distance = Vector2.Distance(playerPosition, hit.point); ropeJoint.enabled = true; ropeHingeAnchorSprite.enabled = true; } } // 5 else { ropeRenderer.enabled = false; ropeAttached = false; ropeJoint.enabled = false; } } if (Input.GetMouseButton(1)) { ResetRope(); } } // 6 private void ResetRope() { ropeJoint.enabled = false; ropeAttached = false; playerMovement.isSwinging = false; ropeRenderer.positionCount = 2; ropeRenderer.SetPosition(0, transform.position); ropeRenderer.SetPosition(1, transform.position); ropePositions.Clear(); ropeHingeAnchorSprite.enabled = false; } 

Esto es lo que hace el código anterior:

  1. Se llama a HandleInput desde el bucle Update() y simplemente sondea la entrada de los botones izquierdo y derecho del mouse.
  2. 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.
  3. Si se detecta un impacto de emisión de rayos, entonces ropeAttached es true y se verifica una lista de las posiciones de los vértices de la cuerda para asegurarse de que no haya ningún punto allí.
  4. 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.
  5. Si raycast no golpea nada, entonces el renderizador de línea y ropeJoint están deshabilitados, y el indicador ropeAttached es falso.
  6. Si se presiona el botón derecho del mouse, se ResetRope() método ResetRope() , 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() { // 1 if (!ropeAttached) { return; } // 2 ropeRenderer.positionCount = ropePositions.Count + 1; // 3 for (var i = ropeRenderer.positionCount - 1; i >= 0; i--) { if (i != ropeRenderer.positionCount - 1) // if not the Last point of line renderer { ropeRenderer.SetPosition(i, ropePositions[i]); // 4 if (i == ropePositions.Count - 1 || ropePositions.Count == 1) { var ropePosition = ropePositions[ropePositions.Count - 1]; if (ropePositions.Count == 1) { ropeHingeAnchorRb.transform.position = ropePosition; if (!distanceSet) { ropeJoint.distance = Vector2.Distance(transform.position, ropePosition); distanceSet = true; } } else { ropeHingeAnchorRb.transform.position = ropePosition; if (!distanceSet) { ropeJoint.distance = Vector2.Distance(transform.position, ropePosition); distanceSet = true; } } } // 5 else if (i - 1 == ropePositions.IndexOf(ropePositions.Last())) { var ropePosition = ropePositions.Last(); ropeHingeAnchorRb.transform.position = ropePosition; if (!distanceSet) { ropeJoint.distance = Vector2.Distance(transform.position, ropePosition); distanceSet = true; } } } else { // 6 ropeRenderer.SetPosition(i, transform.position); } } } 

Explique el código que se muestra arriba:

  1. Salga del método si la cuerda no está unida.
  2. 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).
  3. Hacemos un bucle alrededor de la lista de ropePositions y ropePositions para 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 de ropePositions .
  4. 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 ropeJoint igual a la distancia entre el jugador y la posición actual de la cuerda, que damos la ropeJoint en el bucle.
  5. 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.
  6. Este else bloque 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 :

 // 1 private Vector2 GetClosestColliderPointFromRaycastHit(RaycastHit2D hit, PolygonCollider2D polyCollider) { // 2 var distanceDictionary = polyCollider.points.ToDictionary<Vector2, float, Vector2>( position => Vector2.Distance(hit.point, polyCollider.transform.TransformPoint(position)), position => polyCollider.transform.TransformPoint(position)); // 3 var orderedDictionary = distanceDictionary.OrderBy(e => e.Key); return orderedDictionary.Any() ? orderedDictionary.First().Value : Vector2.zero; } 

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:

  1. 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.
  2. ¡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).
  3. 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:

 // 1 if (ropePositions.Count > 0) { // 2 var lastRopePoint = ropePositions.Last(); var playerToCurrentNextHit = Physics2D.Raycast(playerPosition, (lastRopePoint - playerPosition).normalized, Vector2.Distance(playerPosition, lastRopePoint) - 0.1f, ropeLayerMask); // 3 if (playerToCurrentNextHit) { var colliderWithVertices = playerToCurrentNextHit.collider as PolygonCollider2D; if (colliderWithVertices != null) { var closestPointToHit = GetClosestColliderPointFromRaycastHit(playerToCurrentNextHit, colliderWithVertices); // 4 if (wrapPointsLookup.ContainsKey(closestPointToHit)) { ResetRope(); return; } // 5 ropePositions.Add(closestPointToHit); wrapPointsLookup.Add(closestPointToHit, 0); distanceSet = false; } } } 

Explica este fragmento de código:

  1. Si algunas posiciones se almacenan en la lista de ropePositions , entonces ...
  2. 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.
  3. 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 .
  4. Se comprueba mediante wrapPointsLookup para 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.
  5. Luego, se ropePositions lista ropePositions : se agrega la posición alrededor de la cual la cuerda debe envolverse. El diccionario wrapPointsLookup también se actualiza. Finalmente, el indicador distanceSet se restablece para que el método UpdateRopePositions() 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); // 1 -          var playerToHookDirection = (ropeHook - (Vector2)transform.position).normalized; // 2 -  ,     Vector2 perpendicularDirection; if (horizontalInput < 0) { perpendicularDirection = new Vector2(-playerToHookDirection.y, playerToHookDirection.x); var leftPerpPos = (Vector2)transform.position - perpendicularDirection * -2f; Debug.DrawLine(transform.position, leftPerpPos, Color.green, 0f); } else { perpendicularDirection = new Vector2(playerToHookDirection.y, -playerToHookDirection.x); var rightPerpPos = (Vector2)transform.position + perpendicularDirection * 2f; Debug.DrawLine(transform.position, rightPerpPos, Color.green, 0f); } var force = perpendicularDirection * swingForce; rBody.AddForce(force, ForceMode2D.Force); } else { animator.SetBool("IsSwinging", false); if (groundCheck) { var groundForce = speed * 2f; rBody.AddForce(new Vector2((horizontalInput * groundForce - rBody.velocity.x) * groundForce, 0)); rBody.velocity = new Vector2(rBody.velocity.x, rBody.velocity.y); } } } else { animator.SetBool("IsSwinging", false); animator.SetFloat("Speed", 0f); } if (!isSwinging) { if (!groundCheck) return; isJumping = jumpInput > 0f; if (isJumping) { rBody.velocity = new Vector2(rBody.velocity.x, jumpSpeed); } } } 

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.

  1. Obtenemos el vector de dirección normalizado del jugador al punto de conexión del gancho.
  2. 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 del if(!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() { // 1 if (Input.GetAxis("Vertical") >= 1f && ropeAttached && !isColliding) { ropeJoint.distance -= Time.deltaTime * climbSpeed; } else if (Input.GetAxis("Vertical") < 0f && ropeAttached) { ropeJoint.distance += Time.deltaTime * climbSpeed; } } 

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 trabajo


Sin 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.

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


All Articles