El objetivo de este documento es guiarlo a usted para configurar la navegación de personajes humanoides para moverse utilizando el sistema de navegación.
Nosotros estamos utilizando los sistemas integrados de Unity para la animación y navegación con un scripting personalizado para lograr esto.
Se asume que usted es familiar con lo básico de Unity y el sistema de animación Mecanim.
Un proyecto ejemplo está disponible - para que usted no tenga que agregar scripts o configurar las animaciones y el animation controller desde cero:
Para obtener un animation controller responsivo y versatil - que cubra un rango amplio de movimientos - nosotros necesitamos configurar un conjunto de animaciones utilizando diferentes direcciones. Esto a veces se refiere como strafe-set.
Adicionalmente a la animaciones de movimiento,nosotros necesitamos una animación para el personaje parado.
Nosotros procedemos al arreglar el strafe-set en un 2D blend tree (árbol de mezcla 2D) - escoja el tipo de blend (mezcla): 2D Simple Directional y coloca las animaciones utilizando Compute Positions > Velocity XZ
Para el blending control (control de mezcla) nosotros agregamos dos parámetros float velx y vely, y los asignamos al blend tree (árbol de mezcla).
Aquí nosotros estamos colocando 7 animaciones corriendo - cada uno con una velocidad diferente. En adición de ir hacia adelante (+ izquierda/derecha) hacia atrás (+ izquierda/derecha) nosotros también utilizamos un clip de animación para correr en el lugar. El latter es resaltado en el centro del mapa 2D blend abajo. La razón para tener una animación corriendo en el lugar es dos, primero preserva el estilo de correr cuando se mezcle con otras animaciones. Segundo la animación previene el deslizamiento cuando se mezcla.
Luego nosotros agregamos el clip de animación inactivo en su propio nodo (Idle). Nosotros ahora tenemos dos estados de animación discretos que agrupamos con 2 transiciones.
Para controlar el cambio entre los estados inactivos y moviendo nosotros agregamos un parámetro de control boolean move. Luego desactivamos la propiedad Has Exit Time en las transiciones.
Ahora coloque el nuevo animation controller creado en el personaje que usted quiera mover.
Presione Play (reproducir) y seleccione el personaje en la Hierarchy window. Usted puede ahora manualmente controlar los valores de la animación en la Animator window y cambiar el estado de movimiento y velocidad.
El siguiente paso es crear otros medios de controlar los parámetros de animación.
Coloque un componente NavMeshAgent en el personaje y ajuste el radius (radio), height (altura) para que coincida con el personaje - adicionalmente cambie la propiedad speed (velocidad) para que coincida con la velocidad máxima en el animation blend tree (árbol de mezcla de animación).
Cree un navmesh para la escena en dónde ha colocado el personaje.
Luego nosotros necesitamos decirle al personaje a dónde navegar. Esto típicamente es muy especifico a la aplicación. Aquí nosotros escogemos un comportamiento click para moverse- el personaje movido al punto del mundo dónde el usuario ha hecho clicked en la pantalla.
// ClickToMove.cs
using UnityEngine;
[RequireComponent (typeof (NavMeshAgent))]
public class ClickToMove : MonoBehaviour {
RaycastHit hitInfo = new RaycastHit();
NavMeshAgent agent;
void Start () {
agent = GetComponent<NavMeshAgent> ();
}
void Update () {
if(Input.GetMouseButtonDown(0)) {
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray.origin, ray.direction, out hitInfo))
agent.destination = hitInfo.point;
}
}
}
Al presionar play (reproducir) ahora - y hacer click alrededor de la escena - usted verá mover el personaje alrededor en la escena. Sin embargo . las animaciones no coinciden con el movimiento en absoluto. Nosotros necesitamos comunicar el estado y velocidad del agente al animation controller.
Para transferir la velocidad y la información del estado del agente al animation controller nosotros vamos a agregar otro script.
// LocomotionSimpleAgent.cs
using UnityEngine;
[RequireComponent (typeof (NavMeshAgent))]
[RequireComponent (typeof (Animator))]
public class LocomotionSimpleAgent : MonoBehaviour {
Animator anim;
NavMeshAgent agent;
Vector2 smoothDeltaPosition = Vector2.zero;
Vector2 velocity = Vector2.zero;
void Start ()
{
anim = GetComponent<Animator> ();
agent = GetComponent<NavMeshAgent> ();
// Don’t update position automatically
agent.updatePosition = false;
}
void Update ()
{
Vector3 worldDeltaPosition = agent.nextPosition - transform.position;
// Map 'worldDeltaPosition' to local space
float dx = Vector3.Dot (transform.right, worldDeltaPosition);
float dy = Vector3.Dot (transform.forward, worldDeltaPosition);
Vector2 deltaPosition = new Vector2 (dx, dy);
// Low-pass filter the deltaMove
float smooth = Mathf.Min(1.0f, Time.deltaTime/0.15f);
smoothDeltaPosition = Vector2.Lerp (smoothDeltaPosition, deltaPosition, smooth);
// Update velocity if time advances
if (Time.deltaTime > 1e-5f)
velocity = smoothDeltaPosition / Time.deltaTime;
bool shouldMove = velocity.magnitude > 0.5f && agent.remainingDistance > agent.radius;
// Update animation parameters
anim.SetBool("move", shouldMove);
anim.SetFloat ("velx", velocity.x);
anim.SetFloat ("vely", velocity.y);
GetComponent<LookAt>().lookAtTargetPosition = agent.steeringTarget + transform.forward;
}
void OnAnimatorMove ()
{
// Update position to agent position
transform.position = agent.nextPosition;
}
}
Este script merece una pequeña explicación. Es colocada en el personaje - que tiene un componente Animator y un NavMeshAgent adjunto - al igual que script click to move (haga click para moverse) de arriba.
Primero el script le dice al agente no actualizar la posición del personaje automáticamente.
La mezcla de animación es controlada al leer la velocidad del agente. Es transformada a una velocidad relativa (basada en la orientación del personaje) - y luego suavizada. Los componentes transformed horizontal velocity luego son pasados al Animator y adicionalmente el estado entre cambiar entre inactivo y moviéndose es controlada por la speed (velocidad) (i.e. magnitud de la velocidad)
En el callback OnAnimatorMove()
nosotros actualizamos la posición del personaje para que coincida con el NavMeshAgent.
Reproduciendo la escena nuevamente nos muestra que la animación coincide el movimiento tan cerca como sea posible.
Para mejorar la calidad de lo animado y del personaje navegando nosotros vamos a explorar un par de opciones.
Hacer que el personaje se parezca y se voltee hacia los puntos de interés es importante para transmitir la atención y la anticipación. Nosotros utilizaremos el api del sistema de animación. Esto llama para otro script.
// LookAt.cs
using UnityEngine;
using System.Collections;
[RequireComponent (typeof (Animator))]
public class LookAt : MonoBehaviour {
public Transform head = null;
public Vector3 lookAtTargetPosition;
public float lookAtCoolTime = 0.2f;
public float lookAtHeatTime = 0.2f;
public bool looking = true;
private Vector3 lookAtPosition;
private Animator animator;
private float lookAtWeight = 0.0f;
void Start ()
{
if (!head)
{
Debug.LogError("No head transform - LookAt disabled");
enabled = false;
return;
}
animator = GetComponent<Animator> ();
lookAtTargetPosition = head.position + transform.forward;
lookAtPosition = lookAtTargetPosition;
}
void OnAnimatorIK ()
{
lookAtTargetPosition.y = head.position.y;
float lookAtTargetWeight = looking ? 1.0f : 0.0f;
Vector3 curDir = lookAtPosition - head.position;
Vector3 futDir = lookAtTargetPosition - head.position;
curDir = Vector3.RotateTowards(curDir, futDir, 6.28f*Time.deltaTime, float.PositiveInfinity);
lookAtPosition = head.position + curDir;
float blendTime = lookAtTargetWeight > lookAtWeight ? lookAtHeatTime : lookAtCoolTime;
lookAtWeight = Mathf.MoveTowards (lookAtWeight, lookAtTargetWeight, Time.deltaTime/blendTime);
animator.SetLookAtWeight (lookAtWeight, 0.2f, 0.5f, 0.7f, 0.5f);
animator.SetLookAtPosition (lookAtPosition);
}
}
Agregue el script al personaje y asigne la propiedad head (cabeza) al transform en cabeza en la jerarquía de transform de su personaje. El LookAt script no tiene noción del control de navegación - por lo que controlar a dónde mirar nosotros nos devolvemos al script LocomotionSimpleAgent.cs y agregamos un par de lineas para controlar el aspecto. Agregue el final de Update()
agregue:
LookAt lookAt = GetComponent<LookAt> ();
if (lookAt)
lookAt.lookAtTargetPosition = agent.steeringTarget + transform.forward;
Esto le dirá al script LookAt en configurar el punto de interés a aproximadamente la siguiente esquina a lo largo del camino o - si no hay esquinas - al final del camino:
Inténtelo.
El personaje hasta el momento ha sido controlado completamente por la posición dictada por el agente. Esto asegura que la avoidance (evasión) de otros personajes y obstáculos se traduzca directamente a la posición del personaje. Sin embargo, puede llegar a un deslizamiento de pie si la animación no cubre la velocidad propuesta. Aquí nosotros nos vamos a relajar la restricción del personaje un poco. Básicamente nosotros cambiamos la avoidance quality (calidad de evasión) por una animation quality (calidad de animación).
Remplace el callback OnAnimatorMove()
en el script LocomotionSimpleAgent.cs remplace la linea con la siguiente
void OnAnimatorMove ()
{
// Update position based on animation movement using navigation surface height
Vector3 position = anim.rootPosition;
position.y = agent.nextPosition.y;
transform.position = position;
}
Cuando intente esto, usted podría notar que el personaje ahora se aleja de la posición del agente (un cilindro cubierto por alambre verde). Usted podría necesitar limitar la derivación de animación del personaje (animation drift). Esto se puede hacer ya sea al tirar el agente hacia el personaje - o tirar el personaje hacia la posición del agente . Agregue lo siguiente al final del método Update()
en el script LocomotionSimpleAgent.cs.
// Pull character towards agent
if (worldDeltaPosition.magnitude > agent.radius)
transform.position = agent.nextPosition - 0.9f*worldDeltaPosition;
O - si usted quiere que el agente siga al personaje.
// Pull agent towards character
if (worldDeltaPosition.magnitude > agent.radius)
agent.nextPosition = transform.position + 0.9f*worldDeltaPosition;
Lo que funciona mejor depende en el caso de uso especifico.
Nosotros hemos configurado un personaje que se mueve utilizando el sistema de navegación y se anima de acuerdo a esto. Ajustando los números del blend time (tiempo de mezcla), weights (pesos) de look-at etc. pueden mejorar el aspecto - y es una buena manera de explorar aun más esta configuración.