En este post haremos que el movimiento de nuestro personaje se asemeje más a un juego de aventuras de modo que se desplace con respecto a la posición de la cámara.
Antes de nada por el momento desactivaremos el script de nuestra cámara y la colocaremos en un sitio de modo que enfoque todo nuestro nivel.
Ahora empezaremos por conseguir la posición de la cámara mediante su componente Transform. Para ello crearemos una variable del tipo Transform a la que llamaremos cameraTransform (Transform cameraTransform;
). Ahora en Start almacenaremos en esta variable la posición de nuestra cámara principal. Accediendo a la variable main de la clase Camera obtendremos directamente la primera cámara etiquetada como MainCamera. Como queremos únicamente su posición añadiremos .transform . El código sería el siguiente:
cameraTransform = Camera.main.transform;
A continuación eliminaremos o comentaremos parte de nuestro código, ya que deberemos rehacerlo. Para comentar varias líneas consecutivas podemos poner ‘/*` donde queremos empezar y poner ‘*/’ donde queremos acabar.
Ya en Update, crearemos una variable tipo Vector3 a la que llamaremos horizontalInput y la igualaremos a un new Vector3 que se compondrá de la entrada de Horizontal para el eje X, 0 para el eje Y y la entrada de Vertical para el eje Z : Vector3 horizontalInput = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
El valor máximo de Horizontal y Vertical será 1. Sin embargo si ambos son iguales a 1 la longitud del vector resultante será mayor que 1, por lo que nuestro personaje se desplazaría a mayor velocidad. Para evitar esto comprobaremos mediante la variable magnitude de la clase Vector3 si la longitud del vector es mayor que 1 y, de ser así, usaremos la variable normalized de la clase Vector3 que almacena el vector normalizado (con longitud igual a 1). El código resultante será el siguiente:
Vector3 horizontalInput = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
if (horizontalInput.magnitude > 1)
horizontalInput.Normalize ();
Necesitaremos trabajar sobre la variable horizontalInput, pero también la necesitamos sin modificar posteriormente. Por este motivo crearemos otro Vector3 al que llamaremos targetHorizontalInput al que igualaremos a horizontalInput : Vector3 targetHorizontalMovement = horizontalInput;
Lo siguiente que haremos será emplear la función TransformDirection de la clase Transform para adaptar las coordenadas locales de targetHorizontalMovement a las coordanadas globales de nuestra escena. Básicamente lo que hará será rotar el vector con respecto la posición de nuestra cámara:
targetHorizontalMovement = cameraTransform.TransformDirection (targetHorizontalMovement);
Otra manera de hacer lo mismo sería multiplicando por el Quaternion que representa la rotación de la cámara:
targetHorizontalMovement = cameraTransform.rotation * targetHorizontalMovement;
A continuación asignaremos a nuestro vector de movimiento currentMovement el valor de targetHorizontalMovement multiplicado por la velocidad a la que queremos que se desplace nuestro personaje (moveSpeed) :
currentMovement = targetHorizontalMovement * moveSpeed;
Si probamos cómo se comporta nuestro personaje ahora mismo veremos que se desplazará a izquierda y derecha respecto a la posición de la cámara. Del mismo modo irá adelante y atrás con respecto a la cámara, por lo que si la cámara está inclinada nuestro personaje también se moverá en el eje Y (arriba y abajo) al realizar este tipo de movimiento. Para evitar esto añadiremos previamente una asignación a targetHorizontalMovement para que su componente Y sea siempre 0 :
targetHorizontalMovement.y = 0;
Seguiremos teniendo un problema. La velocidad del personaje para el movimiento adelante-atrás se verá reducida ya que el vector que lo constituye de longitud igual a 1 perderá parte de su valor al eliminar uno de los componentes que lo forman, eje Y, al que igualamos a 0. El vector adquiere esta distribución de su valor en el eje Y después de hacer la conversión mediante la función TransformDirection. Esta pérdida será mayor cuanta más inclinación tenga la cámara. Deberemos normalizar de nuevo el vector para que vuelva a tener una velocidad normal y estable en todas las direcciones en las que se moverá con respecto a la posición de la cámara:
targetHorizontalMovement.Normalize ();
Si bien hemos solucionado nuestro problema, también hemos creado otro. Ahora no importa cual sea el valor de nuestro movimiento para nuestros ejes de entrada (Horizontal y Vertical), debido a esta normalización siempre será 1 ó 0. Si estuviésemos usando un joystick no habría diferencia entre moverlo un poco o moverlo hasta el punto máximo que nos permitiese. Para solucionar esto recurriremos al valor original que nos proporcionará horizontalInput y lo multiplicaremos por targetHorizontalMovement. De este modo, si por ejemplo la entrada original tiene un valor de 0,1, al ser multiplicado por 1 el nuevo valor de horizontalInput será 0,1 :
targetHorizontalMovement *= horizontalInput.magnitude;
El fallo a solucionar ahora es que nuestro personaje es incapaz de saltar aun si le damos al botón de salto (barra espaciadora). Esto se debe a que en cada frame actualizamos el valor del eje Y a 0. Para solucionar esto crearemos fuera de Update una variable de tipo float a la que llamaremos verticalSpeed (float verticalSpeed;
) que nos servirá para almacenar el valor del eje Y de nuestro personaje en lo que respecta a la gravedad y el salto. Por lo tanto, en la parte de nuestro código donde trabajamos con esto sustituiremos currentMovement.y por verticalSpeed. Justo después añadiremos una línea donde asignaremos el valor de verticalSpeed a currentMovement.y .
if (!controller.isGrounded)
verticalSpeed -= gravity * Time.deltaTime;
else
verticalSpeed = 0;
if (controller.isGrounded && Input.GetButtonDown ("Jump"))
verticalSpeed = jumpSpeed;
currentMovement.y = verticalSpeed;
Si deseamos añadir al movimiento de nuestro personaje la fluidez por nuestra cuenta, al margen de la propia de Input, empezaremos por sustituir GetAxis por GetAxisRaw y seguiremos los pasos ya vistos en post previos usando, por ejemplo, la función SmoothDamp.
Nuestro código final quedará del siguiente modo:
using UnityEngine;
using System.Collections;
public class PlayerScript : MonoBehaviour {
public float moveSpeed = 5;
public float rotateSpeed = 180;
public float jumpSpeed = 20;
public float gravity = 9.8f;
public float moveSpeedSmooth = 0.3f;
public float rotateSpeedSmooth = 0.3f;
float currentForwardSpeed;
float forwardSpeedV;
float targetRotation;
float currentRotation;
float rotationV;
CharacterController controller;
Vector3 currentMovement;
Transform cameraTransform;
float verticalSpeed;
void Start () {
controller = GetComponent<CharacterController> ();
cameraTransform = Camera.main.transform;
}
void Update () {
/*
targetRotation += Input.GetAxisRaw ("Horizontal") * rotateSpeed * Time.deltaTime;
if (targetRotation > 360)
targetRotation -= 360;
if (targetRotation < 0)
targetRotation += 360;
currentRotation = Mathf.SmoothDampAngle (currentRotation, targetRotation, ref rotationV, rotateSpeedSmooth);
transform.eulerAngles = new Vector3 (0, currentRotation, 0);
currentForwardSpeed = Mathf.SmoothDamp (currentForwardSpeed, Input.GetAxisRaw ("Vertical") * moveSpeed, ref forwardSpeedV, moveSpeedSmooth);
currentMovement = new Vector3 (0, currentMovement.y, currentForwardSpeed);
currentMovement = transform.rotation * currentMovement;
*/
Vector3 horizontalInput = new Vector3 (Input.GetAxis ("Horizontal"), 0, Input.GetAxis ("Vertical"));
if (horizontalInput.magnitude > 1)
horizontalInput.Normalize ();
Vector3 targetHorizontalMovement = horizontalInput;
targetHorizontalMovement = cameraTransform.TransformDirection (targetHorizontalMovement);
//targetHorizontalMovement = cameraTransform.rotation * targetHorizontalMovement;
targetHorizontalMovement.y = 0;
targetHorizontalMovement.Normalize ();
targetHorizontalMovement *= horizontalInput.magnitude;
currentMovement = targetHorizontalMovement * moveSpeed;
if (!controller.isGrounded)
verticalSpeed -= gravity * Time.deltaTime;
else
verticalSpeed = 0;
if (controller.isGrounded && Input.GetButtonDown ("Jump"))
verticalSpeed = jumpSpeed;
currentMovement.y = verticalSpeed;
controller.Move (currentMovement * Time.deltaTime);
}
}