Logré lograr lo que necesitaba, principalmente con la ayuda de esta publicación blog para la pieza de la superficie del rompecabezas y se me ocurrieron mis propias ideas para el movimiento del jugador y la cámara.
Ajustar jugador a la superficie de un objeto
La configuración básica consiste en una esfera grande (el mundo) y una esfera más pequeña (el jugador), ambas con colisionadores de esferas unidas a ellas.
La mayor parte del trabajo realizado se realizó en los siguientes dos métodos:
private void UpdatePlayerTransform(Vector3 movementDirection)
{
RaycastHit hitInfo;
if (GetRaycastDownAtNewPosition(movementDirection, out hitInfo))
{
Quaternion targetRotation = Quaternion.FromToRotation(Vector3.up, hitInfo.normal);
Quaternion finalRotation = Quaternion.RotateTowards(transform.rotation, targetRotation, float.PositiveInfinity);
transform.rotation = finalRotation;
transform.position = hitInfo.point + hitInfo.normal * .5f;
}
}
private bool GetRaycastDownAtNewPosition(Vector3 movementDirection, out RaycastHit hitInfo)
{
Vector3 newPosition = transform.position;
Ray ray = new Ray(transform.position + movementDirection * Speed, -transform.up);
if (Physics.Raycast(ray, out hitInfo, float.PositiveInfinity, WorldLayerMask))
{
return true;
}
return false;
}
los Vector3 movementDirection
parámetro es tal como suena, la dirección en la que vamos a mover a nuestro jugador en este cuadro, y calcular ese vector, aunque terminó siendo relativamente simple en este ejemplo, fue un poco difícil de entender al principio. Más sobre eso más adelante, pero solo tenga en cuenta que es un vector normalizado en la dirección en que el jugador mueve este cuadro.
Al pasar, lo primero que hacemos es verificar si un rayo, que se origina en la hipotética posición futura dirigida hacia el vector hacia abajo del jugador (-transform.up), llega al mundo usando WorldLayerMask, que es una propiedad pública de LayerMask del script. Si desea colisiones más complejas o varias capas, tendrá que construir su propia máscara de capa. Si la emisión de rayos impacta con éxito en algo, hitInfo se utiliza para recuperar el punto normal y el punto de golpe para calcular la nueva posición y rotación del jugador que debe estar justo en el objeto. La compensación de la posición del jugador puede ser necesaria según el tamaño y el origen del objeto del jugador en cuestión.
Finalmente, esto realmente solo se ha probado y probablemente solo funcione bien en objetos simples como esferas. Como sugiere la publicación del blog en la que basé mi solución, es probable que desee realizar múltiples transmisiones de rayos y promediarlas para su posición y rotación para obtener una transición mucho más agradable al moverse sobre terrenos más complejos. También puede haber otras trampas que no he pensado en este momento.
Cámara y movimiento
Una vez que el jugador se pegaba a la superficie del objeto, la siguiente tarea a abordar era el movimiento. Originalmente había comenzado con el movimiento relativo al jugador, pero comencé a encontrar problemas en los polos de la esfera donde las direcciones cambiaron repentinamente, lo que hizo que mi jugador cambiara rápidamente de dirección una y otra vez sin dejarme pasar nunca por los polos. Lo que terminé haciendo fue hacer que mis jugadores se movieran en relación con la cámara.
Lo que funcionó bien para mis necesidades fue tener una cámara que siguiera estrictamente al jugador basándose únicamente en la posición del jugador. Como resultado, a pesar de que la cámara estaba rotando técnicamente, presionar hacia arriba siempre movía al jugador hacia la parte superior de la pantalla, hacia abajo, hacia abajo, y así sucesivamente con la izquierda y la derecha.
Para hacer esto, se ejecutó lo siguiente en la cámara donde el objeto objetivo era el jugador:
private void FixedUpdate()
{
// Calculate and set camera position
Vector3 desiredPosition = this.target.TransformPoint(0, this.height, -this.distance);
this.transform.position = Vector3.Lerp(this.transform.position, desiredPosition, Time.deltaTime * this.damping);
// Calculate and set camera rotation
Quaternion desiredRotation = Quaternion.LookRotation(this.target.position - this.transform.position, this.target.up);
this.transform.rotation = Quaternion.Slerp(this.transform.rotation, desiredRotation, Time.deltaTime * this.rotationDamping);
}
Finalmente, para mover el reproductor, aprovechamos la transformación de la cámara principal para que con nuestros controles hacia arriba, hacia abajo, hacia abajo, etc. Y aquí es donde llamamos UpdatePlayerTransform, que hará que nuestra posición se ajuste al objeto del mundo.
void Update ()
{
Vector3 movementDirection = Vector3.zero;
if (Input.GetAxisRaw("Vertical") > 0)
{
movementDirection += cameraTransform.up;
}
else if (Input.GetAxisRaw("Vertical") < 0)
{
movementDirection += -cameraTransform.up;
}
if (Input.GetAxisRaw("Horizontal") > 0)
{
movementDirection += cameraTransform.right;
}
else if (Input.GetAxisRaw("Horizontal") < 0)
{
movementDirection += -cameraTransform.right;
}
movementDirection.Normalize();
UpdatePlayerTransform(movementDirection);
}
Para implementar una cámara más interesante, pero los controles deben ser casi los mismos que los que tenemos aquí, puede implementar fácilmente una cámara que no esté renderizada o simplemente otro objeto ficticio para basar el movimiento y luego usar la cámara más interesante para representar lo que quieres que se vea el juego. Esto permitirá transiciones agradables de la cámara a medida que recorres objetos sin romper los controles.