- Home /
Camera following a cannonball that is mouse controlled.
So a cannonball is fired in a 3D world. This cannonball is fired by first instacianting it and then setting some initial velocity into its rigidbody component. The cannonball is controlled with the mouse by first obtaining float x = Input.getAxis("Mouse X")
and then adding force to the rigidbody: rb.AddForce(Vector3.right * x * 50f)
; Also, i added some torque to the ball in order to give it a more natural spinning effect. The figure below shows two situations with a top-down view of the 3D world. The first situation (A) is what is currently happening, the ball is controlled with the mouse but the camera stays fixed always facing forward. What i want is for situation B to happen, where the camera rotates slightly looking into where the ball is going to. So how i can i rotate the camera slightly in order to make it look into the direction of where the ball is heading (Situation B)? Again, the cannonball has some spinning effect to it by adding some torque and the camera is following the cannonball with a Vector3 offset like this: camera.position = cannonBall.transform.position + offset
, updating it in a coroutine; Thank you in advance.
Answer by Ady_M · Jan 05, 2019 at 02:34 PM
Version 1
public Vector3 cameraDirection;
public float cameraDistance = 10; // Replaces your "offset" variable
public float cameraRotateDelay = 0.93f; // Decrease this value if the camera rotates too slowly
public Camera camera;
public Rigidbody ballRigidbody;
// Every frame:
float d = cameraRotateDelay;
cameraDirection = (d * cameraDirection + (1-d) * ballRigidbody.velocity.normalized).normalized;
camera.transform.rotation = Quaternion.LookRotation (cameraDirection);
camera.transform.position = ballRigidbody.position - cameraDirection * cameraDistance;
Version 2
In this version, cameraDirection and cameraDistance are calculated in Awake using the Camera's initial position and an empty object called "Ball SpawnPoint" inside the Cannon object. You can now position the camera wherever you want, but don't bother rotating it yourself. It will look at Ball SpawnPoint on Awake.
To keep the initial camera rotation:
// Replace this:
cameraDirection = (ballSpawnPoint.transform.position - camera.transform.position).normalized;
// With this:
cameraDirection = camera.transform.forward;
I have replaced Rigidbody.position with Rigidbody.transform.position. I had forgotten that using Rigidbody.position gives different results.
For some reason, the first frame after firing messes up the camera position. It jumps down a little and then starts following correctly. As a workaround, I created a property called ballFramesAirborne which increases each frame while the ball is airborne. I prevent the camera from updating if ballFramesAirborne is zero.
The full code:
public float ballInitialSpeed = 20;
public Transform ballSpawnPoint; // An empty child object of the Cannon
public Rigidbody ballRigidbody;
private bool ballIsAirborne = false; // Camera will only move and rotate if this is true
private int ballFramesAirborne = 0; // Read about this in the updateCamera() method.
public Camera camera;
private Vector3 cameraInitialDirection; // Is set only once. Used every time you fire.
private Vector3 cameraDirection; // Changes each frame.
public float cameraDistance = 10;
public float cameraRotateDelay = 0.90f; // (0.0 .. 1.0) Decrease this value if the camera rotates too slowly
private void Awake ()
{
// Disable and hide the ball
ballRigidbody.gameObject.SetActive (false);
// Get cameraDirection and cameraDistance from current state of the camera
// (Allows you to set up the initial state of the camera in the editor)
cameraDirection = (ballSpawnPoint.transform.position - camera.transform.position).normalized;
cameraDistance = Vector3.Distance (ballSpawnPoint.transform.position, camera.transform.position);
// Save the camera's initial position, so we can use it next time we fire
cameraInitialDirection = cameraDirection;
}
public void fire ()
{
ballIsAirborne = true;
ballFramesAirborne = 0;
ballRigidbody.gameObject.SetActive (true);
ballRigidbody.transform.position = ballSpawnPoint.position;
ballRigidbody.velocity = transform.forward * ballInitialSpeed;
cameraDirection = cameraInitialDirection;
}
public void updateCamera ()
{
// The first frame after firing messes up the camera position.
// ballFramesAirborne is used to fix that.
if (ballIsAirborne == false || ballFramesAirborne == 0) return; // Skip the rest of the code
float d = cameraRotateDelay;
cameraDirection = (d * cameraDirection + (1-d) * ballRigidbody.velocity.normalized).normalized;
camera.transform.rotation = Quaternion.LookRotation (cameraDirection);
camera.transform.position = ballRigidbody.position - cameraDirection * cameraDistance;
}
private void Update ()
{
if (Input.GetMouseButtonDown (1))
{
fire ();
}
}
private void LateUpdate ()
{
updateCamera ();
// DEV >> Draw a line in the Scene View indicating where the camera is looking
Debug.DrawRay (camera.transform.position, camera.transform.forward * 100, Color.magenta, 0, false);
// DEV >> Draw a line in the Scene View indicating the velocity vector of the ball
Debug.DrawRay (ballRigidbody.transform.position, ballRigidbody.velocity.normalized * 100, Color.blue, 0, false);
ballFramesAirborne ++;
}
You're welcome :) I'm sure you've already figured this out, but don't forget to do cameraDirection = ballRigidbody.velocity.normalized right after setting the initial velocity.
@Ady_$$anonymous$$ how could i keep that offset when the camera starts following the ball? this is because i wanted a smooth transition when the cannon fires and then the camera starts following the ball.
Do you mean the camera is initially standing still and then accelerates to follow the cannon ball?
yes, the camera is initially behind the cannon and slightly above, then when the cannon fires the camera starts following the ball
Answer by Serge144 · Jan 12, 2019 at 09:06 PM
updating my solution, which is now simpler. I basically do this: when the camera starts following the ball it will follow starting from its position and direction, and then, over time, the position of the camera is slowly moved in such way that the camera direction is aligned with the ball movement (as Ady sugested). The speed of this transition may also be set. This way there's a smooth transition of the camera from its idle state and follow-ball state.
public class CameraController : MonoBehaviour
{
CannonBall cannonBall;
Rigidbody rb;
Vector3 savedCameraPos;
Vector3 cameraDirection;
Vector3 offset;
Quaternion savedCameraRot;
int ballFramesAirbourne = 0;
float ballToCamDistance;
float cameraRotateDelay = 0.65f;
float timer = 1.75f;
//Coroutine that makes the camera follow the cannonball across multiple frames
IEnumerator Transition()
{
savedCameraPos = transform.position; //save the position of the camera
savedCameraRot = transform.rotation; //save the rotation of the camera
float startTime = Time.time;
while (timer > 0)
{
//do not udpate the camera on the first frame because the velocity vector of the ball is not initialized for some reason
if (ballFramesAirbourne == 0) {
Debug.DrawRay(new Vector3(0,0,0), rb.transform.position + offset, Color.yellow, 0, false);
ballFramesAirbourne++;
yield return null;
}
//calculate the cameras direction and update the positions and rotations
cameraDirection = (cameraRotateDelay * cameraDirection + (1 - cameraRotateDelay) * rb.velocity.normalized).normalized;
transform.rotation = Quaternion.LookRotation(cameraDirection);
transform.position = cannonBall.transform.position + offset;
//When the camera starts following the ball, it will start in its initial position
//but then, over time, the camera will transition to be aligned with the camera.
Vector3 newPos = rb.transform.position - cameraDirection * ballToCamDistance;
float timeFrac = (Time.time - startTime) / timer;
transform.position = Vector3.Lerp(transform.position, newPos,timeFrac * 5f);
timer -= Time.deltaTime;
yield return null;
}
timer = 1.75f;
StartCoroutine("Return");
}
//returns the camera into the initial position and initial rotation
IEnumerator Return()
{
float speed = 100f;
float step = speed * Time.deltaTime;
while (!(transform.position == savedCameraPos) || (Quaternion.Angle(transform.rotation,savedCameraRot) > 2))
{
transform.position = Vector3.MoveTowards(transform.position, savedCameraPos, step);
transform.rotation = Quaternion.RotateTowards(transform.rotation, savedCameraRot, step);
yield return null;
}
CannonController.unlockCannon();
}
//initializes the transition coroutine
public void followCannonBall(CannonBall cb)
{
cannonBall = cb;
rb = cb.GetComponent<Rigidbody>();
cameraDirection = transform.forward;
ballToCamDistance = Vector3.Distance(transform.position, cannonBall.transform.position); //this can be on Start since the Distance is always the same
offset = transform.position - cb.transform.position;
ballFramesAirbourne = 0;
StartCoroutine("Transition");
}
}
Answer by Serge144 · Jan 13, 2019 at 02:46 PM
Updating my answer which is basically @Ady_M answer but with a small addition so that there's a smooth transition between the cameras idle state and the follow-ball state. I basically did this: when the camera starts following the ball, it begins with its initial position and direction, then, over time, the camera is moved in such way that it stays aligned with the ball direction. The speed of this transition may also be set.
public class CameraController : MonoBehaviour
{
CannonBall cannonBall;
Rigidbody rb;
Vector3 savedCameraPos;
Vector3 cameraDirection;
Vector3 offset;
Quaternion savedCameraRot;
int ballFramesAirbourne = 0;
float ballToCamDistance;
float cameraRotateDelay = 0.65f;
float timer = 1.75f;
//Coroutine that makes the camera follow the cannonball across multiple frames
IEnumerator Transition()
{
savedCameraPos = transform.position; //save the position of the camera
savedCameraRot = transform.rotation; //save the rotation of the camera
float startTime = Time.time;
while (timer > 0)
{
//do not udpate the camera on the first frame because the velocity vector of the ball is not initialized for some reason
if (ballFramesAirbourne == 0) {
Debug.DrawRay(new Vector3(0,0,0), rb.transform.position + offset, Color.yellow, 0, false);
ballFramesAirbourne++;
yield return null;
}
//calculate the cameras direction and update the positions and rotations
cameraDirection = (cameraRotateDelay * cameraDirection + (1 - cameraRotateDelay) * rb.velocity.normalized).normalized;
transform.rotation = Quaternion.LookRotation(cameraDirection);
transform.position = cannonBall.transform.position + offset;
//When the camera starts following the ball, it will start in its initial position
//but then, over time, the camera will transition to be aligned with the camera.
Vector3 newPos = rb.transform.position - cameraDirection * ballToCamDistance;
float timeFrac = (Time.time - startTime) / timer;
transform.position = Vector3.Lerp(transform.position, newPos,timeFrac * 5f);
timer -= Time.deltaTime;
yield return null;
}
timer = 1.75f;
StartCoroutine("Return");
}
//returns the camera into the initial position and initial rotation
IEnumerator Return()
{
float speed = 100f;
float step = speed * Time.deltaTime;
while (!(transform.position == savedCameraPos) || (Quaternion.Angle(transform.rotation,savedCameraRot) > 2))
{
transform.position = Vector3.MoveTowards(transform.position, savedCameraPos, step);
transform.rotation = Quaternion.RotateTowards(transform.rotation, savedCameraRot, step);
yield return null;
}
CannonController.unlockCannon();
}
//initializes the transition coroutine
public void followCannonBall(CannonBall cb)
{
cannonBall = cb;
rb = cb.GetComponent<Rigidbody>();
cameraDirection = traansform.forward;
ballToCamDistance = Vector3.Distance(transform.position, cannonBall.transform.position); //this can be on Start since the Distance is always the same
offset = transform.position - cb.transform.position;
ballFramesAirbourne = 0;
StartCoroutine("Transition");
}
}
Your answer
Follow this Question
Related Questions
How can I rotate my camera when player has rigidbody? 0 Answers
How to make camera mouse orbit not jittery? 0 Answers
Adjust camera orientation through code 0 Answers
Camera isn't following mouse in FPS 0 Answers
Why Do I Have Camera Stutter? 1 Answer