Object orientation in relation to camera position

Hi there. I am fairly new to Unity and I’ve run into an issue that I cannot wrap my head around.
I am looking to change the rotation of an object (sphere) based on the position of the camera.
Basically I have a scene with a lot of people, and I want their heads to rotate away when the camera is looking toward them, and facing toward the fps controller when the camera is looking downwards.

Hope there is someone who can help me understand :slight_smile:

Thanks in advance!

Disclaimers: It’s a bit hard to tell what exactly you mean by the camera looking downwards, but I’m gonna assume that you mean when the camera looks far enough down that they are a given angle from straight down. Additionally, I’m gonna assume that by ‘look away’, you mean look in the opposite direction. If these are not the case, the following answer should still be able to apply, although some tweaking might be needed. Also, the code that’s written here is untested and intended for example purposes. It is not guaranteed to be functional or to match your use case, so copy-pasting is not recommended.

Anyway, to the answer:

What you’re probably looking for is Transform.LookAt or Quaternion.LookRotation.

Noting that you tagged this question as “Scripting beginner”, I’m gonna go step by step with some explanation, starting with just getting the head to turn, and ending with a result it seems like you are looking for.

Detecting when the player looks down:

Firstly, we’ll create a script that will exist on the head sphere. For this answer I’ll be calling it “Head”. Inherits from monobehaviour, etc etc. We’ll also add a reference to the player’s camera’s transform, and a float threshold for how far down is looking down. The way I’ll implement this is as such:

public class Head : Monobehaviour{
      public Transform playerCam;
      public float threshold;

      void Update(){
            if(Vector3.Angle(playerCam.forward, Vector3.down)<threshold){
                  //The player is looking down
            }else{
                 //The player is NOT looking down
            }
      }
}

Here, we use Vector3.Angle to check the angle of the camera from the down vector. If this is less than your threshold, then the player is looking down. The threshold here is in degrees from straight down, so a threshold of 60 means they are looking down if they are looking 30 degrees below the horizon, 90 means they are looking down if they look at all below the horizon, and 180 means the player is always considered to be looking down.

Now, to turn the heads:

Making the heads look towards and away

For this, we can use the Transform.LookAt function. This function takes in another transform or Vec3 to look at and rotates the transform you call it on so that it looks at this other transform or position with its Z axis (the forward axis in Unity). Using the transform overload might be easier for now, but I’ll be using the Vec3 version to make working with these rotations easier later.

When the player is looking down, and thus the head is looking at the player, we do:

transform.LookAt(playerCam.position);

Now, to look away, we’re gonna mirror the player’s position around the head’s position and look at that. To do this, we do:

transform.LookAt(transform.position-playerCam.position);

This is the same as the last line, but now the playerCam position is subtracted from the head’s position, effectively mirroring it about the head. This has the effect that the head looks in the exact opposite direction as the player.

Adding Interpolation for a Smoother Effect

If you run this code, the heads will turn, but they will do so rather abruptly. If you want the heads to turn smoothly rather than just teleporting into the correct rotation, we’ll have to use quaternions, and some interpolation. If you’re not familiar with quaternions, they’re basically Vec4s, where x,y,z refer to an axis and w refers to an angle around that axis. The’re really handy mathematical tools, but not the most human readable. Thankfully, we’re not gonna edit any directly, and instead just use Quaternion.Lerp for interpolation and Quaternion.LookRotation to make the quaternions.

Quaternion.LookRotation works similarly to Transform.LookAt, but takes in a direction vector, rather than a position vector. To get the direction between the head and the player, simply perform the subtraction playerCam.position-transform.position. The direction to look away is the opposite of that, conveniently being the same transform.position-playerCam.position as before. Now, you can turn the old code into something equivalent by excahnging these lines:

transform.LookAt(playerCam.position);
//Can be replaced with:
transform.rotation = Quaternion.LookRotation(playerCam.position-transform.position);



//And
transform.LookAt(transform.position-playerCam.position);
//Can be replaced with:
transform.rotation = Quaternion.LookRotation(transform.position-playerCam.position);

This code is now functionally equivalent to the previous code, and still ends up ‘teleporting’ the heads. Let’s use Quaternion.Lerp to add some interpolation.

Now, I’m going to be using this function the ‘wrong’ way here. The Lerp function is a linear interpolation function, however linear interpolation often feels mechanical and unnatural, as it moves at the same rate regardless of the distance between the current and target values. The same function can be used, however, to create different sorts of interpolations. The result here will be exponential, and will asymptotically approach the target rotation rather than hitting it exactly, since it slows down as it gets closer to the target rotation, and speeds up when further away. This feels a lot nicer and more natural, and doesn’t end up abruptly stopping like ‘proper’ lerp does.

Regardless, Quaternion.Lerp takes 3 arguments. The first 2 are Quaternions, and the last is a float that serves as a way to crossfade between them, with 0 being entirely the first, and 1 being entirely the second rotation. To do this, however, we’re gonna need to add another variable to our script to control the speed of interpolation. Add this to the top:

public float speed;

And then, we can interpolate. The 2 key lines from before now become:

//Look towards
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookAt(playerCam.position-transform.position), speed*Time.deltaTime);

//Look away
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookAt(transform.position - playerCam.position), speed*Time.deltaTime);

Now, the heads will turn towards and away from the player.

The End Result

The final script will look something like this. Dragging the player’s camera into playerCam, and assigning a threshold and speed (I recommend 45 and 5 respectively, although play around with this), should result in a head that works as described. As stated far above, I don’t guarantee this code to work (I wrote it directly into the answer), but it should point you in the right direction.

 public class Head : Monobehaviour{
       public Transform playerCam;
       public float threshold;
       public float speed;
 
       void Update(){
             if(Vector3.Angle(playerCam.forward, Vector3.down)<threshold){
                   transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookAt(playerCam.position-transform.position), speed*Time.deltaTime);
             }else{
                  transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.LookAt(transform.position - playerCam.position), speed*Time.deltaTime);
             }
       }
 }

Hi @Zarenityx

Thank you so much. You are an absolute champ!
I really appreciate the walk through and breaking it up step by step.
I had to change Quaternion.LookAt with Quaternion.LookRotation and now it
works! I really appreciate the help and patience.

Thank you!