Currently, I am trying to make my game camera smoothly move from 3rd person view to top down view such as this poorly drawn image.
I am having issues getting the camera to end in the proper rotation and position. Due to my lackluster mathematics skills and lack of knowledge in any form of animation, my script might seem a bit messy as I have been trying everything that came to mind.
Currently, the camera makes it almost to the desired location/rotation which I think it caused do to a lack of a better way of telling the camera when it hit its destination.
As a side note, before questions are asked, I am using Time.realtimeSinceStartup because the game is supposed to pause when there is not an input.
This is my script(try not to cringe):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraAnimation : MonoBehaviour {
#region variables
public float topdownHeight = 10;
public float speed;
private float startTime;
private float timer = 0f;
private bool isRunning = false;
Vector3 endPoint;
private Vector3 distance;
public enum FocusState
{
ABILITYTOPDOWN,
SELECTEDPLAYER,
OFF
}
[HideInInspector]
public FocusState focusState = FocusState.OFF;
[HideInInspector]
public GameObject selectedPlayer;
#endregion variables
void Update () {
switch (focusState)
{
case (FocusState.ABILITYTOPDOWN):
timer = Time.realtimeSinceStartup;
TopDown();
break;
case (FocusState.SELECTEDPLAYER):
break;
case (FocusState.OFF):
break;
}
}
private void TopDown()
{
if (!isRunning)
{
Debug.Log("Running...");
startTime = Time.realtimeSinceStartup;
Debug.Log(startTime);
Debug.Log("Start Pos: " + transform.position);
endPoint = new Vector3(transform.position.x, transform.position.y + topdownHeight, transform.position.z);
Debug.Log("End Pos: " + endPoint);
isRunning = true;
distance = endPoint - selectedPlayer.transform.position/speed;
}
if(Mathf.Abs(transform.position.y) >= Mathf.Abs(endPoint.y) && Mathf.Abs(transform.position.x) >=
Mathf.Abs(endPoint.x) && Mathf.Abs(transform.position.z) >= Mathf.Abs(endPoint.z) && gameObject.transform.localRotation.x == 90)
//Vector3.Distance(transform.position, endPoint) <= 1)
{
focusState = FocusState.OFF;
timer = 0;
isRunning = false;
}
else
{
Debug.Log(timer);
gameObject.transform.position = new Vector3(transform.position.x + (distance.x*(timer-startTime)),
transform.position.y + (distance.y * (timer - startTime)), transform.position.z+(distance.z * (timer - startTime)));
Debug.Log("moved camera up by " + (Time.deltaTime - startTime) * speed);
transform.LookAt(selectedPlayer.transform.position);
}
}
}
If the end position is just above the object (character?) I might try using Vector3.Slerp (Unity - Scripting API: Vector3.Slerp) inside a coroutine.
Oh, that’s just for its position, btw. You can try setting LookAt as it goes, or at the end and see if that’s good…
I have tried following the slerp reference page but I still get the same issues of the camera not ending in the correct positon. As far as the rotation, that should fix itself once the position is figured out. I am also trying to move the code into coroutines to make it easier to link up with the UI buttons and not have to use the Update method, but rather than the desired effect, I have successfully found a new way to make unity instantly crash. I am sure the crashing has something to do with some stupid mistake in the coroutine.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraAnimation : MonoBehaviour {
#region variables
public Transform topDown;
public Transform thirdPerson;
private Transform camTransform;
public float speed;
private float startTime;
private bool isRunning = false;
public enum FocusState
{
ABILITYTOPDOWN,
SELECTEDPLAYER,
}
[HideInInspector]
public FocusState focusState = FocusState.SELECTEDPLAYER;
[HideInInspector]
public GameObject selectedPlayer;
#endregion variables
void Update () {
}
public void AbilityButton()
{
if(focusState == FocusState.SELECTEDPLAYER && isRunning == false)
{
camTransform = transform;
StartCoroutine(TransitionTopDown());
}
else
{
//
}
}
private IEnumerator TransitionTopDown()
{
if (!isRunning)
{
Debug.Log("Running...");
startTime = Time.realtimeSinceStartup;
Debug.Log(startTime);
isRunning = true;
}
if (transform.position == topDown.position)
{
Debug.Log("Camera movement complete");
focusState = FocusState.ABILITYTOPDOWN;
isRunning = false;
StopCoroutine(TransitionTopDown());
}
Vector3 center = (camTransform.position + topDown.position);// * 0.5F;
//center -= new Vector3(0, 1, 0);
Vector3 startRelCenter = camTransform.position - center;
Vector3 endRelCenter = topDown.position - center;
float fracComplete = (Time.realtimeSinceStartup - startTime) / speed;
transform.position = Vector3.Slerp(startRelCenter, endRelCenter, fracComplete);
transform.position += center;
transform.LookAt(selectedPlayer.transform.position);
yield return TransitionTopDown();
}
}
You were close, I think this is what you want. The main differences are the center and using thirdPerson, not camTransform, for the startRelCenter vectors.
With the code you had the camera should have been in the correct position once the coroutine was complete, even if the path/movement it was taking wasn’t what you wanted. Are you sure your topDown transform is where you think it is? Is it a child of the selectedPlayer? A fixed position in the scene?
// Center is the center of the "sphere" that Slerp will interpolate around
// We'll use the yposition of the thirdPerson transform, that should produce a natural looking curve
Vector3 center = topDown.position;
center.y = thirdPerson.position.y;
Vector3 startRelCenter = thirdPerson.position - center;
Vector3 endRelCenter = topDown.position - center;
float fracComplete = (Time.realtimeSinceStartup - startTime) / speed;
transform.position = Vector3.Slerp(startRelCenter, endRelCenter, fracComplete);
transform.position += center;
transform.LookAt(selectedPlayer.transform.position);
This is how my camera is currently set up in my scene. The camera is not a child of the selected character but is a child of a container that moves to the position of a character when they are selected.
Main camera is the camera and the other two are empty game objects holding the transform positions the camera moves to as suggested by Billy4184.
Edit* I cannot tell if the provided code segment works because I am still having an issue with unity crashing.
Edit** The code does work but I had to go back to the original setup with the Update method
I didn’t include your pausing code, but you’ll notice this coroutine yields a frame and accumulates time, as part of the Slerp Method parameter. It uses the starting position (from the very beginning of the call) the whole time…and doesn’t try to call the coroutine, again, as your earlier example did.
I think that’ll work for you if you try this route. You could add the pause scenarios, and also send the start and end variables as parameters, if you want this method to run both ways, too
First, why are you calling ‘yield return TransitionTopDown()’ instead of just ‘yield return null’? I’ve never used a coroutine like that but it seems like it would call itself recursively and crash.
You can just put the code inside a while loop and use ‘yield return null’ instead:
while ((Time.realtimeSinceStartup - startTime)/speed < 1)
{
// Some code
yield return null;
}
Also when you are trying to move stuff precisely around a dynamic object, it’s usually best to work in local coordinates to avoid ‘catching up’ problems. The two positions are already children of the container, and if you use the container as the center then, since it’s local position to itself is Vector3.zero, you don’t have to worry about adjusting it.
Also, since you are adjusting the slerp value, the start and end positions must not change, you cannot use the world position or the current position of the transform, rather you must store the start and end positions at the beginning.