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
Awake
comienza al comienzo del juego y deshabilita ropeJoint
(el componente DistanceJoint2D). También establece playerPosition
en 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 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. 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.- 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
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í. - 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
ropeAttached
es falso. - 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() {
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
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
. - 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. - 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
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 :
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
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. - 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);
Los principales cambios aquí son que la bandera se verifica primero isSwinging
para 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 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;
climbSpeed
establecerá la velocidad a la cual la bala puede moverse hacia arriba y hacia abajo de la cuerda, y isColliding
se 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..elseif
lee la entrada a lo largo del eje vertical (arriba / abajo o W / S en el teclado) y, teniendo en cuenta las banderas, ropeAttached iscColliding
aumenta 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 isColliding
valor 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 OnTriggerExit2D
activa cuando un colisionador abandona el área de otro colisionador, configurando la bandera en falso.Recuerde: el método OnTriggerStay2D
puede 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
.